/**********************************************************************
 * 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.
 **********************************************************************/

/*#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

/* Proto für Inlines */

static short rcvbatch(struct Vars *v);
static short tryz(struct Vars *v);
static short rzfiles(struct Vars *v);
static short rzfile(struct Vars *v);
static short procheader(struct Vars *v);
static short putsec(struct Vars *v);
static void  ackbibi(struct Vars *v);

/**********************************************************
 *	long XProtocolReceive(struct XPR_IO *xio)
 *
 * Main file reception routine; called by comm program
 **********************************************************/
long XProtocolReceive(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;
      }

   /* Transfer the files */  
   if (rcvbatch(v) == ERROR)
   {
      upderr(v, "Empfang abgebrochen oder Timeout!");
      err = TRUE;
      }
   else
      updmsg(v, "Übertragung erfolgreich beendet.");

   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
 **********************************************************/
static short rcvbatch(struct Vars *v)
{
   switch (tryz(v))
   {
   case ZCOMPL:
      return OK;
   case ZFILE:
      if (rzfiles(v) == OK)
	 return OK;
      }
   canit(v);
   return ERROR;
   }	/* End of short rcvbatch() */

/**********************************************************
 *	short tryz(struct Vars *v)
 *
 * Negotiate with sender to start a file transfer
 **********************************************************/
static 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 (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);
         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);
         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, "Kommando %s ignoriert!", 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);
         goto again;
      case ZCOMPL:
         goto again;
      case ZFIN:    /* Sender has ended batch */
         ackbibi(v);
         return ZCOMPL;
      case ZCAN:
      case RCDO:
         upderr(v, v->Msgbuf);
         return ERROR;
         }
      }
   return ERROR;
   }	/* End of short tryz() */

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

   /* Keep receiving files until end of batch or error */
   while (TRUE)
   {
      switch (c = rzfile(v))
      {
      case ZEOF:
      case ZSKIP:
         switch (tryz(v))
	 {
         case ZCOMPL:
            return OK;
         default:
            return ERROR;
         case ZFILE:
            break;
            }
         break;
      default:
         bfclose(v);
	    updmsg(v, "Datei nur teilweise empfangen!");
         return c;
         }
      }
   }	/* End of short rzfiles() */

/**********************************************************
 *	short rzfile(struct Vars *v)
 *
 * Receive one file; file name packet already read into
 * Pktbuf by tryz()
 **********************************************************/
static 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.
    */
   if (procheader(v) == ERROR)
      return v->Tryzhdrtype = ZSKIP;

   n = v->ErrorLimit;
   v->Rxbytes = v->Strtpos;
   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 (xpr_chkabort())
         return ERROR;
      switch (c = zgethdr(v))    /* Wait for frame header */
      {
      default:
#ifdef DEBUGLOG
         mysprintf(v->Msgbuf, "rzfile: zgethdr returned %ld\n", (long) c);
         dlog(v, v->Msgbuf);
#endif
         return ERROR;
      case ZNAK:
      case TIMEOUT:
         if (--n < 0)
	    return ERROR;
#ifdef DEBUGLOG
         dlog(v, "rzfile: zgethdr NAK/Timeout\n");
#endif
         v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
         mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; noch %ld Versuche",
	    v->Rxbytes, (long) n);
         v->xpru.xpru_errormsg = (char *) v->Msgbuf;
         ++v->xpru.xpru_timeouts;
         xpr_update(&v->xpru);
         continue;
      case ZFILE:     /* Sender didn't see our ZRPOS yet; try again */
         zrdata(v, v->Pktbuf, KSIZE);   /* Read and discard redundant */
	 continue;			/* filename packet */
      case ZEOF:      /* End of file data */
         if (v->Rxpos != v->Rxbytes)    /* We aren't in sync; go back */
	 {
            mysprintf(v->Msgbuf, "Falsches EOF: hier @%ld, Sender @%ld!",
	       v->Rxbytes, v->Rxpos);
            upderr(v, v->Msgbuf);
            continue;
            }
         bfclose(v);  /* All done; close file */
#ifdef DEBUGLOG
         dlog(v, "rzfile: EOF\n");
#endif
         updmsg(v, "EOF empfangen; warte auf nächste Datei.");
         return c;
      case ERROR:     /* Too much garbage while waiting for frame header */
         if ( --n < 0)
	    return ERROR;
#ifdef DEBUGLOG
         dlog(v, "rzfile: zgethdr garbage overflow\n");
