/*--------------------------------------------------------------------------*/
/* FILE: zreceive.c (Opus zmodem receiver)                                  */
/*                                                                          */
/*                                                                          */
/*               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"

/*--------------------------------------------------------------------------*/
/* External material                                                        */
/*--------------------------------------------------------------------------*/
extern int  dexists(byte *);
extern long zfree (char *);


/*--------------------------------------------------------------------------*/
/* Local routines                                                           */
/*--------------------------------------------------------------------------*/
int cdecl get_Zmodem(byte *,FILE *);

static int  pascal RZ_ReceiveData(byte *,int );
static int  pascal RZ_InitReceiver(void);
static int  pascal RZ_ReceiveBatch(FILE *);
static int  pascal RZ_ReceiveFile(FILE *);
static int  pascal RZ_GetHeader(void);
static int  pascal RZ_SaveToDisk(unsigned long *);
static void pascal RZ_AckBibi(void);


/*--------------------------------------------------------------------------*/
/* Private declarations                                                     */
/*--------------------------------------------------------------------------*/
static long DiskAvail;


/*--------------------------------------------------------------------------*/
/* Private data                                                             */
/*--------------------------------------------------------------------------*/

/* Parameters for ZSINIT frame */
#define ZATTNLEN 32              /* Max length of attention string          */

static char  Attn[ZATTNLEN+1];   /* String rx sends to tx on err            */
static FILE *Outfile;            /* Handle of file being received           */
static byte *Secbuf;             /* Pointer to receive buffer               */
static int   Tryzhdrtype;        /* Hdr type to send for Last rx close      */
static char  isBinary;           /* Current file is binary mode             */
static char  EOFseen;            /* indicates cpm eof (^Z) was received     */
static char  Zconv;              /* ZMODEM file conversion request          */
static int   RxCount;            /* Count of data bytes received            */




/*--------------------------------------------------------------------------*/
/* GET ZMODEM                                                               */
/* Receive a batch of files.                                                */
/* returns TRUE (1) for good xfer, FALSE (0) for bad                        */
/* can be called from f_upload or to get mail from a WaZOO Opus             */
/*--------------------------------------------------------------------------*/
int cdecl get_Zmodem(rcvpath, xferinfo)
   byte *rcvpath;
   FILE *xferinfo;
   begin
      byte        namebuf[PATHLEN];
      int         i;

      errno = 0;

      _BRK_DISABLE();
      XON_ENABLE();
      n_disable();

      Secbuf      = NULL;
      Outfile     = NULL;
      z_size      = 0;

      DiskAvail   = zfree("");

      Rxtimeout   = 10;
      Tryzhdrtype = ZRINIT;
      Secbuf      = zalloc();

      strcpy(namebuf, rcvpath);
      Filename = namebuf;

      if (((i=RZ_InitReceiver())==ZCOMPL) or
          ((i==ZFILE) and ((RZ_ReceiveBatch(xferinfo))==OK)) )
         begin
            free(Secbuf);
            return 1;
         end

      CLEAR_OUTBOUND();
      send_can();          /* transmit at least 10 cans */
      wait_for_clear();

      if (Secbuf)    free(Secbuf);
      if (Outfile)   fclose(Outfile);
      return 0;

   end /* get_Zmodem */





/*--------------------------------------------------------------------------*/
/* RZ RECEIVE DATA                                                          */
/* Receive array buf of max length with ending ZDLE sequence                */
/* and CRC.  Returns the ending character or error code.                    */
/*--------------------------------------------------------------------------*/
static int pascal RZ_ReceiveData(buf, length)
   byte *buf;
   int length;
   begin
      register int   c;
      register word  crc;
      int            d;

      crc   = RxCount   = 0;
      while(1)
         begin
            if ((c = Z_GetZDL()) & ~0xFF)
               begin
