/**********************************************************************
 *   Z M . C
 *    ZMODEM protocol primitives
 *    01-19-87  Chuck Forsberg Omen Technology Inc
 *
 * 29 July 89:
 * Major overhaul by Rick Huebner for adaptation to Amiga XPR protocol spec
 *
 * 28 October 89:
 * Converted to Lattice C 5.04
 *
 * 15 November 1991
 * Code added to support CRC-32 by William M. Perkins.
 **********************************************************************/

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

struct ExecBase *SysBase = (struct ExecBase*)4l;

static char *frametypes[] =
{
   "Carrier Lost",         /* -3 */
   "TIMEOUT",              /* -2 */
   "ERROR",                /* -1 */
#define FTOFFSET 3
   "ZRQINIT",
   "ZRINIT",
   "ZSINIT",
   "ZACK",
   "ZFILE",
   "ZSKIP",
   "ZNAK",
   "ZABORT",
   "ZFIN",
   "ZRPOS",
   "ZDATA",
   "ZEOF",
   "ZFERR",
   "ZCRC",
   "ZCHALLENGE",
   "ZCOMPL",
   "ZCAN",
   "ZFREECNT",
   "ZCOMMAND",
   "ZSTDERR",
   "xxxxx"
#define FRTYPES 22      /* Total number of frame types in this array */
                        /*  not including psuedo negative entries */
   };

static UBYTE DLE_actions[] =
{
   0,0,0,0,0,0,0,0, 0,0,0,0,0,2,0,0, 1,1,0,1,0,0,0,0, 1,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,

   0,0,0,0,0,0,0,0, 0,0,0,0,0,2,0,0, 1,1,0,1,0,0,0,0, 0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
   0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0
   };

char ProtocolInfo[] = {
   "XprZedZap.Library By Yves Konigshofer\r\n",
   "\r\n"
   "Original ZModem Protocol Primitives: Chuck Forsberg (Omen Technology Inc.)\r\n",
   "   Adaptation To Amiga XPR Protocol: Rick Huebner\r\n",
   "        Addition Of CRC-32 Routines: William M. Perkins\r\n",
   "       Combining Of v2.10 And v2.5x: Yves Konigshofer\r\n",
   "                   ZedZap Additions: Yves Konigshofer\r\n",
   "\r\n",
   "This Library IS Public Domain...\r\n"
};

/**********************************************************
 *      void zsbhdr(struct Vars *v, USHORT type)
 *
 * Send ZMODEM binary header hdr of type type
 **********************************************************/
void zsbhdr(struct Vars *v, USHORT type)
{
   UBYTE  *hdr = v->Txhdr;
   short  n;
   USHORT crc;
   ULONG  crc32;

   xsendline(v, ZPAD);
   xsendline(v, ZDLE);

   if (v->Crc32t = v->Txfcs32)  /* zsbh32() */
   {
      xsendline(v, ZBIN32);
      zsendline(v, (UBYTE) type);

      crc32 = 0xFFFFFFFFL;
      crc32 = UPDC32(type, crc32);

      for (n = 4; --n >= 0; ++hdr)
      {
         crc32 = UPDC32((0377 & *hdr), crc32);
         zsendline(v, *hdr);
         }
      crc32 = ~crc32;
      for (n = 4; --n >= 0;)
      {
         zsendline(v, (int) crc32);
         crc32 >>= 8;
         }
      }
   else
   {
      xsendline(v, ZBIN);
      zsendline(v, (UBYTE) type);

      crc = updcrc(type, 0);
      for (n = 4; --n >= 0; )
      {
         zsendline(v, *hdr);
         crc = updcrc(((USHORT) (*hdr++)), crc);
         }

      crc = updcrc(((USHORT) 0), crc);
      crc = updcrc(((USHORT) 0), crc);
      zsendline(v, (UBYTE) (crc >> 8));
      zsendline(v, (UBYTE) crc);
      }
   }    /* End of void zsbhdr() */

/**********************************************************
 *      void zshhdr(struct Vars *v, USHORT type)
 *
 * Send ZMODEM HEX header hdr of type type
 **********************************************************/
