// ********************* 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 ) {
                    error( "ReceiveSingleFile: ReadHeader "
                           "returned %d", c );
                    return ERROR;
                }
                SendAttentionString();
                continue;
            case ZSKIP:
                fclose( file );
                status("ReceiveSingleFile: Sender SKIPPED file");
                return c;
            case ZDATA:
                if ( UnpackHeaderIntoLong( received_header ) != rxbytes ) {
                    if ( ++error_count >= 20 )
                        return ERROR;
                    SendAttentionString();
                    continue;
                }
moredata:
                switch ( c = ReadDataFrame( buffer, 1024 ) ) {
                  case ZCAN:
                    error( "ReceiveSingleFile: ReadData returned %d", 
                           c );
                    return ERROR;
                  case ERROR:
                    if ( ++error_count >= 20 ) {
                        error( "ReceiveSingleFile: ReadData "
                               "returned %d", c );
                        return ERROR;
                    }
                    SendAttentionString();
                    continue;
                  case TIMEOUT:
                    if ( ++error_count >= 20 ) {
                        error( "ReceiveSingleFile: ReadData "
                               "returned %d", c );
                        return ERROR;
                    }
                    continue;
                  case GOTCRCW:
                    error_count = 0;
                    fwrite( buffer, 1, Rxcount, file );
                    rxbytes += Rxcount;
                    PackLongIntoHeader( rxbytes );
                    SendHexHeader( 4, ZACK, transmitted_header );
                    SendChar( XON );
                    goto nxthdr;
                  case GOTCRCQ:
                    error_count = 0;
                    fwrite( buffer, 1, Rxcount, file );
                    rxbytes += Rxcount;
                    PackLongIntoHeader( rxbytes );
                    SendHexHeader( 4, ZACK, transmitted_header );
                    goto moredata;
                  case GOTCRCG:
                    error_count = 0;
                    fwrite( buffer, 1, Rxcount, file );
                    rxbytes += Rxcount;
                    goto moredata;
                  case GOTCRCE:
                    error_count = 0;
                    fwrite( buffer, 1, Rxcount, file );
                    rxbytes += Rxcount;
                    goto nxthdr;
                }
        }
    }
#ifdef _MSC_VER
    return 0;  // MSC 7.0 generates an error w/o this line, 
               // although it can never be reached
#endif
}

// This routine just has to decide whether to send the binary 
// header using CRC-16 or CRC-32.  After that it just spits out 
// the data.

void Zmodem::SendBinaryHeader( int length, 
                               int type, 
                               char *header )
{
    int i;
    Crc32 crc32( 0xFFFFFFFFL );
    unsigned long crc32val;
    Crc16 crc16( 0 );

    status( "SendBinaryHeader: %d %s %lx",
            length,
            frametypes[ type + 4 ],
            UnpackHeaderIntoLong( header ) );
    SendChar( ZPAD );
    SendChar( ZDLE );
    if ( receiver_wants_crc32 ) {
        SendChar( ZBIN32 );
        SendEncodedChar( type );
        crc32.update( type );
        for ( i = 0; i < length; i++ ) {
            crc32.update( 0xff & header[ i ] );
            SendEncodedChar( header[ i ] );
        }
        crc32val = ~crc32.value();
        for ( i = 0 ; i < 4 ; i++ ) {
            SendEncodedChar( (int) crc32val );
            crc32val >>= 8;
        }
    } else {
        SendChar( ZBIN );
        SendEncodedChar( type );
        crc16.update( type );
        for ( i = 0 ; i < length ; i++ ) {
            SendEncodedChar( header[ i ] );
            crc16.update( header[ i ] & 0xff );
        }
        crc16.update( 0 );
        crc16.update( 0 );
        SendEncodedChar( crc16.value() >> 8 );
        SendEncodedChar( crc16.value() );
    }
}

// Sending the hex header involves no decisions whatsoever.

