char *ckxv = "XPR tty I/O, 5A(001), 23 Apr 92";

/*
 * C K X T I O
 *
 * Terminal I/O functions for the Amiga External Protocol (XPR) version
 * of C Kermit.
 */

#include "ckcdeb.h"
#include "ckcker.h"
#include "ckxker.h"
#include "ckxtim.h"

extern int local, what, spackets, fsecs, rpktl, spktl, bctu, numerrs, timeouts;

extern long ffc, tsecs;

extern long (*xupdate) (), (*xswrite) (), (*xfopen) (), (*xfclose) (),
  (*xfread) (), (*xsread) (), (*xchkabort) (void), (*xfnext) (), (*xffirst) (),
  (*xsflush) (void), (*xfwrite) (), (*xgets) (), (*xfinfo) (), (*xunlink)() ,
  (*xsquery) (void), (*xchkmisc) (void), (*xsetserial)();

int
ttchk() {
   if (xsquery != NULL)
      return((*xsquery)());
   else
      return 0;
}

int
ttclos(int ignore) {
   return 0;
}

int
ttfluo(void) {
   return((*xsflush)());
}

int
ttgmdm(void) {
   return -3;
}

long
ttgspd(void) {
   int x;

   x = ((calld(xsetserial, -1L) >> 16) & 255);
   switch(x) {
      case 0:
         return(110L);
      case 1:
         return(300L);
      case 2:
         return(1200L);
      case 3:
         return(2400L);
      case 4:
         return(4800L);
      case 5:
         return(9600L);
      case 6:
         return(19200L);
      case 8:
         return(38400L);
      case 9:
         return(57600L);
      case 10:
         return(76800L);
      case 11:
         return(115200L);
      default:
         return -1;
   }
}

int
tthang(void) {
   return 0;
}

void
ttimoff(void) {
}

#ifdef MYREAD

/* Private buffer for myread() and its companions.  Not for use by anything
 * else.  ttflui() is allowed to reset them to initial values.  ttchk() is
 * allowed to read my_count.
 *
 * my_item is an index into mybuf[].  Increment it *before* reading mybuf[].
 *
 * A global parity mask variable could be useful too.  We could use it to
 * let myread() strip the parity on its own, instead of stripping sign
 * bits as it does now.
 */
#define MYBUFLEN 256
static CHAR mybuf[MYBUFLEN];

static int my_count = 0;		/* Number of chars still in mybuf */
static int my_item = -1;		/* Last index read from mybuf[] */

/* myread() -- Efficient read of one character from communications line.
 *
 * Uses a private buffer to minimize the number of expensive read() system
 * calls.  Essentially performs the equivalent of read() of 1 character, which
 * is then returned.  By reading all available input from the system buffers
 * to the private buffer in one chunk, and then working from this buffer, the
 * number of system calls is reduced in any case where more than one character
 * arrives during the processing of the previous chunk, for instance high
 * baud rates or network type connections where input arrives in packets.
 * If the time needed for a read() system call approaches the time for more
 * than one character to arrive, then this mechanism automatically compensates
 * for that by performing bigger read()s less frequently.  If the system load
 * is high, the same mechanism compensates for that too.
 *
 * myread() is a macro that returns the next character from the buffer.  If the
 * buffer is empty, mygetbuf() is called.  See mygetbuf() for possible error
 * returns.
 *
 * This should be efficient enough for any one-character-at-a-time loops.
 * For even better efficiency you might use memcpy()/bcopy() or such between
 * buffers (since they are often better optimized for copying), but it may not
 * be worth it if you have to take an extra pass over the buffer to strip
 * parity and check for CTRL-C anyway.
 *
 * Note that if you have been using myread() from another program module, you
 * may have some trouble accessing this macro version and the private variables
 * it uses.  In that case, just add a function in this module, that invokes the
 * macro.
 */
#define myread()  (--my_count < 0 ? mygetbuf() : 255 & (int)mybuf[++my_item])

