/*->c.zm */

/*
 *   Z M . C
 *    ZMODEM protocol primitives
 *    05-09-88  Chuck Forsberg Omen Technology Inc
 *
 * Entry point Functions:
 *     zsbhdr(type, hdr) send binary header
 *     zshhdr(type, hdr) send hex header
 *     zgethdr(hdr, eflag) receive header - binary or hex
 *     zsdata(buf, len, frameend) send data
 *     zrdata(buf, len) receive data
 *     stohdr(pos) store position data in Txhdr
 *     long rclhdr(hdr) recover position offset from header
 */


#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <time.h>

#include "h.os"
#include "h.bbc"
#include "h.wimp"

#include "h.def"

#include "h.wos"

#include "h.serial"
#include "h.ftpglue"
/* #include "h.batch" */
#include "h.crctab"


#include "h.zmextra"


#ifndef CANFDX
#include "zmodem.h"
int Rxtimeout = 500;            /* Tenths of seconds to wait for something */
#endif

#ifndef UNSL
#define UNSL
#endif


/* Globals used by ZMODEM functions */
int Rxframeind;         /* ZBIN ZBIN32, or ZHEX type of frame received */
int Rxtype;             /* Type of header received */
int Rxcount;            /* Count of data bytes received */
char Rxhdr[4];          /* Received header */
char Txhdr[4];          /* Transmitted header */
long Rxpos;             /* Received file position */
long Txpos;             /* Transmitted file position */
int Txfcs32;            /* TURE means send binary frames with 32 bit FCS */
int Crc32t;             /* Display flag indicating 32 bit CRC being sent */
int Crc32;              /* Display flag indicating 32 bit CRC being received */
int Znulls;             /* Number of nulls to send at beginning of ZDATA hdr */
char Attn[ZATTNLEN+1];  /* Attention string rx sends to tx on err */

static lastsent;        /* Last char we sent */
static Not8bit;         /* Seven bits seen on header */

