/**********************************************************************
 * Send.c: File transmission routines for xprzmodem.library;
 * Original Version 2.10, 12 February 1991, by Rick Huebner.
 * Based closely on Chuck Forsberg's sz.c example ZModem code,
 * but too pervasively modified to even think of detailing the changes.
 * Released to the Public Domain; do as you like with this code.
 *
 * Version 2.50, 15 November 1991, CRC-32 additions by William M. Perkins.
 * Version 2.51 29, January 1992, RX_timout fix by John Tillema
 * Version 2.52   6 March 1992, Very minor fix with compiled 020 library
 *               by William M. Perkins.
 *
 **********************************************************************
 * Modified to support ZedZap/ZedZip fidonet protocol by
 * Yves Konighofer and Russel McOrmond.
 *
 * Adapted to xprzmodem Version 2.52 by Andrea Cisternino
 **********************************************************************/

#include <proto/all.h>
#include <exec/types.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include "xproto.h"
#include "zmodem.h"
#include "xprzmodem.h"

#ifdef DEBUGLOG
extern void *DebugLog;
#endif

/**********************************************************
 * long XProtocolSend(struct XPR_IO *xio)
 *
 * Main file transmission routine; called by comm program
 **********************************************************/
long __saveds __asm XProtocolSend(register __a0 struct XPR_IO *xio)
{
   struct Vars *v;
   short err;

   /* Perform common setup and initializations */
   if (! (v = setup(xio)) ) return XPRS_FAILURE;

/*  was 600, set to 300 to fix so it uploads correctly */
   v->Rxtimeout = 300;
   v->Wantfcs32 = TRUE;
   v->Rxflags = 0;

   /* first flush the inbound buffer */
   if (v->io.xpr_sflush) (*v->io.xpr_sflush)();

   /* Transfer the files */  
   zmputs(v, "rz\r");
   stohdr(v, 0L);
   zshhdr(v, ZRQINIT);
   sendbuf(v);
   if (getzrxinit(v) == ERROR) upderr(v, "Upload cancelled or timed out");
   else  sendbatch(v);

   /* Clean up and return */
   if (err = v->Errcnt) upderr(v, "One or more files skipped due to errors");
   else  updmsg(v, "Done.");

   if (v->io.xpr_setserial && v->Oldstatus != -1) (*v->io.xpr_setserial)(v->Oldstatus);
   FreeMem(v->Filebuf, v->Filebufmax);
   FreeMem(v, (long) sizeof(struct Vars));
  
#ifdef DEBUGLOG
   if (DebugLog)
   {
      xpr_fclose(&v->io, DebugLog);
      DebugLog = NULL;
   }
#endif
   return (err) ? XPRS_FAILURE : XPRS_SUCCESS;
}  /* End of long XProtocolSend() */

/**********************************************************
 * short getzrxinit(struct Vars *v)
 *
 * Negotiate with receiver to start a file transfer
 **********************************************************/
short getzrxinit(struct Vars *v)
{
   short n;

   for (n = v->ErrorLimit; --n >= 0; )
   {
      /* Check for abort from comm program */
      if (v->io.xpr_chkabort && (*v->io.xpr_chkabort)()) return ERROR;
      switch (zgethdr(v))
      {
         case ZCHALLENGE:        /* Echo receiver's challenge number */
            stohdr(v, v->Rxpos);
            zshhdr(v, ZACK);
            sendbuf(v);
            continue;

         case ZCOMMAND:          /* They didn't see our ZRQINIT; try again */
            stohdr(v, 0L);
            zshhdr(v, ZRQINIT);
            sendbuf(v);
            continue;

         case ZRINIT:            /* Receiver ready; get transfer parameters */
            v->Rxflags = 0xFF & v->Rxhdr[ZF0];
            v->Txfcs32 = (v->Wantfcs32 && (v->Rxflags & CANFC32));
            v->Rxbuflen = ((USHORT) v->Rxhdr[ZP1] << 8) | v->Rxhdr[ZP0];
#ifdef DEBUGLOG
            mysprintf(v->Msgbuf, "Txfcs32=%ld Rxbuflen=%ld Tframlen=%ld\n",
                  (long) v->Txfcs32, (long) v->Rxbuflen, (long) v->Tframlen);
            dlog(v, v->Msgbuf);
#endif

            /* Use shortest of the two side's max frame lengths */
            if (v->Tframlen && (! v->Rxbuflen || v->Tframlen < v->Rxbuflen)) v->Rxbuflen = v->Tframlen;
#ifdef DEBUGLOG
            mysprintf(v->Msgbuf, "Rxbuflen=%ld\n", (long) v->Rxbuflen);
            dlog(v, v->Msgbuf);
#endif
            return OK;

         case ZCAN:
         case RCDO:
         case TIMEOUT:
            upderr(v, v->Msgbuf);
            return ERROR;

         case ZRQINIT:
            if (v->Rxhdr[ZF0] == ZCOMMAND) continue;
            /* fallthrough... */

         default:
            zshhdr(v, ZNAK);
            sendbuf(v);
            continue;

      }  /* End of "switch (zgethdr(v))" */

   }  /* End of "for (n = v->ErrorLimit; --n >= 0; )" */

   return ERROR;
}  /* End of short getzrxinit() */

