#ifndef lint static const char rcsid[] = "$Id: zmodem.c,v 1.1.1.1 2001/03/08 00:01:48 efalk Exp $" ; #endif /* * Copyright (c) 1995 by Edward A. Falk */ /********** * * * @@@@@ @ @ @@@ @@@@ @@@@@ @ @ * @ @@ @@ @ @ @ @ @ @@ @@ * @ @ @ @ @ @ @ @ @@@ @ @ @ * @ @ @ @ @ @ @ @ @ @ @ @ * @@@@@ @ @ @ @@@ @@@@ @@@@@ @ @ @ * * ZMODEM - main logic parser for zmodem library * * * Routines provided here: * * * name (args) * Brief description. * * int ZmodemRcv(u_char *buffer, int len, ZModem *info) * Call whenever characters are received. If this function * returns ZmDone, previous function has completed successfully, * either call ZmodemTFile() to start next file, or call * ZmodemTFinish() to terminate the session. * * * int * ZmodemTimeout(ZModem *info) * Call whenever the timeout period expires and no * characters have been received. * * int * ZmodemAttention(ZModem *info) * Call whenever the attention sequence has been received * from the remote end. It is safe to call this function * from an interrupt handler. * * int * ZmodemAbort(ZModem *info) * Call to abort transfer. Physical connection remains * open until you close it. * * * * * Edward A. Falk * * January, 1995 * * * **********/ #include /**** * * Constants, typedefs, externals, globals, statics, macros, block data * ****/ /* TODO: sample input before initial send */ /* TODO: more intelligent timeout dispatch */ /* TODO: read all pending input before sending next data packet out */ /* TODO: if received ZDATA while waiting for ZFILE/ZFIN, it's probably leftovers */ /* TODO: enable flow control for zmodem, disable for X/YModem */ #include #include #include #include "zmodem.h" #include "crctab.h" static u_char zeros[4] = {0,0,0,0} ; extern int YrcvChar( char c, register ZModem *info ) ; extern int YrcvTimeout( register ZModem *info ) ; extern void ZIdleStr(u_char *buffer, int len, ZModem *info) ; /* LEXICAL BOX: handle characters received from remote end. * These may be header, data or noise. * * This section is a finite state machine for parsing headers * and reading input data. The info->chrCount field is effectively * the state variable. */ static int FinishChar( char c, register ZModem *info ) ; static int DataChar( u_char c, register ZModem *info ) ; static int HdrChar( u_char c, register ZModem *info ) ; static int IdleChar(u_char c, register ZModem *info) ; extern int YsendChar() ; static int ZProtocol(), ZDataReceived() ; int ZmodemRcv( register u_char *str, int len, register ZModem *info ) { register u_char c ; int err ; info->rcvlen = len ; while( --info->rcvlen >= 0 ) { c = *str++ ; if( c == CAN ) { if( ++info->canCount >= 5 ) { ZStatus(RmtCancel, 0, NULL) ; return ZmErrCancel ; } } else info->canCount = 0 ; if( info->InputState == Ysend ) { if( (err = YsendChar(c, info)) ) return err ; } else if( info->InputState == Yrcv ) { if( (err = YrcvChar(c, info)) ) return err ; } else if( c != XON && c != XOFF ) { /* now look at what we have */ switch( info->InputState ) { case Idle: if( (err = IdleChar(c, info)) ) return err ; break ; case Inhdr: if( (err = HdrChar(c, info)) ) return err ; break ; case Indata: if( (err = DataChar(c, info)) ) return err ; break ; case Finish: if( (err = FinishChar(c, info)) ) return err ; break ; default: break ; } } } return 0 ; } /* handle character input while idling * looking for ZPAD-ZDLE sequence which introduces a header */ static int IdleChar(u_char c, register ZModem *info) { if( info->chrCount == 0 ) { if( c == ZPAD ) ++info->chrCount ; else if( info->state == Sending && ++info->noiseCount > MaxNoise ) info->waitflag = 1 ; else if( info->state == TStart && (c == 'C' || c == 'G' || c == NAK) ) { /* switch to ymodem */ info->state = YTStart ; info->InputState = Ysend ; info->Protocol = YMODEM ; return YsendChar(c, info) ; } else ZIdleStr(&c, 1, info) ; } else { switch( c ) { case ZPAD: ++info->chrCount ; break ; case ZDLE: info->InputState = Inhdr ; info->chrCount=0 ; break ; default: while( --info->chrCount >= 0 ) ZIdleStr((u_char *)"*", 1, info) ; info->chrCount = 0 ; break ; } } return 0 ; } static u_int rcvHex( u_int i, char c ) { if( c <= '9' ) c -= '0' ; else if( c <= 'F' ) c -= 'A'-10 ; else c -= 'a'-10 ; return (i<<4)+c ; } /* handle character input in a header */ static int HdrChar( u_char c, register ZModem *info ) { int i ; int crc=0 ; if( c == ZDLE ) { info->escape = 1 ; return 0 ; } if( info->escape ) { info->escape = 0 ; switch( c ) { case ZRUB0: c = 0177 ; break ; case ZRUB1: c = 0377 ; break ; default: c ^= 0100 ; break ; } } if( info->chrCount == 0 ) { /* waiting for format */ switch( c ) { case ZHEX: case ZBIN: case ZBIN32: info->DataType = c ; info->chrCount = 1 ; info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL ; memset(info->hdrData,0,sizeof(info->hdrData)) ; break ; default: info->InputState = Idle ; info->chrCount = 0 ; return ZXmitHdrHex(ZNAK, zeros, info) ; } return 0 ; } switch( info->DataType ) { /* hex header is 14 hex digits, cr, lf. Optional xon is ignored */ case ZHEX: if( info->chrCount <= 14 && !isxdigit(c) ) { info->InputState = Idle ; info->chrCount = 0 ; return ZXmitHdrHex(ZNAK, zeros, info) ; } if( info->chrCount <= 14 ) { i = (info->chrCount-1)/2 ; info->hdrData[i] = rcvHex(info->hdrData[i], c) ; } if( info->chrCount == 16 ) { crc = 0 ; for(i=0; i<7; ++i) crc = updcrc(info->hdrData[i], crc) ; info->InputState = Idle ; info->chrCount = 0 ; if( (crc&0xffff) != 0 ) return ZXmitHdrHex(ZNAK, zeros, info) ; else return ZProtocol(info) ; } else ++info->chrCount ; break ; case ZBIN: /* binary header is type, 4 bytes data, 2 bytes CRC */ info->hdrData[info->chrCount-1] = c ; info->crc = updcrc(c, info->crc) ; if( ++info->chrCount > 7 ) { info->InputState = Idle ; info->chrCount = 0 ; if( (crc&0xffff) != 0 ) return ZXmitHdrHex(ZNAK, zeros, info) ; else return ZProtocol(info) ; } break ; case ZBIN32: /* binary32 header is type, 4 bytes data, 4 bytes CRC */ info->hdrData[info->chrCount-1] = c ; info->crc = UPDC32(c, info->crc) ; if( ++info->chrCount > 9 ) { info->InputState = Idle ; info->chrCount = 0 ; if( info->crc != 0xdebb20e3 ) /* see note below */ return ZXmitHdrHex(ZNAK, zeros, info) ; else return ZProtocol(info) ; } break ; } return 0 ; } /* handle character input in a data buffer */ static int DataChar( u_char c, register ZModem *info ) { if( c == ZDLE ) { info->escape = 1 ; return 0 ; } if( info->escape ) { info->escape = 0 ; switch( c ) { case ZCRCE: case ZCRCG: case ZCRCQ: case ZCRCW: info->PacketType = c ; info->crcCount = (info->DataType == ZBIN32) ? 4 : 2 ; if( info->DataType == ZBIN ) info->crc = updcrc(c, info->crc) ; else info->crc = UPDC32(c, info->crc) ; return 0 ; case ZRUB0: c = 0177 ; break ; case ZRUB1: c = 0377 ; break ; default: c ^= 0100 ; break ; } } switch( info->DataType ) { /* TODO: are hex data packets ever used? */ case ZBIN: info->crc = updcrc(c, info->crc) ; if( info->crcCount == 0 ) info->buffer[info->chrCount++] = c ; else if( --info->crcCount == 0 ) { return ZDataReceived(info, (info->crc&0xffff) == 0) ; } break ; case ZBIN32: info->crc = UPDC32(c, info->crc) ; if( info->crcCount == 0 ) info->buffer[info->chrCount++] = c ; else if( --info->crcCount == 0 ) { return ZDataReceived(info, info->crc == 0xdebb20e3) ; } break ; } return 0 ; } /* wait for "OO" */ static int FinishChar( char c, register ZModem *info ) { if( c == 'O' ) { if( ++info->chrCount >= 2 ) return ZmDone ; } else info->chrCount = 0 ; return 0 ; } int ZPF() ; int Ignore() ; int AnswerChallenge() ; int GotAbort() ; int GotCancel() ; int GotCommand() ; int GotStderr() ; int RetDone() ; static int GotCommandData() ; static int GotStderrData() ; /* PROTOCOL LOGIC: This section of code handles the actual * protocol. This is also driven by a finite state machine * * State tables are sorted by approximate frequency order to * reduce search time. */ /* Extra ZRINIT headers are the receiver trying to resync. */ /* If compiling for Send Only or Receive Only, convert table * entries to no-ops so we don't have to link zmodem[rt].o */ #if SendOnly #define RStartOps DoneOps #define RSinitWaitOps DoneOps #define RFileNameOps DoneOps #define RCrcOps DoneOps #define RFileOps DoneOps #define RDataOps DoneOps #define RFinishOps DoneOps #define GotFileName Ignore #define ResendCrcReq Ignore #define GotSinitData Ignore #define ResendRpos Ignore #define GotFileData Ignore #define SendRinit Ignore #else extern StateTable RStartOps[] ; extern StateTable RSinitWaitOps[] ; extern StateTable RFileNameOps[] ; extern StateTable RCrcOps[] ; extern StateTable RFileOps[] ; extern StateTable RDataOps[] ; extern StateTable RFinishOps[] ; extern int GotFileName() ; extern int ResendCrcReq() ; extern int GotSinitData() ; extern int ResendRpos() ; extern int GotFileData() ; extern int SendRinit() ; #endif #if RcvOnly #define TStartOps DoneOps #define TInitOps DoneOps #define FileWaitOps DoneOps #define CrcWaitOps DoneOps #define SendingOps DoneOps #define SendDoneOps DoneOps #define SendWaitOps DoneOps #define SendEofOps DoneOps #define TFinishOps DoneOps #define SendMoreFileData Ignore #else extern StateTable TStartOps[] ; extern StateTable TInitOps[] ; extern StateTable FileWaitOps[] ; extern StateTable CrcWaitOps[] ; extern StateTable SendingOps[] ; extern StateTable SendDoneOps[] ; extern StateTable SendWaitOps[] ; extern StateTable SendEofOps[] ; extern StateTable TFinishOps[] ; extern int SendMoreFileData() ; #endif static StateTable CommandDataOps[] = { #ifdef COMMENT {ZRQINIT,f,1,1}, {ZRINIT,f,1,1}, {ZSINIT,f,1,1}, {ZACK,f,1,1}, {ZFILE,f,1,1}, {ZSKIP,f,1,1}, {ZNAK,f,1,1}, {ZABORT,f,1,1,TFinish}, {ZFIN,f,1,1}, {ZRPOS,f,1,1}, {ZDATA,f,1,1}, {ZEOF,f,1,1}, {ZFERR,f,1,1,TFinish}, {ZCRC,f,1,1}, {ZCHALLENGE,f,1,1}, {ZCOMPL,f,1,1}, {ZCAN,f,1,1}, {ZFREECNT,f,1,1}, {ZCOMMAND,f,1,1}, {ZSTDERR,f,1,1}, #endif /* COMMENT */ {99,ZPF,0,0,CommandData}, } ; static StateTable CommandWaitOps[] = { #ifdef COMMENT {ZRQINIT,f,1,1}, {ZRINIT,f,1,1}, {ZSINIT,f,1,1}, {ZACK,f,1,1}, {ZFILE,f,1,1}, {ZSKIP,f,1,1}, {ZNAK,f,1,1}, {ZABORT,f,1,1,TFinish}, {ZFIN,f,1,1}, {ZRPOS,f,1,1}, {ZDATA,f,1,1}, {ZEOF,f,1,1}, {ZFERR,f,1,1,TFinish}, {ZCRC,f,1,1}, {ZCHALLENGE,f,1,1}, {ZCOMPL,f,1,1}, {ZCAN,f,1,1}, {ZFREECNT,f,1,1}, {ZCOMMAND,f,1,1}, {ZSTDERR,f,1,1}, #endif /* COMMENT */ {99,ZPF,0,0,CommandWait}, } ; static StateTable StderrDataOps[] = { #ifdef COMMENT {ZRQINIT,f,1,1}, {ZRINIT,f,1,1}, {ZSINIT,f,1,1}, {ZACK,f,1,1}, {ZFILE,f,1,1}, {ZSKIP,f,1,1}, {ZNAK,f,1,1}, {ZABORT,f,1,1,TFinish}, {ZFIN,f,1,1}, {ZRPOS,f,1,1}, {ZDATA,f,1,1}, {ZEOF,f,1,1}, {ZFERR,f,1,1,TFinish}, {ZCRC,f,1,1}, {ZCHALLENGE,f,1,1}, {ZCOMPL,f,1,1}, {ZCAN,f,1,1}, {ZFREECNT,f,1,1}, {ZCOMMAND,f,1,1}, {ZSTDERR,f,1,1}, #endif /* COMMENT */ {99,ZPF,0,0,StderrData}, } ; static StateTable DoneOps[] = { {99,ZPF,0,0,Done}, } ; static StateTable *tables[] = { RStartOps, RSinitWaitOps, RFileNameOps, RCrcOps, RFileOps, RDataOps, RDataOps, /* RDataErr is the same as RData */ RFinishOps, TStartOps, TInitOps, FileWaitOps, CrcWaitOps, SendingOps, SendWaitOps, SendDoneOps, SendEofOps, TFinishOps, CommandDataOps, CommandWaitOps, StderrDataOps, DoneOps, } ; char *hdrnames[] = { "ZRQINIT", "ZRINIT", "ZSINIT", "ZACK", "ZFILE", "ZSKIP", "ZNAK", "ZABORT", "ZFIN", "ZRPOS", "ZDATA", "ZEOF", "ZFERR", "ZCRC", "ZCHALLENGE", "ZCOMPL", "ZCAN", "ZFREECNT", "ZCOMMAND", "ZSTDERR", } ; /* This function is called (indirectly) by the ZmodemRcv() * function when a full header has been received. */ static int ZProtocol( register ZModem *info ) { register StateTable *table ; zmodemlog("received %s: %2.2x %2.2x %2.2x %2.2x = %lx\n", hdrnames[info->hdrData[0]], info->hdrData[1], info->hdrData[2], info->hdrData[3], info->hdrData[4], ZDec4(info->hdrData+1)) ; /* Flags are sent in F3 F2 F1 F0 order. Data is sent in P0 P1 P2 P3 */ info->timeoutCount = 0 ; info->noiseCount = 0 ; table = tables[(int)info->state] ; while( table->type != 99 && table->type != info->hdrData[0] ) ++table ; zmodemlog(" state %s => %s, iflush=%d, oflush=%d, call %x\n", sname(info), sname2(table->newstate), table->IFlush, table->OFlush, table->func) ; info->state = table->newstate ; if( table->IFlush ) {info->rcvlen = 0 ; ZIFlush(info) ;} if( table->OFlush ) ZOFlush(info) ; return table->func(info) ; } static int ZDataReceived( register ZModem *info, int crcGood ) { switch( info->state ) { case RSinitWait: return GotSinitData(info, crcGood) ; case RFileName: return GotFileName(info, crcGood) ; case RData: return GotFileData(info, crcGood) ; case CommandData: return GotCommandData(info, crcGood) ; case StderrData: return GotStderrData(info, crcGood) ; default: return ZPF(info) ; } } int ZmodemTimeout( register ZModem *info ) { /* timed out while waiting for input */ ++info->timeoutCount ; zmodemlog("timeout %d [%s]\n", info->timeoutCount, sname(info) ) ; switch( info->state ) { /* receive */ case RStart: /* waiting for INIT frame from other end */ if( info->timeoutCount > 4 ) return YmodemRInit(info) ; case RSinitWait: case RFileName: if( info->timeout > 0 ) ZStatus(SndTimeout, info->timeoutCount, NULL) ; if( info->timeoutCount > 4 ) return ZmErrRcvTo ; info->state = RStart ; return SendRinit(info) ; case RCrc: case RFile: case RData: ZStatus(SndTimeout, info->timeoutCount, NULL) ; if( info->timeoutCount > 2 ) { info->timeoutCount = 0 ; info->state = RStart ; return SendRinit(info) ; } return info->state == RCrc ? ResendCrcReq(info) : ResendRpos(info) ; case RFinish: ZStatus(SndTimeout, info->timeoutCount, NULL) ; return ZmDone ; case YRStart: case YRDataWait: case YRData: case YREOF: return YrcvTimeout(info) ; /* transmit */ case TStart: /* waiting for INIT frame from other end */ case TInit: /* sent INIT, waiting for ZACK */ case FileWait: /* sent file header, waiting for ZRPOS */ case CrcWait: /* sent file crc, waiting for ZRPOS */ case SendWait: /* waiting for ZACK */ case SendEof: /* sent EOF, waiting for ZACK */ case TFinish: /* sent ZFIN, waiting for ZFIN */ case YTStart: case YTFile: case YTDataWait: case YTData: case YTEOF: case YTFin: ZStatus(RcvTimeout,0,NULL) ; return ZmErrRcvTo ; case Sending: /* sending data subpackets, ready for int */ return SendMoreFileData(info) ; /* general */ case CommandData: /* waiting for command data */ case StderrData: /* waiting for stderr data */ return ZmErrSndTo ; case CommandWait: /* waiting for command to execute */ return ZmErrCmdTo ; case Done: return ZmDone ; default: return 0 ; } } int ZmodemAttention( register ZModem *info ) { /* attention received from remote end */ if( info->state == Sending ) { ZOFlush(info) ; info->interrupt = 1 ; } return 0 ; } int ZmodemAbort( register ZModem *info ) { static u_char canistr[] = { CAN,CAN,CAN,CAN,CAN,CAN,CAN,CAN,8,8,8,8,8,8,8,8,8,8 } ; info->state = Done ; ZIFlush(info) ; ZOFlush(info) ; return ZXmitStr(canistr, sizeof(canistr), info) ; } /* used to completely ignore headers */ int Ignore( ZModem *info ) { return 0 ; } /* ignore header contents, return ZmDone */ int RetDone( ZModem *info ) { return ZmDone ; } /* ignore header contents, return ZmErrCancel */ int GotCancel( ZModem *info ) { return ZmErrCancel ; } /* utility: set up to receive a data packet */ int dataSetup( register ZModem *info ) { info->InputState = Indata ; info->chrCount = 0 ; info->crcCount = 0 ; info->crc = (info->DataType != ZBIN32) ? 0 : 0xffffffffL ; return 0 ; } /* called when a remote command received. For now, we * refuse to execute commands. Send EPERM and ignore. */ int GotCommand( ZModem *info ) { u_char rbuf[4] ; /* TODO: add command capability */ rbuf[0] = EPERM ; rbuf[1] = rbuf[2] = rbuf[3] = 0 ; return ZXmitHdrHex(ZCOMPL, rbuf, info) ; } static int GotCommandData( register ZModem *info ) { /* TODO */ return 0 ; } /* called when the remote system wants to put something to * stderr */ int GotStderr( register ZModem *info ) { info->InputState = Indata ; info->chrCount = 0 ; return 0 ; } static int GotStderrData( register ZModem *info ) { info->buffer[info->chrCount] = '\0' ; ZStatus(RemoteMessage, info->chrCount, (char *)info->buffer) ; return 0 ; } /* Protocol failure: An unexpected packet arrived. This could * be from many sources, such as old pipelined info finally arriving * or a serial line with echo enabled. Report it and ignore it. */ int ZPF( ZModem *info ) { info->waitflag = 1 ; /* pause any in-progress transmission */ ZStatus(ProtocolErr, info->hdrData[0], NULL) ; return 0 ; } int AnswerChallenge( register ZModem *info ) { return ZXmitHdrHex(ZACK, info->hdrData+1, info) ; } int GotAbort( register ZModem *info ) { ZStatus(RmtCancel, 0, NULL) ; return ZXmitHdrHex(ZFIN, zeros, info) ; }