CRCfoo:
                  switch (c)
                     begin
                        case GOTCRCE:
                        case GOTCRCG:
                        case GOTCRCQ:
                        case GOTCRCW:  /*-----------------------------------*/
                                       /* C R C s                           */
                                       /*-----------------------------------*/
                                       crc = Z_UpdateCRC(((d=c)&0xFF), crc);
                                       if ((c=Z_GetZDL()) & ~0xFF) goto CRCfoo;

                                       crc = Z_UpdateCRC(c, crc);
                                       if ((c=Z_GetZDL()) & ~0xFF) goto CRCfoo;

                                       crc = Z_UpdateCRC(c, crc);
                                       if (crc & 0xFFFF)
                                          begin
                                             z_message( CRC_msg );
                                             return ERROR;
                                          end
                                       return d;

                        case GOTCAN:   /*-----------------------------------*/
                                       /* Cancel                            */
                                       /*-----------------------------------*/
                                       z_log(Cancelled_msg);
                                       return ZCAN;

                        case TIMEOUT:  /*-----------------------------------*/
                                       /* Timeout                           */
                                       /*-----------------------------------*/
                                       z_message( TIME_msg );
                                       return c;

                        case RCDO:     /*-----------------------------------*/
                                       /* No carrier                        */
                                       /*-----------------------------------*/
                                       z_log( CARRIER_msg );
                                       return c;

                        default:       /*-----------------------------------*/
                                       /* Something bizarre                 */
                                       /*-----------------------------------*/
                                       z_message("Debris");
                                       return c;
                     end /* switch */
               end /* if */

            if (--length < 0)
               begin
                  z_message("Long pkt");
                  return ERROR;
               end

            ++RxCount;
            *buf++ = c;
            crc = Z_UpdateCRC(c, crc);
            continue;
         end /* while(1) */

   end /* RZ_ReceiveData */







/*--------------------------------------------------------------------------*/
/* RZ INIT RECEIVER                                                         */
/* Initialize for Zmodem receive attempt, try to activate Zmodem sender     */
/* Handles ZSINIT, ZFREECNT, and ZCOMMAND frames                            */
/*                                                                          */
/* Return codes:                                                            */
/*    ZFILE .... Zmodem filename received                                   */
/*    ZCOMPL ... transaction finished                                       */
/*    ERROR .... any other condition                                        */
/*--------------------------------------------------------------------------*/
static int pascal RZ_InitReceiver()
   begin
      register int   n;
      int            errors = 0;


      for (n=10; --n>=0; )
         begin

            /*--------------------------------------------------------------*/
            /* Set buffer length (0=unlimited, don't wait).                 */
            /* Also set capability flags                                    */
            /*--------------------------------------------------------------*/
            Z_PutLongIntoHeader(0L);
            Txhdr[ZF0] = CANFDX|CANOVIO;
            Z_SendHexHeader(Tryzhdrtype, Txhdr);

AGAIN:

            switch (Z_GetHeader(Rxhdr))
               begin
                  case ZFILE:    /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 Zconv = Rxhdr[ZF0];
                                 Tryzhdrtype = ZRINIT;
                                 if (RZ_ReceiveData(Secbuf,WAZOOMAX)==GOTCRCW)
                                    return ZFILE;
                                 Z_SendHexHeader(ZNAK, Txhdr);
                                 goto AGAIN;

                  case ZSINIT:   /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 if (RZ_ReceiveData(Attn, ZATTNLEN) == GOTCRCW)
                                    Z_SendHexHeader(ZACK, Txhdr);
                                 else Z_SendHexHeader(ZNAK, Txhdr);
                                 goto AGAIN;

                  case ZFREECNT: /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 Z_PutLongIntoHeader(DiskAvail);
                                 Z_SendHexHeader(ZACK, Txhdr);
                                 goto AGAIN;

                  case ZCOMMAND: /*-----------------------------------------*/
                                 /* Paranoia is good for you...             */
                                 /* Ignore command from remote, but lie and */
                                 /* say we did the command ok.              */
                                 /*-----------------------------------------*/
                                 if (RZ_ReceiveData(Secbuf,WAZOOMAX)==GOTCRCW)
                                    begin
                                       status_line("!Ignoring `%s'", Secbuf);
                                       Z_PutLongIntoHeader(0L); 
                                       do
                                          begin
                                             Z_SendHexHeader(ZCOMPL, Txhdr);
                                          end
                                       while (++errors<10 && Z_GetHeader(Rxhdr) != ZFIN);
                                       RZ_AckBibi();
                                       return ZCOMPL;
                                    end
                                 else Z_SendHexHeader(ZNAK, Txhdr);
                                 goto AGAIN;

                  case ZCOMPL:   /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 goto AGAIN;

                  case ZFIN:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 RZ_AckBibi();
                                 return ZCOMPL;

                  case ZCAN:
                  case RCDO:     /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 return ERROR;
               end /* switch */

         end /* for */

      return ERROR;

   end /* RZ_InitReceiver */




