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

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


/**********
 *
 *
 *	@   @  @   @   @@@   @@@@   @@@@@  @   @  @@@@   
 *	 @ @   @@ @@  @   @  @   @  @      @@ @@  @   @  
 *	  @    @ @ @  @   @  @   @  @@@    @ @ @  @@@@   
 *	 @ @   @ @ @  @   @  @   @  @      @ @ @  @  @   
 *	@   @  @ @ @   @@@   @@@@   @@@@@  @ @ @  @   @  
 *
 *	XMODEMR - receiver side of xmodem/ymodem protocol
 *
 *	Caller sets flags defined in xmodem.h as appropriate.
 *	(default is basic xmodem)
 *
 *	This code is designed to be called from inside a larger
 *	program, so it is implemented as a state machine where
 *	practical.
 *
 *
 *	functions:
 *
 *	XmodemRInit(char *filename, Protocol p)
 *		Initiate a receive
 *
 *	XmodemRTimeout()
 *		called after timeout expired
 *
 *	XmodemRRcv(char c)
 *		called after character received
 *
 *	XmodemRAbort()
 *		abort transfer
 *
 *	all functions return 0 on success, 1 on abort
 *
 *
 *
 *	Edward A. Falk
 *
 *	January, 1995
 *
 *
 *
 **********/




#include <stdio.h>
#include <sys/types.h>
#include "xmodem.h"


	/* TODO: WXmodem */


	bool	xmodem1k = False ;
	Protocol protocol = Xmodem ;
	int	xmTfd = -1 ;
	int	xmRfd = -1 ;

	int	xmTimeout = 0 ;

	char	xmDefPath[MAXPATHLEN] ;
	char	xmFilename[MAXPATHLEN] ;

typedef	enum {
	  Start,	/* waiting to begin */
	  Init,		/* sent initial NAK, 'C' or 'W' */
	  Packet,	/* receiving a packet */
	  Wait,		/* wait for start of next packet */
	} XmodemState ;

static	bool		ymodem ;
static	XmodemState	state = Start ;
static	int		errorCount = 0 ;
static	int		errorCount2 ;
static	int		ignoreCount ;
static	int		eotCount ;	/* count EOT's, reject first one */
static	int		inCount ;	/* characters received this packet */
static	int		pktLen ;	/* length of this packet data */
static	int		pktHdrLen ;	/* id, cmpl, checksum or crc */
static	char		packet[MAXPACKET+5], *optr ;
static	int		packetId ;	/* id of last received packet */
static	int		packetCount ;	/* # packets received */

static	FILE		*ofile ;	/* output file fd */
static	int		fileLen, fileDate, fileMode ;

static	int	XmodemRStart() ;
static	int	processPacket() ;
static	int	rejectPacket() ;
static	int	acceptPacket() ;


int
XmodemRInit( char *file, Protocol prot )
{
	int	err ;

	state = Start ;
	protocol = prot ;
	ymodem = prot == Ymodem || prot == YmodemG ;

	if( ymodem )
	  strcpy(xmDefPath, file) ;
	else
	  strcpy(xmFilename, file) ;

	eotCount = errorCount = errorCount2 = 0 ;

	if( err=XmodemRStart() )
	  return err ;

	state = Init ;
	packetId = ymodem ? 255 : 0 ;
	packetCount = 0 ;

	pktHdrLen = protocol == Xmodem ? 3 : 4 ;

	return 0 ;
}


	/* send startup character */

static	int
XmodemRStart()
{
static	char	pchars[5] = {NAK,'C','W','C','C'} ;
static	int	timeouts[5] = {INITTO, INITTO2, INITTO2, INITTO, INITTO} ;
	char	c = pchars[(int)protocol] ;
	int	err ;

	if( err=sendFlush(c) )
	  return err ;

	xmTimeout = timeouts[(int)protocol] ;

	return 0 ;
}


int
XmodemRRcv(char c)
{
	errorCount = 0 ;

	switch( state ) {
	  case Start:		/* shouldn't happen, ignore */
	    if( c == CAN )
	      return XmErrCancel ;
	    break ;

	  case Init:		/* waiting */
	  case Wait:
	    switch( c ) {
	      case SOH:
	      case STX:
		pktLen = c == STX ? 1024 : 128 ;
		inCount = 0 ;
		optr = packet ;
		state = Packet ;
		xmTimeout = PKTTO ;
		break ;

	      case EOT:
		if( ++eotCount > 1 ) {
		  sendFlush(ACK) ;
		  if( ymodem )
		    return XmodemRInit() ;	/* restart protocol */
		  else
		    return XmDone ;
		}
		else
		  return rejectPacket() ;	/* make xmitter try again */

	      case CAN: return XmErrCancel ;

	      default:		/* ignore all others */
		if( ++ignoreCount > 1030 ) {
		  ignoreCount = 0 ;
		  return sendFlush(NAK) ;
		}
		break ;
	    }
	    break ;


	  case Packet:		/* mid packet */
	    *optr++ = c ;
	    if( ++inCount >= pktLen + pktHdrLen )
	      ProcessPacket() ;
	    break ;
	}
	return 0 ;
}