void Zmodem::SendHexHeader( int len, int type, char *header )
{
    int n;
    Crc16 crc( 0 );

    status( "SendHexHeader: %d %s %lx", len,
            frametypes[type+4], UnpackHeaderIntoLong( header ) );
    SendChar( ZPAD );
    SendChar( ZPAD );
    SendChar( ZDLE );
    SendChar( ZHEX );
    SendHexEncodedChar( type );
    crc.update( type );
    for ( n = 0 ; n < len ; n++ ) {
        SendHexEncodedChar( header[ n ] );
        crc.update( 0xff & header[ n ] );
    }
    crc.update( 0 );
    crc.update( 0 );
    SendHexEncodedChar( crc.value() >> 8 );
    SendHexEncodedChar( crc.value() );
    SendChar( CR );
    SendChar( LF | 0x80 );
    if ( type != ZFIN && type != ZACK )
        SendChar( XON );
}

// Reading in headers and data subpackets is a relatively 
// difficult job. The next four routines combine to read in 
// headers.  The first routine dispatches one of the next three, 
// depending on what the header type is.

int Zmodem::ReadHeader( char *header )
{
    int c;
    int n;
    int cancount;
    Settings settings;

    port->ReadSettings( settings );
    n = 1400;
    n += ( settings.BaudRate > 19200L ) ? 
            19200 : (int) settings.BaudRate;

startover:
    cancount = 0;
again:
    switch ( c = ReadChar( 10000L ) ) {
        case TIMEOUT:
            goto finished;
        case CAN:
gotcan:
            if ( ++cancount >= 5 ) {
                c = ZCAN;
                goto finished;
            }
            switch ( c = ReadChar( 100 ) ) {
                case TIMEOUT:
                    goto again;
                case ZCRCW:
                switch ( ReadChar( 100 ) ) {
                    case TIMEOUT:
                        c = ERROR;
                        goto finished;
                    default:
                        goto agn2;
                }
                default:
                    break;
                case CAN:
                    if ( ++cancount >= 5 ) {
                        c = ZCAN;
                        goto finished;
                    }
                    goto again;
            }
        default:
agn2:
            if ( --n == 0 ) {
                c = GARBAGE_COUNT;
                goto finished;
             }
             goto startover;
        case ZPAD | 0x80:        /* This is what we want. */
        case ZPAD:
             break;
    }
    cancount = 0;
splat:
    switch ( c = ReadUnencodedByte() ) {
        case ZPAD:
            goto splat;
        case TIMEOUT:
            goto finished;
        default:
            goto agn2;
        case ZDLE:        /* This is what we want. */
            break;
    }

    c = ReadUnencodedByte();
    switch ( c ) {
        case ZBIN32:
            current_frame_uses_crc32 = 1;
            c = ReadBinaryHeaderCRC32( header );
            break;
        case TIMEOUT:
            goto finished;
        case ZBIN:
            current_frame_uses_crc32 = 0;
            c = ReadBinaryHeaderCRC16( header );
            break;
        case ZHEX:
            current_frame_uses_crc32 = 0;
            c = ReadHexHeaderCRC16( header );
            break;
        case CAN:
            goto gotcan;
        default:
            goto agn2;
    }
    received_file_position = header[ ZP3 ] & 0xff;
    received_file_position <<= 8;
    received_file_position += header[ ZP2 ] & 0xff;
    received_file_position <<= 8;
    received_file_position += header[ ZP1 ] & 0xff;
    received_file_position <<= 8;
    received_file_position += header[ ZP0 ] & 0xff;
finished:
    switch ( c ) {
        case GOTCAN:
            c = ZCAN;
        /* **** FALL THRU TO **** */
        case ZNAK:
        case ZCAN:
        case ERROR:
        case TIMEOUT:
        case GARBAGE_COUNT:
            error( "Got %s", frametypes[ c + 4 ] );
        /* **** FALL THRU TO **** */
        default:
            if ( c >= -4 && c <= 22 )
                error( "ReadHeader: %s %lx",
                       frametypes[ c + 4 ], 
                       received_file_position );
            else
                error( "ReadHeader: %d %lx", 
                       c, 
                       received_file_position );
    }
    return c;
}

