/**********************************************************************
 * Receive.c:  File reception routines for xprzmodem.library;
 * Version 2.10, 12 February 1991, by Rick Huebner.
 * Based closely on Chuck Forsberg's rz.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.
 **********************************************************************
 * Modified to support ZedZap/ZedZip fidonet protocol by
 * Yves Konighofer and Russel McOrmond.
 *
 * Adapted to xprzmodem Version 2.52 by Andrea Cisternino (30 Nov 92)
 **********************************************************************/

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

#ifdef DEBUGLOG
extern void *DebugLog;
#endif

/**********************************************************
 * long XProtocolReceive(struct XPR_IO *xio)
 *
 * Main file reception routine; called by comm program
 **********************************************************/
long __saveds __asm XProtocolReceive(register __a0 struct XPR_IO *xio)
{
   struct SetupVars *sv;
   struct Vars *v;
   UBYTE err = FALSE;

   /* Perform common setup and initializations */
   if (! (v = setup(xio))) return XPRS_FAILURE;
   v->Tryzhdrtype = ZRINIT;
   v->Rxtimeout = 100;

   sv = (void *) v->io.xpr_data;
   if (sv->bufpos)
   {
      v->Modemchar = v->Modembuf;
      if (sv->buflen > sizeof(v->Modembuf)) sv->buflen = sizeof(v->Modembuf);
      memcpy(v->Modembuf, sv->bufpos,sv->buflen);
      v->Modemcount = sv->buflen;
   }

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

   /* Transfer the files */  
   if (rcvbatch(v) == ERROR)
   {
      upderr(v, "Download cancelled or timed out");
      err = TRUE;
   }
   else  updmsg(v, "Done.");

   /* Clean up and return */
   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 XProtocolReceive() */

/**********************************************************
 * short rcvbatch(struct Vars *v)
 *
 * Start the batch transfer
 **********************************************************/
short rcvbatch(struct Vars *v)
{
   switch (tryz(v))
   {
      case ZCOMPL:
         return OK;
      case ZFILE:
         if (rzfiles(v) == OK) return OK;
   }
   /* Do we really want to just can it? */
   canit(v);
   return ERROR;
}  /* End of short rcvbatch() */

/**********************************************************
 * short tryz(struct Vars *v)
 *
 * Negotiate with sender to start a file transfer
 **********************************************************/
short tryz(struct Vars *v)
{
   short n, errors = 0;

   for (n = v->ErrorLimit; --n >= 0; )
   {
      /* Set max frame length and capability flags */
      stohdr(v, (long) v->Tframlen);
      v->Txhdr[ZF0] = CANFC32 | CANFDX | CANOVIO;
      zshhdr(v, v->Tryzhdrtype);
      sendbuf(v);
again:
      /* Check for abort from comm program */
      if (v->io.xpr_chkabort && (*v->io.xpr_chkabort)()) return ERROR;

      switch (zgethdr(v))
      {
         case ZFILE:    /* File name and info packet */
            v->Zconv  = v->Rxhdr[ZF0];  /* Suggested txt mode; ZCNL = text, */
                                        /* ZCBIN = binary, 0 = don't know. */
            v->Zmanag = v->Rxhdr[ZF1];  /* Suggested file management mode. */
            v->Ztrans = v->Rxhdr[ZF2];  /* Suggested file transport mode. */
            v->Tryzhdrtype = ZRINIT;
            if (zrdata(v, v->Pktbuf, KSIZE) == GOTCRCW) return ZFILE;
            zshhdr(v, ZNAK);   /* Packet mangled, ask for retry */
            sendbuf(v);
            if (--n < 0)
            {
               upderr(v,"Unable to Get A Valid Header (ZFILE)");
               return ERROR;
            }
            goto again;

         case ZSINIT:   /* Special attention-grabbing string to use to */
                        /* interrupt sender */
            if (zrdata(v, v->Attn, ZATTNLEN) == GOTCRCW) zshhdr(v, ZACK);
            else zshhdr(v, ZNAK);
            sendbuf(v);
            if (--n < 0)
            {
               upderr(v,"Unable to Get A Valid Header (ZFILE)");
               return ERROR;
            }
            goto again;

         case ZFREECNT:   /* Sender wants to know how much room we've got */
            stohdr(v, getfree());
            zshhdr(v, ZACK);
            sendbuf(v);
            goto again;

         case ZCOMMAND:   /* Sender wants us to do remote commands, */
                          /* but we don't do requests. */
            if (zrdata(v, v->Pktbuf, KSIZE) == GOTCRCW)
            {
               mysprintf(v->Msgbuf, "Ignoring command: %s", v->Pktbuf);
               upderr(v, v->Msgbuf); /* Ignore and report all uploaded commands */
               stohdr(v, 0L);        /* whilst telling sender they worked; */
               do
               {
                  zshhdr(v, ZCOMPL); /* paranoia can be good for you... */
                  sendbuf(v);
               } while (++errors < v->ErrorLimit && zgethdr(v) != ZFIN);
               ackbibi(v);
               return ZCOMPL;
            }
            else zshhdr(v, ZNAK);
            sendbuf(v);
            if (--n < 0)
            {
               upderr(v,"Unable to Get A Valid Header (ZFILE)");
               return ERROR;
            }
            goto again;

         case ZCOMPL:
            goto again;

         case ZFIN:    /* Sender has ended batch */
            ackbibi(v);
            return ZCOMPL;

         case ZCAN:
            upderr(v,"Transfer Cancelled (Remote Sent ZCAN)");
            return ERROR;

         case RCDO:
            upderr(v,"Transfer Cancelled (Remote Dropped Carrier)");
            return ERROR;

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

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

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

/**********************************************************
 * short rzfiles(struct Vars *v)
 *
 * Receive a batch of files
 **********************************************************/
short rzfiles(struct Vars *v)
{
   struct SetupVars *sv;
   short c;

   /* Keep receiving files until end of batch or error */
   while (TRUE)
   {
      v->Filename[0]='\0';
      switch (c = rzfile(v))
      {
         case ZSKIP:
         case ZEOF:
            if(strlen(v->Filename)) updstatus(v,v->Filename,XPRS_SUCCESS);
            switch (tryz(v))
            {
               case ZCOMPL:
                  return OK;
               default:
                  return ERROR;
               case ZFILE:
                  break;
            }
            break;

         default:
            if (c == ERROR) updmsg(v, "34-File Transfer FAILED");
            bfclose(v);
            /* Tell the system it failed! */
            if(strlen(v->Filename)) updstatus(v,v->Filename,XPRS_FAILURE);
            sv = (void *) v->io.xpr_data;
            if (*sv->option_k == 'N' && v->io.xpr_extension >= 2 && v->io.xpr_unlink)
            {
               updmsg(v, "41-Deleting partially received file");
               (*v->io.xpr_unlink)(v->Filename);
            }
            else updmsg(v, "Keeping partially received file");
            return c;
      }
   }
}  /* End of short rzfiles() */

/**********************************************************
 * short rzfile(struct Vars *v)
 *
 * Receive one file; file name packet already read into
 * Pktbuf by tryz()
 **********************************************************/
short rzfile(struct Vars *v)
{
   short c, n;

   /*
    * Process file name packet; either open file and prepare to receive,
    * or tell us to skip this one. File is now opened!
    */

   c = procheader(v);
   if (c == ERROR || c == ZSKIP) return v->Tryzhdrtype = ZSKIP;

   n = v->ErrorLimit;
   v->Rxbytes = v->Strtpos;
   v->Rxcount = 0;
   v->Eofseen = FALSE;

   /* Receive ZDATA frames until finished */
   while (TRUE)
   {
      stohdr(v, v->Rxbytes);      /* Tell sender where to start frame */
      zshhdr(v, ZRPOS);
      sendbuf(v);

nxthdr:
      /* Check for abort from comm program */
      if (v->io.xpr_chkabort && (*v->io.xpr_chkabort)()) return ERROR;
      switch (c = zgethdr(v))    /* Wait for frame header */
      {
         case ZDATA:     /* More file data packets forthcoming */
            if ((v->Rxpos > v->Rxbytes) || ((v->Rxpos + 2*(v->Rxcount)) < v->Rxbytes))
            /* We aren't in sync; go back */
            {
               if ( --n < 0) return ERROR;
               v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
               mysprintf(v->Msgbuf, "Data at bad position; here=%ld, there=%ld", v->Rxbytes, v->Rxpos);
               v->xpru.xpru_errormsg = (char *) v->Msgbuf;
               ++v->xpru.xpru_errors;
               (*v->io.xpr_update)(&v->xpru);
               zmputs(v, v->Attn);
               continue;
            }

         /* Receive file data packet(s) */

moredata:
         /* 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)())
            {
               upderr(v,"Transfer Cancelled (Local Abort)");
               return ERROR;
            }

            switch (c = zrdata(v, v->Pktbuf,KSIZE))
            {
               case ZCAN:
                  upderr(v,"Transfer Cancelled (Remote Sent ZCAN)");
                  return ERROR;

               case RCDO:
                  upderr(v,"Transfer cancelled (Remote Dropped Carrier)");
                  return ERROR;

               case ERROR:     /* CRC error or packet too long */
                  if ( --n < 0) return ERROR;
                  v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
                  mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; %ld retries left", v->Rxbytes, (long) n);
                  v->xpru.xpru_errormsg = (char *) v->Msgbuf;
                  ++v->xpru.xpru_errors;
                  (*v->io.xpr_update)(&v->xpru);
                  zmputs(v, v->Attn);
                  continue;

               case TIMEOUT:
                  if ( --n < 0) return ERROR;
                  v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
                  mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; %ld retries left", v->Rxbytes, (long) n);
                  v->xpru.xpru_errormsg = (char *) v->Msgbuf;
                  ++v->xpru.xpru_timeouts;
                  (*v->io.xpr_update)(&v->xpru);
                  continue;

               case GOTCRCW:   /* Sender says it's waiting for an ACK */
                  n = v->ErrorLimit;
                  if (putsec(v) == ERROR) return ERROR;
                  stohdr(v, v->Rxbytes);
                  zshhdr(v, ZACK);
                  sendbuf(v);
                  goto nxthdr;

               case GOTCRCQ:   /* Sender says it's not waiting, */
                               /* but ACK anyway (rarely used) */
                  n = v->ErrorLimit;
                  if (putsec(v) == ERROR) return ERROR;
                  stohdr(v, v->Rxbytes);
                  zshhdr(v, ZACK);
                  sendbuf(v);
                  goto moredata;

               case GOTCRCG:   /* Sender says keep receiving, */
                               /* there's more coming */
                  n = v->ErrorLimit;
                  if (putsec(v) == ERROR) return ERROR;
                  goto moredata;

               case GOTCRCE:   /* Sender says this is the last packet */
                  n = v->ErrorLimit;
                  if (putsec(v) == ERROR) return ERROR;
                  goto nxthdr;
            }  /* End of "switch (c = zrdata(v, v->Pktbuf,KSIZE))" */

         case ZNAK:
         case TIMEOUT:
            if (--n < 0) return ERROR;
            v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
            mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; %ld retries left",v->Rxbytes, (long) n);
            v->xpru.xpru_errormsg = (char *) v->Msgbuf;
            ++v->xpru.xpru_timeouts;
            (*v->io.xpr_update)(&v->xpru);
            continue;

         case ZFILE:
            zrdata(v, v->Pktbuf, KSIZE);   /* Read and discard redundant */
            continue;                      /* filename packet */

         case ZEOF:
             if (v->Rxpos != v->Rxbytes)   /* We aren't in sync; go back */
             {
                mysprintf(v->Msgbuf, "Bad EOF; here=%ld, there=%ld",v->Rxbytes, v->Rxpos);
                upderr(v, v->Msgbuf);
                continue;
             }
             bfclose(v);    /* All done; close file */
             updmsg(v,"33-File Transfer OK");
             updmsg(v,"28-Checking For Next File");
             return c;

         case ERROR:     /* Too much garbage while waiting for frame header */
            if ( --n < 0) return ERROR;
            v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
            mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; %ld retries left",v->Rxbytes, (long) n);
            v->xpru.xpru_errormsg = (char *) v->Msgbuf;
            ++v->xpru.xpru_errors;
            (*v->io.xpr_update)(&v->xpru);
            zmputs(v, v->Attn);
            continue;

         case ZSKIP:
            return c;

         default:
            return ERROR;

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

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

}  /* End of short rzfile() */

/**********************************************************
 * short procheader(struct Vars *v)
 *
 * Process file name & info packet; either open file and
 * prepare to receive, or return ERROR if we should skip
 * this one for some reason
 **********************************************************/
short procheader(struct Vars *v)
{
   struct SetupVars *sv;
   UBYTE *p, *openmode, buff[PATHLEN];
   long n;

   openmode = "w";
   v->Strtpos = 0;

   /* Extract expected filesize from file info packet, if given */
   v->Fsize = -1;
   p = strchr(v->Pktbuf, '\0') + 1;
   if (*p) v->Fsize = atol(p);

/*
 *    Make sure we have room for file; skip it if not.
 *    Commented out for now, since getfree() isn't implemented yet.

   if (v->Fsize > getfree())
   {
      mysprintf(v->Msgbuf, "Insufficient disk space; need %ld bytes, have %ld", v->Fsize, getfree());
      upderr(v, v->Msgbuf);
      v->Noroom = TRUE;
      return ERROR;
   }
*/

   /* If option RY set, use full received file path */
   sv = (void *) v->io.xpr_data;
   if (*sv->option_r == 'Y') strcpy(v->Filename, v->Pktbuf);
   else
   {
      /* else use the default directory path specified in the setup options */

      strcpy(v->Filename, sv->option_p);
      p = v->Filename + strlen(v->Filename) - 1;
      if (p >= v->Filename && *p != '/' && *p != ':') *++p = '/';
      *++p = '\0';

      /*
       * Append the filename from the file info packet; ignore anything before
       * last /, \, or : in filename (received directory path is ignored)
       */

      p = strchr(v->Pktbuf, '\0'); /* start at end and scan back */
                                   /* to start of name */
      while (p >= v->Pktbuf && *p != '/' && *p != '\\' && *p != ':') --p;
      strcat(v->Filename, ++p);
   }

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

   /*
    * If a file with this name already exists, handle in
    * accordance with O option
    */
   if (exist(v))
   {
      switch (*sv->option_o)
      {
         case 'N':   /* Don't overwrite; change name to prevent collision */
            strcpy(buff, v->Filename);
            strcat(v->Filename, ".dup");
            n = 2;
            while (exist(v))
            {
               mysprintf(v->Filename, "%s.dup%ld", buff, n);
               ++n;
            }
            /* Update filename display to show new name */
            (*v->io.xpr_update)(&v->xpru);
            break;

         case 'R':   /* Resume transfer from current end of file */
            openmode = "a";
            v->Strtpos = (*v->io.xpr_finfo)(v->Filename, 1L);
            break;

         case 'S':   /* Skip it */
            upderr(v, "File already exists; skipping");
            return ZSKIP;

         /* Else 'Y', go ahead and overwrite it (openmode = w) */
      }
   }

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

   /*
    * Figure out file translation mode to use; either binary (verbatim
    * transfer) or ASCII (perform end-of-line conversions).  If user has
    * specified a mode (TY or TN), that's what we use.  If user says use
    * sender's suggestion (T?), set mode according to Zconv flag.  If neither
    * side specifies, default to binary mode.
    */
   v->Thisbinary =  v->Rxbinary || !v->Rxascii;
   if (! v->Rxbinary && v->Zconv == ZCNL) v->Thisbinary = FALSE;
   if (! v->Rxascii && v->Zconv == ZCBIN) v->Thisbinary = TRUE;

   /* Open the file (finally) */
   if (!bfopen(v, openmode))
   {
      ++v->Errcnt;
      upderr(v, "Can't open file; skipping");
      return ERROR;
   }
   getsystime(&v->Starttime);

   /* Initialize comm program transfer status display */
   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->Thisbinary) ? "Receiving binary file..." : "Receiving text file...";
   v->xpru.xpru_blocks = v->xpru.xpru_errors = v->xpru.xpru_timeouts = 0;
   v->xpru.xpru_blockcheck = v->Crc32 ? "CRC-32" : "CRC-16";
   v->xpru.xpru_bytes = v->Strtpos;
   update_rate(v);
   (*v->io.xpr_update)(&v->xpru);

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

/**********************************************************
 * short putsec(struct Vars *v)
 *
 * Writes the received file data to the output file.
 * If in ASCII mode, stops writing at first ^Z, and converts all
 * \r\n pairs or solo \r's to \n's.
 **********************************************************/
short putsec(struct Vars *v)
{
   static char nl = '\n';
   UBYTE *p;
   short n;

   /* If in binary mode, write it out verbatim */
   if (v->Thisbinary)      /* Ignore data received already */
   {
      if(v->Rxpos != v->Rxbytes)
      {
         v->xpru.xpru_updatemask = XPRU_ERRORMSG;
         sprintf(v->Msgbuf,"Redundant Data ignored; Here=%ld, There=%ld",v->Rxbytes,v->Rxpos);
         v->xpru.xpru_errormsg = (char *)v->Msgbuf;
         (*v->io.xpr_update)(&v->xpru);
         return OK;
      }
      if (bfwrite(v, v->Pktbuf, (long) v->Rxcount) != v->Rxcount) goto diskfull;
   }
   else
   {
      /* If in text mode, perform end-of-line cleanup */
      if (v->Eofseen) return OK;
      for (p = v->Pktbuf, n = v->Rxcount; --n >= 0; ++p)
      {
         if (*p == CPMEOF)
         {
            v->Eofseen = TRUE;
            return OK;
         }
         else if (*p != '\n' && v->Lastsent == '\r')
         {
            if (bfwrite(v, &nl, 1L) != 1) goto diskfull;
         }  
         if (*p != '\r' && bfwrite(v, p, 1L) != 1) goto diskfull;
         v->Lastsent = *p;
      }
   }

   /* 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 = v->Rxcount;
   v->xpru.xpru_bytes = v->Rxbytes += v->Rxcount;
   v->xpru.xpru_blockcheck = v->Crc32 ? "CRC-32" : "CRC-16";
   update_rate(v);
   (*v->io.xpr_update)(&v->xpru);

   v->Rxpos += v->Rxcount;  /* update this pointer */
   return OK;

diskfull:
   upderr(v, "Error writing file; disk full?");
   v->Noroom = TRUE;
   return ERROR;

}  /* End of short putsec() */

/**********************************************************
 * void ackbibi(struct Vars *v)
 *
 * End of batch transmission; disengage cleanly from sender
 **********************************************************/
void ackbibi(struct Vars *v)
{
   stohdr(v, 0L);

   /* just send a ZFIN and that's it... */
   zshhdr(v,ZFIN);
   sendbuf(v);
   return;

}  /* End of void ackbibi() */

/* End of Receive.c source */
