/**********************************************************************
 * 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 "xprzmodem_all.h"

#define CATCOMP_NUMBERS
#include "xprzmodem_catalog.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;
    }

  /* Transfer the files */
  if (rcvbatch (v) == ERROR)
    {
      upderr (v, GetLocalString( &li, MSG_DOWNLOAD_USER_ERROR ));
      err = TRUE;
    }
  else
    updmsg (v, GetLocalString( &li, MSG_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)
    {
      (*v->io.xpr_fclose) ((long) 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)
{
#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  switch (tryz (v))
    {
    case ZCOMPL:
      return OK;
    case ZFILE:
      if (rzfiles (v) == OK)
	return OK;
    }

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  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;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  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);
	  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)
	    {
	      xprsprintf (v->Msgbuf, "%s: %s", GetLocalString( &li, MSG_IGNORING_COMMAND), 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
 **********************************************************/
short
rzfiles (struct Vars *v)
{
  struct SetupVars *sv;
  short c;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* 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);
	  sv = (void *) v->io.xpr_data;
	  if (*sv->option_k == 'N' && v->io.xpr_extension >= 2
	      && v->io.xpr_unlink)
	    {
	      updmsg (v, GetLocalString( &li, MSG_DELETING_RECEIVED_FILE ));
	      (*v->io.xpr_unlink) (v->Filename);
	    }
	  else
	    updmsg (v, GetLocalString( &li, MSG_KEEPING_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;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /*
     * 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 (v->io.xpr_chkabort && (*v->io.xpr_chkabort) ())
	return ERROR;
      switch (c = zgethdr (v))	/* Wait for frame header */
	{
	default:
#ifdef DEBUGLOG
	  xprsprintf (v->Msgbuf, "rzfile: zgethdr returned %ld\n", (long) c);
	  dlog (v, v->Msgbuf);
	  D (DEBUGINFO);

#endif
	  return ERROR;
	case ZNAK:
	case TIMEOUT:
	  if (--n < 0)
	    return ERROR;
#ifdef DEBUGLOG
	  dlog (v, "rzfile: zgethdr NAK/Timeout\n");
	  D (DEBUGINFO);
#endif
	  v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
	  xprsprintf (strchr (v->Msgbuf, '\0'), "@ %ld; %ld %s",
		      v->Rxbytes, (long) n, GetLocalString( &li, MSG_RETRIES_LEFT ));
	  v->xpru.xpru_errormsg = (char *) v->Msgbuf;
	  ++v->xpru.xpru_timeouts;
	  (*v->io.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 */
	    {
	      xprsprintf (v->Msgbuf, GetLocalString( &li, MSG_BAD_EOF ),
			  v->Rxbytes, v->Rxpos);
	      upderr (v, v->Msgbuf);
	      continue;
	    }
	  bfclose (v);		/* All done; close file */
#ifdef DEBUGLOG
	  dlog (v, "rzfile: EOF\n");
	  D (DEBUGINFO);
#endif
	  updmsg (v, GetLocalString( &li, MSG_EOF_RECEIVED ));
	  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");
	  D (DEBUGINFO);
#endif
	  v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
	  xprsprintf (strchr (v->Msgbuf, '\0'), "@ %ld; %ld %s",
		      v->Rxbytes, (long) n, GetLocalString( &li, MSG_RETRIES_LEFT ));
	  v->xpru.xpru_errormsg = (char *) v->Msgbuf;
	  ++v->xpru.xpru_errors;
	  (*v->io.xpr_update) (&v->xpru);
	  zmputs (v, v->Attn);
	  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;
	      xprsprintf (v->Msgbuf, GetLocalString( &li, MSG_DATA_AT_BAD_POSITION ),
			  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) ())
	    goto aborted;
	  switch (c = zrdata (v, v->Pktbuf, KSIZE))
	    {
	    case ZCAN:
	    case RCDO:
	    aborted:
#ifdef DEBUGLOG
	      dlog (v, "rzfile: zrdata returned CAN\n");
	      D (DEBUGINFO);
#endif
	      upderr (v, GetLocalString( &li, MSG_TRANSFER_CANCELLED ));
	      return ERROR;
	    case ERROR:	/* CRC error or packet too long */
	      if (--n < 0)
		return ERROR;
#ifdef DEBUGLOG
	      dlog (v, "rzfile: zrdata returned error\n");
	      D (DEBUGINFO);
#endif
	      v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_ERRORS;
	      xprsprintf (strchr (v->Msgbuf, '\0'), "@ %ld; %ld %s",
			  v->Rxbytes, (long) n, GetLocalString( &li, MSG_RETRIES_LEFT ));
	      v->xpru.xpru_errormsg = (char *) v->Msgbuf;
	      ++v->xpru.xpru_errors;
	      (*v->io.xpr_update) (&v->xpru);
#ifdef DEBUGLOG
	      dlog (v, v->Msgbuf);
	      dlog (v, "\n");
	      D (DEBUGINFO);
#endif
	      zmputs (v, v->Attn);
	      continue;
	    case TIMEOUT:
	      if (--n < 0)
		return ERROR;
#ifdef DEBUGLOG
	      dlog (v, "rzfile: zrdata returned timeout\n");
	      D (DEBUGINFO);
#endif
	      v->xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_TIMEOUTS;
	      xprsprintf (strchr (v->Msgbuf, '\0'), "@ %ld; %ld %s",
			  v->Rxbytes, (long) n, GetLocalString( &li, MSG_RETRIES_LEFT ));
	      v->xpru.xpru_errormsg = (char *) v->Msgbuf;
	      ++v->xpru.xpru_timeouts;
	      (*v->io.xpr_update) (&v->xpru);
#ifdef DEBUGLOG
	      dlog (v, v->Msgbuf);
	      dlog (v, "\n");
	      D (DEBUGINFO);
#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
 **********************************************************/
short
procheader (struct Vars *v)
{
  struct SetupVars *sv;
  UBYTE *p, *openmode, buff[PATHLEN];
  long n;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  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())
     {
     xprsprintf(v->Msgbuf, GetLocalString( &li, MSG_INSUFFICIENT_DISK_SPACE ),
     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))
	    {
	      xprsprintf (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, GetLocalString( &li, MSG_FILE_EXISTS ));
	  return ERROR;
	  /* 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 (!(v->File = bfopen (v, openmode)))
    {
      ++v->Errcnt;
      upderr (v, GetLocalString( &li, MSG_CANT_OPEN_FILE ));
      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 = (v->Thisbinary) ? GetLocalString( &li, MSG_RECEIVE_BINARY )
    : GetLocalString( &li, MSG_RECEIVE_TEXT );
  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);

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  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;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* 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);
  (*v->io.xpr_update) (&v->xpru);

  return OK;

diskfull:
  upderr (v, GetLocalString( &li, MSG_ERROR_WRITING_FILE ));
  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)
{
  short n;

#ifdef DEBUGLOG
  dlog (v, "ackbibi:\n");
  D (DEBUGINFO);
#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 */
