/*--------------------------------------------------------------------------*/
/* FILE: zsend.c    (Opus zmodem transmitter)                               */
/*                                                                          */
/*                                                                          */
/*               The Opus Computer-Based Conversation System                */
/*       (c) Copyright 1986, Wynn Wagner III, All Rights Reserved           */
/*                                                                          */
/*      This implementation of Chuck Forsberg's ZMODEM protocol was         */
/*              for Opus by Rick Huebner and Wynn Wagner III                */
/*                                                                          */
/* (MSC/4 with /Zp /Ox)                                                     */
/*                                                                          */
/*                                                                          */
/*                                                                          */
/*                                                                          */
/*  This module is similar to a routine used by Opus-Cbcs (1.00).  It is    */
/*  provided for your information only.  You will find routines that need   */
/*  to be coded and identifiers to be resolved.                             */
/*                                                                          */
/*  There is absolutely no guarantee that anything here will work.  If you  */
/*  break this routine, you own both pieces.                                */
/*                                                                          */
/*  USAGE:  You may use this material in any program with no obligation     */
/*          as long as there is no charge for your program.  For more       */
/*          information about commercial use, contact the "OPUSinfo HERE"   */
/*          BBS (124/111).                                                  */
/*                                                                          */
/*  NOTE:   There are a couple of things the Opus implementation does that  */
/*          aren't part of the original ZModem protocol.  They all deal     */
/*          with WaZOO type ("ZedZap") netmail and should only show up when */
/*          used under that condition.                                      */
/*                                                                          */
/*             * The maximum packet size can grow larger than 1k.  It is    */
/*               sensitive to the baud rate.  (2400b=2048k; 9600b=8192k)    */
/*             * The sender must be able to send nothing.  In other words,  */
/*               the sending system must be able to initiate and terminate  */
/*               a zmodem send session without having to actually send a    */
/*               file.  Normally this kind of thing would never happen in   */
/*               zmodem.                                                    */
/*                                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

#include "zmodem.h"
#include "com.h"


/*--------------------------------------------------------------------------*/
/* Global routines                                                          */
/*--------------------------------------------------------------------------*/
int cdecl send_Zmodem(byte *,byte *,int,int,int);


/*--------------------------------------------------------------------------*/
/* Private routines                                                         */
/*--------------------------------------------------------------------------*/
static void pascal ZS_SendBinaryHeader(unsigned short ,byte *);
static void pascal ZS_SendData(byte *,int,unsigned short);
static void pascal ZS_SendByte(byte);
static int  pascal ZS_GetReceiverInfo(void);
static int  pascal ZS_SendFile(int,int);
static int  pascal ZS_SendFileData(int);
static int  pascal ZS_SyncWithReceiver(void);
static void pascal ZS_EndSend(void);



/*--------------------------------------------------------------------------*/
/* Private data                                                             */
/*--------------------------------------------------------------------------*/
static FILE *Infile;             /* Handle of file being sent               */
static byte *Txbuf;              /* Pointer to transmit buffer              */
static long  Strtpos;            /* Starting byte position of download      */
static long  Txpos;              /* Transmitted file position               */
static int Rxbuflen;             /* Receiver's max buffer length            */


/*--------------------------------------------------------------------------*/
/* External data not otherwise declared                                     */
/*--------------------------------------------------------------------------*/
extern char *FLAGGING_msg;
extern char *NOTSENT_msg;
extern char *TRUNC_msg;
extern char *KBD_msg;