/**********************************************************
 * void sendbatch(struct Vars *v)
 *
 * Send a batch of files
 **********************************************************/
void sendbatch(struct Vars *v)
{
   UBYTE single, done = FALSE;
   long fstate;

   /* Lets just clear this in case the host program fails to set this */
   v->Filcnt = 0;    /* Up to now we have not sent any files */

   /* If template routines not provided, must be single filename */
   if (!(v->io.xpr_ffirst) || !(v->io.xpr_fnext))
   {
      single = TRUE;
      strcpy(v->Filename,(v->io.xpr_filename) ? (v->io.xpr_filename) : "");
      if (!(v->Filename[0]))
      {
         if (USEZERO) updmsg(v,"30-Detected No Files...");
         else  upderr(v,"No files Are Specified");
         done = TRUE;
      }
      /* Else use the template routines to get the first filename */
   }
   else
   {
      single = FALSE;
      fstate = (*v->io.xpr_ffirst)(v->Filename, v->io.xpr_filename);
      if (!(fstate) || !(v->Filename[0]))  /* There are no files found */
      {
         if (USEZERO)   /* That's OK... we allow this kind of thing */
         {
            updmsg(v,"30-Detected No files");
            done = TRUE;
         }
         else     /* In that case, I'm leaving */
         {
            if (!(v->io.xpr_filename) || !(v->io.xpr_filename[0])) upderr(v,"No Files Are Specified");
            else  upderr(v,"No Files Match Template");
            return;
         }
      }
   }

   /* If using templates, keep getting names & sending until done */
   while (! done)
   {
      long ret;

      if ((ret=sendone(v)) == ERROR)
      {
         if(strlen(v->Filename)) updstatus(v,v->Filename,XPRS_FAILURE);
         updmsg(v,"40-Transfer Aborted Due To Error");
         return;
      }
      if(strlen(v->Filename))   /* Either OK or ZSKIP */
      {                         /* Skip does not delete the file! */
         updstatus(v,v->Filename,(ret==OK) ? XPRS_SUCCESS : XPRS_FAILURE);
      }
      if (single) break;
      fstate = (*v->io.xpr_fnext)(fstate, v->Filename, v->io.xpr_filename);
      done = !fstate;
   }

   /* End batch and return; if we never got started, just cancel receiver */
   if ((v->Filcnt == NULL) && (USEZERO == TRUE))
   {
      updmsg(v,"31-Sent No Files..."); /* We may have wanted to send no files */
      /* Anyway, we had that option enabled and I just assumed that because */
      /* no files could be sent we were not going to be sending any... */
   }
   else
   {
      if (v->Filcnt == NULL) updmsg(v,"32-Could Not Send Any Files...");
      /* We did NOT want to send no files!!! */
      /* Remember, if we even so much as find the file then ++v->Filcnt ! */
   }

   /* If we opened up a file correctly or if we are doing  ZedZap... */
   if ((v->Filcnt) || (USEZERO == TRUE)) saybibi(v);

   /* Else... well... we do this... (but not during ZedZap/ZedZip) */
   else  canit(v);

}  /* End of void sendbatch() */

/**********************************************************
 * short sendone(struct Vars *v)
 *
 * Send the file named in v->Filename
 **********************************************************/
