/*--------------------------------------------------------------------------*/
/* FILE: zmisc.c    (Opus zmodem routines used by send and receive)         */
/*                                                                          */
/*                                                                          */
/*               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"



static int Rxtype;               /* Type of header received                 */
static int Rxframeind;           /* ZBIN ZBIN32,ZHEX type of frame received */

static char hex[] = "0123456789abcdef";

/* Send a byte as two hex digits */
#define Z_PUTHEX(i,c) {i=(c);SENDBYTE(hex[((i)&0xF0)>>4]);SENDBYTE(hex[(i)&0xF]);}



/*--------------------------------------------------------------------------*/
/* Routines used by other ZModem modules...                                 */
/*--------------------------------------------------------------------------*/
int  pascal Z_GetByte(int);
void pascal Z_PutString(unsigned char *);
void pascal Z_SendHexHeader(unsigned short,unsigned char *);
int  pascal Z_GetHeader(unsigned char *);
int  pascal Z_GetZDL(void);
void pascal Z_PutLongIntoHeader(long);
unsigned short pascal Z_UpdateCRC(unsigned short,unsigned short);


/*--------------------------------------------------------------------------*/
/* Private routines                                                         */
/*--------------------------------------------------------------------------*/
static int  pascal _Z_qk_read(void);
static int  pascal _Z_GetBinaryHeader(unsigned char *);
static int  pascal _Z_GetHexHeader(unsigned char *);
static int  pascal _Z_GetHex(void);
static int  pascal _Z_TimedRead(void);
static long pascal _Z_PullLongFromHeader(unsigned char *);



extern byte *local_CEOL;
extern char *KBD_msg;


void pascal z_message( s )
   byte *s;
   begin
   	gotoxy( locate_x+20, locate_y );
      if (s) cputs(s);
      cputs( local_CEOL );
   end


void z_log(s)
   byte *s;
   begin
      word x, y;

      z_message(s);

      x  = locate_x;
      y  = locate_y;
      status_line(s); /* also does disk file logging */
      locate_x = x;
      locate_y = y;

   end


void show_loc(l,w)
   unsigned long  l;
   unsigned int   w;
   begin
      gotoxy( locate_x+35, locate_y );
      cprintf("Ofs=%ld Retries=%d%s",l,w,local_CEOL);
   end


byte * pascal zalloc()
   begin
      byte *sptr;

      sptr  = malloc(WAZOOMAX);
      if (!sptr)
         begin
         	status_line("!Z-MEMOVFL");
            adios(2);
         end
      return sptr;
   end







/*--------------------------------------------------------------------------*/
/* Z GET BYTE                                                               */
/* Get a byte from the modem;                                               */
/* return TIMEOUT if no read within timeout tenths,                         */
/* return RCDO if carrier lost                                              */
/*--------------------------------------------------------------------------*/
int pascal Z_GetByte(tenths)
   register int tenths;
   begin
      register int i;
      long timeout, timerset();

      i^=i;
      do
      	begin
            if (CHAR_AVAIL()) return MODEM_IN();
         end
      while((i++)<2000);


      timeout = timerset(tenths * 10);

      while(!CHAR_AVAIL())
         begin
            if (!CARRIER)     return RCDO;

            time_release();
            if (timeup(timeout)) return TIMEOUT;
         end

      return MODEM_IN();

   end







/*--------------------------------------------------------------------------*/
/* QK_READ  (like Z_GetByte, but assumes the time to be Rxtimeout)          */
/* Get a byte from the modem;                                               */
/* return TIMEOUT if no read within timeout tenths,                         */
/* return RCDO if carrier lost                                              */
/*--------------------------------------------------------------------------*/
static int pascal _Z_qk_read()
   begin
      register int i;
      register int time_val;
      long timeout, timerset();

      time_val^=time_val;
      do
      	begin
            if ((i=MODEM_STATUS())&DATA_READY)  return MODEM_IN();
            if (!(i&ctl.carrier_mask))          return RCDO;
         end
      while((time_val++)<2000);

      timeout = timerset(Rxtimeout*10);
      do
         begin
            if ((i=MODEM_STATUS())&DATA_READY)  return MODEM_IN();
            if (!(i&ctl.carrier_mask))          return RCDO;
            time_release();
         end
      while(!timeup(timeout));

      return TIMEOUT;

   end







/*--------------------------------------------------------------------------*/
/* Z PUT STRING                                                             */
/* Send a string to the modem, processing for \336 (sleep 1 sec)            */
/* and \335 (break signal, ignored)                                         */
/*--------------------------------------------------------------------------*/
void pascal Z_PutString(s)
   register unsigned char *s;
   begin
      register int c;

      while (*s)
         begin
            switch (c = *s++)
               begin
                  case '\336':   big_pause(2);
                  case '\335':   break;
                  default:       SENDBYTE(c);
               end /* switch */

         end /* while */

   end /* Z_PutString */









