/***************************************************************************
 *                                                                         *
 *   FTFXYMD.C                                                             *
 *                                                                         *
 *   Copyright (c) 1991-1997 Galacticomm, Inc.      All Rights Reserved.   *
 *                                                                         *
 *   File Transfer Software for XMODEM and YMODEM                          *
 *                                                                         *
 *   With all operating-system-specific functions removed (I/O, disk,      *
 *   memory, time), the pure file transfer algorithm can be isolated,      *
 *                                                                         *
 *                                               - R. Stein  5/27/91       *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "ftf.h"
#include "ftfxymd.h"

#define FILREV "$Revision: 4 $"

/*--- X/YMODEM receiver timing, etc.  ---*/
#define XSWAIT   (10*16)                    /* wait for output buffer empty */
#define XUWAIT    (2*16)            /* timeout between NAKs for first block */
#define XUABWAIT  (1*16)           /* settling period when aborting receive */
#define XUABMAX   (2*16)   /* max wait for abort to settle, even amid noise */
#define XUWNOISE     (8)   /* settling period after incoming noise detected */
#define XANWAIT      (1)         /* wait for noise to subside when aborting */
#define XUEWAIT   (2*16)     /* max patience for ACK of final EOT to go out */

/*--- X/YMODEM receiver states ---*/
#define XUENTRY   1          /* instant initial NAK (only state <= XUSTART) */
#define XUSTART   2   /* quick retry of inital NAK's before any chars rec'd */
#define XUHEAD    3           /* waiting for initial SOH or STX (quiescent) */
#define XUBLKNUM  4             /* got first byte, waiting for block number */
#define XUBLKNOT  5       /* got block number, waiting for block number not */
#define XUBLOCK   6                         /* receiving bytes of the block */
#define XUCRCH    7                /* waiting for high-byte of CRC (if any) */
#define XUCRCL    8                        /* waiting for (low byte of) CRC */
#define XULOGGED  9                     /* transition to next received file */
#define XUEOT     10   /* NAK'd 1st EOT in batch mode, wait to ACK next EOT */
#define XUNOISE   11                      /* waiting for silence amid noise */
#define XUEND     12          /* session all done, waiting for some silence */

/*

File Receive State Diagram, Batch Protocols
-------------------------------------------
          |
        XUENTRY
          |
        XUSTART
   _____  |  _____
  |     | | |     |      send C or G periodically
  |     XUHEAD    |                               receive SOH or STX or EOT
  |   __| |       |
  |  |    |       |
  |  |  XUBLKNUM  |                               receive block number
  |  |    |       |
  |  |  XUBLKNOT  |                               receive NOT(block number)
  |  |    |       |
  |  |  XUBLOCK   |                               receive 128 or 1024 byte blk
  |  |    |       |
  |  |  XUCRCH    |                               receive CRC high byte
  |  |    |       |
  |  |  XUCRCL    |                               receive CRC low byte or cksum
  |  |    |_______|      send ACK
  |  |    |
  |  |  XUEND
  |  |
  |  |____
  |       |              send NAK to EOT
  |     XUEOT                                     receive another EOT
  |       |              send ACK to EOT
  |     XULOGGED
  |_______|              send C or G


File Receive State Diagram, Non-Batch Protocols
-----------------------------------------------
          |
        XUENTRY
          |
        XUSTART
          |  _____
          | |     |      send NAK or C periodically
        XUHEAD    |                               receive SOH or STX or EOT
      __| |       |
     |    |       |
     |  XUBLKNUM  |                               receive block number
     |    |       |
     |  XUBLKNOT  |                               receive ~(block number)
     |    |       |
     |  XUBLOCK   |                               receive 128 or 1024 byte blk
     |    |       |
     |  XUCRCH    |                               receive CRC high byte
     |    |       |
     |  XUCRCL    |                               receive CRC low byte or cksum
     |    |_______|      send ACK
     |
     |____
          |              send ACK to EOT
        XUEND

*/