static char *frametypes[] = {
       "{ZM16}",         /* -3 */
       "{ZM11}",              /* -2 */
       "{ZM04}",                /* -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 char badcrc[] = "{ZM17}";


int readline(int time)
{
 int byte;

 byte=ftpgetbyte(time*10);

 if(byte==-1)
 {
  if(ftponline()) return(TIMEOUT);
  else            return(RCDO);
 }
 else return(byte);

}




/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON, XOFF. Escape CR following @ (Telenet net escape)
 */

void zsendline(int c)
{
 c=c & 0xFF;

 /* Quick check for non control characters */

 if(c & 0140) xsendline(lastsent = c);
 else
 {
  switch(c &= 0377)
  {
   case ZDLE:
             xsendline(ZDLE);
             xsendline(lastsent=(c^=0100));
             break;

    case 015:
   case 0215:
             if(!Zctlesc && (lastsent & 0177)!='@') goto sendit;
             /* **** FALL THRU TO **** */
    case 020:
    case 021:
    case 023:
   case 0220:
   case 0221:
   case 0223:
             xsendline(ZDLE);
             c^=0100;
      sendit:
             xsendline(lastsent=c);
             break;

     default:
             if(Zctlesc && ! (c & 0140))
             {
              xsendline(ZDLE);
              c^=0100;
             }
             xsendline(lastsent=c);
  }
 }
}








/* Send ZMODEM binary header hdr of type type */

void zsbh32(char * hdr,int type)
{
       register int n;
       register UNSL long crc;

       xsendline(ZBIN32);
       zsendline(type);

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

       for (n=4; --n >= 0; ++hdr) {
               crc = UPDC32((0377 & *hdr), crc);
               zsendline(*hdr);
       }
       crc = ~crc;
       for (n=4; --n >= 0;) {
               zsendline((int)crc);
               crc >>= 8;
       }
}







/* Send ZMODEM binary header hdr of type type */

void zsbhdr(int type,char * hdr)
{
 register int n;
 register unsigned short crc;

 zdeb3("zsbhdr: %s %lx", frametypes[type+FTOFFSET], rclhdr(hdr));

       if (type == ZDATA)
               for (n = Znulls; --n >=0; )
                       xsendline(0);

       xsendline(ZPAD); xsendline(ZDLE);

       if ((Crc32t=Txfcs32)!=0)
               zsbh32(hdr, type);
       else {
               xsendline(ZBIN); zsendline(type); crc = updcrc(type, 0);

               for (n=4; --n >= 0; ++hdr) {
                       zsendline(*hdr);
                       crc = updcrc((0377& *hdr), crc);
               }
               crc = updcrc(0,updcrc(0,crc));
               zsendline(crc>>8);
               zsendline(crc);
       }
       if (type != ZDATA)
               flushmo();
}










/* Send a byte as two hex digits */

void zputhex(int c)
{
 static char digits[]="0123456789abcdef";

 zdeb2("zputhex: %02X", c);

 sendline(digits[(c&0xF0)>>4]);
 sendline(digits[(c)&0xF]);
}










/*
 * Read a character from the modem line with timeout.
 *  Eat parity, XON and XOFF characters.
 */

int noxrd7(void)
{
 int c;

 for(;;)
 {
  if(!ftp_ok) return(GOTCAN);

  if((c=readline(Rxtimeout))<0) return(c);

  switch(c &= 0177)
  {
   case  XON:
   case XOFF:
             continue;

     default:
             if (Zctlesc && !(c & 0140))
                               continue;
   case '\r':
   case '\n':
   case ZDLE:
             return c;
  }
 }
}









/* Send ZMODEM HEX header hdr of type type */

void zshhdr(int type,char * hdr)
{
 register int n;
 register unsigned short crc;

 zdeb3("zshhdr: %s %lx", frametypes[type+FTOFFSET], rclhdr(hdr));

 sendline(ZPAD);
 sendline(ZPAD);
 sendline(ZDLE);
 sendline(ZHEX);

 zputhex(type);
 Crc32t=0;

 crc=updcrc(type, 0);

 for(n=4;--n>=0;++hdr)
 {
  zputhex(*hdr);
  crc=updcrc((0377 & *hdr), crc);
 }

 crc=updcrc(0,updcrc(0,crc));

 zputhex(crc>>8);
 zputhex(crc);

 /* Make it printable on remote machine */

 sendline(015);
 sendline(0212);

 /*
  * Uncork the remote in case a fake XOFF has stopped data flow
  */

 if(type!=ZFIN && type!=ZACK) sendline(021);

/* flushmo(); */
}









void zsda32(char * buf,int length,int frameend)
{
       register int c;
       register UNSL long crc;

       crc = 0xFFFFFFFFL;
       for (;--length >= 0; ++buf) {
               c = *buf & 0377;
               if (c & 0140)
                       xsendline(lastsent = c);
               else
                       zsendline(c);
               crc = UPDC32(c, crc);
       }
       xsendline(ZDLE); xsendline(frameend);
       crc = UPDC32(frameend, crc);

       crc = ~crc;
       for (length=4; --length >= 0;) {
               zsendline((int)crc);  crc >>= 8;
       }
}







/*
 * Send binary array buf of length length, with ending ZDLE sequence frameend
 */

#ifdef ZMDEBUG

static char *Zendnames[] = { "ZCRCE", "ZCRCG", "ZCRCQ", "ZCRCW"};

#endif


void zsdata(char * buf,int length,int frameend)
{
 register unsigned short crc;

 zdeb3("zsdata: %d %s", length, Zendnames[frameend-ZCRCE&3]);

 if(Crc32t) zsda32(buf, length, frameend);
 else
 {
  crc=0;
  for(;--length>=0;++buf)
  {
   zsendline(*buf);
   crc=updcrc((0377 & *buf), crc);
  }

  xsendline(ZDLE);
  xsendline(frameend);

  crc=updcrc(frameend, crc);

  crc=updcrc(0,updcrc(0,crc));

  zsendline(crc>>8);
  zsendline(crc);
 }

 if(frameend==ZCRCW)
 {
  xsendline(XON);
  flushmo();
 }
}






/*
 * Read a byte, checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */

int zdlread(void)
{
 register int c;

 again:
       /* Quick check for non control characters */

       if(!ftp_ok) return(GOTCAN);

       if((c=readline(Rxtimeout)) & 0140) return(c);

       switch(c)
       {
        case ZDLE:
                  break;
        case  023:
        case 0223:
        case  021:
        case 0221:
                  goto again;
          default:
                  if(Zctlesc && !(c & 0140))
                  {
                   goto again;
                  }
                  return(c);
       }

again2:
       if((c=readline(Rxtimeout))<0) return(c);

       if(c==CAN && (c=readline(Rxtimeout))<0) return(c);

       if(c==CAN && (c=readline(Rxtimeout))<0) return(c);

       if(c==CAN && (c=readline(Rxtimeout))<0) return(c);

       switch(c)
       {
       case   CAN:
                  return(GOTCAN);
       case ZCRCE:
       case ZCRCG:
       case ZCRCQ:
       case ZCRCW:
                  return(c | GOTOR);
       case ZRUB0:
                  return 0177;
       case ZRUB1:
                  return 0377;
       case   023:
       case  0223:
       case   021:
       case  0221:
                  goto again2;
          default:
                  if(Zctlesc && ! (c & 0140))
                  {
                   goto again2;
                  }
                  if((c & 0140)== 0100) return(c ^ 0100);
                  break;
       }

 zdeb2("Bad escape sequence %x", c);
 return(ERROR);
}







/* Store long integer pos in Txhdr */

void stohdr(long pos)
{
 Txhdr[ZP0] = (char)pos;
 Txhdr[ZP1] = (char)(pos>>8);
 Txhdr[ZP2] = (char)(pos>>16);
 Txhdr[ZP3] = (char)(pos>>24);
}




/* Recover a long integer from a header */

long rclhdr(char * hdr)
{
 register long l;

 l = (long)(hdr[ZP3]);
 l = (l << 8) | hdr[ZP2];
 l = (l << 8) | hdr[ZP1];
 l = (l << 8) | hdr[ZP0];

 return(l);
}











int zrdat32(char * buf,int length)
{
 int c;
 UNSL long crc;
 char *end;
 int d;

 crc=0xFFFFFFFFL;
 Rxcount=0;
 end=buf+length;

 while(buf<=end)
 {
  if((c=zdlread()) & ~0377)
  {
   crcfoo:
          switch(c)
          {
           case GOTCRCE:
           case GOTCRCG:
           case GOTCRCQ:
           case GOTCRCW:
                        d=c;
                        c&=0377;
                        crc=UPDC32(c,crc);
                        if((c=zdlread()) & ~0377)
                        goto crcfoo;

                        crc=UPDC32(c,crc);
                        if((c=zdlread()) & ~0377) goto crcfoo;
                        crc=UPDC32(c,crc);
                        if((c=zdlread()) & ~0377) goto crcfoo;
                        crc=UPDC32(c,crc);
                        if((c=zdlread()) & ~0377) goto crcfoo;
                        crc=UPDC32(c,crc);
                        if(crc!=0xDEBB20E3)
                        {
                         zdeb3("zrdat32 Bad CRC c=%X  crc=%lX", c, crc);
                         ftpinfo(badcrc);
                         return(ERROR);
                        }
                        Rxcount=length-(end-buf);
                        zdeb3("zrdat32: %d %s",Rxcount,Zendnames[d-GOTCRCE&3]);
                        return(d);
            case GOTCAN:
                        ftpinfo("{ZM10}");
                        return(ZCAN);

           case TIMEOUT:
                        ftpinfo("{ZM11}");
                        return(c);

                default:
                        ftpinfo("{ZM12}");
                        return(c);
          }
  }
  *buf++=c;
  crc=UPDC32(c,crc);
 }
 ftpinfo("{ZM13}");
 return(ERROR);
}














/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */

int zrdata(char * buf,int length)
{
       register int c;
       register unsigned short crc;
       register char *end;
       register int d;

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

       crc = Rxcount = 0;  end = buf + length;
       while (buf <= end) {
               if ((c = zdlread()) & ~0377) {
crcfoo:
                       switch (c) {
                       case GOTCRCE:
                       case GOTCRCG:
                       case GOTCRCQ:
                       case GOTCRCW:
                               crc = updcrc((d=c)&0377, crc);
                               if ((c = zdlread()) & ~0377)
                                       goto crcfoo;
                               crc = updcrc(c, crc);
                               if ((c = zdlread()) & ~0377)
                                       goto crcfoo;
                               crc = updcrc(c, crc);
                               if (crc & 0xFFFF) {
                               zdeb3("zrdata bad CRC c=%X  crc=%lX", c, crc);
                                       ftpinfo(badcrc);
                                       return ERROR;
                               }
                               Rxcount = length - (end - buf);
                               zdeb3("zrdata: %d  %s", Rxcount,
                                Zendnames[d-GOTCRCE&3]);
                               return d;

                       case GOTCAN:
                               ftpinfo("{ZM14}");
                               return ZCAN;

                       case TIMEOUT:
                               ftpinfo("{ZM11}");
                               return c;

                       default:
                               ftpinfo("{ZM12}");
                               return c;
                       }
               }
               *buf++ = c;
               crc = updcrc(c, crc);
       }
       ftpinfo("{ZM13}");
       return ERROR;
}








/* Receive a binary style header (type and position) */

int zrbhdr(char * hdr)
{
       register int c, n;
       register unsigned short crc;

       if ((c = zdlread()) & ~0377)
               return c;
       Rxtype = c;
       crc = updcrc(c, 0);

       for (n=4; --n >= 0; ++hdr) {
               if ((c = zdlread()) & ~0377)
                       return c;
               crc = updcrc(c, crc);
               *hdr = c;
       }
       if ((c = zdlread()) & ~0377)
               return c;
       crc = updcrc(c, crc);
       if ((c = zdlread()) & ~0377)
               return c;
       crc = updcrc(c, crc);
       if (crc & 0xFFFF) {
               zdeb3("zrbhdr bad CRC c=%X  crc=%lX", c, crc);
               ftpinfo(badcrc);
               return ERROR;
       }

       return Rxtype;
}






/* Receive a binary style header (type and position) with 32 bit FCS */

int zrbhdr32(char * hdr)
{
 register int c, n;
 register UNSL long crc;

 if((c=zdlread()) & ~0377) return c;

 Rxtype=c;
 crc=0xFFFFFFFFL;
 crc=UPDC32(c, crc);

 zdeb3("zrbhdr32 c=%X  crc=%lX", c, crc);

 for(n=4;--n>=0;++hdr)
 {
  if((c = zdlread()) & ~0377) return(c);

  crc=UPDC32(c, crc);
  *hdr = c;

  zdeb3("zrbhdr32 c=%X  crc=%lX", c, crc);

 }

 for(n=4;--n>=0;)
 {
  if((c = zdlread()) & ~0377) return(c);

  crc = UPDC32(c, crc);

  zdeb3("zrbhdr32 c=%X  crc=%lX", c, crc);

 }

 if(crc!= 0xDEBB20E3)
 {
  zdeb3("zrbhdr32 bad CRC c=%X  crc=%lX", c, crc);
  ftpinfo(badcrc);
  return(ERROR);
 }

 return(Rxtype);
}









int zgeth1(void)
{
 register int c, n;

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







/* Decode two lower case hex digits into an 8 bit byte value */

int zgethex(void)
{
 register int c;

 c=zgeth1();
 zdeb2("zgethex: %02X", c);
 return(c);
}






/* Receive a hex style header (type and position) */

int zrhhdr(char * hdr)
{
 int c;
 unsigned short crc;
 int n;

 if((c=zgethex())<0) return(c);

 Rxtype=c;
 crc=updcrc(c,0);

 for(n=4;--n>=0;++hdr)
 {
  if((c=zgethex())<0) return(c);
  crc=updcrc(c,crc);
  *hdr=c;
 }

 if((c=zgethex())<0) return(c);

 crc=updcrc(c,crc);
 if((c=zgethex())<0) return(c);

 crc=updcrc(c,crc);
 if(crc & 0xFFFF)
 {
  ftpinfo(badcrc);
  return(ERROR);
 }

 switch(c=readline(1))
 {
  case 0215:
            Not8bit = c;
            /* **** FALL THRU TO **** */
   case 015:
            /* Throw away possible cr/lf */
            switch (c = readline(1)) {
            case 012:
            Not8bit |= c;
  }
 }

 return(Rxtype);
}






/*
 * Read a ZMODEM header to hdr, either binary or hex.
 *  eflag controls local display of non zmodem characters:
 *     0:  no display
 *     1:  display printing characters only
 *     2:  display all non ZMODEM characters
 *  On success, set Zmodem to 1, set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence, for fast error recovery.
 */


int zgethdr(char * hdr,int eflag)
{
 int c;
 int n;
 int cancount;

 n=Zrwindow+rxbitrate;        /* Max bytes before start of frame */
 Rxframeind=Rxtype=0;

startover:
 cancount = 5;
again:
       /* Return immediate ERROR if ZCRCW sequence seen */


 if(!ftp_ok)
 {
  c=GOTCAN;
  goto fifi;
 }

 switch(c=readline(Rxtimeout))
 {
     case RCDO:
  case TIMEOUT:
               goto fifi;
      case CAN:
gotcan:
               if(--cancount<=0)
               {
                c=ZCAN;
                goto fifi;
               }

               switch(c=readline(1))
               {
                case TIMEOUT:
                             goto again;
                  case ZCRCW:
                             c=ERROR;
               /* **** FALL THRU TO **** */
                   case RCDO:
                             goto fifi;
                     default:
                             break;
                    case CAN:
                             if(--cancount<=0)
                             {
                              c=ZCAN;
                              goto fifi;
                             }
                             goto again;
               }
               /* **** FALL THRU TO **** */
       default:
agn2:
               if(--n==0)
               {
                ftpinfo("{ZM15}");
                return(ERROR);
               }
               if(eflag && ((c &= 0177) & 0140)) bttyout(c);
               else if(eflag>1)                  bttyout(c);
               goto startover;

case ZPAD|0200:         /* This is what we want. */
               Not8bit = c;
     case ZPAD:              /* This is what we want. */
               break;
 }
 cancount=5;

splat:
 switch(c=noxrd7())
 {
     case ZPAD:
               goto splat;
     case RCDO:
  case TIMEOUT:
               goto fifi;
       default:
               goto agn2;
     case ZDLE:              /* This is what we want. */
               break;
 }

 switch(c=noxrd7())
 {
      case RCDO:
   case TIMEOUT:
                goto fifi;
      case ZBIN:
                Rxframeind = ZBIN;  Crc32 = FALSE;
                c =  zrbhdr(hdr);
                break;
    case ZBIN32:
                Crc32 = Rxframeind = ZBIN32;
                c =  zrbhdr32(hdr);
                break;
      case ZHEX:
                Rxframeind = ZHEX;  Crc32 = FALSE;
                c =  zrhhdr(hdr);
                break;
       case CAN:
                goto gotcan;
        default:
                goto agn2;
 }
 Rxpos=hdr[ZP3];
 Rxpos=(Rxpos<<8)+(hdr[ZP2]);
 Rxpos=(Rxpos<<8)+(hdr[ZP1]);
 Rxpos=(Rxpos<<8)+(hdr[ZP0]);

fifi:
 switch(c)
 {
   case GOTCAN:
               c=ZCAN;
                                           /* **** FALL THRU TO **** */
     case ZNAK:
     case ZCAN:
    case ERROR:
  case TIMEOUT:
     case RCDO:
               zdeb2("Got %s", frametypes[c+FTOFFSET]);
                                           /* **** FALL THRU TO **** */
       default:
               if(c>=-3 && c<=FRTYPES)
               {
                zdeb3("zgethdr: %s %lx", frametypes[c+FTOFFSET], Rxpos);
               }
               else
               {
                zdeb3("zgethdr: %d %lx", c, Rxpos);
               }
               if(c<0) ftpinfo(frametypes[c+FTOFFSET]);
               break;
 }
 return(c);
}