/* Specification: Push back up to one character onto myread()'s queue.
 *
 * This implementation: Push back characters into mybuf. At least one character
 * must have been read through myread() before myunrd() may be used.  After
 * EOF or read error, again, myunrd() can not be used.  Sometimes more than
 * one character can be pushed back, but only one character is guaranteed.
 * Since a previous myread() must have read its character out of mybuf[],
 * that guarantees that there is space for at least one character.  If push
 * back was really needed after EOF, a small addition could provide that.
 *
 * myunrd() is currently not called from anywhere inside kermit...
 */
#ifdef NOTUSED
myunrd(ch) CHAR ch; {
    if (my_item >= 0) {
	mybuf[my_item--] = ch;
	++my_count;
    }
}
#endif

/* mygetbuf() -- Fill buffer for myread() and return first character.
 *
 * This function is what myread() uses when it can't get the next character
 * directly from its buffer.  First, it calls a system dependent myfillbuf()
 * to read at least one new character into the buffer, and then it returns
 * the first character just as myread() would have done.  This function also
 * is responsible for all error conditions that myread() can indicate.
 *
 * Returns: When OK	=> a positive character, 0 or greater.
 *	    When EOF	=> -2.
 *	    When error	=> -3, error code in errno.
 *
 * Older myread()s additionally returned -1 to indicate that there was nothing
 * to read, upon which the caller would call myread() again until it got
 * something.  The new myread()/mygetbuf() always gets something.  If it 
 * doesn't, then make it do so!  Any program that actually depends on the old
 * behaviour will break.
 *
 * The older version also used to return -2 both for EOF and other errors,
 * and used to set errno to 9999 on EOF.  The errno stuff is gone, EOF and
 * other errors now return different results, although Kermit currently never
 * checks to see which it was.  It just disconnects in both cases.
 *
 * Kermit lets the user use the quit key to perform some special commands
 * during file transfer.  This causes read(), and thus also mygetbuf(), to
 * finish without reading anything and return the EINTR error.  This should
 * be checked by the caller.  Mygetbuf() could retry the read() on EINTR,
 * but if there is nothing to read, this could delay Kermit's reaction to
 * the command, and make Kermit appear unresponsive.
 *
 * The debug() call should be removed for optimum performance.
 */
int myfillbuf(void);

int
mygetbuf(void) {
    my_count = myfillbuf();
    debug(F101, "myfillbuf read", "", my_count);
    if (my_count <= 0)
      return(my_count < 0 ? -3 : -2);
    --my_count;
    return(255 & (int)mybuf[my_item = 0]);
}

/* myfillbuf():
 * System-dependent read() into mybuf[], as many characters as possible.
 *
 * Returns: OK => number of characters read, always more than zero.
 *          EOF => 0
 *          Error => -1, error code in errno.
 *
 * If there is input available in the system's buffers, all of it should be
 * read into mybuf[] and the function return immediately.  If no input is
 * available, it should wait for a character to arrive, and return with that
 * one in mybuf[] as soon as possible.  It may wait somewhat past the first
 * character, but be aware that any such delay lengthens the packet turnaround
 * time during kermit file transfers.  Should never return with zero characters
 * unless EOF or irrecoverable read error.
 *
 * Correct functioning depends on the correct tty parameters being used.
 * Better control of current parameters is required than may have been the
 * case in older Kermit releases.  For instance, O_NDELAY (or equivalent) can 
 * no longer be sometimes off and sometimes on like it used to, unless a 
 * special myfillbuf() is written to handle that.  Otherwise the ordinary 
 * myfillbuf()s may think they have come to EOF.
 *
 * If your system has a facility to directly perform the functioning of
 * myfillbuf(), then use it.  If the system can tell you how many characters
 * are available in its buffers, then read that amount (but not less than 1).
 * If the system can return a special indication when you try to read without
 * anything to read, while allowing you to read all there is when there is
 * something, you may loop until there is something to read, but probably that
 * is not good for the system load.
 */

/*
 * Following the example of the Unix code, I have chosen to write the XPR
 * version as follows:  if the xpr_squery exists, then it is called.
 * If it does not exist, or returns 0, then we call xsread of 1 character
 * with a one-second timeout.  Thus, we simply return 0 if no characters
 * arrive in one second, at which point we recheck our overall timeout.
 */