// At this point most of the hard work has been done.  The next 
//three routines just read in the type of header and the data 
// associated with it, then check to see if the CRC is correct.

int Zmodem::ReadBinaryHeaderCRC16( char *header )
{
    int c;
    int i;
    Crc16 crc( 0 );
    int header_type;

    if ( ( c = ReadEncodedByte() ) & ~0xff )
        return c;
    header_type = c;
    crc.update( c );

    for ( i = 0 ; i < 4 ; i++ ) {
        if ( ( c = ReadEncodedByte() ) & ~0xff )
            return c;
        crc.update( c );
        header[ i ] = (char) c;
    }
    if ( ( c = ReadEncodedByte() ) & ~0xff )
        return c;
    crc.update( c );
    if ( ( c = ReadEncodedByte() ) & ~0xff )
        return c;
    crc.update( c );
    if ( crc.value() & 0xFFFF ) {
        error( "Bad CRC" );
        return ERROR;
    }
    return header_type;
}

int Zmodem::ReadBinaryHeaderCRC32( char *header )
{
    int c;
    int i;
    Crc32 crc( 0xFFFFFFFFL );
    int header_type;

    if ( ( c = ReadEncodedByte() ) & ~0xff )
        return c;
    header_type = c;
    crc.update( c );

    for ( i = 0 ; i < 4 ; i++ ) {
        if ( ( c = ReadEncodedByte() ) & ~0xff )
            return c;
        crc.update( c );
        header[ i ] = (char) c;
    }
    for ( i = 0; i < 4 ; i++ ) {
        if ( ( c = ReadEncodedByte() ) & ~0xff )
            return c;
        crc.update( c );
    }
    if ( crc.value() != 0xDEBB20E3L ) {
        error( "Bad CRC" );
        return ERROR;
    }
    return header_type;
}

int Zmodem::ReadHexHeaderCRC16( char *header )
{
    int c;
    Crc16 crc( 0 );
    int i;
    int header_type;

    if ( ( c = ReadHexByte() ) < 0 )
        return c;
    header_type = c;
    crc.update( c );

    for ( i = 0 ; i < 4 ; i++ ) {
        if ( ( c = ReadHexByte() ) < 0 )
            return c;
        crc.update( c );
        header[ i ] = (char) c;
    }
    if ( ( c = ReadHexByte() ) < 0 )
        return c;
    crc.update( c );
    if ( ( c = ReadHexByte() ) < 0 )
        return c;
    crc.update( c );
    if ( crc.value() & 0xFFFF ) {
        error( "Bad CRC" );
        return ERROR;
    }
    switch ( c = ReadChar( 100 ) ) {
        case CR :
        case CR | 0x80 :
            ReadChar( 100 );
    }
    return header_type;
}

// The next three routines are used to read in binary data
// subpackets.  This code is somewhat simpler than the code
// used to read in a header, mostly because there are fewer
// things to go wrong.

int Zmodem::ReadDataFrame( char *buffer, int length )
{
    if ( current_frame_uses_crc32 )
        return ReadDataFrameCRC32( buffer, length );
    else
        return ReadDataFrameCRC16( buffer, length );
}