/*--- X/YMODEM transmitter timing, etc.  ---*/
#define XDEWAIT  (4*16)  /* timeout before giving up on ACK of EOT (or EOB) */
#define XDABWAIT (1*16)                                 /* wait after abort */
#define XDBWAIT  (5*60*16)                           /* transmitter timeout */

/*--- X/YMODEM transmitter states ---*/
#define XDFILE    1            /* waiting for C or G requesting file header */
#define XDFAGN    2     /* sent file header, waiting for ACK (YMODEM-Batch) */
#define XDBLK1    3           /* waiting for NAK/C/G to first block of file */
#define XDBLOCK   4         /* waiting for ACK to subsequent blocks of file */
#define XDFDONE   5      /* wait for all bytes of last block to get xmitted */
#define XDEOF     6                                  /* wait for ACK to EOT */
#define XDFIN     7 /* wait for C or G requesting what will be end-of-batch */
#define XDFACK    8                         /* wait for ACK to end-of-batch */
#define XDEND     9      /* that's it, next contin() will terminate session */

/****************************************************************************

File Transmit State Diagram, Batch Protocols
--------------------------------------------
      ________|
     |        |
     |      XDFILE                             receive C or G
     |        |       send file header
     |      XDFAGN                             receive ACK
     |        |
     |      XDBLK1                             receive C or G
     |    ____|       send 1st block of file
     |   |    |
     |   |  XDBLOCK                            receive ACK's
     |   |____|       send other blks of file
     |        |
     |      XDFDONE
     |        |       send EOT
     |      XDEOF                              receive ACK
     |________|
              |
            XDFIN                              receive C or G
              |       send end-of-batch header
            XDFACK                             receive ACK
              |
            XDEND


File Transmit State Diagram, Non-Batch Protocols
------------------------------------------------
              |
            XDBLK1                             receive NAK or C
          ____|       send 1st block of file
         |    |
         |  XDBLOCK                            receive ACK's
         |____|       send other blks of file
              |
            XDFDONE
              |       send EOT
            XDEOF                              receive ACK
              |
            XDEND

*****************************************************************************/

/*  Protocol Characters  */
/*-----------------------*/
#define SOH     '\001'
#define STX     '\002'
#define EOT     '\004'
#define ACK     '\006'
#define NAK     '\025'
#define CAN     '\030'
#define CPMEOF  '\032'

#define EOTSTG  "\004"
#define ACKSTG  "\006"
#define XONSTG  "\021"
#define XOFFSTG "\023"
#define NAKSTG  "\025"
#define CANSTG  "\030"
#define EOFSTG  "\032"

/*--- X/YMODEM utilities ---*/

static CHAR
cksum(                              /* Compute the checksum of a byte array */
CHAR *cp,
INT n)
{
     CHAR cks=0;

     while (--n >= 0) {
          cks+=*cp++;
     }
     return(cks);
}

/*--- X/YMODEM Receiving ---*/

static VOID
xyrini(VOID)                                /* Initialize X/YMODEM receiver */
{
}

static INT
frstart(VOID)                        /* time to open up that receiving file */
{                                                           /* 1=ok 0=can't */
            /* called by fhproc() for batch sessions, by xrfile() otherwise */

     if (ftfrop(0,0,0) != 0) {
          ftfabt(ftfscb->abwhy);
          return(0);
     }
     ftfsrt();
     if (xymscb->flags&XUFG) {
          ftfout("G",1);
     }
     xymscb->flags|=XUTIME;
                    /* (restart timer now that header is about to be ACK'd) */
     return(1);
}

static INT
xrfile(VOID)                              /* prepare for next received file */
{                                              /* 1=ok 0=couldn't open file */
     xymscb->nak=(xymscb->flags&XUFG) ? 'G' :
                ((xymscb->flags&XUFCRC) ? 'C' : NAK);
     xymscb->retry=ftfpsp->retrys;
     if (xymscb->flags&XUBATCH) {
          xymscb->flags|=XUNAMNX;
          xymscb->blknum=0;
          ftfstf();        /* (wouldn't be necessary normally, but XULOGGED */
     }               /* transitions directly to XUHEAD for longer timeouts) */
     else {
          xymscb->blknum=1;
          if (!frstart()) {
               return(0);
          }
     }
     return(1);
}