int
myfillbuf(void) {
    long avail;
    int x;

    if (xsquery)
        avail = (*xsquery)();
    if (!xsquery || avail == 0)
      avail = 1;

    if (avail > MYBUFLEN)
      avail = MYBUFLEN;

    return(calladd(xsread, mybuf, avail, 1000000L));
}


#endif /* MYREAD */

/*  T T F L U I  --  Flush tty input buffer */

int
ttflui() {

#ifdef MYREAD
/*
  Flush internal MYREAD buffer *FIRST*, in all cases.
*/
    my_count = 0;			/* Reset count to zero */
    my_item = -1;			/* And buffer index to -1 */
#endif /* MYREAD */

    if (xsflush)
        (void) (*xsflush)();
    return(0);
}

/*
 * T T I N L
 *
 * Return a line in the buffer pointed to by s from the serial line.
 * The line must end with eol and be no longer than max characters.
 * timeout is a timeout interval in seconds.
 *
 * Returns
 *	> 0	Success--number of characters read
 *	= 0	Shouldn't happen!
 * 	-1	Timeout
 *	-2	Error of some other kind
 *
 * This version for XPR Kermit.  We ignore negative returns from
 * the xpr_sread() function, preferring to take care of these
 * via the timeout mechanism.
 */
int ttinl(CHAR *s, int max, int timeout, CHAR eol)
{
   int x = 0;
   long i;
   struct timerequest *Timereq;
   unsigned mask;
   extern int parity;

   if (xchkmisc)
      (void) (*xchkmisc)();
   *s = '\0';
   mask = (parity ? 0177 : 0377);
   /*
    * Set up and post timer request for overall timeout.
    * As a compromise between correct and efficient, I only check
    * the overall timeout if a one-character read with a one second
    * timeout fails first.
    */
   if (!(Timereq = CreateTimer(UNIT_VBLANK)))
           return -1;
   Timereq->tr_time.tv_secs = timeout;
   Timereq->tr_time.tv_micro = 0;
   Timereq->tr_node.io_Command = TR_ADDREQUEST;
   SendIO((struct IORequest *) Timereq);
#ifndef MYREAD
   /*
    * Willy Langeveld assures me that, since serial.device does its
    * own buffering, calling xsread once for each character isn't
    * going to take too long.
    *
    */
   for (x = 0; x < max; ) {
      if ((i = calladd(xsread, s, 1L, 1000000L)) < 0) {
         if (chkint() < 0) {
            x =  -2;
            goto leave;
         }
      } else if (i == 1) {
         x++;
         if ((*s++ &= mask) == eol)
            break;
      } else if (CheckIO((struct IORequest *) Timereq)) { /* timeout */
         x = -1;                 /* Flag timeout abort */
         goto leave;
      }
   }
   *s = '\0';                              /* Normal termination */
leave:                                     /* Common cleanup code */
   AbortIO((struct IORequest *) Timereq);
   WaitIO((struct IORequest *) Timereq);
   DeleteTimer(Timereq);
   return(x);
#else
   /*
    * Despite the comment above, the myread() macro significantly improves
    * performance on terminal emulators whose xpr_sread() is not as finely
    * tuned, apparently as VLT's.  Term 3.n comes to mind...
    */

   for (x = 0; x < max; ) {
      if ((i = myread()) < -1) {
         /*
          * The following code deals with the case that
          * xsread() returns a negative value if the
          * user has clicked on the "Cancel File" or
          * "Cancel Batch" gadgets.  However, we don't
          * want to abort receiving the current packet
          * in this case;  calling chkint() now sets
          * the global cx and/or cz flags, but in the
          * case of a complete abort, chkint() returns
          * a negative value.
          */
         if (chkint() < 0) {
            x = -2;         /* Error */
            goto leave;
         } else if (CheckIO((struct IORequest *) Timereq)) { /* timeout */
            x = -1;                 /* Flag timeout abort */
            goto leave;
         }
      } else if (i >= 0) {           /* Successful get of character */
         x++;
         *s = i;
         if ((*s++ &= mask) == eol)
            break;
      }
   }
   *s = '\0';				/* Normal termination */
leave:					/* Common cleanup code */
   AbortIO((struct IORequest *) Timereq);
   WaitIO((struct IORequest *) Timereq);
   DeleteTimer(Timereq);
   if (chkint() < 0)
       return -2;
   return(x);
#endif /*MYREAD*/
}

