#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 <stdio.h>

/****
 *
 * 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 <ctype.h>
#include <errno.h>
#include <string.h>

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