/*--------------------------------------------------------------------------*/
/* RZFILES                                                                  */
/* Receive a batch of files using ZMODEM protocol                           */
/*--------------------------------------------------------------------------*/
static int pascal RZ_ReceiveBatch(xferinfo)
   FILE *xferinfo;
   begin
      register int c;

      set_xy("");
      while(1)
         begin
            switch (c = RZ_ReceiveFile(xferinfo))
               begin
                  case ZEOF:
                  case ZSKIP:
                              switch (RZ_InitReceiver())
                                 begin
                                    case ZCOMPL:   return OK;
                                    default:       return ERROR;
                                    case ZFILE:    break;
                                 end /* switch */
                              break;

                  default:
                              fclose(Outfile);
                              Outfile  = NULL;
                              unlink(Filename);
                              errno    = 0;
                              return c;
               end /* switch */

         end /* while */

   end /* RZ_ReceiveBatch */




/*--------------------------------------------------------------------------*/
/* RZ RECEIVE FILE                                                          */
/* Receive one file; assumes file name frame is preloaded in Secbuf         */
/*--------------------------------------------------------------------------*/
static int pascal RZ_ReceiveFile(xferinfo)
   FILE *xferinfo;
   begin
      register int   c;
      int            n;
      long           rxbytes;

      EOFseen=FALSE;
      if (RZ_GetHeader() == ERROR)  return (Tryzhdrtype = ZSKIP);

      n        = 10;
      rxbytes  = 0L;

      while(1)
         begin
            Z_PutLongIntoHeader(rxbytes);
            Z_SendHexHeader(ZRPOS, Txhdr);
NxtHdr:
            switch (c = Z_GetHeader(Rxhdr))
               begin
                  case ZDATA:    /*-----------------------------------------*/
                                 /* Data Packet                             */
                                 /*-----------------------------------------*/
                                 if (Rxpos != rxbytes)
                                    begin
                                       if ( --n < 0) return ERROR;
                                       z_message(NULL);
                                       cprintf("Bad pos; %ld/%ld",rxbytes,Rxpos);
                                       Z_PutString(Attn);
                                       continue;
                                    end