int
ttinc(int ttimo) {
#ifndef MYREAD
   unsigned char ch;
#else
   int n;
#endif /*MYREAD*/

#ifdef MYREAD
   n = myread();
   return (n < 0 ? n : n & 0377);
#else
   if (calladd(xsread, &ch, 1, ttimo*1000000L) <= 0)
      return -1;
   return(ch);
#endif /*MYREAD*/
}

int
ttoc(char c) {
   if (callad(xswrite, &c, 1) == 0L)
      return 1;
   else
      return -1;
}

int
ttol(CHAR *s, int n) {
   if (callad(xswrite, s, n) == 0L)
      return n;
   else
      return -1;
}

int
ttopen(char *ttname, int *lcl, int modem, int timo) {
   return 0;
}

int
ttpkt(long speed, int flow, int parity) {
   return 0;
}

int
ttres(void) {
   return 0;
}

int
ttscarr(int carrier) {
   return carrier;
}

int
ttsndb(void) {
   return 0;
}

int
ttsndlb(void) {
   return 0;
}

int
ttsspd(int cps) {
   return 0;
}

int
ttvt(long speed, int flow) {
   return 0;
}

int
ttwmdm(int mdmsig, int timo) {
   return -3;
}

int
ttxin(int n, CHAR *buf) {
#ifdef MYREAD
   int c, i;
#endif

#ifdef MYREAD
   for (i = 0; i < n; i++) {
      c = myread();
      if (c < 0)
         return i;
      *buf++ = c & 0377;
   }
   return n;
#else
   return(calladd(xsread, buf, n, 0L));
#endif /*MYREAD*/
}

void
ztime(char **s) {
   *s = "Ddd Mmm 00 00:00:00 0000\n";	/* Return dummy in asctime() format */
}

int
msleep(int m) {
   struct timeval tv;

   tv.tv_secs = m/1000;
   tv.tv_micro = (m % 1000)*1000;
   return(MyDelay(&tv, 0L));
}

int
sleep(int m) {
   return(msleep(1000*m));
}

static struct timeval now, elapsed;

void
rtimer(void) {
   GetSysTime(&now);
}

int
gtimer(void) {
   GetSysTime(&elapsed);
   return((((1000000L*elapsed.tv_secs + elapsed.tv_micro) - 
	    (1000000L*now.tv_secs + now.tv_micro)) + 500000L)/1000000L);
}

int
sysinit(void) {
   return 0;
}

int
syscleanup(void) {
   return 0;
}

void
connoi(void) {
}

int
conoll(char *s) {
   return 0;
}

int
conol(char *s) {
   return 0;
}

int
conoc(char c) {
   return 1;
}

static int oldtyp = 0, olderrs = -1, oldlen = -1, oldffc = 0L, oldtimes = -1;

char *					/* Convert seconds to hh:mm:ss */
hhmmss(long x)
{
    static char buf[10];
    long s, h, m;
    h = x / 3600L;			/* Hours */
    x = x % 3600L;
    m = x / 60L;			/* Minutes */
    s = x % 60L;			/* Seconds */
    if (x > -1L)
      sprintf(buf,"%02ld:%02ld:%02ld",h,m,s);
    else buf[0] = '\0';
    return((char *)buf);
}

/*
 * S C R E E N
 *
 * Display status on the screen.  This version is the go-between from
 * what C Kermit needs and what XPR provides.
 */