/*--------------------------------------------------------------------------*/
/* SEND ZMODEM (send a file)                                                */
/*   returns TRUE (1) for good xfer, FALSE (0) for bad                      */
/*   sends one file per call; 'fsent' flags start and end of batch          */
/*--------------------------------------------------------------------------*/
int cdecl send_Zmodem(fname,alias,doafter,fsent,wazoo)
   byte  *fname;
   byte  *alias;
   int   doafter;
   int   fsent;
   int   wazoo;
   begin
      register byte *p;
      register byte *q;
      struct stat    f;

      int   i;
      int   rc;


      _BRK_DISABLE();
      XON_ENABLE();

      errno    =
      z_size   = 0;
      Infile   = NULL;
      Txbuf    = NULL;



      if (fname) set_xy("");

      switch(fsent)
         begin
            case 0:
            case NOTHING_TO_DO:  n_disable();
                                 if (!wazoo)
                                    begin
                                       Z_PutString("rz\r");
                                       Z_PutLongIntoHeader(0L);
                                       Z_SendHexHeader(ZRQINIT, Txhdr);
                                    end
                                 Rxtimeout   = 20;
                                 if (ZS_GetReceiverInfo() == ERROR) return FALSE;
         end


      Rxtimeout   = (int )(614400L/(long )cur_baud);
      Rxtimeout  /= 10;

      if (Rxtimeout < 10) Rxtimeout = 10;


      if (fname == NULL) goto Done;

      /*--------------------------------------------------------------------*/
      /* Prepare the file for transmission.  Just ignore file open errors   */
      /* because there may be other files that can be sent.                 */
      /*--------------------------------------------------------------------*/
      Filename = fname;
      CLEAR_IOERR();
      Infile   = fopen(Filename,read_binary);
      if (got_error(OPEN_msg,Filename)) return OK;
      
      Txbuf    = zalloc();

      /*--------------------------------------------------------------------*/
      /* Send the file                                                      */
      /*--------------------------------------------------------------------*/
      rc       = TRUE;


      /*--------------------------------------------------------------------*/
      /* Display outbound filename, size, and ETA for sysop                 */
      /*--------------------------------------------------------------------*/
      fstat(fileno(Infile), &f);

      message(NULL);
      cprintf( "Z-Send %s, %ldb",Filename,f.st_size);

      i  = (int)(f.st_size*10/cur_baud+27)/54;
      if (i) cprintf(", %d min.",i);

      set_xy(NULL);

      /*--------------------------------------------------------------------*/
      /* Get outgoing file name; no directory path, lower case              */
      /*--------------------------------------------------------------------*/
      for (p=(alias!=NULL)?alias:Filename, q=Txbuf ; *p; )
         begin
            if ((*q++ = tolower(*p)) == '\\') q = Txbuf;
            p++;
         end

      *q++  = '\0';
      p     = q;

      /*--------------------------------------------------------------------*/
      /* Zero out remainder of file header packet                           */
      /*--------------------------------------------------------------------*/
      while (q < (Txbuf + KSIZE)) *q++ = '\0';

      /*--------------------------------------------------------------------*/
      /* Store filesize, time last modified, and file mode in header packet */
      /*--------------------------------------------------------------------*/
      sprintf(p, "%lu %lo %o", f.st_size, f.st_mtime, f.st_mode);

      /*--------------------------------------------------------------------*/
      /* Transmit the filename block and begin the download                 */
      /*--------------------------------------------------------------------*/
      throughput(0,0L);


      /*--------------------------------------------------------------------*/
      /* Check the results                                                  */
      /*--------------------------------------------------------------------*/
      switch( ZS_SendFile(1+strlen(p)+(p-Txbuf), wazoo) )
         begin

            case ERROR: /*--------------------------------------------------*/
                        /* Something tragic happened                        */
                        /*--------------------------------------------------*/
                        goto Err_Out;


            case OK:    /*--------------------------------------------------*/
                        /* File was sent                                    */
                        /*--------------------------------------------------*/
                        CLEAR_IOERR();
                        fclose(Infile);
                        got_error(CLOSE_msg,Filename);
                        Infile   = NULL;

                        status_line( "=DL %s", Filename );

                        /*--------------------------------------------------*/
                        /* Special WaZOO file handling following a          */
                        /* successful transmission.                         */
                        /*--------------------------------------------------*/
                        switch(doafter)
                           begin
                              case DELETE_AFTER:   /*-----------------------*/
                                                   /* Delete File           */
                                                   /*-----------------------*/
                                                   CLEAR_IOERR();
                                                   unlink(Filename);
                                                   got_error(UNLINK_msg,Filename);
                                                   break;
   
                              case TRUNC_AFTER:    /*-----------------------*/
                                                   /* Truncate File         */
                                                   /*-----------------------*/
                                                   CLEAR_IOERR();
                                                   i = open(Filename,O_TRUNC,S_IWRITE);
                                                   got_error( TRUNC_msg, Filename );
                                                   status_line(FLAGGING_msg,Filename);
                                                   close(i);
                                                   break;
                           end /* switch */


            default:    /*--------------------------------------------------*/
                        /* Probably a ZSKIP                                 */
                        /*--------------------------------------------------*/
                        goto Done;

         end /* switch */

