#ifndef lint
static const char rcsid[] = "$Id: zmodemr.c,v 1.1.1.1 2001/03/08 00:01:48 efalk Exp $" ;
#endif

/*
 * Copyright (c) 1995 by Edward A. Falk
 */


/**********
 *
 *
 *	@@@@@  @   @   @@@   @@@@   @@@@@  @   @  @@@@   
 *	   @   @@ @@  @   @  @   @  @      @@ @@  @   @  
 *	  @    @ @ @  @   @  @   @  @@@    @ @ @  @@@@   
 *	 @     @ @ @  @   @  @   @  @      @ @ @  @  @   
 *	@@@@@  @ @ @   @@@   @@@@   @@@@@  @ @ @  @   @  
 *
 *	ZMODEMR - receive side of zmodem protocol
 *
 * receive side of zmodem protocol
 *
 * This code is designed to be called from inside a larger
 * program, so it is implemented as a state machine where
 * practical.
 *
 * functions:
 *
 *	ZmodemRInit(ZModem *info)
 *		Initiate a connection
 *
 *	ZmodemRAbort(ZModem *info)
 *		abort transfer
 *
 * all functions return 0 on success, 1 on failure
 *
 *
 *	Edward A. Falk
 *
 *	January, 1995
 *
 *
 *
 **********/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "zmodem.h"
#include "crctab.h"

extern	int	errno ;

extern	int	ZWriteFile(u_char *buffer, int len, FILE *, ZModem *);
extern	int	ZCloseFile(ZModem *info) ;
extern	void	ZFlowControl(int onoff, ZModem *info) ;

static	u_char	zeros[4] = {0,0,0,0} ;





int
ZmodemRInit(ZModem *info)
{
	info->packetCount = 0 ;
	info->offset = 0 ;
	info->errCount = 0 ;
	info->escCtrl = info->escHibit = info->atSign = info->escape = 0 ;
	info->InputState = Idle ;
	info->canCount = info->chrCount = 0 ;
	info->filename = NULL ;
	info->interrupt = 0 ;
	info->waitflag = 0 ;
	info->attn = NULL ;
	info->file = NULL ;

	info->buffer = (u_char *)malloc(8192) ;

	info->state = RStart ;
	info->timeoutCount = 0 ;

	ZIFlush(info) ;

	/* Don't send ZRINIT right away, there might be a ZRQINIT in
	 * the input buffer.  Instead, set timeout to zero and return.
	 * This will allow ZmodemRcv() to check the input stream first.
	 * If nothing found, a ZRINIT will be sent immediately.
	 */
	info->timeout = 0 ;

	zmodemlog("ZmodemRInit[%s]: flush input, new state = RStart\n",
	    sname(info)) ;

	return 0 ;
}

int
YmodemRInit(ZModem *info)
{
	info->errCount = 0 ;
	info->InputState = Yrcv ;
	info->canCount = info->chrCount = 0 ;
	info->noiseCount = 0 ;
	info->filename = NULL ;
	info->file = NULL ;

	if( info->buffer == NULL )
	  info->buffer = (u_char *)malloc(1024) ;

	info->state = YRStart ;
	info->packetCount = -1 ;
	info->timeoutCount = 0 ;
	info->timeout = 10 ;
	info->offset = 0 ;

	ZIFlush(info) ;

	return ZXmitStr((u_char *)"C", 1, info) ;
}





extern	int	ZPF() ;
extern	int	Ignore() ;
extern	int	GotCommand() ;
extern	int	GotStderr() ;

	int	SendRinit() ;