int Zmodem::ReadDataFrameCRC32( char *buffer, int length )
{
    int c;
    Crc32 crc( 0xFFFFFFFFL );
    char *end;
    int d;

    Rxcount = 0;
    end = buffer + length;
    while ( buffer <= end ) {
        if ( ( c = ReadEncodedByte() ) & ~0xff ) {
crcfoo:
            switch ( c ) {
                case GOTCRCE:
                case GOTCRCG:
                case GOTCRCQ:
                case GOTCRCW:
                    d = c;
                    c &= 0xff;
                    crc.update( c );
                    if ( ( c = ReadEncodedByte() ) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( ( c = ReadEncodedByte() ) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( ( c = ReadEncodedByte() ) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( ( c = ReadEncodedByte() ) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( crc.value() != 0xDEBB20E3L ) {
                        error( "Bad CRC" );
                        return ERROR;
                    }
                    Rxcount = (int) ( length - (end - buffer) );
                    error( "ReadDataFrameCRC32: %d %s",
                           Rxcount, 
                           Zendnames[ d - GOTCRCE & 3 ] );
                    return d;
                case GOTCAN:
                    error( "Sender Canceled" );
                    return ZCAN;
                case TIMEOUT:
                    error( "TIMEOUT" );
                    return c;
                default:
                    error( "Garbled data subpacket" );
                    return c;
            }
        }
        *buffer++ = (char) c;
        crc.update( c );
    }
    error( "Data subpacket too long" );
    return ERROR;
}

int Zmodem::ReadDataFrameCRC16( char *buffer, int length )
{
    int c;
    Crc16 crc( 0 );
    char *end;
    int d;

    Rxcount = 0;
    end = buffer + length;
    while ( buffer <= end ) {
        if ( ( c = ReadEncodedByte() ) & ~0xff ) {
crcfoo:
            switch ( c ) {
                case GOTCRCE:
                case GOTCRCG:
                case GOTCRCQ:
                case GOTCRCW:
                    crc.update( (d = c) & 0xff );
                    if ( ( c = ReadEncodedByte()) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( ( c = ReadEncodedByte() ) & ~0xff )
                        goto crcfoo;
                    crc.update( c );
                    if ( crc.value() & 0xFFFF ) {
                        error( "Bad CRC");
                        return ERROR;
                    }
                    Rxcount = (int) ( length - ( end - buffer ) );
                    error( "ReadDataFrame: %d  %s",
                           Rxcount, 
                           Zendnames[ d - GOTCRCE & 3 ] );
                    return d;
                case GOTCAN:
                    error( "Sender Canceled" );
                    return ZCAN;
                case TIMEOUT:
                    error( "TIMEOUT" );
                    return c;
                default:
                    error( "Garbled data subpacket" );
                    return c;
            }
        }
        *buffer++ = (char) c;
        crc.update( c );
    }
    error( "Data subpacket too long" );
    return ERROR;
}

// The attention string processor has to process a couple of 
// special characters used to sleep and send breaks.

void Zmodem::SendAttentionString()
{
    int i = 0;
    int c;
    long timer;

    while ( port->TXSpaceUsed() > 0 )
        port->IdleFunction();
    while ( ( c = attention_string[ i++ ] ) != 0 ) {
        switch ( c ) {
            case 0xde :
                timer = ReadTime() + 1000L;
                while ( ReadTime() < timer )
                    port->IdleFunction();
                break;
            case 0xdd :
                port->Break();
                break;
            default:
                SendChar( c );
        }
    }
}

// This function is called by the receiver before exiting.

void Zmodem::AckZFIN( void )
{
    int n;

    status( "AckZFIN" );
    PackLongIntoHeader( 0L );
    for ( n = 0 ; n < 4 ; n++ ) {
        port->FlushRXBuffer();
        SendHexHeader( 4, ZFIN, transmitted_header );
        switch ( ReadChar( 10000L ) ) {
            case 'O':
                ReadChar( 120 );    /* Discard 2nd 'O' */
                status( "AckZFIN complete" );
                return;
            case TIMEOUT:
            default:
                break;
        }
    }
}

// This utility routine has to scan the incoming data subpacket 
// for the file name and length.

int Zmodem::OpenInputFile( char *data )
{
    strcpy( file_name, data );
    if (sscanf(data+strlen(data)+1, "%ld", &file_length ) < 1 )
        file_length = -1L;
    file = fopen( file_name, "wb" );
    if ( !file)
        return ERROR;
    return OK;
}

// File position values are longs packed into a four byt header.
// The following two routines are resposible for packing and
// unpacking the data.

void Zmodem::PackLongIntoHeader( long header_data )
{
    transmitted_header[ ZP0 ] = (char) header_data;
    transmitted_header[ ZP1 ] = (char) ( header_data >> 8 );
    transmitted_header[ ZP2 ] = (char) ( header_data >> 16 );
    transmitted_header[ ZP3 ] = (char) ( header_data >> 24 );
}

long Zmodem::UnpackHeaderIntoLong( char *header )
{
    long l;

    l = header[ ZP3 ] & 0xff;
    l = ( l << 8 ) | ( header[ ZP2 ] & 0xff );
    l = ( l << 8 ) | ( header[ ZP1 ] & 0xff );
    l = ( l << 8 ) | ( header[ ZP0 ] & 0xff );
    return l;
}

// Hex headers need to send data in hex format.
void Zmodem::SendHexEncodedChar( int c )
{
    static char *digits = "0123456789abcdef";

    SendChar( digits[ ( c & 0xF0 ) >> 4 ] );
    SendChar( digits[ c & 0xF ] );
}

// This routine handles all the escape sequences necessary to send
// control characters.

void Zmodem::SendEncodedChar( int c )
{
    if ( c & 0x60 )
        SendChar( last_char_sent = c );
    else {
        switch ( c &= 0xff ) {
            case CR :
            case CR | 0x80 :
                if ( ( last_char_sent & 0x7f ) != '@' ) {
                    SendChar( last_char_sent = c );
                    break;
                } // else fall through
            case ZDLE :
            case DLE :
            case XON :
            case XOFF :
            case DLE | 0x80 :
            case XON | 0x80 :
            case XOFF | 0x80 :
                SendChar( ZDLE );
                c ^= 0x40;
                SendChar( last_char_sent = c );
                break;
            default:
                SendChar( last_char_sent = c );
        }
    }
}

// Read a byte, taking into account escape sequences, and checking
// for the 5*CAN abort sequence.

int Zmodem::ReadEncodedByte( void )
{
    int c;

    for ( ; ; ) {
        c = ReadChar( 10000L );
        if ( c == ZDLE )
            break;
        switch ( c ) {
            case XON :
            case XON | 0x80 :
            case XOFF :
            case XOFF | 0x80 :
                break;
            default:
                return c;
        }
    }
    for ( ; ; ) {
        if ( ( c = ReadChar( 10000L ) ) < 0 )
            return c;
        if ( c == CAN && ( c = ReadChar( 10000L ) ) < 0 )
            return c;
        if ( c == CAN && ( c = ReadChar( 10000L ) ) < 0 )
            return c;
        if ( c == CAN && ( c = ReadChar( 10000L ) ) < 0 )
            return c;
        switch ( c ) {
            case CAN:
                return GOTCAN;
            case ZCRCE:
            case ZCRCG:
            case ZCRCQ:
            case ZCRCW:
                return c | GOTFLAG;
            case ZRUB0:
                return 0x7f;
            case ZRUB1:
                 return 0xff;
            case XOFF :
            case XOFF | 0x80 :
            case XON :
            case XON | 0x80 :
                break;
            default:
                if ( ( c & 0x60 ) ==  0x40 )
                    return ( c ^ 0x40 );
                else
                    return ERROR;
        }
    }
}

// This routine reads a raw data byte, throws out the parity bit and
// ignores handshaking characters.

int Zmodem::ReadUnencodedByte( void )
{
    int c;

    for ( ; ; ) {
        if ( ( c = ReadChar( 10000L ) ) < 0 )
            return c;
        switch ( c &= 0x7f ) {
            case XON:
            case XOFF:
                continue;
            default:
                return c;
        }
    }
}

// When reading Hex headers, we need to convert hex values to a
// usable format.

int Zmodem::ReadHexByte( void )
{
    int c;
    int n;

    if ( ( c = ReadUnencodedByte() ) < 0 )
        return c;
    n = c - '0';
    if ( n > 9 )
        n -= ( 'a' - ':' );
    if ( n & ~0xF )
        return ERROR;
    if ( ( c = ReadUnencodedByte( )) < 0 )
        return c;
    c -= '0';
    if ( c > 9 )
        c -= ( 'a' - ':' );
    if ( c & ~0xF )
        return ERROR;
    c += ( n << 4 );
    return c;
}

// The sender needs to get the RINIT frame before it can start
// sending the file.  This routine takes care of that.

int Zmodem::GetRinitHeader( void )
{
  int i;

  for ( i = 0 ; i < 10 ; i++ ) {
    switch ( ReadHeader( received_header ) ) {
      case ZCHALLENGE:    // Echo receiver's challenge number 
        PackLongIntoHeader( received_file_position );
        SendHexHeader( 4, ZACK, transmitted_header );
        continue;
      case ZCOMMAND:        // They didn't see our ZRQINIT 
        PackLongIntoHeader( 0L );
        SendHexHeader( 4, ZRQINIT, transmitted_header );
        continue;
      case ZRINIT:
        receiver_wants_crc32 = received_header[ ZF0 ] & CANFC32;
        receiver_buffer_length =
                ( received_header[ ZP0 ] & 0xff ) +
                ( ( received_header[ ZP1 ] & 0xff ) << 8 );
        return OK;
      case ZCAN:
      case TIMEOUT:
        return ERROR;
      case ZRQINIT:
        if ( received_header[ ZF0 ] == ZCOMMAND )
          continue;
      default:
        SendHexHeader( 4, ZNAK, transmitted_header );
        continue;
    }
  }
  return ERROR;
}

// This routine sends a data subpacket, which is used here to send
// file names and file data.

void Zmodem::SendDataFrame( char *buffer, int length, int frameend )
{
    Crc32 crc32( 0xFFFFFFFFL );
    unsigned long crc32val;
    Crc16 crc16( 0 );
    int i;

    status( "SendDataFrame: %d %s",
            length, Zendnames[ frameend - ZCRCE & 3 ] );
    if ( receiver_wants_crc32 ) {
        for ( i = 0 ; i < length ; i++ ) {
            SendEncodedChar( buffer[ i ] );
            crc32.update( buffer[ i ] & 0xff );
        }
        SendChar( ZDLE );
        SendChar( frameend );
        crc32.update( frameend );
        crc32val = ~crc32.value();
        for ( i = 0 ; i < 4 ; i++ ) {
            SendEncodedChar( (int) crc32val );
            crc32val >>= 8;
        }
    } else {
        for ( i = 0 ; i < length ; i++ ) {
            SendEncodedChar( buffer[ i ] );
            crc16.update( buffer[ i ] & 0xff );
        }
        SendChar( ZDLE);
        SendChar( frameend);
        crc16.update( frameend );
        crc16.update( 0 );
        crc16.update( 0 );
        SendEncodedChar( crc16.value() >> 8 );
        SendEncodedChar( crc16.value() );
    }
    if ( frameend == ZCRCW )
        SendChar( XON );
}

// The sender sends a ZFIN frame just before exiting.

void Zmodem::SendZFIN( void )
{
    for ( ; ; ) {
        PackLongIntoHeader( 0L );
        SendHexHeader( 4, ZFIN, transmitted_header );
        switch ( ReadHeader( received_header ) ) {
            case ZFIN:
                SendChar( 'O');
                SendChar( 'O');
            case ZCAN:
            case TIMEOUT:
                return;
        }
    }
}

int Zmodem::SyncWithReceiver( int flag )
{
  int c;

  for ( ; ; ) {
    c = ReadHeader( received_header );
    switch ( c ) {
      case ZCAN:
      case ZABORT:
      case ZFIN:
      case TIMEOUT:
        return ERROR;
      case ZRPOS:
        if ( fseek( file, received_file_position, SEEK_SET ) )
          return ERROR;
        file_at_eof = 0;
        byte_count = last_reported_position
           = transmitted_file_position = received_file_position;
        last_sync_position = received_file_position;
        return c;
      case ZACK:
        last_reported_position = received_file_position;
        if ( flag || transmitted_file_position ==
                     received_file_position )
          return ZACK;
        continue;
      case ZRINIT:
      case ZSKIP:
        fclose( file );
        return c;
      case ERROR:
      default:
        SendBinaryHeader( 4, ZNAK, transmitted_header );
        continue;
    }
  }
}

// ********************* END OF ZMODEM.CPP **********************

