/* XmodemCRC.c - Checksum/CRC Xmodem */
/*
 * Adapted from Djj's Aterm1.3 version by Jeff Lydiatt, Vancouver, Canada
 * This version supports the following features:
 *   . Both Cyclic Redundancy Check and Checksum Xmodem are supported.
 *   . AutoChop logic swiped from Aterm Version 6.1
 *   . Timeouts for receive are 5 seconds within a block, and
 *     20 seconds outside a block.
 *   . Timeouts use the timer, rather than polling the elapsed time
 *     in the standard date function.
 *   . In send mode, the last block is padded with the Amiga HUNKEND
 *     symbol (0x0f2) if I think you're sending a loadable program.  This
 *     is detected by all of the conditions, a) last block has 4 or more 
 *     characters, and b) is a even multiple of four bytes, and c) it
 *     already ends with a HUNKEND symbol.
 *        If it's not a loadable module, the padding character is a
 *     control-Z, unless it already ends with control-Z.  In this case
 *     the send file is padded with nulls ('\0').
 *   . Also for sends, the program gives a theoretical time to transmit the
 *     file based on it's size and the baud rate.
 *   . 17Aug86 - by Jal. Chop modified to allow for files like Arc which
 *     end in hexadecimal 1A00.
 *   . 17Aug86 - by Jal. Send routine padding character changed to standard
 *     of '\0' instead of CPM_EOF.  
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <stdio.h>
#include <fcntl.h>
#include <libraries/dos.h>
#include <functions.h>
#include "ConsoleIO.h"
#include "SerialIO.h"
#include "Timer.h"

/* things for xmodem send and recieve */
#define SECSIZ    128
#define BLOCKTIME 20	    /* max seconds to timeout for a new block */
#define CHARTIME  5	    /* max seconds to timeout within a block */
#define BufSize   SECSIZ*2  /* Text buffer */
#define ERRORMAX  10        /* Max errors before abort */
#define RETRYMAX  10        /* Maximum retrys before abort */
#define SOH       1         /* Start of sector char */
#define EOT       4         /* end of transmission char */
#define ACK       6         /* acknowledge sector transmission */
#define NAK      21         /* error in transmission detected */
#define HUNKEND  0x3F2L     /* Normal Load Module end marker. */
#define CAN      24         /* Cancel character -- abort transmission */
#define TIMEOUT  -1         /* timeout during receive character */
#define ABORT    -2         /* user aborted XMODEM transfer with escape key */
#define OK       TRUE       /* sector sent OK */
#define ESC      '\x1b'	    /* Use ESC to abort Xmodem transfer */
#define BELL    ('G'& 0x1F) /* Bell */
#define CPM_EOF   26
#define FOREVER for(;;)

/* Note: the fillbuf() routine depends upon buffer being longword aligned.
 *	 Having bytes_xferred immediately preceding buffer will accomplish
 *	 this for us.
 */
 
static long bytes_xferred;
static char buffer[BufSize];
static int
    fd,
    sector;
static BOOL
    cancel   = FALSE,
    timeout  = FALSE;

static short crcflag;		/* TRUE if crc mode, FALSE for checksum */
			/* can be changed in send mode by remote receiver */
static unsigned short crc;	/* 16 bit crc value */
static BOOL autoChop;
static unsigned char checksum;
static ULONG waitMask;

/*--------------------------------------------------------------*/
/*	check_abort: return TRUE if ESC pressed.		*/
/*--------------------------------------------------------------*/

static BOOL check_abort()
{
  if ( CheckKey() != ESC )
     cancel = FALSE;
  else
     {
        PutString( "Xmodem Cancelled!\n");
	PutChar( BELL );
        cancel = TRUE;
     }
  return cancel;
}


/*--------------------------------------------------------------*/
/*	sendchar: send a character to the modem.		*/
/*--------------------------------------------------------------*/