void
screen(int f, char c, long n, char *s) {
   static struct XPR_UPDATE update;
   static char string[51], time1[15], time2[15];
   static long fsiz = -1L;              /* Copy of file size */
   static long fcnt = 0L;               /* File count */
   static long fbyt = 0L;               /* Total file bytes */
   static char fn[257];			/* Local copy of file name */

   update.xpru_updatemask = 0;
   debug(F101,"screen f = ","",f);	/* Handle our function code */
   if (n) debug(F101, "screen n = ", "", n);
   if (s) debug(F110, "screen s = ", s, n);
   switch(f) {
      case SCR_FN:
         if (what == W_SEND)
	   sprintf(string, "Sending: %s", s);
	 else
	   sprintf(string, "Receiving: %s", s);
         update.xpru_filename = string;
         update.xpru_updatemask = XPRU_FILENAME;
         fsiz = -1L;			/* Invalidate file size */
         strncpy(fn, s, 256);
         break;
      case SCR_AN:
         if (what == W_SEND)
	   sprintf(string, "Sending: %s as %s", fn, s);
	 else
	   sprintf(string, "Receiving: %s as %s", fn, s);
         update.xpru_filename = string;
         update.xpru_updatemask = XPRU_FILENAME;
         break;
      case SCR_FS:
         fsiz = update.xpru_filesize = n;
         update.xpru_updatemask = XPRU_FILESIZE;
         break;
      case SCR_ST:
         switch (c) {                   /* Print new status message */
            case ST_OK:                 /* Transfer OK */
               fcnt++;                  /* Count this file */
               if (ffc > 0)             /* For some reason ffc is off by 1 */
                  fbyt += ffc - 1L;     /* Count its bytes */
               strcpy(string, "Transfer OK");
               break;
            case ST_DISC:
               strcpy(string, "File discarded");
               break;
            case ST_INT:
               strcpy(string, "Transfer interrupted");
               break;
            case ST_SKIP:
               strcpy(string, "File skipped");
               break;
            case ST_ERR:
               sprintf(string, "%.50s", s);
               break;
            case ST_REFU:
               strcpy(string, "File refused");
               break;
            case ST_INC:
               strcpy(string, "Incompletely received");
               break;
            default:
               strcpy(string, "screen() called with bad status");
               break;
            }
         if (c == ST_OK) {
            update.xpru_updatemask = XPRU_MSG;
            update.xpru_msg = string;
         } else {
            update.xpru_updatemask = XPRU_ERRORMSG;
            update.xpru_errormsg = string;
         }
         break;
      case SCR_PN:
         update.xpru_blocks = n;
         update.xpru_updatemask = XPRU_BLOCKS;
         break;
      case SCR_PT:
         /*
          * Following the Unix version, we check everything and only
          * update what's actually changed since last time.  The intent
          * is to prevent the screen updates from slowing the file
          * transfers.
          */
         update.xpru_updatemask = 0;
         if (spackets < 5) {
            switch (bctu) {
               case 1:
                  update.xpru_blockcheck = "Sum-6";
                  break;
               case 2:
                  update.xpru_blockcheck = "Sum-12";
                  break;
               case 3:
                  update.xpru_blockcheck = "CRC-16";
                  break;
            }
            update.xpru_updatemask |= XPRU_BLOCKCHECK;
         }
	 if ((update.xpru_blocksize = (what == W_RECV) ? rpktl + 1 : spktl +
               1) != oldlen) {
            update.xpru_updatemask |= XPRU_BLOCKSIZE;
            oldlen = update.xpru_blocksize;
         }
	 update.xpru_blocks = spackets;
	 update.xpru_updatemask |= XPRU_BLOCKS;
	 if (c != oldtyp && c != 'Y' && c != 'N' && c != '%') {
	    oldtyp = update.xpru_packettype = c;
	    update.xpru_updatemask |= XPRU_PACKETTYPE;
	 }
	 if (numerrs != olderrs) {
	    olderrs = update.xpru_errors = numerrs;
	    update.xpru_updatemask |= XPRU_ERRORS;
	 }
	 if (ffc != oldffc) {
	    oldffc = update.xpru_bytes = ffc;
	    update.xpru_updatemask |= XPRU_BYTES;
	 }
	 switch (c) {			/* Now handle specific packet types */
	    long x, y;
	    case 'S':			/* Beginning of transfer */
	       fcnt = fbyt = 0L;	/* Clear counters */
	       break;
	    case 'D':                     /* Data packet */
	       /* fsecs is the time from gtimer() this file started at */
	       y = ((unsigned) gtimer() - fsecs); /* Secs so far */
	       strcpy(time1, hhmmss(y));
	       update.xpru_elapsedtime = time1;
	       update.xpru_updatemask |= XPRU_ELAPSEDTIME;
	       if (y > 0) {
	          update.xpru_datarate = ((10L * ffc)/y)/10L;
	          update.xpru_updatemask |= XPRU_DATARATE;
	       }
	       if (y > 0L && fsiz > 0L && ffc > 0L) {	/* Update expected time so on */
	          x = (y*fsiz)/ffc;
	          strcpy(time2, hhmmss(x));
	          update.xpru_expecttime = time2;
	          update.xpru_updatemask |= XPRU_EXPECTTIME;
	       }
	       break;
	    case 'E':				/* Error packet */
	       if (*s) {                        /* Print its data field */
                  update.xpru_errormsg = s;
	          update.xpru_updatemask |= XPRU_ERRORMSG;
	       }
	       fcnt = fbyt = 0;                 /* No bytes for this file */
	       break;
	    case 'Q':
	       update.xpru_errormsg = "Damaged packet";
	       update.xpru_updatemask |= XPRU_ERRORMSG;
	       break;
	    case 'T':
	       update.xpru_errormsg = "Timeout";
	       update.xpru_updatemask |= XPRU_ERRORMSG;
	       if ((update.xpru_timeouts = timeouts) != oldtimes) {
	          update.xpru_updatemask |= XPRU_TIMEOUTS;
	          oldtimes = timeouts;
	       }
	       break;
	    default:
	       break;
	    }
	 break;
      case SCR_TC:
         if (tsecs > 0)
            sprintf(string, "Files: %ld, Total Bytes: %ld, %ld cps",
                    fcnt, fbyt, ((fbyt * 10L) / (long) tsecs) / 10L);
         else
            sprintf(string, "Files: %ld, Total Bytes: %ld", fcnt, fbyt);
         update.xpru_msg = string;
         update.xpru_updatemask = XPRU_MSG;
         break;
      case SCR_EM:
         update.xpru_errormsg = s;
         update.xpru_updatemask = XPRU_ERRORMSG;
         break;
      case SCR_QE:                      /* Quantity equals */
      case SCR_TU:                      /* Undelmited text */
      case SCR_TN:                      /* Text delimited at start */
      case SCR_TZ:                      /* Text delimited at end */
         break;
      case SCR_XD:                      /* X packet data */
         update.xpru_filename = s;
         update.xpru_updatemask = XPRU_FILENAME;
         break;
      case SCR_CW:                      /* Close window */
         oldtyp = 0;                    /* Reset these counters for next time */
         olderrs = oldtimes = oldlen = -1;
         oldffc = 0;
         break;
      default:
         break;
   }
   if (update.xpru_updatemask) (void) calla(xupdate, &update);
   return;
}