static	int	GotSinit() ;
static	int	GotFile() ;
static	int	GotFin() ;
static	int	GotData() ;
static	int	GotEof() ;
static	int	GotFreecnt() ;
static	int	GotFileCrc() ;
	int	ResendCrcReq() ;
	int	ResendRpos() ;

		/* sent ZRINIT, waiting for ZSINIT or ZFILE */
	StateTable	RStartOps[] = {
	  {ZSINIT,GotSinit,0,1,RSinitWait},	/* SINIT, wait for attn str */
	  {ZFILE,GotFile,0,0,RFileName},	/* FILE, wait for filename */
	  {ZRQINIT,SendRinit,0,1,RStart},	/* sender confused, resend */
	  {ZFIN,GotFin,1,0,RFinish},		/* sender shutting down */
	  {ZNAK,SendRinit,1,0,RStart},		/* RINIT was bad, resend */
#ifdef	TODO
	  {ZCOMPL,f,1,1,s},
#endif	/* TODO */
	  {ZFREECNT,GotFreecnt,0,0,RStart},	/* sender wants free space */
	  {ZCOMMAND,GotCommand,0,0,CommandData}, /* sender wants command */
	  {ZSTDERR,GotStderr,0,0,StderrData},	/* sender wants to send msg */
	  {99,ZPF,0,0,RStart},			/* anything else is an error */
	} ;

	StateTable	RSinitWaitOps[] = {	/* waiting for data */
	  {99,ZPF,0,0,RSinitWait},
	} ;

	StateTable	RFileNameOps[] = {	/* waiting for file name */
	  {99,ZPF,0,0,RFileName},
	} ;

	StateTable	RCrcOps[] = {		/* waiting for CRC */
	  {ZCRC,GotFileCrc,0,0,RFile},		  /* sender sent it */
	  {ZNAK,ResendCrcReq,0,0,RCrc},		  /* ZCRC was bad, resend */
	  {ZRQINIT,SendRinit,1,1,RStart},	  /* sender confused, restart */
	  {ZFIN,GotFin,1,1,RFinish},		  /* sender signing off */
	  {99,ZPF,0,0,RCrc},
	} ;

	StateTable	RFileOps[] = {		/* waiting for ZDATA */
	  {ZDATA,GotData,0,0,RData},		  /* got it */
	  {ZNAK,ResendRpos,0,0,RFile},		  /* ZRPOS was bad, resend */
	  {ZEOF,GotEof,0,0,RStart},		  /* end of file */
	  {ZRQINIT,SendRinit,1,1,RStart},	  /* sender confused, restart */
	  {ZFILE,ResendRpos,0,0,RFile},		  /* ZRPOS was bad, resend */
	  {ZFIN,GotFin,1,1,RFinish},		  /* sender signing off */
	  {99,ZPF,0,0,RFile},
	} ;

	/* waiting for data, but a packet could possibly arrive due
	 * to error recovery or something
	 */
	StateTable	RDataOps[] = {
	  {ZRQINIT,SendRinit,1,1,RStart},	/* sender confused, restart */
	  {ZFILE,GotFile,0,1,RFileName},	/* start a new file (??) */
	  {ZNAK,ResendRpos,1,1,RFile},		/* ZRPOS was bad, resend */
	  {ZFIN,GotFin,1,1,RFinish},		/* sender signing off */
	  {ZDATA,GotData,0,1,RData},		/* file data follows */
	  {ZEOF,GotEof,1,1,RStart},		/* end of file */
	  {99,ZPF,0,0,RData},
	} ;

	/* here if we've sent ZFERR or ZABORT.  Waiting for ZFIN */

	StateTable	RFinishOps[] = {
	  {ZRQINIT,SendRinit,1,1,RStart},	/* sender confused, restart */
	  {ZFILE,GotFile,1,1,RFileName},	/* start a new file */
	  {ZNAK,GotFin,1,1,RFinish},		/* resend ZFIN */
	  {ZFIN,GotFin,1,1,RFinish},		/* sender signing off */
	  {99,ZPF,0,0,RFinish},
	} ;




extern	char	*hdrnames[] ;

extern	int	dataSetup() ;


	/* RECEIVE-RELATED STUFF BELOW HERE */


	/* resend ZRINIT header in response to ZRQINIT or ZNAK header */