void zshhdr(struct Vars *v, USHORT type)
{
   UBYTE *hdr = v->Txhdr;
   short n;
   USHORT crc;

   sendline(v, ZPAD);
   sendline(v, ZPAD);
   sendline(v, ZDLE);
   sendline(v, ZHEX);
   zputhex(v, (UBYTE) type);
   v->Crc32t = 0;

   crc = updcrc(type, 0);
   for (n = 4; --n >= 0; )
   {
      zputhex(v, *hdr);
      crc = updcrc(((USHORT) (*hdr++)), crc);
      }

   crc = updcrc(((USHORT) 0), crc);
   crc = updcrc(((USHORT) 0), crc);
   zputhex(v, (UBYTE) (crc >> 8));
   zputhex(v, (UBYTE) crc);

   /* Make it printable on remote machine */
   sendline(v, '\r');
   sendline(v, '\n');
   /* Uncork the remote in case a fake XOFF has stopped data flow */
   if (type != ZFIN)
      sendline(v, XON);
   }    /* End of void zshhdr() */

/**********************************************************
 *      void zsdata() and void zsda32()
 *
 * Send binary array buf of length length, with ending
 * ZDLE sequence frameend
 **********************************************************/
void zsdata(struct Vars *v, short length, USHORT frameend)
{
   UBYTE  *buf, *outptr, c;
   USHORT crc;
   ULONG  crc32;

   buf = v->Pktbuf;
   outptr = v->Outbuf + v->Outbuflen;
   crc32 = 0xFFFFFFFFL;                 /* zsda32() */
   crc = 0;

   while (--length >= 0)
   {
      switch (DLE_actions[c = *buf])
      {
      case 2:
         if (v->Lastzsent != '@')
            goto sendit;
         /* Fallthrough */
      case 1:
         *outptr++ = ZDLE;
         c ^= 0x40;
sendit:
      case 0:
         *outptr++ = v->Lastzsent = c;
         }
      if (v->Crc32t)                    /* zsda32() */
         crc32 = UPDC32((0xFF & *buf++), crc32);
      else
         crc = updcrc(((USHORT) (*buf++)), crc);
      }
   *outptr++ = ZDLE;
   *outptr++ = frameend;
   v->Outbuflen = outptr - v->Outbuf;

   if (v->Crc32t)                               /* zsda32() */
   {
      crc32 = UPDC32(frameend, crc32);
      crc32 = ~crc32;
      for (length = 4; --length >= 0; )
      {
         zsendline(v, (int) crc32);
         crc32 >>= 8;
         }
      }
   else
   {
      crc = updcrc(frameend, crc);
      crc = updcrc(((USHORT) 0), crc);
      crc = updcrc(((USHORT) 0), crc);
      zsendline(v, (UBYTE) (crc >> 8));
      zsendline(v, (UBYTE) crc);
      }

   if (frameend == ZCRCW)
      xsendline(v, XON);
   }    /* End of void zsdata() */

/**********************************************************
 *      short zrdata(struct Vars *v, UBYTE *buf, short length)
 *
 * Receive array buf of max length with ending ZDLE sequence
 * and CRC-16.  Returns the ending character or error code.
 **********************************************************/
short zrdata(struct Vars *v, UBYTE *buf, short length)
{
   short c, d;
   USHORT crc;

   if (v->Rxframeind == ZBIN32)
      return zrdat32(v, buf, length);

   crc = v->Rxcount = 0;
   for (;;)
   {
      if ((c = zdlread(v)) & ~0xFF)
      {
crcfoo:
         switch (c)
         {
         case GOTCRCE:
         case GOTCRCG:
         case GOTCRCQ:
         case GOTCRCW:
            crc = updcrc(((d = c) & 0xFF), crc);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc = updcrc(c, crc);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc = updcrc(c, crc);
            if (crc & 0xFFFF)
            {
               strcpy(v->Msgbuf, "Bad Data Packet CRC-16 ");
               return ERROR;
               }
            return d;
         case GOTCAN:
            strcpy(v->Msgbuf, "Transfer Cancelled (Remote Sent ZCAN)");
            return ZCAN;
         case TIMEOUT:
            strcpy(v->Msgbuf, "Data Packet Timeout ");
            return c;
         case RCDO:
            strcpy(v->Msgbuf, "Transfer Cancelled (Remote Dropped Carrier)");
            return c;
         default:
            strcpy(v->Msgbuf, "Unrecognizable Data Packet ");
            return c;
            }
         }
      if (--length < 0)
      {
         strcpy(v->Msgbuf, "Data Packet Too Long ");
         return ERROR;
         }
      ++v->Rxcount;
      *buf++ = c;
      crc = updcrc(c, crc);
      continue;
      }
   }    /* End of short zrdata() */