short sendone(struct Vars *v)
{
   struct SetupVars *sv;

#ifdef DEBUGLOG
   mysprintf(v->Msgbuf, "*** Sending %s\n", v->Filename);
   dlog(v, v->Msgbuf);
#endif

   /* Display name of file being sent for user */
   v->xpru.xpru_updatemask = XPRU_FILENAME;
   v->xpru.xpru_filename = v->Filename;
   (*v->io.xpr_update)(&v->xpru);

   /* Set text/binary mode according to options before opening file */
   set_textmode(v);

   /* Open the file, if possible */
   if (! bfopen(v, "r"))
   {
      ++(v->Errcnt);
      upderr(v, "Can't open file; skipping");
      return OK;      /* pass over it, there may be others */
   }

   ++(v->Filcnt);
   getsystime(&v->Starttime);

   /* Kick off the file transfer */
   sv = (void *) v->io.xpr_data;
   switch (sendname(v))
   {
      case ZSKIP:
         bfclose(v);    /* make sure it's closed! */
         return ZSKIP;

      case OK:
         bfclose(v);    /* make sure it's closed! */
         /* File sent; if option DY, delete file after sending */
         if (*sv->option_d == 'Y' && v->io.xpr_extension >= 2 && v->io.xpr_unlink)
         {
            updmsg(v, "41-Deleting file ...");
            (*v->io.xpr_unlink)(v->Filename);
         }
         break;

      case ERROR:
      default:
         bfclose(v); /* make sure it's closed! */
         ++(v->Errcnt);
         return ERROR;
   }  /* End of "switch (sendname(v))" */

   return OK;
}  /* End of short sendone() */

/**********************************************************
 * short sendname(struct Vars *v)
 *
 * Build file info block consisting of file name, length,
 * time, and mode
 **********************************************************/
short sendname(struct Vars *v)
{
   struct SetupVars *sv;
   UBYTE *p, *q, buff [32];

   /* Initialize comm program transfer status display */
   v->Fsize = (v->io.xpr_finfo) ? (*v->io.xpr_finfo)(v->Filename, 1L) : -1;
   v->xpru.xpru_updatemask = XPRU_PROTOCOL | XPRU_FILESIZE | XPRU_MSG
      | XPRU_BLOCKS | XPRU_ERRORS | XPRU_TIMEOUTS | XPRU_BLOCKCHECK
      | XPRU_BYTES | XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE;
   v->xpru.xpru_protocol = "ZedZap";
   v->xpru.xpru_filesize = v->Fsize;
   v->xpru.xpru_msg = (v->Lzconv == ZCNL) ? "Sending text file..." :
      ( (v->Lzconv == ZCBIN) ? "Sending binary file..." : "Sending file...");
   v->xpru.xpru_blocks = v->xpru.xpru_errors = v->xpru.xpru_timeouts = 0;
   v->xpru.xpru_blockcheck = v->Crc32t ? "CRC-32" : "CRC-16";
   v->xpru.xpru_bytes = v->Strtpos = 0;
   update_rate(v);
   (*v->io.xpr_update)(&v->xpru);

   sv = (void *) v->io.xpr_data;
   if (*sv->option_s == 'Y')
   {
      /* If "SY" option selected, send full path */
      strcpy(v->Pktbuf, v->Filename);
      p = v->Pktbuf + strlen(v->Pktbuf) + 1;
   }
   else
   {
      /* else extract outgoing file name without directory path */
      for (p = v->Filename, q = v->Pktbuf ; *p; ++p, ++q)
      {
         if ((*q = *p) == '/' || *q == ':') q = v->Pktbuf - 1;
      }
      *q = '\0';
      p = ++q;
   }

   /* Zero out remainder of file info packet */
   memset(p, 0, sizeof(v->Pktbuf) - (p - v->Pktbuf));

   /* Store file size, timestamp, and mode in info packet */
   /*
    * XPR spec doesn't provide a way to get the file timestamp or file mode,
    * so we'll just fake it with the current time and a dummy 0.
    */
   stcl_o(buff, getsystime(NULL) + UnixTimeOffset);

   /* amiga.lib mysprintf() can't do %lo format, so we do it the hard way */
   /* Yes, octal; ZModem was originally done on Unix, and they like octal there */
   mysprintf(p, "%ld %s 0", (v->Fsize < 0) ? 0L : v->Fsize,buff);

   /* Send filename packet */
   return zsendfile(v, (short) (p - v->Pktbuf + strlen(p) + 1));

}  /* End of short sendname() */

/**********************************************************
 * short zsendfile(struct Vars *v, short blen)
 *
 * Send the filename packet and see if receiver will accept
 * file
 **********************************************************/