Err_Out:
      rc = FALSE;

Done:
      if (Infile) fclose(Infile);
      if (Txbuf)  free(Txbuf);

      if (fsent < 0) ZS_EndSend();

      return rc;

   end /* send_Zmodem */






/*--------------------------------------------------------------------------*/
/* ZS SEND BINARY HEADER                                                    */
/* Send ZMODEM binary header hdr of type type                               */
/*--------------------------------------------------------------------------*/
static void pascal ZS_SendBinaryHeader(type, hdr)
   unsigned short type;
   byte          *hdr;
   begin
      register byte          *hptr;
      register unsigned short crc;
      int                     n;

      SENDBYTE(ZPAD);
      SENDBYTE(ZDLE);

      SENDBYTE(ZBIN);
      ZS_SendByte(type);

      crc = Z_UpdateCRC(type, 0);

      hptr  = hdr;
      for (n=4; --n >= 0;)
         begin
            ZS_SendByte(*hptr);
            crc = Z_UpdateCRC(((unsigned short)(*hptr++)), crc);
         end

      crc = Z_UpdateCRC(0,crc);
      crc = Z_UpdateCRC(0,crc);

      ZS_SendByte(crc>>8);
      ZS_SendByte(crc);

      if (type != ZDATA) wait_for_clear();

   end /* ZS_SendBinaryHeader */





/*--------------------------------------------------------------------------*/
/* ZS SEND DATA                                                             */
/* Send binary array buf with ending ZDLE sequence frameend                 */
/*--------------------------------------------------------------------------*/
static void pascal ZS_SendData(buf, length, frameend)
   byte          *buf;
   int            length;
   unsigned short frameend;
   begin

      register unsigned short crc;
      int                     t;

      crc = 0;
      for (;--length >= 0;)
         begin
            ZS_SendByte(*buf);
            crc = Z_UpdateCRC(((unsigned short)(*buf++)), crc);
         end

      SENDBYTE(ZDLE);
      SENDBYTE(frameend);
      crc = Z_UpdateCRC(frameend, crc);

      crc = Z_UpdateCRC(0,crc);
      crc = Z_UpdateCRC(0,crc);
      ZS_SendByte(crc>>8);
      ZS_SendByte(crc);

      if (frameend == ZCRCW)
         begin
            SENDBYTE(XON);
            wait_for_clear();
         end

   end /* ZS_SendData */






/*--------------------------------------------------------------------------*/
/* ZS SEND BYTE                                                             */
/* Send character c with ZMODEM escape sequence encoding.                   */
/* Escape XON, XOFF. Escape CR following @ (Telenet net escape)             */
/*--------------------------------------------------------------------------*/
static void pascal ZS_SendByte(c)
   register byte c;
   begin
      static byte lastsent;

      switch (c)
         begin
            case 015:
            case 0215:  /*--------------------------------------------------*/
                        /*                                                  */
                        /*--------------------------------------------------*/
                        if ((lastsent & 0x7F) != '@') goto SendIt;
            case 020:
            case 021:
            case 023:
            case 0220:
            case 0221:
            case 0223:
            case ZDLE:  /*--------------------------------------------------*/
                        /* Quoted characters                                */
                        /*--------------------------------------------------*/
                        SENDBYTE(ZDLE);
                        c ^= 0x40;

            default:    /*--------------------------------------------------*/
                        /* Normal character output                          */