static void sendchar(ch)
int ch;
{
   SerIOWrite( (UBYTE)(ch & 0xFF) );
}


/*--------------------------------------------------------------*/
/*	readchar: timed wait for char to arrive.		*/
/*--------------------------------------------------------------*/

/*--------------------------------------
readchar() waits for a character
  timeout built in.
  Timeout condition causes error return
  'seconds' controls duration of wait.
  'doabort' TRUE indicates that CAN recvd
     will set cancel flag
----------------------------------------*/

static int readchar( seconds, doabort )
int doabort;
int seconds;
{
   UBYTE ch;
   BOOL  timerOn = FALSE;

   timeout = FALSE;
   do
     {      
	if ( CheckSerIO() )
	   {
	     ch = SerIORead();
	     if ( doabort && (ch == CAN) )
		cancel = TRUE;
	     return (int)(ch);
	   }
	else if ( (ch = CheckKey()) != '\0' )
	   if ( ch == ESC )
	     {
	        PutString( "Xmodem Cancelled!\n");
		PutChar( BELL );
		cancel = TRUE;
		return ABORT; 
	     }
	    else;
	if ( !timerOn )
	  {
	    StartTimer( (long)seconds, 0L );
	    timerOn = TRUE;
	  }
	Wait( waitMask );

      } while( !TimerExpired() );

   /* if not the above, the timer must have timed out */
   PutString( "  XMODEM Timeout.\n");
   timeout = TRUE;
   return TIMEOUT;
 
}


/*--------------------------------------------------------------*/
/*	purge: purge the serial port buffer until timeout.	*/
/*--------------------------------------------------------------*/

static void purge( purgetime )
int purgetime;
{
   timeout = FALSE;
   FlushSerIO();
   while ( timeout == FALSE )
      (void)readchar ( purgetime, FALSE );
}

/*--------------------------------------------------------------*/
/*	do_crc: computes CRC using the CCITT polynomial.	*/
/*--------------------------------------------------------------*/

static void do_crc( val )
unsigned char val;
{
   register unsigned short shifter,flag;

   if(crcflag)
     for( shifter = 0x80 ; shifter ; shifter >>= 1 )
       {
         flag = (crc & 0x8000);
         crc <<= 1;
         crc |= ((shifter & val) ? 1 : 0);
         if( flag )
            crc ^= 0x1021;
       }
   else
     checksum += val;
}

/*-----------------------------------------------------------*/
/* SetWaitMask: for Console, Serial Read or timer expired.   */
/*-----------------------------------------------------------*/

static void SetWaitMask()
{
   waitMask = ( 1L << GetConsoleSigBit() )
	    | ( 1L << GetSerialSigBit()  )
	    | ( 1L << GetTimerSigBit()   );
}

/*--------------------------------------------------------------*/
/*	CloseEmUp: write and chop the last block.		*/
/*--------------------------------------------------------------*/

static void CloseEmUp( sector )
int sector;
{
   register int i;
   int len;
   ULONG filesize = 0;
   register UBYTE c;
   register char *buf;

 if ( sector > 0 )		/* skip if no data at all */
  {
    buf = &buffer[ (sector & 1) * SECSIZ ]; 
    i = SECSIZ - 1;
    c = '\0';

    if ( !autoChop ) 
      {
	c = 0xff;
	PutString( "Chop Disabled: " );
      }
    else
      {
        c = buf[ i ];	   /* c gets last char in block */
        if ( (c == '\0') || (c == CPM_EOF) || (c == 0xFF) )
	  {
	     while ( i > 0 )
	       if ( buf[ --i ] != c )
		  break;
	  }
      }

    if ( c == '\0' && buf[ i ] == CPM_EOF )
	/* It may be an Arc file which normally ends in 1A00 so ... */
	i++;
    i++;
    if ( write( fd, buf, i) != i )
        PutString("Error writing final block\n");

    len = SECSIZ - i;
    format( PutFormat, "%d bytes chopped from final block\n\xff", &len);

    filesize = ( --sector ) * (long)(SECSIZ) + (long)( i );
    format( PutFormat, "File size: %ld\n\xff", &filesize);
   }

}