MoreData:
                                 switch (c = RZ_ReceiveData(Secbuf,WAZOOMAX))
                                    begin
                                       case ZCAN:
                                       case RCDO:  /*-----------------------*/
                                                   /* CAN or CARRIER        */
                                                   /*-----------------------*/
                                                   z_log(Cancelled_msg);
                                                   return ERROR;

                                       case ERROR: /*-----------------------*/
                                                   /* CRC error             */
                                                   /*-----------------------*/
                                                   if (--n<0)
                                                      begin
                                                         z_log( FUBAR_msg );
                                                         return ERROR;
                                                      end
                                                   show_loc(rxbytes,n);
                                                   Z_PutString(Attn);
                                                   continue;

                                       case TIMEOUT: /*---------------------*/
                                                   /*                       */
                                                   /*-----------------------*/
                                                   if (--n<0)
                                                      begin
                                                         z_log( TIME_msg );
                                                         return ERROR;
                                                      end
                                                   show_loc(rxbytes,n);
                                                   continue;

                                       case GOTCRCW: /*---------------------*/
                                                   /* End of frame          */
                                                   /*-----------------------*/
                                                   n = 10;
                                                   if (RZ_SaveToDisk(&rxbytes)==ERROR)
                                                      return ERROR;
                                                   Z_PutLongIntoHeader(rxbytes);
                                                   Z_SendHexHeader(ZACK, Txhdr);
                                                   goto NxtHdr;

                                       case GOTCRCQ: /*---------------------*/
                                                   /* Zack expected         */
                                                   /*-----------------------*/
                                                   n = 10;
                                                   if (RZ_SaveToDisk(&rxbytes)==ERROR)
                                                      return ERROR;
                                                   Z_PutLongIntoHeader(rxbytes);
                                                   Z_SendHexHeader(ZACK, Txhdr);
                                                   goto MoreData;

                                       case GOTCRCG: /*---------------------*/
                                                   /* Non-stop              */
                                                   /*-----------------------*/
                                                   n = 10;
                                                   if (RZ_SaveToDisk(&rxbytes)==ERROR)
                                                      return ERROR;
                                                   goto MoreData;

                                       case GOTCRCE: /*---------------------*/
                                                   /* Header to follow      */
                                                   /*-----------------------*/
                                                   n = 10;
                                                   if (RZ_SaveToDisk(&rxbytes)==ERROR)
                                                      return ERROR;
                                                   goto NxtHdr;
                                    end /* switch */

                  case ZNAK:
                  case TIMEOUT:  /*-----------------------------------------*/
                                 /* Packed was probably garbled             */
                                 /*-----------------------------------------*/
                                 if ( --n < 0)
                                    begin
                                       z_log( "Garbled packet" );
                                       return ERROR;
                                    end
                                 show_loc(rxbytes,n);
                                 continue;

                  case ZFILE:    /*-----------------------------------------*/
                                 /* Sender didn't see our ZRPOS yet         */
                                 /*-----------------------------------------*/
                                 RZ_ReceiveData(Secbuf, WAZOOMAX);
                                 continue;

                  case ZEOF:     /*-----------------------------------------*/
                                 /* End of the file                         */
                                 /*-----------------------------------------*/
                                 if (Rxpos != rxbytes)
                                    begin
                                       status_line("-Bad EOF; %ld/%ld", rxbytes, Rxpos);
                                       continue;
                                    end
                                 throughput(2,rxbytes);
                                 errno = 0;

                                 fclose(Outfile);
                                 got_error(CLOSE_msg,Filename);

                                 status_line("=UL %s",Filename);

                                 Outfile  = NULL;
                                 if (xferinfo != NULL)
                                    begin
                                       fprintf(xferinfo, "%s\n", Filename);
                                       got_error(WRITE_msg,"XferInfo");
                                    end
                                 return c;

                  case ERROR:    /*-----------------------------------------*/
                                 /* Too much garbage in header search error */
                                 /*-----------------------------------------*/
                                 if ( --n < 0) return ERROR;
                                 show_loc(rxbytes,n);
                                 Z_PutString(Attn);
                                 continue;

                  default:       /*-----------------------------------------*/
                                 /*                                         */
                                 /*-----------------------------------------*/
                                 z_log( IDUNNO_msg );
                                 return ERROR;

               end /* switch */

         end /* while */

   end /* RZ_ReceiveFile */





/*--------------------------------------------------------------------------*/
/* RZ GET HEADER                                                            */
/* Process incoming file information header                                 */
/*--------------------------------------------------------------------------*/
static int pascal RZ_GetHeader()
   begin

      static char    suffix[] = ".001";

      register byte *p;
      register int   n;

      byte          *ourname;
      byte          *theirname;
      unsigned long  filesize;


      /*--------------------------------------------------------------------*/
      /* Setup the transfer mode                                            */
      /*--------------------------------------------------------------------*/
      isBinary  = (!RXBINARY && Zconv == ZCNL)? 0 : 1;


      /*--------------------------------------------------------------------*/
      /* Extract and verify filesize, if given.                             */
      /* Reject file if not at least 10K free                               */
      /*--------------------------------------------------------------------*/
      filesize = 0L;
      p        = Secbuf + 1 + strlen(Secbuf);
      if (*p) sscanf(p, "%ld", &filesize);
      if (filesize+10240 > DiskAvail)
         begin
            status_line("!Disk space");
            return ERROR;
         end

      /*--------------------------------------------------------------------*/
      /* Get and/or fix filename for uploaded file                          */
      /*--------------------------------------------------------------------*/
      p  = Filename+strlen(Filename)-1;     /* Find end of Opus upload path */
      while (p>=Filename && *p!='\\') p--;
      ourname = ++p;

      p = Secbuf+strlen(Secbuf)-1;      /* Find transmitted simple filename */
      while (p >= Secbuf && *p!='\\' && *p!='/' && *p!=':') p--;
      theirname = ++p;


      strcpy(ourname,theirname);          /* Start w/ our path & their name */

      if (dexists(Filename))
         begin                            /* If file already exists...      */
            p = ourname;
            while (*p && *p!='.') p++;    /* ...find the extension, if any  */
            for (n=0; n<4; n++)           /* ...fill it out if neccessary   */
               if (!*p)
                  begin
                     *p       = suffix[n];
                     *(++p)   = '\0';
                  end
               else p++;

            while (dexists(Filename))    /* ...If 'file.ext' exists suffix++ */
               begin
                  p = ourname+strlen(ourname)-1;
                  for (n=3; n--;)
                     begin
                        if (!isdigit(*p)) *p = '0';
                        if (++(*p) <= '9') break;
                        else *p-- = '0';
                     end /* for */
               end /* while */
         end /* if exist */


      errno = 0;
      if (strcmp(ourname,theirname))
         status_line("+Dupe renamed: %s",ourname);
   
      errno = 0;
      Outfile = fopen( Filename, write_binary );
      if (got_error(OPEN_msg,Filename)) return ERROR;

   	set_xy(NULL);
      cprintf("Z-Recv %s, %s%ldb, %d min.",
                  Filename,
                  (isBinary)? "": "ASCII ",
                  filesize,
                  (int)(filesize*10/cur_baud+27)/54);

      set_xy(NULL);

      throughput(0,0L);

      return OK;

   end /* RZ_GetHeader */