SendIt:                 /*--------------------------------------------------*/
                        SENDBYTE(lastsent = c);

         end /* switch */

   end /* ZS_SendByte */






/*--------------------------------------------------------------------------*/
/* ZS GET RECEIVER INFO                                                     */
/* Get the receiver's init parameters                                       */
/*--------------------------------------------------------------------------*/
static int pascal ZS_GetReceiverInfo()
   begin
      int   n;
      int   rxflags;

      for (n=10; --n>=0; )
         begin

            switch ( Z_GetHeader(Rxhdr) )
               begin
                  case ZCHALLENGE:  /*--------------------------------------*/
                                    /* Echo receiver's challenge number     */
                                    /*--------------------------------------*/
                                    Z_PutLongIntoHeader(Rxpos);
                                    Z_SendHexHeader(ZACK, Txhdr);
                                    continue;

                  case ZCOMMAND:    /*--------------------------------------*/
                                    /* They didn't see our ZRQINIT          */
                                    /*--------------------------------------*/
                                    Z_PutLongIntoHeader(0L);
                                    Z_SendHexHeader(ZRQINIT, Txhdr);
                                    continue;

                  case ZRINIT:      /*--------------------------------------*/
                                    /*                                      */
                                    /*--------------------------------------*/
                                    Rxbuflen = ((word )Rxhdr[ZP1]<<8)|Rxhdr[ZP0];
                                    return OK;

                  case ZCAN:
                  case RCDO:
                  case TIMEOUT:     /*--------------------------------------*/
                                    /*                                      */
                                    /*--------------------------------------*/
                                    return ERROR;

                  case ZRQINIT:     /*--------------------------------------*/
                                    /*                                      */
                                    /*--------------------------------------*/
                                    if (Rxhdr[ZF0] == ZCOMMAND) continue;

                  default:          /*--------------------------------------*/
                                    /*                                      */
                                    /*--------------------------------------*/
                                    Z_SendHexHeader(ZNAK, Txhdr);
                                    continue;
               end /* switch */

         end /* for */

      return ERROR;

   end /* ZS_GetReceiverInfo */





/*--------------------------------------------------------------------------*/
/* ZS SEND FILE                                                             */
/* Send ZFILE frame and begin sending ZDATA frame                           */
/*--------------------------------------------------------------------------*/
static int pascal ZS_SendFile(blen, wazoo)
   int blen;
   int wazoo;
   begin
      register int c;

      while(1)

         begin

            if (((KEYPRESS()) and (READKB()==27)))
               begin
                  send_can();
                  z_log( KBD_msg );
                  return ERROR;
               end
            else if (!CARRIER) return ERROR;

   
            Txhdr[ZF0] = LZCONV;    /* Default file conversion mode */
            Txhdr[ZF1] = LZMANAG;   /* Default file management mode */
            Txhdr[ZF2] = LZTRANS;   /* Default file transport mode */
            Txhdr[ZF3] = 0;
            ZS_SendBinaryHeader(ZFILE, Txhdr);
            ZS_SendData(Txbuf, blen, ZCRCW);
   
Again:
            switch (c = Z_GetHeader(Rxhdr))
               begin

                  case ZRINIT:   /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 goto Again;

                  case ZCAN:     
                  case ZCRC:     
                  case RCDO:     
                  case TIMEOUT:  
                  case ZFIN:
                  case ZABORT:   
                                 /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 return ERROR;
   
                  case ZSKIP:    /*-----------------------------------------*/
                                 /* Other system wants to skip this file    */
                                 /*-----------------------------------------*/
                                 return c;
   
                  case ZRPOS:    /*-----------------------------------------*/
                                 /* Resend from this position...            */
                                 /*-----------------------------------------*/
                                 fseek(Infile, Rxpos, SEEK_SET);
                                 Strtpos = Txpos = Rxpos;
                                 CLEAR_INBOUND();
                                 return ZS_SendFileData(wazoo);
               end /* switch */

         end /* while */

   end /* ZS_SendFile */