/*
 * This is the XPR Kermit version of chkint.  We allow multiple levels
 * of interrupt, if available.
 */
int
chkint(void) {			/* Check for console interrupts. */
   int x;
   extern int cxseen, czseen;

   x = (*xchkabort)();
   switch (x) {
      case 0:                 /* No abort pending */
         return 0;
      case 1:                 /* Abort file only. */
         screen(SCR_TN,0,0l,"Cancelling File ");
         cxseen = 1;
         break;
      case 2:                 /* Abort file and batch. */
         screen(SCR_TN,0,0l,"Cancelling Batch ");
         czseen = 1;
         break;
      case -1:                /* Emergency escape -- kill all */
         return -1;
      default:
         ;
}
   return 1;
}

void
intmsg(long n) {
   screen(SCR_TN, 0, 0L, "Interrupt using mouse or keyboard");
}

void
ermsg(msg) char *msg; {			/* Print error message */
   if (local)
      screen(SCR_EM,0,0L,msg);
   tlog(F110,"Protocol Error:",msg,0L);
}

void
fatal(char *msg) {
   if (local)
      screen(SCR_EM,0,0L,msg);
   tlog(F110,"Fatal Error:",msg,0L);
}

void
doclean(void) {
}

int
psuspend(int x) {
   return 0;
}