short zsendfile(struct Vars *v, short blen)
{
   short c;

   while (TRUE)
   {
      v->Txhdr[ZF0] = v->Lzconv; /* Text or Binary mode; from config string */
      v->Txhdr[ZF1] = LZMANAG;   /* Default file management mode */
      v->Txhdr[ZF2] = LZTRANS;   /* Default file transport mode */
      v->Txhdr[ZF3] = 0;
      zsbhdr(v, ZFILE);
      zsdata(v, blen, ZCRCW);
      sendbuf(v);
again:
      /* Check for abort from comm program */
      if (v->io.xpr_chkabort && (*v->io.xpr_chkabort)())
      {
         bfclose(v);
         return ERROR;
      }
      switch (c = zgethdr(v))
      {
         case ZRINIT:
            goto again;

         case ZCAN:
         case ZCRC:
         case RCDO:
         case TIMEOUT:
         case ZABORT:
         case ZFIN:
            upderr(v, v->Msgbuf);
            return ERROR;

         case ZSKIP:       /* Receiver doesn't want this one */
            upderr(v, "SKIP command received");
            bfclose(v);
            return c;

         case ZRPOS:       /* Receiver wants it; this is starting position */
            bfseek(v, v->Rxpos);
            v->Strtpos = v->Txpos = v->Rxpos;
            if (v->io.xpr_sflush) (*v->io.xpr_sflush)();
            v->Modemcount = 0;
            return zsendfdata(v);

      }  /* End of "switch (c = zgethdr(v))" */

   }  /* End of "while (TRUE)" */

}  /* End of short zsendfile() */

/**********************************************************
 * short zsendfdata(struct Vars *v)
 *
 * Send the file data
 **********************************************************/
short zsendfdata(struct Vars *v)
{
   short c, e, blklen, goodbytes = 0;
   USHORT framelen, maxblklen, goodneeded = 512;

   /* Figure out max data packet size to send */
   maxblklen = KSIZE;
   if (v->Rxbuflen && maxblklen > v->Rxbuflen) maxblklen = v->Rxbuflen;
   blklen = (v->Baud < 1200) ? 256 : KSIZE;
   if (blklen > maxblklen) blklen = maxblklen;

#ifdef DEBUGLOG
   mysprintf(v->Msgbuf, "Rxbuflen=%ld blklen=%ld\n", (long) v->Rxbuflen, (long) blklen);
   dlog(v, v->Msgbuf);
#endif

   /* If an interruption happened, handle it; else keep sending data */
somemore:
   while (char_avail(v))
   {
      /* Check for another incoming packet while discarding line noise */
      switch (readock(v, 1))
      {
         case CAN:
         case RCDO:
         case ZPAD:
            break;

         default:
            continue;
      }

waitack:
#ifdef DEBUGLOG
      dlog(v, "--- At waitack\n");
#endif
      switch (c = getinsync(v))
      {
         default:
            upderr(v, "Transfer cancelled");
            bfclose(v);
            return ERROR;

         case ZSKIP:  /* Receiver changed its mind and wants to skip the file */
            return c;

         case ZACK:   /* ACK at end of frame; resume sending data */
            break;

         case ZRPOS:  /* An error; resend data from last good point */
            blklen >>= 2;
            if (blklen < MINBLOCK) blklen = MINBLOCK;
            if (goodneeded < MAXGOODNEEDED) goodneeded <<= 1;
            v->xpru.xpru_updatemask = XPRU_ERRORS;
            ++v->xpru.xpru_errors;
            (*v->io.xpr_update)(&v->xpru);
            break;

         case ZRINIT:
            updmsg(v, "Done.");
            return OK;

      }  /* End of "switch (c = getinsync(v))" */

   }  /* End of "while (char_avail(v))" */

   /* Transmit ZDATA frame header */
   framelen = v->Rxbuflen;
   stohdr(v, v->Txpos);
   zsbhdr(v, ZDATA);

   /* Keep sending data packets until finished or interrupted */
   do
   {
      /* Read next chunk of file data */
      c = bfread(v, v->Pktbuf, (long) blklen);

      /* Figure out how to handle this data packet */
      if (c < blklen)
      {
         e = ZCRCE;     /* If end of file, this is last data packet */
      }
      else if (v->Rxbuflen && (framelen -= c) <= 0)
      {
         e = ZCRCW;     /* If end of frame, ask for ACK */
      }
      else  e = ZCRCG;  /* Else tell receiver to expect more data packets */

      zsdata(v, c, e);  /* Send the packet */
      sendbuf(v);

      /* Update comm program status display */
      v->xpru.xpru_updatemask = XPRU_BLOCKS | XPRU_BLOCKSIZE | XPRU_BYTES
            | XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE
            | XPRU_BLOCKCHECK;
      ++v->xpru.xpru_blocks;
      v->xpru.xpru_blocksize = c;
      v->xpru.xpru_blockcheck = v->Crc32t ? "CRC-32" : "CRC-16";
      v->xpru.xpru_bytes = v->Txpos += c;
      update_rate(v);
      (*v->io.xpr_update)(&v->xpru);

      /*
       * If we've been sending smaller than normal packets, see if it's
       * time to bump the packet size up a notch yet
       */
      if (blklen < maxblklen && (goodbytes += c) >= goodneeded)
      {
         blklen <<= 1;
         if (blklen > maxblklen) blklen = maxblklen;
         goodbytes = 0;

#ifdef DEBUGLOG
         mysprintf(v->Msgbuf, "Bumping packet size to %ld at %ld\n", (long) blklen, v->Txpos);
         dlog(v, v->Msgbuf);
#endif
      }

      /* Give comm program its timeslice if it needs one */
      if (v->io.xpr_chkmisc) (*v->io.xpr_chkmisc)();

      /* Check for abort from comm program */
      if (v->io.xpr_chkabort && (*v->io.xpr_chkabort)()) goto aborted;

      /* If this was last packet in frame, go wait for ACK from receiver */
      if (e == ZCRCW) goto waitack;

      /*
       * Check if receiver trying to interrupt us; look for incoming packet
       * while discarding line noise
       */
      while (char_avail(v))
      {
         switch (readock(v, 1))
         {
            case CAN:
            case RCDO:
            case ZPAD:
               /* Interruption detected; stop sending and process complaint */
#ifdef DEBUGLOG
               dlog(v, "--- Interrupted send\n");
#endif
               zsdata(v, 0, ZCRCE);
               sendbuf(v);
               goto waitack;
         }
      }

   } while (e == ZCRCG);  /* If no interruption, keep sending data packets */

   /* Done sending file data; send EOF and wait for receiver to acknowledge */
   while (TRUE)
   {
      updmsg(v, "Sending EOF");
      stohdr(v, v->Txpos);
      zsbhdr(v, ZEOF);
      sendbuf(v);
      switch (c = getinsync(v))
      {
         case ZACK:
            continue;

         case ZRPOS:
            goto somemore;

         case ZRINIT:
            updmsg(v, "EOF acknowledged");
            ++v->Starttime.tv_secs;
            update_rate(v);
            v->xpru.xpru_updatemask = XPRU_EXPECTTIME | XPRU_ELAPSEDTIME | XPRU_DATARATE;
            (*v->io.xpr_update)(&v->xpru);
            return OK;

         case ZSKIP:
            return c;

         default:
aborted:
            upderr(v, "Transfer cancelled");
            bfclose(v);
            return ERROR;

      }  /* End of "switch (c = getinsync(v))" */

   }  /* End of "while (TRUE)" */

}  /* End of short zsendfdata() */