static INT
fhproc(VOID)                                /* process incoming file header */
{                 /* returns 1=more data to come, 0=something else going on */
     CHAR *cp;

     xymscb->flags&=~XUNAMNX;
     if (xymscb->block[0] == '\0') {                        /* end of batch */
          if (!(xymscb->flags&XUFG)) {
               ftfout(ACKSTG,1);
          }
          ftfnew(XUEND);
          return(0);
     }
     movmem(xymscb->block,ftfscb->fname,8+1+3+1);
     fnmcse(ftfscb->fname);
     ftfscb->unxtim=0L;
     ftfscb->estbyt=0L;
     cp=xymscb->block;
     cp+=strlen(cp)+1;
     if (*cp != '\0') {
          sscanf(cp,"%lu%lo",&ftfscb->estbyt,&ftfscb->unxtim);
     }
     return(frstart());
}

static INT
toxmin(                    /* apply xymscb->toxtra minimum to timeout value */
INT tmo)
{
     return(max(tmo,xymscb->toxtra));
}

static VOID
xmrsrt(VOID)                       /* Begin XMODEM-Checksum receive session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=0;
     if (xrfile()) {
          ftfnew(XUENTRY);
     }
}

static VOID
xcrsrt(VOID)                            /* Begin XMODEM-CRC receive session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUFCRC;
     if (xrfile()) {
          ftfnew(XUENTRY);
     }
}

static VOID
x1rsrt(VOID)                             /* Begin XMODEM-1K receive session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC;
     if (xrfile()) {
          ftfnew(XUENTRY);
     }
}

static VOID
ybrsrt(VOID)                          /* Begin YMODEM Batch receive session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC+XUBATCH;
     if (xrfile()) {
          ftfnew(XUENTRY);
     }
}

static VOID
ygrsrt(VOID)                              /* Begin YMODEM-g receive session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC+XUBATCH+XUFG;
     if (xrfile()) {
          ftfnew(XUENTRY);
     }
}

static INT
xgabort(                           /* 'G' option is intollerant of msitakes */
CHAR *why)
{
     ftfscb->stecnt++;
     if (xymscb->flags&XUFG) {
          ftfabt(why);
          return(1);
     }
     return(0);
}

static VOID
xyrtmo(                                         /* X/YMODEM receive timeout */
INT newstt)                 /* new state to go to (if not too many retries) */
{
     ftfscb->tmouts++;
     if (xymscb->flags&XUNAMNX) {
          ftfscb->stecnt++;
     }
     else if (xgabort("Timeout.")) {
          return;
     }
     if (xymscb->retry >= 0 && xymscb->retry-- <= 0) {
          ftfabt("Too many retries.");
     }
     else {
          ftfout(&xymscb->nak,1);
          ftfnew(newstt);
     }
}

static VOID
hdleot(VOID)                            /* Acknowledge received end-of-file */
{
     ftfout(ACKSTG,1);
     ftfrcl(1);
     ftfscb->isopen=0;
     ftfscb->actfil++;
     ftfnew((xymscb->flags&XUBATCH) ? XULOGGED : XUEND);
}