/*--------------------------------------------------------------*/
/*	verify_checksum: return TRUE if checksum or crc ok.	*/
/*--------------------------------------------------------------*/

static BOOL verify_checksum()
{
   if( crcflag )
      {
         do_crc( readchar(CHARTIME,FALSE) );
         do_crc( readchar(CHARTIME,FALSE) );
         return (int) ~crc;
      }
   else
      return ( checksum == readchar(CHARTIME,FALSE) );
}

/*--------------------------------------------------------------*/
/*	getfile: Do the handshake, read a sector, until EOT.	*/
/*--------------------------------------------------------------*/

static BOOL getfile()
{
   unsigned char ch, currsect, compsect, response;
   int i, j;
   register char *bptr;      /* rx buffer index */

   sector = 1;
   response = (crcflag) ? 'C' : NAK;
   cancel = FALSE;
   FlushSerIO();

   FOREVER           /* sector receive loop */
   {

      /*--------------------------------------------------------*/
      /*	Handshake loop.					*/
      /*--------------------------------------------------------*/

      for( i=1 ; !cancel && (i <= RETRYMAX) ; i++ )
        {
          sendchar( response );
          if (timeout && (sector == 1) && ( i == RETRYMAX / 2) )
            {
		crcflag = (crcflag == FALSE);
		response = (crcflag) ? 'C' : NAK;
		PutString( "Sender not responding, switching to ");
		PutString(  (crcflag) ? "CRC" : "Checksum");
		PutString( " mode\n");
            }
          if( (ch = readchar(BLOCKTIME,TRUE)) == SOH )
               break;
          if( ch == EOT )   /* if EOT , we're done */
           {
	     sendchar( ACK );
             CloseEmUp( --sector );
             return TRUE;
           }
         if( ch == CAN )  /* aborted */
           {
	     purge( CHARTIME );
             PutString( "Sender cancelled transmission.\n");
             return FALSE;
           }
         if( i == RETRYMAX )   /* timed out */
            {
              PutString( "Sender not responding\nXMODEM Cancelled!\n" );
              return FALSE;
            }
      } /* end for (i <= RETRYMAX) */

      if ( cancel )
	{
	   purge( CHARTIME );
	   sendchar( CAN );
	   return FALSE;
	}
	
      /*--------------------------------------------------------*/
      /*	 Here Comes a Block				*/
      /*--------------------------------------------------------*/

      currsect = readchar(CHARTIME,FALSE);
      if ( !cancel )
         compsect = readchar(CHARTIME,FALSE);

      checksum = crc = 0;
      bptr = &buffer[ ( sector & 1 ) * SECSIZ ];
      for( j = 0 ; !(cancel || timeout) && j < SECSIZ ; j++ )
        {
          *bptr = readchar( CHARTIME, FALSE );
          do_crc( *bptr++ );
        }


      /*--------------------------------------------------------*/
      /*	Any errors in receiving the block?		*/
      /*--------------------------------------------------------*/

      if ( cancel || timeout ) 
	 continue;

      response = NAK;      /* default response */
      if (!verify_checksum())
        {
           PutString( "Checksum error\n");
           continue;
        }

      if ( (~currsect & 0xFF) != (compsect & 0xFF) )
         continue;

      if ( (currsect == (sector - 1) & 0xFF ) )   /* dup sector number? */
        {
         response = ACK;
         continue;                              /* ignore it */
        }
      if ( currsect != (sector & 0xFF) )         /* sequence error? */
         {
            PutString(  "Sector numbering error - Aborting." );
            sendchar(CAN);
            return FALSE;
         }

      if ( sector > 1 )
	if( write(fd, &buffer[ ((sector-1) & 1) * SECSIZ ], SECSIZ) != SECSIZ)
	   {
	     PutString( "Error writing file\n");
	     sendchar(CAN);
	     return FALSE;
	   }

      format( PutFormat, "\x0bReceived block # %d    \n\xFF", &sector );
      response = ACK;      /* sector received ok */
      sector++;

   } /* end FOREVER */
 
}