/*--------------------------------------------------------------------------*/
/* RZ SAVE TO DISK                                                          */
/* Writes the received file data to the output file.                        */
/* If in ASCII mode, stops writing at first ^Z, and converts all            */
/*   solo CR's or LF's to CR/LF pairs.                                      */
/*--------------------------------------------------------------------------*/
static int pascal RZ_SaveToDisk(rxbytes)
   unsigned long *rxbytes;
   begin
      static byte    lastsent;

      register byte *p;
      register int   count;

      count = RxCount;

      if (((KEYPRESS()) and (READKB()==27)))
         begin
            send_can();				/* Cancel file */
            while (Z_GetByte(20)!=TIMEOUT)	/* Wait for line to clear */
		;
            send_can();				/* and Cancel Batch */
            z_log( KBD_msg );
            return ERROR;
         end

      if (count!=z_size)
         begin
            gotoxy( locate_x+10, locate_y );
            cputs( ultoa(((unsigned long )(z_size=count)),e_input,10) );
            putch(' ');
         end

      errno = 0;

      if (isBinary)
         begin
            fwrite( Secbuf, count, 1, Outfile );
            if (errno) goto oops;
         end
      else
         begin
            if (EOFseen) return OK;
            for (p=Secbuf; --count>=0; ++p )
               begin
                  if ( *p == CPMEOF)
                     begin
                        EOFseen = TRUE;
                        return OK;
                     end
                  if ( *p=='\n' ) {
                     if (lastsent!='\r' && putc('\r', Outfile) == EOF)
                        goto oops;
                  } else {
                     if (lastsent=='\r' && putc('\n', Outfile) == EOF)
                        goto oops;
                  }
                  if (putc((lastsent=*p), Outfile) == EOF)  goto oops;
               end
         end

      *rxbytes += RxCount;
      
      gotoxy( locate_x, locate_y );
      cputs( ultoa(((unsigned long )(*rxbytes)),e_input,10) );
      return OK; 

oops:
      got_error(WRITE_msg,Filename);
      return ERROR;

   end /* RZ_SaveToDisk */



/*--------------------------------------------------------------------------*/
/* RZ ACK BIBI                                                              */
/* Ack a ZFIN packet, let byegones be byegones                              */
/*--------------------------------------------------------------------------*/
static void pascal RZ_AckBibi()
   begin
      register int n;

      Z_PutLongIntoHeader(0L);
      for (n=4; --n;)
         begin
            Z_SendHexHeader(ZFIN, Txhdr);
            switch (Z_GetByte(100))
               begin
                  case 'O':      Z_GetByte(1);    /* Discard 2nd 'O' */

                  case TIMEOUT:
                  case RCDO:     return;
               end /* switch */
         end /* for */

   end /* RZ_AckBibi */