static INT
xyrctn(VOID)                                   /* Continue X/YMODEM receive */
{                                                          /* return 0=done */
     INT since,sncbyt;

     since=ftftck-ftfscb->tckstt;
     sncbyt=ftftck-ftfscb->tckbyt;
     switch(ftfscb->state) {
     case XUSTART:
          if (since <= toxmin(XUWAIT)) {
               break;
          }
     case XUENTRY:
          ftfout(&xymscb->nak,1);
          ftfnew(XUSTART);
          break;
     case XUHEAD:
     case XUBLKNUM:
     case XUBLKNOT:
     case XUBLOCK:
     case XUCRCH:
     case XUCRCL:
          if (since > toxmin(ftfpsp->paktmo)
          || sncbyt > toxmin(ftfpsp->byttmo)) {
               xyrtmo(XUHEAD);
          }
          break;
     case XUEOT:
          if (since > toxmin(ftfpsp->paktmo)
          || sncbyt > toxmin(ftfpsp->byttmo)) {
               xyrtmo(XUEOT);
          }
          break;
     case XUNOISE:                        /* waiting for silense amid noise */
          if (sncbyt > XUWNOISE) {
               ftfnew(XUHEAD);
          }
          else if (since > toxmin(ftfpsp->paktmo)) {
               xyrtmo(XUNOISE);
          }
          break;
     case XULOGGED:
          xrfile();
          ftfout(&xymscb->nak,1);
          ftfnew(XUHEAD);
          break;
     case FTFABORT:
          ftfrca();
          ftfcan();
          ftfnew(FTFABWAIT);
          ftfscb->tckbyt=ftftck-XANWAIT;                      /* (PCP help) */
          break;
     case FTFABWAIT:
          if (since > XUABWAIT && (sncbyt > XUABWAIT || since > XUABMAX)) {
               return(-1);
          }
          break;
     case XUEND:
          if (ftfoba() == ftfomt || since > XUEWAIT) {
               return(0);
          }
          break;
     }
     return(1);
}

static VOID
xyrinc(        /* Handle incoming character during X/YMODEM receive session */
CHAR c)
{
     LONG ln;
     INT rc;

     switch(ftfscb->state) {
     case XUENTRY:
     case XUSTART:
          if (xymscb->flags&XUTIME) {
               ftfstf();
               xymscb->flags&=~XUTIME;
          }
     case XUHEAD:
          if (c == SOH) {
               ftfscb->paksiz=128;
               ftfnew(XUBLKNUM);
          }
          else if (c == STX && (xymscb->flags&XUF1024)) {
               ftfscb->paksiz=1024;
               ftfnew(XUBLKNUM);
          }
          else if (c == EOT && !(xymscb->flags&XUNAMNX)) {
               if ((xymscb->flags&(XUBATCH+XUFG)) == XUBATCH) {
                    ftfout(NAKSTG,1);
                    ftfnew(XUEOT);
               }
               else {
                    hdleot();
               }
          }
          else if (c == CAN) {
               ftfabt("Operator CTRL-X abort.");
          }
          else if (c == '\r' && ftfscb->state <= XUSTART) {
          }                    /* swallow Telix's <CR> at start of G upload */
          else {
               ftfscb->garbag++;
               if (!xgabort("Bad character.")) {
                    ftfnew(XUNOISE);
               }
               break;
          }
          ftfscb->stecnt=0;
          break;
     case XUBLKNUM:
          xymscb->gotbkn=c;
          ftfnew(XUBLKNOT);
          break;
     case XUBLKNOT:
          xymscb->notbkn=c;
          xymscb->nbytes=0;
          ftfnew(XUBLOCK);
          break;
     case XUBLOCK:
          xymscb->block[xymscb->nbytes++]=c;
          if (xymscb->nbytes >= ftfscb->paksiz) {
               if (xymscb->flags&XUFCRC) {
                    xymscb->block[ftfscb->paksiz]=0;
                    xymscb->block[ftfscb->paksiz+1]=0;
                    xymscb->crc=crc16(xymscb->block,xymscb->nbytes+2);
                    ftfnew(XUCRCH);
               }
               else {
                    xymscb->crc=cksum(xymscb->block,xymscb->nbytes);
                    ftfnew(XUCRCL);
               }
          }
          break;
     case XUCRCH:
          xymscb->crc-=(c<<8);
          ftfnew(XUCRCL);
          break;
     case XUCRCL:
          if (xymscb->crc == c) {
               if (xymscb->gotbkn == xymscb->blknum-1 &&
                   xymscb->notbkn == ((xymscb->blknum-1)^0xFF)) {
                            /* previous block again?  ignore it, but ACK it */
                    ftfscb->retrys++;
                    if (xgabort("Duplicate block.")) {
                         break;     /* (except YMODEM-g wont tolerate dups) */
                    }
               }
               else if (xymscb->gotbkn == xymscb->blknum &&
                        xymscb->notbkn == (xymscb->blknum^0xFF)) {
                                               /* got expected block number */
                    if (xymscb->flags&XUNAMNX) {        /* file header rcd: */
                         if (!fhproc()) {
                              break;
                         }
                    }
                    else {                           /* data block received */
                         ftfscb->actbyt+=xymscb->nbytes;
                         if (ftfscb->estbyt > 0L
                          && (ln=ftfscb->actbyt-ftfscb->estbyt) > 0L) {
                              if (ln > xymscb->nbytes) {
                                   xymscb->nbytes=0;
                              }
                              else {
                                   xymscb->nbytes-=(INT)ln;
                              }                              /* correct for */
                              ftfscb->actbyt-=ln;     /* partial last block */
                         }
                         ftfnwp();
                         if (xymscb->nbytes > 0) {
                              if ((rc=ftfrwr(xymscb->block,xymscb->nbytes))
                                                        != xymscb->nbytes) {
                                   ftfabt(rc < 0 ? "Error writing file."
                                                 : "Disk full.");
                                   break;
                              }
                              else if (!ftfcbl()) {
                                   break;
                              }
                         }
                         xymscb->nak=NAK;        /* revert to NAK's, as nec */
                    }
                    xymscb->blknum++;              /* AOK: count that block */
               }
               else {                              /* gibberish - don't ACK */
                    ftfscb->garbag++;
                    if (!xgabort("Bad block.")) {
                         ftfnew(XUNOISE);
                    }
                    break;
               }
               ftfscb->stecnt=0;
               if (!(xymscb->flags&XUFG)) {
                    ftfout(ACKSTG,1);
               }
               xymscb->retry=ftfpsp->retrys;
               ftfnew(XUHEAD);
          }
          break;
     case XUEOT:                                     /* wait for second EOT */
          if (c == EOT) {
               hdleot();
          }
          else {
               ftfscb->stecnt++;
               ftfscb->garbag++;
          }
          break;
     case FTFABWAIT:
          if (ftftck-ftfscb->tckbyt >= XANWAIT) {    /* re-cancel stubborn  */
               if (c != CAN && c != '\b') {          /* PCPlus Ymodem-Batch */
                    ftfout(CANSTG,1);
                    ftfcli();
               }
          }
          break;
     }
}