int
XmodemRTimeout()
{
	if( ++errorCount > MAXERROR )
	  return state == Init ? XmErrInitTo : XmErrRcvTo ;

	switch( state ) {
	  case Start: return -1 ;		/* shouldn't happen */
	  case Init:
	    if( ++errorCount2 >= 3 )
	      switch( protocol ) {
		case WXmodem: protocol = XmodemCrc ; errorCount2 = 0 ; break ;
		case XmodemCrc: protocol = Xmodem ; pktHdrLen = 3 ; break ;
	      }
	    return XmodemRStart() ;

	  case Wait:			/* timeout while waiting */
	  case Packet:			/* timeout in mid packet */
	    return rejectPacket() ;
	}
}

int
XmodemRAbort()
{
	  return sendCancel() ;
}


static	int
ProcessPacket()
{
	int	id = (u_char)packet[0] ;
	int	idc = (u_char)packet[1] ;
	int	i ;

	if( idc != 255-id )
	  return rejectPacket() ;

	if( id == packetId )		/* duplicate */
	  return acceptPacket() ;

	if( id != (packetId+1)%256 ) {	/* out of sequence */
	  (void) sendCancel() ;
	  return XmErrSequence ;
	}

	if( protocol == Xmodem )
	{
	  /* compute checksum */
	  register int csum = calcChecksum(packet+2, pktLen) ;
	  if( csum != (u_char) packet[2+pktLen] )
	    return rejectPacket() ;
	}
	else
	{
	  int crc0 = (u_char)packet[pktLen+2] << 8 | (u_char)packet[pktLen+3] ;
	  int crc1 = calcrc(packet+2, pktLen) ;
	  if( crc0 != crc1 )
	    return rejectPacket() ;
	}

	/* it's a good packet */
	packetId = (packetId+1)%256 ;


	/* is this the first packet? */

	if( packetCount == 0 )
	{
	  if( ymodem )
	  {
	    if( packet[2] == '\0' )	/* last file */
	    {
	      (void) acceptPacket() ;
	      return XmDone ;
	    }

	    if( packet[2] == '/' )
	      strcpy(xmFilename, packet+2) ;
	    else {
	      strcpy(xmFilename, xmDefPath) ;
	      strcat(xmFilename, packet+2) ;
	    }

	    fileLen = fileDate = fileMode = -1 ;
	    sscanf(packet+2+strlen(packet)+1, "%d %o %o",
		&fileLen, &fileDate, &fileMode) ;
	  }

	  if( (ofile = fopen(xmFilename, "w")) == NULL ) {
	    sendCancel() ;
	    return XmErrCantOpen ;
	  }

	  if( ymodem ) {
	    packetCount = 1 ;
	    (void) acceptPacket() ;
	    return sendFlush('C') ;
	  }
	  else
	    state = Packet ;
	}

	++packetCount ;

	/* TODO: ymodem: if this is last packet, truncate it */
	if( (i=fwrite(packet+2, 1, pktLen, ofile)) != pktLen )
	{
	  sendCancel() ;
	  return XmErrSys ;
	}
	else
	  return acceptPacket() ;
}


static	int
rejectPacket()
{
	state = Wait ;
	xmTimeout = INITTO ;
	return sendFlush(NAK) ;
}

static	int
acceptPacket()
{
	state = Wait ;
	xmTimeout = INITTO ;
	return sendFlush(ACK) ;
}



#ifdef	COMMENT		/* stand-alone testing */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/termios.h>


main(argc,argv)
	int	argc ;
	char	**argv ;
{
	struct	termios	old_settings, new_settings ;
	fd_set	readfds ;
	struct timeval timeout ;
	int	i ;
	int	len ;
	char	buffer[1024] ;
	bool	done = False ;

	if( argc < 2 )
	  exit(2) ;

	xmTfd = xmRfd = open(argv[1], O_RDWR) ;

	if( xmTfd == -1 )
	  exit(1) ;

	tcgetattr(xmTfd,&old_settings) ;
	new_settings = old_settings ;

	new_settings.c_iflag &=
	  ~(ISTRIP|INLCR|IGNCR|ICRNL|IUCLC|IXON|IXOFF|IMAXBEL) ;
	new_settings.c_oflag = 0 ;
	new_settings.c_cflag = B300|CS8|CREAD|CLOCAL ;
	new_settings.c_lflag = 0 ;
	new_settings.c_cc[VMIN] = 32 ;
	new_settings.c_cc[VTIME] = 1 ;
	tcsetattr(xmTfd,TCSADRAIN, &new_settings) ;


	xmodem1k = 0 ;
	done = XmodemRInit("foo", XmodemCrc) != 0 ;
#ifdef	COMMENT
	xmodem1k = 1 ;
	done = XmodemRInit("./", Ymodem) != 0 ;
#endif	/* COMMENT */

	while(!done)
	{
	  FD_ZERO(&readfds) ;
	  FD_SET(xmTfd, &readfds) ;
	  timeout.tv_sec = xmTimeout ;
	  timeout.tv_usec = 0 ;
	  i = select(xmTfd+1, &readfds,NULL,NULL, &timeout) ;
	  if( i<0 )
	    perror("select") ;
	  else if( i==0 )
	    done = XmodemRTimeout() != 0 ;
	  else {
	    len = read(xmTfd, buffer, sizeof(buffer)) ;
	    for(i=0; !done && i<len; ++i)
	      done = XmodemRRcv(buffer[i]) != 0 ;
	  }
	}




	tcsetattr(xmTfd,TCSADRAIN, &old_settings) ;
	exit(0) ;
}
#endif	/* COMMENT */