int
SendRinit( register ZModem *info )
{
	u_char	dbuf[4] ;

#ifdef	COMMENT
	if( info->timeoutCount >= 5 )
	  /* TODO: switch to Ymodem */
#endif	/* COMMENT */

	zmodemlog("SendRinit[%s]: send ZRINIT\n", sname(info)) ;

	info->timeout = ResponseTime ;
	dbuf[0] = info->bufsize&0xff ;
	dbuf[1] = (info->bufsize>>8)&0xff ;
	dbuf[2] = 0 ;
	dbuf[3] = info->zrinitflags ;
	return ZXmitHdrHex(ZRINIT, dbuf, info) ;
}



	/* received a ZSINIT header in response to ZRINIT */

static	int
GotSinit( register ZModem *info )
{
	zmodemlog("GotSinit[%s]: call dataSetup\n", sname(info)) ;

	info->zsinitflags = info->hdrData[4] ;
	info->escCtrl = info->zsinitflags & TESCCTL ;
	info->escHibit = info->zsinitflags & TESC8 ;
	ZFlowControl(1, info) ;
	return dataSetup(info) ;
}

	/* received rest of ZSINIT packet */

int
GotSinitData( register ZModem *info, int crcGood )
{
	info->InputState = Idle ;
	info->chrCount=0 ;
	info->state = RStart ;

	zmodemlog("GotSinitData[%s]: crcGood=%d\n", sname(info), crcGood) ;

	if( !crcGood )
	  return ZXmitHdrHex(ZNAK, zeros, info) ;

	if( info->attn != NULL )
	  free(info->attn) ;
	info->attn = NULL ;
	if( info->buffer[0] != '\0' )
	  info->attn = strdup((char *)info->buffer) ;
	return ZXmitHdrHex(ZACK, ZEnc4(SerialNo), info) ;
}


	/* got ZFILE.  Cache flags and set up to receive filename */

static	int
GotFile( register ZModem *info )
{
	zmodemlog("GotFile[%s]: call dataSetup\n", sname(info)) ;

	info->errCount = 0 ;
	info->f0 = info->hdrData[4] ;
	info->f1 = info->hdrData[3] ;
	info->f2 = info->hdrData[2] ;
	info->f3 = info->hdrData[1] ;
	return dataSetup(info) ;
}


	/* utility: see if ZOpenFile wants this file, and if
	 * so, request it from sender.
	 */

static	int
requestFile( register ZModem *info, u_long crc )
{
	info->file = ZOpenFile((char *)info->buffer, crc, info) ;

	if( info->file == NULL ) {
	  zmodemlog("requestFile[%s]: send ZSKIP\n", sname(info)) ;

	  info->state = RStart ;
	  ZStatus(FileSkip, 0, info->filename) ;
	  return ZXmitHdrHex(ZSKIP, zeros, info) ;
	}
	else {
	  zmodemlog("requestFile[%s]: send ZRPOS(%ld)\n",
	    sname(info), info->offset) ;
	  info->offset = info->f0 == ZCRESUM ? ftell(info->file) : 0 ;
	  info->state = RFile ;
	  ZStatus(FileBegin, 0, info->filename) ;
	  return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info) ;
	}
}


	/* parse filename info. */

static	void
parseFileName( register ZModem *info, char *fileinfo )
{
	char	*ptr ;
	int	serial=0 ;

	info->len = info->mode = info->filesRem =
		info->bytesRem = info->fileType = 0 ;
	ptr = fileinfo + strlen(fileinfo) + 1 ;
	if( info->filename != NULL )
	  free(info->filename) ;
	info->filename = strdup(fileinfo) ;
	sscanf(ptr, "%d %lo %o %o %d %d %d", &info->len, &info->date,
	    &info->mode, &serial, &info->filesRem, &info->bytesRem,
	    &info->fileType) ;
}


	/* got filename.  Parse arguments from it and execute
	 * policy function ZOpenFile(), provided by caller
	 */