/**********************************************************
 *	short getinsync(struct Vars *v)
 *
 * Respond to receiver's complaint, get back in sync with
 * receiver
 **********************************************************/
short getinsync(struct Vars *v)
{
   short c;

   while (TRUE)
   {

#ifdef DEBUGLOG
      dlog(v, "--- At getinsync\n");
#endif

      c = zgethdr(v);
      if (v->io.xpr_sflush) (*v->io.xpr_sflush)();
      v->Modemcount = 0;
      switch (c)
      {
         case ZCAN:
         case ZABORT:
         case ZFIN:
         case RCDO:
         case TIMEOUT:
            upderr(v, v->Msgbuf);
            return ERROR;

         case ZRPOS:
            bfseek(v, v->Rxpos);
            v->Txpos = v->Rxpos;
            mysprintf(v->Msgbuf, "Resending from %ld", v->Txpos);
            upderr(v, v->Msgbuf);
            return c;

         case ZSKIP:
            upderr(v, "SKIP command received");

            /* fallthrough... */
         case ZRINIT:
            bfclose(v);

            /* fallthrough... */
         case ZACK:
            return c;

         default:
            zsbhdr(v, ZNAK);
            sendbuf(v);
            continue;
      }
   }
}  /* End of short getinsync() */

/**********************************************************
 * void saybibi(struct Vars *v)
 *
 * End of batch transmission; disengage cleanly from receiver
 **********************************************************/
void saybibi(struct Vars *v)
{
   long q;

   while (TRUE)
   {
      stohdr(v, 0L);
      zsbhdr(v, ZFIN);
      sendbuf(v);
      q = zgethdr(v);
      switch (q)
      {
         case ZFIN:
            sendline(v, 'O');
            sendline(v, 'O');
            sendbuf(v);

         /* fallthrough... */
         case ZCAN:
         case RCDO:
         case TIMEOUT:
            return;
      }
   }
}  /* End of void saybibi() */

/* End of Send.c source */