/*--------------------------------------------------------------------------*/
/* Z UPDATE CRC                                                             */
/* update CRC                                                               */
/*--------------------------------------------------------------------------*/
unsigned short pascal Z_UpdateCRC(c, crc)
   unsigned short c;
   unsigned short crc;
   begin
      register int    count;
      register word   temp;

      temp   = crc;

      for (count=8; --count>=0; )
         begin
            if (temp & 0x8000)
               begin
                  temp <<= 1;
                  temp += (((c<<=1) & 0400)  !=  0);
                  temp ^= 0x1021;
               end
            else
               begin
                  temp <<= 1;
                  temp += (((c<<=1) & 0400)  !=  0);
               end
         end /* for */

      return temp;

   end /* z_crc update */




/*--------------------------------------------------------------------------*/
/* Z SEND HEX HEADER                                                        */
/* Send ZMODEM HEX header hdr of type type                                  */
/*--------------------------------------------------------------------------*/
void pascal Z_SendHexHeader(type, hdr)
   unsigned short type;
   unsigned char *hdr;
   begin
      register int   n;
      register int   i;
      unsigned short crc;

      SENDBYTE(ZPAD);
      SENDBYTE(ZPAD);
      SENDBYTE(ZDLE);
      SENDBYTE(ZHEX);
      Z_PUTHEX(i,type);

      crc = Z_UpdateCRC(type, 0);
      for (n=4; --n >= 0;)
         begin
            Z_PUTHEX(i,(*hdr));
            crc = Z_UpdateCRC(((unsigned short)(*hdr++)), crc);
         end
      crc = Z_UpdateCRC(0,crc);
      crc = Z_UpdateCRC(0,crc);
      Z_PUTHEX(i,(crc>>8));
      Z_PUTHEX(i,crc);

      /* Make it printable on remote machine */
      SENDBYTE('\r');
      SENDBYTE('\n');

      /* Uncork the remote in case a fake XOFF has stopped data flow */
      if (type != ZFIN) SENDBYTE(021);

      wait_for_clear();

   end /* Z_SendHexHeader */





/*--------------------------------------------------------------------------*/
/* Z GET HEADER                                                             */
/* Read a ZMODEM header to hdr, either binary or hex.                       */
/*   On success, set Zmodem to 1 and return type of header.                 */
/*   Otherwise return negative on error                                     */
/*--------------------------------------------------------------------------*/
int pascal Z_GetHeader(hdr)
   byte *hdr;
   begin

      register int   c;
      register int   n;
      int            cancount;


      n        = cur_baud;   /* Max characters before start of frame */
      cancount = 5;

Again:

      if (((KEYPRESS()) and (READKB()==27)))
         begin
            send_can();
            z_log( KBD_msg );
            return ZCAN;
         end

      Rxframeind = Rxtype = 0;

      switch (c = _Z_TimedRead())
         begin

            case ZPAD:     /*-----------------------------------------------*/
                           /* This is what we want.                         */
                           /*-----------------------------------------------*/
                           break;

            case RCDO:
            case TIMEOUT:  /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Done;

            case CAN:      /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           if (--cancount <= 0)
                              begin
                                 c = ZCAN;
                                 goto Done;
                              end

                           /* fallthrough... */

            default:       /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
Agn2:

                           if (--n <= 0)
                              begin
                                 z_log( FUBAR_msg );
                                 send_can();
                                 return ZCAN;
                              end

                           if (c != CAN) cancount = 5;
                           goto Again;

         end /* switch */

      cancount = 5;

Splat:

      switch (c = _Z_TimedRead())
         begin
            case ZDLE:     /*-----------------------------------------------*/
                           /* This is what we want.                         */
                           /*-----------------------------------------------*/
                           break;

            case ZPAD:     /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Splat;

            case RCDO:
            case TIMEOUT:  /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Done;

            default:       /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Agn2;

         end /* switch */


      switch (c = _Z_TimedRead())
         begin

            case ZBIN:     /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           Rxframeind = ZBIN;
                           c =  _Z_GetBinaryHeader(hdr);
                           break;

            case ZHEX:     /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           Rxframeind = ZHEX;
                           c =  _Z_GetHexHeader(hdr);
                           break;

            case CAN:      /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           if (--cancount <= 0)
                              begin
                                 c = ZCAN;
                                 goto Done;
                              end
                           goto Agn2;

            case RCDO:
            case TIMEOUT:  /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Done;

            default:       /*-----------------------------------------------*/
                           /*                                               */
                           /*-----------------------------------------------*/
                           goto Agn2;

         end /* switch */

      Rxpos = _Z_PullLongFromHeader(hdr);

Done:

      return c;

   end /* Z_GetHeader */





/*--------------------------------------------------------------------------*/
/* Z GET BINARY HEADER                                                      */
/* Receive a binary style header (type and position)                        */
/*--------------------------------------------------------------------------*/
static int pascal _Z_GetBinaryHeader(hdr)
   byte *hdr;
   begin
      register int    c;
      register word   crc;
      int             n;

      if ((c   = Z_GetZDL()) & ~0xFF)   return c;
      Rxtype   = c;
      crc      = Z_UpdateCRC(c, 0);

      for (n=4; --n >= 0;)
         begin
            if ((c = Z_GetZDL()) & ~0xFF) return c;
            crc = Z_UpdateCRC(c, crc);
            *hdr++ = c;
         end
      if ((c   = Z_GetZDL()) & ~0xFF) return c;

      crc      = Z_UpdateCRC(c, crc);
      if ((c   = Z_GetZDL()) & ~0xFF) return c;

      crc = Z_UpdateCRC(c, crc);
      if (crc & 0xFFFF)
         begin
            z_message( CRC_msg );
            return ERROR;
         end

      return Rxtype;

   end /* _Z_GetBinaryHeader */




