/** xprascii.c
*
*   These are the protocol transfer routines for a simple ASCII upload.
*
**/
#include <exec/exec.h>
#include <functions.h>
#include <stdio.h>
/*
*   xproto.h is the include file given in Appendix B.
*/
#include "xproto.h"
/*
*   The following two strings must exist.
*/
char  XPRname[]   = "xprascii.library";
char  XPRid[]     = "xprascii 0.9 (May 89)\r\n";
UWORD XPRrevision = 9;

long atol();
/*
*   The callxx...() routines are described later. They provide the 
*   assembler interface from the XPR library to the call-back routines.
*/
long calla(), callaa(), callad(), calladd(), calladda();

char *malloc();


/**
*
*   Send a file
*
**/
long XProtocolSend(IO)
struct XPR_IO *IO;
{
   long fp, r, i;
   long brkflag = 0, fl = 0L, sd = 0L;
   long (*xupdate)(), (*xswrite)(), (*xfopen)(), (*xfclose)(), (*xfread)(),
        (*xsread)(),  (*xchkabort)();
   unsigned char *buff = NULL, *serbuff = NULL;
   struct XPR_UPDATE xpru;

/*
*   These are the call-backs we need. If any of them isn't provided, quit.
*   Could do some error reporting if at least xupdate is there.
*/
   if ((xupdate    = IO->xpr_update)   == NULL) return(0L);
   if ((xswrite    = IO->xpr_swrite)   == NULL) return(0L);
   if ((xfopen     = IO->xpr_fopen)    == NULL) return(0L);
   if ((xfclose    = IO->xpr_fclose)   == NULL) return(0L);
   if ((xfread     = IO->xpr_fread)    == NULL) return(0L);
   if ((xsread     = IO->xpr_sread)    == NULL) return(0L);
   if ((xchkabort  = IO->xpr_chkabort) == NULL) return(0L);
/*
*   Allocate a few buffers.
*/
   buff    = (unsigned char *) malloc(80);
   serbuff = (unsigned char *) malloc(80);
/*
*   If we ran out of memory, print a message.
*   The argument needs to go in A0: calla does this for us. 
*/
   if (buff == NULL || serbuff == NULL) {
      xpru.xpru_updatemask = XPRU_ERRORMSG;
      xpru.xpru_errormsg   = "Ran out of memory!";
      calla(xupdate, &xpru);
      return(0L);
   }
/*
*   Read the send delay, if a XProtocolSetup() was done before.
*   If send delay is too large, cut it off at 10 seconds.
*   In this example, the xpr_data field contains a null terminated string
*   containing the number of ticks to delay each 80 characters.
*/
   if (IO->xpr_data) {
      sd = atol(IO->xpr_data);
      if (sd > 500L) sd = 500L;
   }
/*
*   Open the file. One could do wild card detection here.
*   xfopen requires two arguments, in a0 and a1 respectively.
*   Again, this must be done in assembler, and callaa does it.
*/
   fp = callaa(xfopen, IO->xpr_filename, "r");
   if (fp == NULL) {
      free(buff);
      free(serbuff);
      xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_FILENAME;
      xpru.xpru_errormsg   = "Failed to open input file";
      xpru.xpru_filename   = IO->xpr_filename;
      calla(xupdate, &xpru);
      return(0L);
   }
/*
*   Start the transfer. See 3.8 for a discussion on how to implement
*   xupdate. 
*/
   xpru.xpru_updatemask = XPRU_MSG | XPRU_FILENAME;
   xpru.xpru_msg        = "Starting ASCII Send";
   xpru.xpru_filename   = IO->xpr_filename;
   calla(xupdate, &xpru);
/*
*   Now read 80 byte chunks from the file using xfread.
*   xfread requires four arguments, a0, d0, d1 and a1.
*/
   xpru.xpru_blocks = 0L;
   while (r = calladda(xfread, buff, 1L, 80L, fp)) {
/*
*   Convert line feeds to carriage returns before sending to host.
*   fl counts the characters. Display how many characters are sent.
*/
      for (i = 0L; i < r; i++) if (buff[i] == '\n') buff[i] = '\r';
      fl += r;
      xpru.xpru_updatemask = XPRU_BYTES | XPRU_BLOCKS | XPRU_BLOCKSIZE;
      xpru.xpru_bytes      = fl;
      xpru.xpru_blocks++;
      xpru.xpru_blocksize  = r;
      calla(xupdate, &xpru);
      callad(xswrite, buff, r);
/*
*   Every 80 bytes, put out a message and delay if requested.
*/
      xpru.xpru_updatemask  = XPRU_PACKETDELAY;
      xpru.xpru_packetdelay = sd * 20L;  /* msec! */
      calla(xupdate, &xpru);
/*
*   Can't use Delay() here, because Delay() is in dos.library!
*   However writing an equivalent function using the timer.device is
*   trivial.
*/
      TimeOut(sd);
/*
*   Eat any characters that might arrive from the serial port.
*   calladd stores arg1 in a0, arg2 in d0, arg3 in d1.
*   We're not really waiting for any characters: use a timeout of 0L.
*/
      while (calladd(xsread, serbuff, 80L, 0L) > 0L) ;
/*
*   Check for "abort" here. Perhaps should call chkmisc() as well.
*/
      if (brkflag = xchkabort()) break;
   }
/*
*   Close the file
*/
   calla(xfclose, fp);
   free(buff);
   free(serbuff);
/*
*   If we got here through chkabort() say Aborted.
*/
   xpru.xpru_updatemask       = XPRU_MSG;
   if (brkflag) xpru.xpru_msg = "Aborted";
   else         xpru.xpru_msg = "Done"; 
   calla(xupdate, &xpru);
   if (brkflag) return(0L);
   else		return(1L);
}