/**********************************************************
 *      short zrdat32(struct Vars *v, UBYTE *buf, short length)
 *
 * Receive array buf of max length with ending ZDLE sequence
 * and CRC-32.  Returns the ending character or error code.
 **********************************************************/
short zrdat32(struct Vars *v, UBYTE *buf, short length)
{
   short c, d;
   ULONG crc32;

   crc32 = 0xFFFFFFFFL;
   v->Rxcount = 0;

   for (;;)
   {
      if ((c = zdlread(v)) & ~0xFF)
      {
crcfoo:
         switch (c)
         {
         case GOTCRCE:
         case GOTCRCG:
         case GOTCRCQ:
         case GOTCRCW:
            d = c;
            c &= 0xFF;
            crc32 = UPDC32(c, crc32);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc32 = UPDC32(c, crc32);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc32 = UPDC32(c, crc32);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc32 = UPDC32(c, crc32);
            if ((c = zdlread(v)) & ~0xFF)
               goto crcfoo;
            crc32 = UPDC32(c, crc32);
            if (crc32 != 0xDEBB20E3)
            {
               strcpy(v->Msgbuf, "Bad Data Packet CRC-32 ");
               return ERROR;
               }
            return d;
         case GOTCAN:
            strcpy(v->Msgbuf, "Transfer Cancelled (Remote Sent ZCAN)");
            return ZCAN;
         case TIMEOUT:
            strcpy(v->Msgbuf, "Data Packet Timeout ");
            return c;
         case RCDO:
            strcpy(v->Msgbuf, "Transfer Cancelled (Remote Dropped Carrier)");
            return c;
         default:
            strcpy(v->Msgbuf, "Unrecognizable Data Packet ");
            return c;
            }
         }
      if (--length < 0)
      {
         strcpy(v->Msgbuf, "Data Packet Too Long ");
         return ERROR;
         }
      ++v->Rxcount;
      *buf++ = c;
      crc32 = UPDC32(c, crc32);
      continue;
      }
   }    /* End of short zrdat32() */

/**********************************************************
 *      short zgethdr(struct Vars *v)
 *
 * Read a ZMODEM header to hdr, either binary or hex.
 *  On success return type of header.
 *  Otherwise return negative on error.
 **********************************************************/
short zgethdr(struct Vars *v)
{
   short c, cancount;
   long n;
#ifdef DEBUGLOG
   UBYTE msgbuf [128];
#endif

/* n = v->Baud; */  /* Max characters before start of frame */
   n = v->KSIZE * 2;/* We assume that since extra data may be generated */
                    /* or lost due to line noise, that the maximum data */
                    /* to the next header will be less than 8129 bytes. */
                    /* Due to the fact that the Amiga's serial port can */
                    /* loose data and thereby fall into the next header */
                    /* this should allow another header to be found!    */
   cancount = 5;
again:
   v->Rxframeind = v->Rxtype = 0;
   switch (c = noxrd7(v))
   {
   case RCDO:
   case TIMEOUT:
      goto fifi;
   case CAN:
      if (--cancount <= 0)
      {
         c = ZCAN;
         goto fifi;
         }
   default:
agn2:
      if (--n <= 0)
      {
         strcpy(v->Msgbuf, "Header Search Garbage Count Exceeded ");
         return ERROR;
         }
      if (c != CAN)
         cancount = 5;
      goto again;
   case ZPAD:              /* This is what we want. */
      break;
      }
   cancount = 5;
splat:
   switch (c = noxrd7(v))
   {
   case ZPAD:
      goto splat;
   case RCDO:
   case TIMEOUT:
      goto fifi;
   default:
      goto agn2;
   case ZDLE:              /* This is what we want. */
      break;
      }

   switch (c = noxrd7(v))
   {
   case RCDO:
   case TIMEOUT:
      goto fifi;
   case ZBIN:
      v->Rxframeind = ZBIN;
      v->Crc32 = FALSE;
      c =  zrbhdr(v);
      break;
   case ZBIN32:
      v->Crc32 = v->Rxframeind = ZBIN32;
      c =  zrbhdr32(v);
      break;
   case ZHEX:
      v->Rxframeind = ZHEX;
      v->Crc32 = FALSE;
      c =  zrhhdr(v);
      break;
   case CAN:
      if (--cancount <= 0)
      {
         c = ZCAN;
         goto fifi;
         }
      goto agn2;
   default:
      goto agn2;
      }
   v->Rxpos = rclhdr(v);
fifi:
   switch (c)
   {
   case GOTCAN:
      c = ZCAN;
   case ZNAK:
   case ZCAN:
   case ERROR:
   case TIMEOUT:
   case RCDO:
      sprintf(v->Msgbuf, "%s %s ", frametypes[c + FTOFFSET],
         (c >= 0) ? "header" : "error");
      }
   return c;
   }    /* End of short zgethdr() */