VOID
xyrtrm(                    /* Terminate X/YMODEM receive session gracefully */
CHAR *msg)
{
     ftfabt(msg);
}



/*--- X/YMODEM Transmitting ---*/

static VOID
xyxini(VOID)                             /* Initialize X/YMODEM transmitter */
{
}

static VOID
fxstart(                          /* time to open up that transmitable file */
INT ifcant)                        /* state to go to if you can't open file */
{
     if (ftfxop() == 0) {
          ftfsrt();
          ftfscb->paksiz=ftfpsp->paksiz;
          if (xymscb->flags&XUBATCH) {
               ftfnew(XDFILE);
          }
          else {
               xymscb->nak=(xymscb->flags&XUFCRC) ? 'C' : NAK;
               xymscb->blknum=1;
               ftfnew(XDBLK1);
          }
     }
     else {
          ftfnew(ifcant);
     }
}

static VOID
xmxsrt(VOID)                      /* Begin XMODEM-Checksum transmit session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=0;
     fxstart(XDEND);
}

static VOID
xcxsrt(VOID)                           /* Begin XMODEM-CRC transmit session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUFCRC;
     fxstart(XDEND);
}

static VOID
x1xsrt(VOID)                            /* Begin XMODEM-1K transmit session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC;
     fxstart(XDEND);
}

static VOID
ybxsrt(VOID)                         /* Begin YMODEM Batch transmit session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC+XUBATCH;
     fxstart(XDEND);
}

static VOID
ygxsrt(VOID)                             /* Begin YMODEM-g transmit session */
{
     setmem(xymscb,sizeof(*xymscb),0);
     xymscb->flags=XUF1024+XUFCRC+XUBATCH+XUFG;
     fxstart(XDEND);
}

