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


#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"


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



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

#ifdef DEBUGLOG
  sprintf(v->Msgbuf,"zsbhdr: %s %lx\n",frametypes[type+FTOFFSET],v->Txpos);
  dlog(v,v->Msgbuf);
#endif
  xsendline(v,ZPAD);
  xsendline(v,ZDLE);
  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);
}


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

#ifdef DEBUGLOG
  sprintf(v->Msgbuf,"zshhdr: %s %lx\n",frametypes[type+FTOFFSET],v->Rxbytes);
  dlog(v,v->Msgbuf);
#endif
  sendline(v,ZPAD);
  sendline(v,ZPAD);
  sendline(v,ZDLE);
  sendline(v,ZHEX);
  zputhex(v,(UBYTE)type);

  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);
}



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

#ifdef DEBUGLOG
  sprintf(v->Msgbuf,"zsdata: length=%ld end=%lx\n",(long)length,(long)frameend);
  dlog(v,v->Msgbuf);
#endif

  buf = v->Pktbuf;
  outptr = v->Outbuf + v->Outbuflen;
  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;
    }
    crc = updcrc(((USHORT)(*buf++)), crc);
  }

  *outptr++ = ZDLE;
  *outptr++ = frameend;
  v->Outbuflen = outptr - v->Outbuf;
  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);
}


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

  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 ");
            return ERROR;
          }
#ifdef DEBUGLOG
          sprintf(v->Msgbuf,"zrdata: cnt = %ld ret = %lx\n",(long)v->Rxcount,(long)d);
          dlog(v,v->Msgbuf);
#endif
          return d;
        case GOTCAN:
          return ZCAN;
        case TIMEOUT:
          strcpy(v->Msgbuf,"Data packet timeout ");
          return c;
        case RCDO:
          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;
  }
}


/* 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 */
  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;
      c =  zrbhdr(v);
      break;
    case ZHEX:
      v->Rxframeind = ZHEX;
      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");
#ifdef DEBUGLOG
    default:
      if (c >= -3 && c <= FRTYPES)
        sprintf(msgbuf,"zgethdr: %s @ %ld\n",frametypes[c+FTOFFSET],v->Rxpos);
      else
        sprintf(msgbuf,"zgethdr: Unknown type %ld @ %ld\n",(long)c,v->Rxpos);
      dlog(v,msgbuf);
#endif
  }
  return c;
}


/* 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 ");
    return ERROR;
  }
  return v->Rxtype;
}


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


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


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


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


/* 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 ZMODEM escape sequence ");
  return ERROR;
}


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


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


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