#endif
         v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
         mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; noch %ld Versuche",
	    v->Rxbytes, (long) n);
         v->xpru.xpru_errormsg = (char *) v->Msgbuf;
         ++v->xpru.xpru_errors;
         xpr_update(&v->xpru);
         zmputs(v, v->Attn);
		 xpr_sflush();
		 v->Modemcount = 0;
         continue;
      case ZDATA:     /* More file data packets forthcoming */
         if (v->Rxpos != 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, "Synchronisationsfehler: hier @%ld, Sender @%ld!",
	       v->Rxbytes, v->Rxpos);
            v->xpru.xpru_errormsg = (char *) v->Msgbuf;
            ++v->xpru.xpru_errors;
            xpr_update(&v->xpru);

			// flushen
			v->Modemcount = 0;
            zmputs(v, v->Attn);
			xpr_sflush();

            continue;
            }
         /* Receive file data packet(s) */
moredata:
         /* Check for abort from comm program */
         if (xpr_chkabort())
	    goto aborted;
         switch (c = zrdata(v, v->Pktbuf,KSIZE))
	 {
	 case ZCAN:
         case RCDO:
aborted:
#ifdef DEBUGLOG
            dlog(v, "rzfile: zrdata returned CAN\n");
#endif
            upderr(v, "Übertragung abgebrochen!");
            return ERROR;
         case ERROR:     /* CRC error or packet too long */
            if ( --n < 0)
	       return ERROR;
#ifdef DEBUGLOG
            dlog(v, "rzfile: zrdata returned error\n");
#endif
            v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
            mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; noch %ld Versuche",
	       v->Rxbytes, (long) n);
            v->xpru.xpru_errormsg = (char *) v->Msgbuf;
            ++v->xpru.xpru_errors;
            xpr_update(&v->xpru);
#ifdef DEBUGLOG
            dlog(v, v->Msgbuf);
            dlog(v, "\n");
#endif
            zmputs(v, v->Attn);
            continue;
         case TIMEOUT:
            if ( --n < 0)
	       return ERROR;
#ifdef DEBUGLOG
            dlog(v, "rzfile: zrdata returned timeout\n");
#endif
            v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
            mysprintf(strchr(v->Msgbuf, '\0'), "@ %ld; noch %ld Versuche",
	       v->Rxbytes, (long) n);
            v->xpru.xpru_errormsg = (char *) v->Msgbuf;
            ++v->xpru.xpru_timeouts;
            xpr_update(&v->xpru);
#ifdef DEBUGLOG
            dlog(v, v->Msgbuf);
            dlog(v, "\n");
#endif
            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 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
 **********************************************************/
static short procheader(struct Vars *v)
{
   UBYTE *p, *openmode;

   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;
      }
   */

     /* else use the default directory path specified in the setup options */
      p = strchr(v->Pktbuf, '\0'); /* start at end and scan back */
				   /* to start of name */
      while (p >= v->Pktbuf && *p != '/' && *p != '\\' && *p != ':')
	 --p;
      strcpy(v->Filename, ++p);

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

   /*
    * If a file with this name already exists, handle in
    * accordance with O option
    */
   if (exist(v))
   {
         openmode = "a";
         v->Strtpos = xpr_finfo(v->Filename, 1L);
      }

   /* 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 (! (v->File = bfopen(v, openmode)))
   {
      ++v->Errcnt;
      upderr(v, "Kann Datei nicht öffnen!");
      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 = "ZModem";
   v->xpru.xpru_filesize = v->Fsize;
   v->xpru.xpru_msg = "Empfange Datei...";
   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);
   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.
 **********************************************************/
static short putsec(struct Vars *v)
{
   static char nl = '\n';
   UBYTE *p;
   short n;

   /* If in binary mode, write it out verbatim */
   if (v->Thisbinary)
   {
      if (bfwrite(v, v->Pktbuf, (long) v->Rxcount) != v->Rxcount)
	 goto diskfull;
      /* If in text mode, perform end-of-line cleanup */
      }
   else
   {
      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);
   xpr_update(&v->xpru);

   return OK;

diskfull:
   upderr(v, "Fehler beim Schreiben in die Datei!");
   v->Noroom = TRUE;
   return ERROR;
   }	/* End of short putsec() */

/**********************************************************
 *	void ackbibi(struct Vars *v)
 *
 * End of batch transmission; disengage cleanly from sender
 **********************************************************/
static void ackbibi(struct Vars *v)
{
   short n;

#ifdef DEBUGLOG
   dlog(v, "ackbibi:\n");
#endif
   stohdr(v, 0L);
   for (n = 4; --n; )
   {
      zshhdr(v, ZFIN);
      sendbuf(v);
      switch (readock(v, 100))
      {
      case 'O':
         readock(v, 1);    /* Discard 2nd 'O' */
      case TIMEOUT:
      case RCDO:
         return;
         }
      }
   }	/* End of void ackbibi() */
/* End of Receive.c source */
