/* Comm XMODEM receive file routines */
#define  XMDMRECV 1
#include "globals.h"
#include <fcntl.h>

#define  STARTUP     1
#define  STARTBLK    2
#define  BLKNUM      3
#define  COMPBLK     4
#define  DATA        5
#define  CHKSUM      6
#define  CHKSUM1     7

static int   index, state;
extern void emit_tx(), emit_rx(), emits_tx(), emits_rx();

/****************************/
/* xmodem recieve functions */
/****************************/

XMODEM_Read_File(file)
UBYTE *file;
{
   extern void sendchar();
   extern int  readchar();

   void chop_file();
   UBYTE  currsect, compsect, response;
   USHORT dirty, syncerr, errors, blkerrok;
   ULONG  bytes;
   unsigned int i, noresponse, errsect, naks;
   int ch;

   errors = errsect = naks = syncerr = noresponse = index = bytes = 0;

   blkerrok = abort = dirty = FALSE;
   crcflag = xfrmode;
   if ((fd = creat(file, O_CREAT | O_WRONLY)) == -1)
     {
       sprintf(sbuff,"\nCannot Open File %s\n",file);
       emits_rx(sbuff);
       send_proto(CAN); send_proto( CAN );
       return FALSE;
     }
   else
     emits_rx("\nReady to Receive File -- ESC aborts transfer\n\n");

   if(viewflg)
      emit_vw(12);
   Xconfig(TRUE);           /* set serial port to 8/N/1  */
   sector = 1;

   if(wxflag)
      if(sendWs() == TRUE)
         return(WXmodem_rec(file));

   response = (crcflag) ? 'C' : NAK;
   state = STARTUP;

   emits_rx("Waiting for initial handshake\n");
   send_proto(response);

   while( TRUE )
   {
     Process_window_event();
     ch = readchar(TTIME,FALSE);
     if(ch == TIMEOUT)  noresponse++;
     if(errors < RETRYMAX)
       switch(state)
       {
       case STARTUP:
            if(abort) return FALSE;
            if( ch == TIMEOUT)
            {
              if(noresponse == RETRYMAX/2)
              {
                crcflag = (crcflag == FALSE);
                response = crcflag ? 'C' : NAK;
                sprintf(sbuff,"\rSender not responding. Switching to %s mode\n",
                crcflag ? "CRC" : "Checksum");
                emits_rx(sbuff);
              }
              else if(noresponse == RETRYMAX)
                      break;
              send_proto(response);
              continue;
            }  /* fall thru to STARTBLK state */

       case STARTBLK:
            if(abort) break;
            switch( ch )
            {
              case SOH:
                  emit_rx_protocol(ch);
                  state = BLKNUM;
                  noresponse = 0;
                  response = NAK;
                  if(dirty)
                  {
                     movmem(xbuffer,&diskbuff[index++ * SECSIZ],SECSIZ);
                     dirty = FALSE;
                  }
                  break;
              case EOT:
                  if(syncerr) continue;
                  emit_rx_protocol(ch);
                  chop_file(index);
                  close( fd );
                  send_proto( ACK );
                  return TRUE;
              case TIMEOUT:
                     send_proto( NAK );
                     naks++; blkerrok = TRUE;
                     errsect = sector;
                     break;
              default:
                  syncerr = 1;
                  break;
            }   /* switch( ch ) */
            break;

       case BLKNUM:
            if(ch == TIMEOUT) break;
            noresponse = 0;
            emit_rx_protocol(ch);
            currsect = ch;
            state = COMPBLK;
            if(syncerr)
               if((currsect != (UBYTE)sector) && (currsect != (UBYTE)(sector - 1)))
               {
                  if(ch == SOH )
                     state = BLKNUM;
                  else
                     state = STARTBLK;
               }
            continue;

       case COMPBLK:
            if(ch == TIMEOUT) break;
            emit_rx_protocol(ch);
            compsect = ch;
            noresponse = i = checksum = crc = 0;
            state = DATA;
            if(syncerr)
            {
               if(currsect == ~compsect)
                  syncerr = 0;
               else state = STARTBLK;
            }
            continue;

       case DATA:
            if(ch == TIMEOUT) break;
            noresponse = 0;
            xbuffer[ i++ ] = ch;
            do_crc( ch );
            if(viewflg)
               emit_vw( ch );
            if( i == SECSIZ)
               state = CHKSUM;
            continue;

       case CHKSUM:
            if(ch == TIMEOUT) break;
            noresponse = 0;
            if(crcflag)
            {
              state = CHKSUM1;
              emit_rx_protocol(ch);
              do_crc( ch );
              continue;
            }  /* fall thru to CHKSUM1 state */

       case CHKSUM1:
            if(ch == TIMEOUT) break;
            noresponse = 0;
            emit_rx_protocol(ch);
            state = STARTBLK;
            response = NAK;
            if(abort)
            {
               abort_xfer();
               return FALSE;
            }
            if(!verify_checksum( ch ))
            {
              emits_rx("  Checksum error\n");
              send_proto( NAK );
              errsect = sector; naks++; errors++;
              status(file,bytes,naks,errsect);
              continue;
            }
            if(currsect != ~compsect)
            {
              emits_rx("  Bad block number received\n");
              send_proto( NAK );
              errsect = sector; naks++; errors++;
              status(file,bytes,naks,errsect);
              continue;
            }
            if(currsect == (UBYTE)(sector -1))
            {
               if(blkerrok)  blkerrok = FALSE;
               else
               {
                 sprintf(sbuff,"  Duplicate block %d received\n",sector - 1);
                 emits_rx(sbuff);
               }
               status(file,bytes,naks,errsect);
               if(check_line() == FALSE)  /* if an RX char is ready */
                  send_proto( ACK );        /* don't send ACK */
               response = ACK;
               errors++;
               continue;
            }
            if(currsect != (UBYTE)sector )
            {
              emits_rx("  Sector numbering error\n");
              send_proto( NAK );
              errsect = sector; naks++; errors++;
              status(file,bytes,naks,errsect);
              continue;
            }
            errors = 0;
            bytes += SECSIZ;
            dirty = TRUE;
            sprintf(sbuff,"\rReceived block %d",sector);
            emits_rx(sbuff);
            status(file,bytes,naks,errsect);
            sector++;
            if(index == numbufs)
            {
               if(write(fd,diskbuff,numbufs * SECSIZ) != numbufs * SECSIZ)
               {
                 emits_rx("\nERROR writing to file\n");
                 send_proto( CAN ); send_proto( CAN );
                 return FALSE;
               }
               index = 0;
            }
            if(check_line() == FALSE)  /* if an RX char is ready */
               send_proto( ACK );        /* don't send ACK */
            response = ACK;
            continue;

     default:
            sprintf(sbuff,"\nIllegal internal state %d\n",state);
            emits_rx(sbuff);
            state = STARTBLK;
            continue;
     } /* end switch */
     if( noresponse == RETRYMAX || errors == RETRYMAX )
     {
       emits_rx("Maximum errors exceeded. XMODEM receive aborted\n");
       abort_xfer();
       return FALSE;
     }
     if(abort)
     {
        abort_xfer();
        return FALSE;
     }
   } /* end while(TRUE) */
}