/*--------------------------------------------------------------------------*/
/* Z GET HEX HEADER                                                         */
/* Receive a hex style header (type and position)                           */
/*--------------------------------------------------------------------------*/
static int pascal _Z_GetHexHeader(hdr)
   unsigned char *hdr;
   begin
      register int   c;
      register word  crc;
      int            n;

      if ((c   = _Z_GetHex()) < 0) return c;
      Rxtype   = c;
      crc      = Z_UpdateCRC(c, 0);

      for (n=4; --n >= 0;)
         begin
            if ((c = _Z_GetHex()) < 0) return c;
            crc      = Z_UpdateCRC(c, crc);
            *hdr++   = c;
         end

      if ((c = _Z_GetHex()) < 0) return c;
      crc = Z_UpdateCRC(c, crc);
      if ((c = _Z_GetHex()) < 0) return c;
      crc = Z_UpdateCRC(c, crc);
      if (crc & 0xFFFF)
         begin
            z_message( CRC_msg );
            return ERROR;
         end
      if (Z_GetByte(1) == '\r') Z_GetByte(1);  /* Throw away possible cr/lf */

      return Rxtype;

   end





/*--------------------------------------------------------------------------*/
/* Z GET HEX                                                                */
/* Decode two lower case hex digits into an 8 bit byte value                */
/*--------------------------------------------------------------------------*/
static int pascal _Z_GetHex()
   begin
      register int c, n;

      if ((n = _Z_TimedRead()) < 0) return n;
      n -= '0';
      if (n > 9) n -= ('a' - ':');
      if (n & ~0xF) return ERROR;

      if ((c = _Z_TimedRead()) < 0) return c;
      c -= '0';
      if (c > 9) c -= ('a' - ':');
      if (c & ~0xF) return ERROR;

      return (n<<4 | c);
   end




/*--------------------------------------------------------------------------*/
/* Z GET ZDL                                                                */
/* Read a byte, checking for ZMODEM escape encoding                         */
/* including CAN*5 which represents a quick abort                           */
/*--------------------------------------------------------------------------*/
int pascal Z_GetZDL()
   begin
      register int c;

      if ((c = _Z_qk_read()) != ZDLE)         return c;

      switch (c=_Z_qk_read())
         begin
            case CAN:   return ((c=_Z_qk_read())<0)?               c :
                               ((c==CAN) && ((c=_Z_qk_read())<0))? c :
                               ((c==CAN) && ((c=_Z_qk_read())<0))? c : (GOTCAN);

            case ZCRCE:
            case ZCRCG:
            case ZCRCQ:
            case ZCRCW: return (c | GOTOR);

            case ZRUB0: return 0x7F;

            case ZRUB1: return 0xFF;

            default:    return   (c<0)?            c :
                                 ((c&0x60)==0x40)? (c ^ 0x40)  : ERROR;

         end /* switch */

   end /* Z_GetZDL */





/*--------------------------------------------------------------------------*/
/* Z TIMED READ                                                             */
/* Read a character from the modem line with timeout.                       */
/*  Eat parity, XON and XOFF characters.                                    */
/*--------------------------------------------------------------------------*/
static int pascal _Z_TimedRead()
   begin
      register int c;

      for (;;)
         begin
            if ((c = _Z_qk_read()) < 0) return c;

            switch (c &= 0x7F)
               begin
                  case XON:
                  case XOFF:  continue;

                  default:    return c;
               end /* switch */

         end /* for */

   end /* _Z_TimedRead */




/*--------------------------------------------------------------------------*/
/* Z LONG TO HEADER                                                         */
/* Store long integer pos in Txhdr                                          */
/*--------------------------------------------------------------------------*/
void pascal Z_PutLongIntoHeader(pos)
   long pos;
   begin
      Txhdr[ZP0] = pos;
      Txhdr[ZP1] = pos>>8;
      Txhdr[ZP2] = pos>>16;
      Txhdr[ZP3] = pos>>24;
   end /* Z_PutLongIntoHeader */




/*--------------------------------------------------------------------------*/
/* Z PULL LONG FROM HEADER                                                  */
/* Recover a long integer from a header                                     */
/*--------------------------------------------------------------------------*/
static long pascal _Z_PullLongFromHeader(hdr)
   unsigned char *hdr;
   begin
      long l;

      l = hdr[ZP3];
      l = (l << 8) | hdr[ZP2];
      l = (l << 8) | hdr[ZP1];
      l = (l << 8) | hdr[ZP0];
      return l;
   end /* _Z_PullLongFromHeader */


/* END OF FILE: n_zmodem.c */