static VOID
prphdr(VOID)             /* prepare block for YMODEM Batch file header info */
{
     xymscb->headc=SOH;
     xymscb->gotbkn=0x00;
     xymscb->notbkn=0xFF;
     setmem(xymscb->block,128,0);
}

static VOID
xdcks(                 /* compute checksum for X/YMODEM block, and transmit */
INT nbytes)                                                  /* 128 or 1024 */
{                                 /* assumes raw data is in xymscb->block[] */
     UINT crc;

     if (xymscb->flags&XUFCRC) {
          xymscb->block[nbytes]=0;
          xymscb->block[nbytes+1]=0;
          crc=crc16(xymscb->block,nbytes+2);
          xymscb->block[nbytes]=(CHAR)(crc>>8);
          xymscb->block[nbytes+1]=(CHAR)(crc&0xFF);
          nbytes+=5;
     }
     else {
          xymscb->block[nbytes]=cksum(xymscb->block,nbytes);
          nbytes+=4;
     }
     ftfout(&xymscb->headc,nbytes);
}

static VOID
xdgo(VOID)                                 /* send YMODEM batch file header */
{
     prphdr();
     sprintf(xymscb->block,"%s%c%lu %lo",ftfscb->fname,'\0',
                                         (ULONG)ftfscb->estbyt,ftfscb->unxtim);
     strlwr(xymscb->block);
     xdcks(128);
}

static VOID
ackfhd(VOID)                         /* handle received ACK for file header */
{
     xymscb->blknum=1;
     ftfnew(XDBLK1);
}

static VOID
sndblk(VOID)                                           /* send a data block */
{
     if (ftfscb->estbyt > 0L && ftfscb->estbyt-ftfscb->actbyt <= 128*4) {
          ftfscb->paksiz=128;                 /* up to 4 final tiny packets */
     }
     setmem(&xymscb->headc,ftfscb->paksiz+5,0);
     xymscb->headc=(ftfscb->paksiz == 1024) ? STX : SOH;
     xymscb->gotbkn=xymscb->blknum;
     xymscb->notbkn=~xymscb->blknum;
     xymscb->nbytes=ftfxrd(xymscb->block,ftfscb->paksiz);
     if (xymscb->nbytes < 0) {
          ftfabt("Error reading file.");
     }
     else if (xymscb->nbytes == 0) {
          ftfnew(XDFDONE);
     }
     else {
          if (xymscb->nbytes < ftfscb->paksiz) {
               setmem(xymscb->block+xymscb->nbytes,
                      ftfscb->paksiz-xymscb->nbytes,CPMEOF);
          }
          xdcks(ftfscb->paksiz);
          ftfnew(XDBLOCK);
     }
}

static VOID
ackdta(VOID)                          /* handle received ACK for data block */
{
     xymscb->nak=NAK;
     xymscb->blknum++;
     if (ftfscb->state != XDBLK1) {  /* don't count ACK before 1st data blk */
          ftfscb->actbyt+=xymscb->nbytes;
          ftfnwp();
     }
}

static VOID
ackeot(VOID)                                 /* handle received ACK for EOT */
{
     if (xymscb->flags&XUBATCH) {
          fxstart(XDFIN);
     }
     else {
          ftfnew(XDEND);
     }
}

static VOID
xdstop(VOID)       /* end YMODEM Batch transmit session (empty file header) */
{
     prphdr();
     xdcks(128);
}