/**
*
*   Receive a file.
*
**/
long XProtocolReceive(IO)
struct XPR_IO *IO;
{
   long fp, r, i;
   long brkflag = 0, fl = 0L, sd = 0L;
   long (*xupdate)(), (*xswrite)(), (*xfopen)(), (*xfclose)(), (*xfwrite)(),
        (*xsread)(),  (*xchkabort)();
   unsigned char *serbuff = NULL;
   struct XPR_UPDATE xpru;

/*
*   These are the call-backs we need. If any of them isn't provided, quit.
*   Could do some error reporting if at least xupdate is there.
*/
   if ((xupdate    = IO->xpr_update)   == NULL) return(0L);
   if ((xswrite    = IO->xpr_swrite)   == NULL) return(0L);
   if ((xfopen     = IO->xpr_fopen)    == NULL) return(0L);
   if ((xfclose    = IO->xpr_fclose)   == NULL) return(0L);
   if ((xfwrite    = IO->xpr_fwrite)   == NULL) return(0L);
   if ((xsread     = IO->xpr_sread)    == NULL) return(0L);
   if ((xchkabort  = IO->xpr_chkabort) == NULL) return(0L);
/*
*   Allocate a buffer.
*/
   serbuff = (unsigned char *) malloc(80);
/*
*   If we ran out of memory, print a message.
*   The argument needs to go in A0: calla does this for us. 
*/
   if (serbuff == NULL) {
      xpru.xpru_updatemask = XPRU_ERRORMSG;
      xpru.xpru_errormsg   = "Ran out of memory!";
      calla(xupdate, &xpru);
      return(0L);
   }
/*
*   Open the file. One could do wild card detection here.
*   xfopen requires two arguments, in a0 and a1 respectively.
*   Again, this must be done in assembler, and callaa does it.
*/
   fp = callaa(xfopen, IO->xpr_filename, "w");
   if (fp == NULL) {
      free(serbuff);
      xpru.xpru_updatemask = XPRU_ERRORMSG | XPRU_FILENAME;
      xpru.xpru_errormsg   = "Failed to open output file";
      xpru.xpru_filename   = IO->xpr_filename;
      calla(xupdate, &xpru);
      return(0L);
   }
/*
*   Start the transfer. See 3.8 for a discussion on how to implement
*   xupdate. 
*/
   xpru.xpru_updatemask = XPRU_MSG | XPRU_FILENAME;
   xpru.xpru_msg        = "Starting ASCII Receive";
   xpru.xpru_filename   = IO->xpr_filename;
   calla(xupdate, &xpru);
/*
*   Now read 80 byte chunks from the serial port using xsread. Stop
*   when no characters arrive for 5 sec.
*/
   xpru.xpru_blocks = 0L;
   while ((r = calladd(xsread, serbuff, 80L, 5000000L)) > 0L)  {
/*
*   Strip high-bit before storing in file.
*   fl counts the characters. Display how many characters are received.
*/
      for (i = 0L; i < r; i++) serbuff[i] &= 0177;
      fl += r;
      xpru.xpru_updatemask = XPRU_BYTES | XPRU_BLOCKS | XPRU_BLOCKSIZE;
      xpru.xpru_bytes      = fl;
      xpru.xpru_blocks++;
      xpru.xpru_blocksize  = r;
      calla(xupdate, &xpru);
/* 
*   Write 80 byte chunks to the file using xwrite
*/
      calladda(xfwrite, serbuff, 1L, r, fp);

/*
*   Check for "abort" here. Perhaps should call chkmisc() as well.
*/
      if (brkflag = xchkabort()) break;
   }
/*
*   Close the file
*/
   calla(xfclose, fp);
   free(serbuff);
/*
*   If we got here through chkabort() say Aborted.
*/
   xpru.xpru_updatemask       = XPRU_MSG;
   if (brkflag) xpru.xpru_msg = "Aborted";
   else         xpru.xpru_msg = "Done"; 
   calla(xupdate, &xpru);
   if (brkflag) return(0L);
   else		return(1L);
}