int
GotFileName( register ZModem *info, int crcGood )
{
	info->InputState = Idle ;
	info->chrCount=0 ;

	if( !crcGood ) {
	zmodemlog("GotFileName[%s]: bad crc, send ZNAK\n", sname(info)) ;
	  info->state = RStart ;
	  return ZXmitHdrHex(ZNAK, zeros, info) ;
	}

	parseFileName(info, (char *)info->buffer) ;

	if( (info->f1 & ZMMASK) == ZMCRC ) {
	  info->state = RCrc ;
	  return ZXmitHdrHex(ZCRC, zeros, info) ;
	}

	zmodemlog("GotFileName[%s]: good crc, call requestFile\n",
		sname(info)) ;
	info->state = RFile ;
	return requestFile(info,0) ;
}


int
ResendCrcReq(ZModem *info)
{
	zmodemlog("ResendCrcReq[%s]: send ZCRC\n", sname(info)) ;
	return ZXmitHdrHex(ZCRC, zeros, info) ;
}


	/* received file CRC, now we're ready to accept or reject */

static	int
GotFileCrc( register ZModem *info )
{
	zmodemlog("GotFileCrc[%s]: call requestFile\n", sname(info)) ;
	return requestFile(info, ZDec4(info->hdrData+1)) ;
}


	/* last ZRPOS was bad, resend it */

int
ResendRpos( register ZModem *info )
{
	zmodemlog("ResendRpos[%s]: send ZRPOS(%ld)\n",
	  sname(info), info->offset) ;
	return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info) ;
}


	/* recevied ZDATA header */

static	int
GotData( register ZModem *info )
{
	int	err ;

	zmodemlog("GotData[%s]:\n", sname(info)) ;

	if( ZDec4(info->hdrData+1) != info->offset ) {
	  if( info->attn != NULL  &&  (err=ZAttn(info)) != 0 )
	    return err ;
	  zmodemlog("  bad, send ZRPOS(%ld)\n", info->offset);
	  return ZXmitHdrHex(ZRPOS, ZEnc4(info->offset), info) ;
	}

	/* Let's do it! */
	zmodemlog("  call dataSetup\n");
	return dataSetup(info) ;
}


	/* Utility: flush input, send attn, send specified header */

static	int
fileError( register ZModem *info, int type, int data )
{
	int	err ;

	info->InputState = Idle ;
	info->chrCount=0 ;

	if( info->attn != NULL  &&  (err=ZAttn(info)) != 0 )
	  return err ;
	return ZXmitHdrHex(type, ZEnc4(data), info) ;
}

	/* received file data */

int
GotFileData( register ZModem *info, int crcGood )
{
	/* OK, now what?  Fushing the buffers and executing the
	 * attn sequence has likely chopped off the input stream
	 * mid-packet.  Now we switch to idle mode and treat all
	 * incoming stuff like noise until we get a new valid
	 * packet.
	 */

	if( !crcGood ) {		/* oh bugger, an error. */
	  zmodemlog(
	    "GotFileData[%s]: bad crc, send ZRPOS(%ld), new state = RFile\n",
	    sname(info), info->offset) ;
	  ZStatus(DataErr, ++info->errCount, NULL) ;
	  if( info->errCount > MaxErrs ) {
	    ZmodemAbort(info) ;
	    return ZmDataErr ;
	  }
	  else {
	    info->state = RFile ;
	    info->InputState = Idle ;
	    info->chrCount=0 ;
	    return fileError(info, ZRPOS, info->offset) ;
	  }
	}

	if( ZWriteFile(info->buffer, info->chrCount, info->file, info) )
	{
	  /* RED ALERT!  Could not write the file. */
	  ZStatus(FileErr, errno, NULL) ;
	  info->state = RFinish ;
	  info->InputState = Idle ;
	  info->chrCount=0 ;
	  return fileError(info, ZFERR, errno) ;
	}

	zmodemlog("GotFileData[%s]: %ld.%d,",
	  sname(info), info->offset, info->chrCount) ;
	info->offset += info->chrCount ;
	ZStatus(RcvByteCount, info->offset, NULL) ;

	/* if this was the last data subpacket, leave data mode */
	if( info->PacketType == ZCRCE  ||  info->PacketType == ZCRCW ) {
	  zmodemlog("  ZCRCE|ZCRCW, new state RFile") ;
	  info->state = RFile ;
	  info->InputState = Idle ;
	  info->chrCount=0 ;
	}
	else {
	  zmodemlog("  call dataSetup") ;
	  (void) dataSetup(info) ;
	}

	if( info->PacketType == ZCRCQ || info->PacketType == ZCRCW ) {
	  zmodemlog(",  send ZACK\n") ;
	  return ZXmitHdrHex(ZACK, ZEnc4(info->offset), info) ;
	}
	else
	zmodemlog("\n") ;

	return 0 ;
}


	/* received ZEOF packet, file is now complete */