static INT
xyxctn(VOID)                                  /* continue X/YMODEM transmit */
{                                                          /* return 0=done */
     if ((ftftck-ftfscb->tckstt) > XDBWAIT) {
          ftfabt("Sender timeout.");
     }
     switch(ftfscb->state) {
     case XDBLOCK:  /* waiting to be able to send after first block of file */
          if (xymscb->flags&XUFG && ftfoba() >= ftfscb->paksiz+5) {
               ackdta();       /* YMODEM-g data blocks have implicit ACK's! */
               sndblk();
               ftfscb->tckact=ftftck;
               ftfact=1;
          }
          break;
     case XDFDONE:         /* wait for all bytes of file to get transmitted */
          if (ftfoba() >= ftfomt) {
               ftfout(EOTSTG,1);
               ftfxcl(1);
               ftfscb->isopen=0;
               ftfscb->actfil++;
               ftfnew(XDEOF);
          }
          break;
     case XDEOF:
          if (ftftck-ftfscb->tckstt > XDEWAIT && !(xymscb->flags&XUBATCH)) {
               ackeot();         /* wait just a little while for ACK to EOT */
          }
          break;
     case XDFACK:
          if (ftftck-ftfscb->tckstt > XDEWAIT) {
               ftfnew(XDEND);
          }
          break;
     case FTFABORT:
          ftfxca();
          ftfcan();
          ftfnew(FTFABWAIT);
          break;
     case FTFABWAIT:
          if (ftftck-ftfscb->tckstt > XDABWAIT) {
               return(-1);
          }
          break;
     case XDEND:
          return(0);
     }
     return(1);
}

static VOID
xyxinc(       /* handle received character during X/YMODEM transmit session */
CHAR c)
{
     CHAR ok=0;

     if (c == CAN) {
          ftfclo();
          ftfabt("Operator CTRL-X abort.");
          return;
     }
     switch(ftfscb->state) {
     case XDFILE:                 /* Waiting for C/G requesting file header */
          xymscb->nak=(xymscb->flags&XUFG) ? 'G' : 'C';
          if (c == (CHAR)xymscb->nak) {
               xymscb->flags|=XUFCRC;
               xdgo();
               if (xymscb->flags&XUFG) {
                    ackfhd();     /* YMODEM-g file header has implicit ACK! */
               }
               else {
                    ftfnew(XDFAGN);
               }
               ok=1;
          }
          break;
     case XDFAGN:                      /* sent file header, waiting for ACK */
          if (c == (CHAR)xymscb->nak) {
               xdgo();
               ok=1;
          }
          else if (c == ACK) {
               ackfhd();
               ok=1;
          }
          break;
     case XDBLK1:     /* waiting for NAK or ACK to send first block of file */
          ftfstf();                 /* (restart timer after file hdr ack'd) */
     case XDBLOCK:     /* waiting for NAK or ACK to send next piece of file */
          if (c == 'S'-64) {
               ftfxlk(1);
               xymscb->flags|=XXLOCK;
               break;
          }
          if (xymscb->flags&XXLOCK) {
               ftfxlk(0);
               xymscb->flags&=~XXLOCK;
               break;
          }
          if (ftfoba() >= ftfomt) { /* ignore ACK/NAK if block not all sent */
               if (c == ACK) {
                    ackdta();
                    sndblk();
                    ok=1;
               }
               else if (c == (CHAR)xymscb->nak) {
                    ok=1;
                    if (ftfscb->state == XDBLOCK) {
                         ftfscb->retrys++;
                    }
                    ftfxsk(ftfscb->actbyt);
                    sndblk();
               }
          }
          break;
     case XDEOF:
          switch(c) {
          case NAK:
          case 'C':
          case 'G':
               ftfout(EOTSTG,1);
               ftfnew(XDEOF);
               ok=1;
               break;
          case ACK:
               ackeot();
               ok=1;
               break;
          }
          break;
     case XDFIN:             /* waiting for NAK requesting next file header */
          switch(c) {
          case 'C':
          case 'G':
               xdstop();
               ftfnew(XDFACK);
               ok=1;
               break;
          }
          break;
     case XDFACK:                        /* waiting for ACK to end-of-batch */
          switch(c) {
          case 'C':
          case 'G':
               xdstop();
               ftfnew(XDFACK);
               ok=1;
               break;
          case ACK:
               ftfnew(XDEND);
               ok=1;
               break;
          }
          break;
     }
     if (ok) {
          ftfscb->stecnt=0;
     }
     else {
          ftfscb->garbag++;
          ftfscb->stecnt++;
     }
}