/**********************************************************
 *      short zrbhdr(struct Vars *v)
 *
 * Receive a binary style header (type and position)
 **********************************************************/
short zrbhdr(struct Vars *v)
{
   UBYTE *hdr = v->Rxhdr;
   short c, n;
   USHORT crc;

   if ((c = zdlread(v)) & ~0xFF)
      return c;
   v->Rxtype = c;
   crc = updcrc(c, 0);

   for (n = 4; --n >= 0; )
   {
      if ((c = zdlread(v)) & ~0xFF)
         return c;
      crc = updcrc(c, crc);
      *hdr++ = c;
      }
   if ((c = zdlread(v)) & ~0xFF)
      return c;
   crc = updcrc(c, crc);
   if ((c = zdlread(v)) & ~0xFF)
      return c;
   crc = updcrc(c, crc);
   if (crc & 0xFFFF)
   {
      strcpy(v->Msgbuf, "Bad Header CRC-16 ");
      return ERROR;
      }
   return v->Rxtype;
   }    /* End of short zrbhdr() */

/**********************************************************
 *      short zrbhdr32(struct Vars *v)
 *
 * Receive a binary style header (type and position) with
 * 32 bit FCS
 **********************************************************/
short zrbhdr32(struct Vars *v)
{
   UBYTE *hdr = v->Rxhdr;
   short c, n;
   ULONG crc32;

   if ((c = zdlread(v)) & ~0xFF)
      return c;
   v->Rxtype = c;
   crc32 = 0xFFFFFFFFL;
   crc32 = UPDC32(c, crc32);

   for (n = 4; --n >= 0; )
   {
      if ((c = zdlread(v)) & ~0xFF)
         return c;
      crc32 = UPDC32(c, crc32);
      *hdr++ = c;
      }
   for (n = 4; --n >= 0; )
   {
      if ((c = zdlread(v)) & ~0xFF)
         return c;
      crc32 = UPDC32(c, crc32);
      }
   if (crc32 != 0xDEBB20E3)
   {
      strcpy(v->Msgbuf, "Bad Header CRC-32 ");
      return ERROR;
      }
   return v->Rxtype;
   }    /* End of short zrbhdr32() */

/**********************************************************
 *      short zrhhdr(struct Vars *v)
 *
 * Receive a hex style header (type and position)
 **********************************************************/
short zrhhdr(struct Vars *v)
{
   UBYTE *hdr = v->Rxhdr;
   short c, n;
   USHORT crc;

   if ((c = zgethex(v)) < 0)
      return c;
   v->Rxtype = c;
   crc = updcrc(c, 0);

   for (n = 4; --n >= 0; )
   {
      if ((c = zgethex(v)) < 0)
         return c;
      crc = updcrc(c, crc);
      *hdr++ = c;
      }
   if ((c = zgethex(v)) < 0)
      return c;
   crc = updcrc(c, crc);
   if ((c = zgethex(v)) < 0)
      return c;
   crc = updcrc(c, crc);
   if (crc & 0xFFFF)
   {
      strcpy(v->Msgbuf, "Bad Header CRC ");
      return ERROR;
      }
   if (readock(v, 1) == '\r')
      readock(v, 1);            /* Throw away possible cr/lf */
   return v->Rxtype;
   }    /* End of short zrhhdr() */

/**********************************************************
 *      void zputhex(struct Vars *v, UBYTE c)
 *
 * Send a byte as two hex digits
 **********************************************************/