static	int
GotEof( register ZModem *info )
{
	zmodemlog("GotEof[%s]: offset=%ld\n", sname(info), info->offset) ;
	if( ZDec4(info->hdrData+1) != info->offset ) {
	  zmodemlog("  bad length, state = RFile\n") ;
	  info->state = RFile ;
	  return 0 ;		/* it was probably spurious */
	}

	/* TODO: if we can't close the file, send a ZFERR */

	ZCloseFile(info) ; info->file = NULL ;
	ZStatus(FileEnd, 0, info->filename) ;
	if( info->filename != NULL ) {
	  free(info->filename) ;
	  info->filename = NULL ;
	}
	return SendRinit(info) ;
}



	/* got ZFIN, respond in kind */

static	int
GotFin( register ZModem *info )
{
	zmodemlog("GotFin[%s]: send ZFIN\n", sname(info)) ;
	info->InputState = Finish ;
	info->chrCount = 0 ;
	if( info->filename != NULL )
	  free(info->filename) ;
	return ZXmitHdrHex(ZFIN, zeros, info) ;
}



static	int
GotFreecnt( register ZModem *info )
{
	/* TODO: how do we find free space on system? */
	return ZXmitHdrHex(ZACK, ZEnc4(0xffffffff), info) ;
}



	/* YMODEM */

static	u_char	AckStr[1] = {ACK} ;
static	u_char	NakStr[1] = {NAK} ;
static	u_char	CanStr[2] = {CAN,CAN} ;

static	int	ProcessPacket() ;
static	int	acceptPacket() ;
static	int	rejectPacket() ;
static	int	calcCrc() ;

int
YrcvChar( char c, register ZModem *info )
{
	int	err ;

	if( info->canCount >= 2 ) {
	  ZStatus(RmtCancel, 0, NULL) ;
	  return ZmErrCancel ;
	}

	switch( info->state ) {
	  case YREOF:
	    if( c == EOT ) {
	      ZCloseFile(info) ; info->file = NULL ;
	      ZStatus(FileEnd, 0, info->filename) ;
	      if( info->filename != NULL )
		free(info->filename) ;
	      if( (err = acceptPacket(info)) != 0 )
		return err ;
	      info->packetCount = -1 ;
	      info->offset = 0 ;
	      info->state = YRStart ;
	      return ZXmitStr((u_char *)"C", 1, info) ;
	    }
	    /* else, drop through */

	  case YRStart:
	  case YRDataWait:
	    switch( c ) {
	      case SOH:
	      case STX:
		info->pktLen = c == SOH ? (128+4) : (1024+4) ;
		info->state = YRData ;
		info->chrCount = 0 ;
		info->timeout = 1 ;
		info->noiseCount = 0 ;
		info->crc = 0 ;
		break ;

	      case EOT:
		/* ignore first EOT to protect against false eot */
		info->state = YREOF ;
		return rejectPacket(info) ;

	      default:
		if( ++info->noiseCount > 135 )
		  return ZXmitStr(NakStr, 1, info) ;
		break ;
	    }
	    break ;

	  case YRData:
	    info->buffer[info->chrCount++] = c ;
	    if( info->chrCount >= info->pktLen )
	      return ProcessPacket(info) ;
	    break ;

	  default:
	    break ;
	}

	return 0 ;
}