/*--------------------------------------------------------------------------*/
/* ZS SEND FILE DATA                                                        */
/* Send the data in the file                                                */
/*--------------------------------------------------------------------------*/
static int pascal ZS_SendFileData(wazoo)
   int wazoo;
   begin

      register int c, e;
      word         newcnt, blklen, maxblklen, goodblks, goodneeded = 1;
      byte        *p;


      maxblklen = (cur_baud<300) ? 128 : cur_baud/300*256;

      if (maxblklen>WAZOOMAX)              maxblklen = WAZOOMAX;
      if (!wazoo && maxblklen>KSIZE)       maxblklen = KSIZE;
      if (Rxbuflen && maxblklen>Rxbuflen)  maxblklen = Rxbuflen;
      blklen = maxblklen;


SomeMore:

      if (CHAR_AVAIL())
         begin
WaitAck:

            switch (c = ZS_SyncWithReceiver())
               begin
                  default:       /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_log(Cancelled_msg);
                                 errno = 0;
                                 fclose(Infile);
                                 got_error(CLOSE_msg,Filename);
                                 return ERROR;

                  case ZSKIP:    /*-----------------------------------------*/
                                 /* Skip this file                          */
                                 /*-----------------------------------------*/
                                 return c;

                  case ZACK:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 break;

                  case ZRPOS:    /*-----------------------------------------*/
                                 /* Resume at this position                 */
                                 /*-----------------------------------------*/
                                 blklen = (blklen>>2 > 64) ? blklen>>2 : 64;
                                 goodblks = 0;
                                 goodneeded = (goodneeded<<1) | 1;
                                 break;

                  case ZRINIT:   /*-----------------------------------------*/
                                 /* Receive init                            */
                                 /*-----------------------------------------*/
                                 throughput(1,Txpos-Strtpos);
                                 return OK;
               end /* switch */

            while (CHAR_AVAIL())
               begin
                  switch (MODEM_IN())
                     begin
                        case CAN:
                        case RCDO:
                        case ZPAD:  goto WaitAck;
                     end /* switch */
               end /* while */

         end /* while */

      newcnt = Rxbuflen;
      Z_PutLongIntoHeader(Txpos);
      ZS_SendBinaryHeader(ZDATA, Txhdr);

      do
         begin

            if (((KEYPRESS()) and (READKB()==27)))
               begin
                  send_can();
                  z_log( KBD_msg );
                  goto oops;
               end

            if (!CARRIER) goto oops;

            if ((c=fread(Txbuf,1,blklen,Infile))!=z_size)
               begin
                  gotoxy( locate_x+10, locate_y );
                  cputs( ultoa(((unsigned long )(z_size=c)),e_input,10) );
                  putch(' ');
               end

            if (c < blklen) e = ZCRCE;
            else if (Rxbuflen && (newcnt -= c) <= 0) e = ZCRCW;
            else e = ZCRCG;

            ZS_SendData(Txbuf, c, e);

            gotoxy( locate_x, locate_y );
            cputs( ultoa(((unsigned long )Txpos),e_input,10) );
            putch(' ');

            Txpos += c;
            if (blklen<maxblklen && ++goodblks>goodneeded)
               begin
                  blklen = (blklen<<1 < maxblklen) ? blklen<<1 : maxblklen;
                  goodblks = 0;
               end

            if (e == ZCRCW) goto WaitAck;

            while (CHAR_AVAIL())
               begin
                  switch (MODEM_IN())
                     begin
                        case CAN:
                        case RCDO:
                        case ZPAD:  /*--------------------------------------*/
                                    /* Interruption detected;               */
                                    /* stop sending and process complaint   */
                                    /*--------------------------------------*/
                                    z_message("Trouble?");
                                    CLEAR_OUTBOUND();
                                    ZS_SendData(Txbuf, 0, ZCRCE);
                                    goto WaitAck;
                     end /* switch */
               end /* while */
      
         end /* do */
      while (e == ZCRCG);

      while(1)
         begin
            Z_PutLongIntoHeader(Txpos);
            ZS_SendBinaryHeader(ZEOF, Txhdr);

            switch (ZS_SyncWithReceiver())
               begin
                  case ZACK:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 continue;

                  case ZRPOS:    /*-----------------------------------------*/
                                 /* Resume at this position...              */
                                 /*-----------------------------------------*/
                                 goto SomeMore;

                  case ZRINIT:   /*-----------------------------------------*/
                                 /* Receive init                            */
                                 /*-----------------------------------------*/
                                 throughput(1,Txpos-Strtpos);
                                 return OK;

                  case ZSKIP:    /*-----------------------------------------*/
                                 /* Request to skip the current file        */
                                 /*-----------------------------------------*/
                                 z_log(Skip_msg);
                                 CLEAR_IOERR();
                                 fclose(Infile);
                                 got_error(CLOSE_msg,Filename);
                                 return c;

                  default:       /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