/*--------------------------------------------------------------*/
/*	XMODEM_Read_File: the external entry point for readfile.*/
/*--------------------------------------------------------------*/

BOOL XMODEM_Read_File(file, xfermode, chopFlag)
char *file;
BOOL xfermode, chopFlag;
{
   BOOL status;

   cancel = FALSE;
   crcflag = xfermode;
   autoChop = chopFlag;
   SetWaitMask();
   
   if ((fd = creat(file, 0)) < 0)
     {
       PutString( "Cannot Open File\n");
       sendchar(CAN);
       return FALSE;
     }
   else
     PutString( "Receiving File\n\n" );

   status = getfile();
   close( fd );
   return status;
}

/*--------------------------------------------------------------*/
/*	doTellSize: tell file size and how long to send it.	*/
/*--------------------------------------------------------------*/

static void doTellSize(file, baud)
char *file;
int   baud;
{
   struct parms
     {
       LONG size;
       WORD sectors;
       WORD minutes;
       WORD seconds;
       WORD baudrate;
     } args;
   struct FileInfoBlock *FBlock;
   struct FileLock      *FLock;

   if ( (FLock = (struct FileLock *) Lock(file, ACCESS_READ)) == NULL)
      return;

   if ( (FBlock = (struct FileInfoBlock *)
     AllocMem( (long)sizeof(struct FileInfoBlock), (long)(MEMF_CHIP))) != NULL)
   if ( Examine( FLock, FBlock) )
     {
	args.size = FBlock->fib_Size;
	if (args.size != 0)
          {
	    args.sectors = (args.size + SECSIZ - 1) / SECSIZ;
	    format( PutFormat, 
		"File contains %ld bytes, %d sectors.\n\xff", &args.size );

	   /* compute time for sending file */
	    args.baudrate = baud;
	    args.seconds = (float)args.sectors / (float)args.baudrate
                         * 133 * 11;
	    args.minutes = args.seconds / 60;
	    args.seconds %= 60;
	    format( PutFormat,
    "Approximately %d minutes and %d seconds for transfer at %d bps.\n\xff",
	    &args.minutes );
          }
	FreeMem( FBlock, (long)sizeof(struct FileInfoBlock) );
     }

   UnLock( FLock );
   return;
}

/*--------------------------------------------------------------*/
/* sendsector: send a sector with resend on error from receiver.*/
/*--------------------------------------------------------------*/

static int sendsector( buf )
unsigned char *buf;
{
   int badblock;
   register int i;
   register unsigned char *bptr;

   badblock = 0;
   cancel = FALSE;

   FOREVER
   {
      if( badblock == ERRORMAX )
         return TIMEOUT;
      if( cancel || check_abort() )
         return ABORT;

      FlushSerIO();             /* flush any garbage from line */
      format( PutFormat,  "\x0bSending sector  %d\n\xFF", &sector );
      sendchar( SOH );          /* start of block */
      sendchar( sector );       /* sector number  */
      sendchar( ~sector );      /* compliment of sector number */
      checksum = crc = 0;       /* clear CRC and checksum */

      bptr = buf;
      for( i = 0 ; i < SECSIZ ; i++ )
        {
          sendchar( *bptr );
          do_crc( *bptr++ );
        }
      FlushSerIO();              /* flush garbage from receiver */
      if( crcflag )
      {
         do_crc(0); do_crc(0);   /* cycle CRC generator */
         sendchar( crc >> 8 );   /* High byte */
         sendchar( crc );        /* Low byte */
      }
      else
         sendchar( checksum );

      if( readchar(BLOCKTIME, TRUE ) == ACK)
         break;      
      else
         badblock++;
   }
   sector++;            /* bump sector count   */
   return OK;
}