int
YrcvTimeout( register ZModem *info )
{
	switch( info->state )
	{
	  case YRStart:
	    if( info->timeoutCount >= 10 ) {
	      (void) ZXmitStr(CanStr, 2, info) ;
	      return ZmErrInitTo ;
	    }
	    return ZXmitStr((u_char *)"C", 1, info) ;

	  case YRDataWait:
	  case YREOF:
	  case YRData:
	    if( info->timeoutCount >= 10 ) {
	      (void) ZXmitStr(CanStr, 2, info) ;
	      return ZmErrRcvTo ;
	    }
	    return ZXmitStr(NakStr, 1, info) ;
	  default: return 0 ;
	}
}



static	int
ProcessPacket( register ZModem *info )
{
	int	idx = (u_char) info->buffer[0] ;
	int	idxc = (u_char) info->buffer[1] ;
	int	crc0, crc1 ;
	int	err ;

	info->state = YRDataWait ;

	if( idxc != 255 - idx ) {
	  ZStatus(DataErr, ++info->errCount, NULL) ;
	  return rejectPacket(info) ;
	}

	if( idx == (info->packetCount%256) )	/* quietly ignore dup */
	  return acceptPacket(info) ;

	if( idx != (info->packetCount+1)%256 ) { /* out of sequence */
	  (void) ZXmitStr(CanStr, 2, info) ;
	  return ZmErrSequence ;
	}

	crc0 = (u_char)info->buffer[info->pktLen-2] << 8 |
	      (u_char)info->buffer[info->pktLen-1] ;
	crc1 = calcCrc(info->buffer+2, info->pktLen-4) ;
	if( crc0 != crc1 ) {
	  ZStatus(DataErr, ++info->errCount, NULL) ;
	  return rejectPacket(info) ;
	}

	++info->packetCount ;

	if( info->packetCount == 0 )		/* packet 0 is filename */
	{
	  if( info->buffer[2] == '\0' ) {	/* null filename is FIN */
	    (void) acceptPacket(info) ;
	    return ZmDone ;
	  }

	  parseFileName(info, (char *)info->buffer+2) ;
	  info->file = ZOpenFile(info->filename, 0, info) ;
	  if( info->file == NULL ) {
	    (void) ZXmitStr(CanStr, 2, info) ;
	    return ZmErrCantOpen ;
	  }
	  if( (err = acceptPacket(info)) != 0 )
	    return err ;
	  return ZXmitStr((u_char *)"C", 1, info) ;
	}


	if( ZWriteFile(info->buffer+2, info->pktLen-4, info->file, info) ) {
	  ZStatus(FileErr, errno, NULL) ;
	  (void) ZXmitStr(CanStr, 2, info) ;
	  return ZmErrSys ;
	}
	info->offset += info->pktLen-4 ;
	ZStatus(RcvByteCount, info->offset, NULL) ;

	(void) acceptPacket(info) ;
	return 0 ;
}


static	int
rejectPacket( register ZModem *info )
{
	info->timeout = 10 ;
	return ZXmitStr(NakStr, 1, info) ;
}


static	int
acceptPacket( register ZModem *info )
{
	info->state = YRDataWait ;
	info->timeout = 10 ;
	return ZXmitStr(AckStr, 1, info) ;
}


static	int
calcCrc( u_char *str, int len )
{
	int	crc = 0 ;
	while( --len >= 0 )
	  crc = updcrc(*str++, crc) ;
	crc = updcrc(0,crc) ; crc = updcrc(0,crc) ;
	return crc & 0xffff ;
}