oops:                            z_log(Cancelled_msg);
                                 errno = 0;
                                 fclose(Infile);
                                 got_error(CLOSE_msg,Filename);
                                 return ERROR;
               end /* switch */
         end /* while */

   end /* ZS_SendFileData */






/*--------------------------------------------------------------------------*/
/* ZS SYNC WITH RECEIVER                                                    */
/* Respond to receiver's complaint, get back in sync with receiver          */
/*--------------------------------------------------------------------------*/
static int pascal ZS_SyncWithReceiver()
   begin
      register int c;
      int          num_errs   = 7;

      while(1)
         begin
            c = Z_GetHeader(Rxhdr);
            CLEAR_INBOUND();
            switch (c)
               begin
                  case TIMEOUT:  z_message( TIME_msg );
                                 if ((num_errs--) >= 0) break;

                  case ZCAN:
                  case ZABORT:
                  case ZFIN:
                  case RCDO:
                                 /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_log("Err");
                                 return ERROR;

                  case ZRPOS:    /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 rewind(Infile);   /* In case file EOF seen */
                                 fseek(Infile, Rxpos, SEEK_SET);
                                 Txpos = Rxpos;
                                 z_message(NULL);
                                 cputs("Resending from ");
                                 cputs( ultoa(((unsigned long )(Txpos)),e_input,10) );
                                 return c;

                  case ZSKIP:    /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_log(Skip_msg);

                  case ZRINIT:   /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 CLEAR_IOERR();
                                 fclose(Infile);
                                 got_error(CLOSE_msg,Filename);
                                 return c;

                  case ZACK:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_message( NULL );
                                 return c;

                  default:       /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_message( IDUNNO_msg );
                                 ZS_SendBinaryHeader(ZNAK, Txhdr);
                                 continue;
               end /* switch */
         end /* while */
   end /* ZS_SyncWithReceiver */




/*--------------------------------------------------------------------------*/
/* ZS END SEND                                                              */
/* Say BIBI to the receiver, try to do it cleanly                           */
/*--------------------------------------------------------------------------*/
static void pascal ZS_EndSend()
   begin

      while(1)
         begin
            Z_PutLongIntoHeader(0L);
            ZS_SendBinaryHeader(ZFIN, Txhdr);

            switch (Z_GetHeader(Rxhdr))
               begin
                  case ZFIN:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 SENDBYTE('O');
                                 SENDBYTE('O');
                                 wait_for_clear();
                                 /* fallthrough... */
                  case ZCAN:
                  case RCDO:
                  case TIMEOUT:  /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 return;
               end /* switch */
         end /* while */
   end /* ZS_EndSend */


/* END OF FILE: z_send.c */