/**
*
*   Setup
*
**/
long XProtocolSetup(IO)
struct XPR_IO *IO;
{
   long (*xupdate)(), (*xgets)();
   struct XPR_UPDATE xpru;

   if ((xupdate = IO->xpr_update) == NULL) return(0L);
   if ((xgets   = IO->xpr_gets)   == NULL) return(0L);
/*
*   Allocate a bit of memory for a data buffer
*/
   if (IO->xpr_data == NULL) {
      if ((IO->xpr_data = (long *) malloc(256)) == NULL) {
         xpru.xpru_updatemask = XPRU_ERRORMSG;
         xpru.xpru_errormsg   = "ASCII - Out of memory!";
         calla(xupdate, &xpru);
         return(0L);
      }
   }
/*
*   If setup string isn't handed to us, ask questions
*/
   if (IO->xpr_filename == NULL) {
/*
*   Get the value for the send dealy
*/
      callaa(xgets, "Enter ASCII send delay (ticks, 1 tick = 20 msec)",
                  IO->xpr_data);
   }
   else {
      strcpy(IO->xpr_data, IO->xpr_filename);
   }
   
   return(1L);
}

/**
*
*   Cleanup
*
**/
long XProtocolCleanup(IO)
struct XPR_IO *IO;
{
   if (IO->xpr_data) free(IO->xpr_data);
   IO->xpr_data = NULL;

   return(1L);
}

/**
*
*   The following functions setup the proper registers for the call-back 
*   functions.
*
**/
#asm
        public _callad
_callad:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
/*
*   Now this is a trick to avoid using another register.
*   Charlie taught me this...
*/
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calladda
_calladda:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
        move.l  16(sp),d1               ; Fourth argument goes in d1
        movea.l 20(sp),a1               ; Fifth  argument goes in a1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calla
_calla:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _callaa
_callaa:
        movea.l 8(sp),a0                ; Second argument goes in a0
        movea.l 12(sp),a1               ; Third  argument goes in a1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calladd
_calladd:
        move.l  8(sp),a0                ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
        move.l  16(sp),d1               ; Fourth argument goes in d1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

#endasm
/*
*   Could have added any other functions needed for other call-backs.
*   Could have written a fancier single one... Could've...
*/