send_proto(ch)
UBYTE ch;
{
   sendchar(ch);
   emit_tx_protocol(ch);
}


abort_xfer()
{
   int ch, errs = 0;

   chop_file(index);
   while((ch = readchar(1,FALSE)) != TIMEOUT)
      ;
   while(errs++ < 3)
   {
      send_proto(CAN); send_proto(CAN);
      if(readchar(3,FALSE) == ACK)  break;
   }
}

status(file,bytes,naks,errsect)
UBYTE *file;
ULONG bytes;
USHORT naks,errsect;
{
    sprintf(sbuff,"File=%s  bytes=%ld   naks=%d [ %d ]\r",
          file,bytes,naks,errsect);
    status_line(0,sbuff);
}

verify_checksum( byte )
UBYTE byte;
{
   if(crcflag)
      {
         do_crc( byte );
         return (int)(crc == 0);
      }
   else
      return ( checksum == byte );
}



/*******************************
 remove pad characters from the
 end of the last sector of the
 file.
*******************************/

void chop_file(index)
{
   int count,i;
   UBYTE c;

/*
   strip control Z  -- CP/M and Aterm 1.4 use this for PAD character.
   Don't remove NULLs.  Some programs pad with NULLs.  This practice
   should stop, because some icon.INFO files require a NULL as the last
   character in the file to terminate an ASCII string.
*/
  if(chopflg)
   {
     for(i = SECSIZ-1; i >= 0; i--)
       {
         c = xbuffer[ i ];
         if( c == PAD )
            continue;
         break;
       }
   }
  else i = SECSIZ-1;    /* not chopping, write last sector intact */

  count = 0;
  if(index)
     count = write(fd,diskbuff,index * SECSIZ);

  count += write(fd,xbuffer,++i);
  if ( count != index * SECSIZ + i)
      emits_rx("\nError writing file\n");
}