/*--------------------------------------------------------------*/
/*	fillbuf: fill the buffer. Return TRUE data to be sent.	*/
/*--------------------------------------------------------------*/
static BOOL fillbuf()
{
   register int bytes_read;
   register long *iptr;
   char     padchar;

   bytes_read = read(fd, buffer, SECSIZ);
   if (bytes_read <= 0)    /* end of file */
     {
	if ( bytes_read < 0 )
	   PutString( "Error reading Send File. Xmodem Cancelled.\n" );
	return FALSE;
     }

   else if( bytes_read == SECSIZ )
	return TRUE;
 
   /* Pad with Hunk ends if this is a load module */
      
   if ( (bytes_read >= 4) && ( (bytes_read % 4) == 0 ) )
      iptr = (long * ) ((long) buffer + bytes_read - 4);
   else
      iptr = NULL;
   if ( (iptr != NULL) && (*iptr == HUNKEND) )
      for ( iptr++; bytes_read < SECSIZ; bytes_read += 4 )
	  *iptr++ = HUNKEND;

   /* Otherwise, make the padding character != last character of data */
        
   else
      {
	if ( buffer[ bytes_read - 1 ] != '\0' )
	   padchar = '\0';
	else
	   padchar = CPM_EOF;
        for(; bytes_read < SECSIZ; bytes_read++)
	   buffer[bytes_read] = padchar;
      }    
   return TRUE;
}

/*--------------------------------------------------------------*/
/*	putfile: Handshake & send the file by sector.		*/
/*--------------------------------------------------------------*/

static BOOL putfile()
{
   int timeoutcount, status;
   UBYTE ch;

   sector = 1;  timeoutcount = 0;

   FlushSerIO();     /* flush any garbage in receiver */
   do                /* start handshake with receiver */
      {
         ch = readchar(BLOCKTIME,TRUE);
         if(timeout)
           {
             if(++timeoutcount == RETRYMAX)
               {
                  PutString( "No response, aborting\n");
                  return FALSE;
               }
            }
         if( cancel ) return FALSE;
      } while (ch != NAK && ch != 'C');

   status = OK;
   crcflag = (ch == 'C');
   PutString(  crcflag ? "CRC" : "Checksum");
   PutString( " mode requested\n\n");

   while( fillbuf() && status == OK )  /* fill file buffer until EOF */
      status = sendsector( buffer );   /* send sector */

   cancel = FALSE;
   timeoutcount = 0;
   do
     {
      FlushSerIO();
      sendchar( EOT );     /* end transmission */
      if ( timeout )
	{
	  if ( timeoutcount++ == RETRYMAX || cancel || check_abort())
	     return FALSE;
	  PutString( "Awaiting Acknowledge of EOT...\n" );
	}
     } while ( readchar( BLOCKTIME, TRUE ) != ACK);

   if ( status == TIMEOUT || status == ABORT || timeoutcount >= RETRYMAX )
      return FALSE;
   else
      return TRUE;
}

/*--------------------------------------------------------------*/
/*	XMODEM_Send_File: External entry for Xmodem Send.	*/
/*--------------------------------------------------------------*/

XMODEM_Send_File(file, baudrate)
char *file;
int baudrate;
{
    int status, timeoutcount;
    unsigned char ch;

    cancel = FALSE;
    SetWaitMask();
    
    doTellSize(file, baudrate);
    if ((fd = open(file, O_RDONLY)) < 0)
      {
        PutString( "Cannot Open Send File\n");
        sendchar(EOT);
        return FALSE;
      }
    else
        PutString( "Sending File\n \n \n");
    status = putfile();

    close( fd );
    return status;
}