struct ftfpsp ftpxmr={                         /* XMODEM-Checksum receiving */
     NULL,
     "M",                                  /* 1-3 code letters for protocol */
     "XMODEM-Checksum",                                 /* name of protocol */
     FTFXTD,                                   /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     128,       /* .paksiz                        packet size 0=auto-figure */
     xyrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     xmrsrt,    /* .start()                                Start a transfer */
     xyrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyrinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     xyrtrm,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpxmx={                      /* XMODEM-Checksum transmitting */
     NULL,
     "M",                                  /* 1-3 code letters for protocol */
     "XMODEM-Checksum",                                 /* name of protocol */
     FTFXMT+FTFXTD,                            /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     128,       /* .paksiz                        packet size 0=auto-figure */
     xyxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     xmxsrt,    /* .start()                                Start a transfer */
     xyxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyxinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpxcr={                              /* XMODEM-CRC receiving */
     NULL,
     "C",                                  /* 1-3 code letters for protocol */
     "XMODEM-CRC",                                      /* name of protocol */
     FTFXTD,                                   /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     128,       /* .paksiz                        packet size 0=auto-figure */
     xyrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     xcrsrt,    /* .start()                                Start a transfer */
     xyrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyrinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     xyrtrm,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpxcx={                           /* XMODEM-CRC transmitting */
     NULL,
     "C",                                  /* 1-3 code letters for protocol */
     "XMODEM-CRC",                                      /* name of protocol */
     FTFXMT+FTFXTD,                            /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     128,       /* .paksiz                        packet size 0=auto-figure */
     xyxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     xcxsrt,    /* .start()                                Start a transfer */
     xyxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyxinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpx1r={                               /* XMODEM-1K receiving */
     NULL,
     "1",                                  /* 1-3 code letters for protocol */
     "XMODEM-1K",                                       /* name of protocol */
     FTFXTD,                                   /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     x1rsrt,    /* .start()                                Start a transfer */
     xyrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyrinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     xyrtrm,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpx1x={                            /* XMODEM-1K transmitting */
     NULL,
     "1",                                  /* 1-3 code letters for protocol */
     "XMODEM-1K",                                       /* name of protocol */
     FTFXMT+FTFXTD,                            /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     x1xsrt,    /* .start()                                Start a transfer */
     xyxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyxinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpybr={                            /* YMODEM Batch receiving */
     NULL,
     "B",                                  /* 1-3 code letters for protocol */
     "YMODEM Batch",                                    /* name of protocol */
     FTFMUL+FTFXTD,                            /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     ybrsrt,    /* .start()                                Start a transfer */
     xyrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyrinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     xyrtrm,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpybx={                         /* YMODEM Batch transmitting */
     NULL,
     "B",                                  /* 1-3 code letters for protocol */
     "YMODEM Batch",                                    /* name of protocol */
     FTFXMT+FTFMUL+FTFXTD,                     /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     ybxsrt,    /* .start()                                Start a transfer */
     xyxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyxinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpygr={                                /* YMODEM-g receiving */
     NULL,
     "G",                                  /* 1-3 code letters for protocol */
     "YMODEM-g",                                        /* name of protocol */
     FTFMUL+FTFXTD,                            /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     ygrsrt,    /* .start()                                Start a transfer */
     xyrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyrinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     xyrtrm,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpygx={                             /* YMODEM-g transmitting */
     NULL,
     "G",                                  /* 1-3 code letters for protocol */
     "YMODEM-g",                                        /* name of protocol */
     FTFXMT+FTFMUL+FTFXTD,                     /* protocol capability flags */
     sizeof(struct xymdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     1L,        /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     xyxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     ygxsrt,    /* .start()                                Start a transfer */
     xyxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     xyxinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
                /* .hdlinb()              Handle an array of incoming bytes */
     (VOID *)ftfinbc,
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};