void zputhex(struct Vars *v, UBYTE c)
{
   static char digits[] = "0123456789abcdef";

   sendline(v, digits[(c >> 4) & 0x0F]);
   sendline(v, digits[c & 0x0F]);
   }    /* End of void zputhex() */

/**********************************************************
 *      void zsendline(struct Vars *v, UBYTE c)
 *
 * Send character c with ZMODEM escape sequence encoding.
 * Escape ZDLE, real DLE, XON, XOFF, and CR following @ (Telenet net escape)
 **********************************************************/
void zsendline(struct Vars *v, UBYTE c)
{
   switch (DLE_actions[c])
   {
   case 2:
      if (v->Lastzsent != '@')
         goto sendit;
      /* Fallthrough */
   case 1:
      xsendline(v, ZDLE);
      c ^= 0x40;
sendit:
   case 0:
      xsendline(v, v->Lastzsent = c);
      }
   }    /* End of void zsendline() */

/**********************************************************
 *      short zgethex(struct Vars *v)
 *
 * Decode two lower case hex digits into an 8 bit byte value
 **********************************************************/
short zgethex(struct Vars *v)
{
   short c, n;

   if ((n = noxrd7(v)) < 0)
      return n;
   n -= '0';
   if (n > 9)
      n -= ('a' - ':');
   if (n & ~0xF)
      return ERROR;

   if ((c = noxrd7(v)) < 0)
      return c;
   c -= '0';
   if (c > 9)
      c -= ('a' - ':');
   if (c & ~0xF)
      return ERROR;

   return (short) (n << 4 | c);
   }    /* End of short zgethex() */

/**********************************************************
 *      short zdlread(struct Vars *v)
 *
 * Read a byte, checking for ZMODEM escape encoding
 * including CAN*5 which represents a quick abort.
 **********************************************************/
short zdlread(struct Vars *v)
{
   short c;

   if ((c = readock(v, v->Rxtimeout)) != ZDLE)
      return c;
   if ((c = readock(v, v->Rxtimeout)) < 0)
      return c;
   if (c == CAN && (c = readock(v, v->Rxtimeout)) < 0)
      return c;
   if (c == CAN && (c = readock(v, v->Rxtimeout)) < 0)
      return c;
   if (c == CAN && (c = readock(v, v->Rxtimeout)) < 0)
      return c;
   switch (c)
   {
   case CAN:
      return GOTCAN;
   case ZCRCE:
   case ZCRCG:
   case ZCRCQ:
   case ZCRCW:
      return (short) (c | GOTOR);
   case ZRUB0:
      return 0x7F;
   case ZRUB1:
      return 0xFF;
   default:
      if ((c & 0x60) ==  0x40)
         return (short) (c ^ 0x40);
      break;
      }
   strcpy(v->Msgbuf, "Bad ZedZap Escape Sequence ");
   return ERROR;
   }    /* End of short zdlread() */

/**********************************************************
 *      short noxrd7(struct Vars *v)
 *
 * Read a character from the modem line with timeout.
 * Eat parity, XON and XOFF characters.
 **********************************************************/
short noxrd7(struct Vars *v)
{
   short c;

   for (;;)
   {
      if ((c = readock(v, v->Rxtimeout)) < 0)
         return c;
      switch (c &= 0x7F)
      {
      case XON:
      case XOFF:
         continue;
      default:
         return c;
         }
      }
   }    /* short noxrd7() */

/**********************************************************
 *      void stohdr(struct Vars *v, long pos)
 *
 * Store long integer pos in Txhdr
 **********************************************************/
void stohdr(struct Vars *v, long pos)
{
   v->Txhdr[ZP0] = pos;
   pos >>= 8;
   v->Txhdr[ZP1] = pos;
   pos >>= 8;
   v->Txhdr[ZP2] = pos;
   pos >>= 8;
   v->Txhdr[ZP3] = pos;
   }    /* End of void stohdr() */

/**********************************************************
 *      long rclhdr(struct Vars *v)
 *
 * Recover a long integer from a header
 **********************************************************/
long rclhdr(struct Vars *v)
{
   long l;

   l = v->Rxhdr[ZP3];
   l = (l << 8) | v->Rxhdr[ZP2];
   l = (l << 8) | v->Rxhdr[ZP1];
   l = (l << 8) | v->Rxhdr[ZP0];
   return l;
   }    /* End of long rclhdr() */
/* End of Zm.c source */
