

/**
 * Copyright (c) 1985 by Steve Wilhite, Worthington, Ohio
 *
 * Permission is granted to use or distribute this software without any
 * restrictions as long as this entire copyright notice is included intact.
 * You may include it in any software product that you sell for profit.
 *
 * This software is distributed as is, and is not guaranteed to work on any
 * given hardware/software configuration.  Furthermore, no liability is
 * granted with this software.
 *
 * ABSTRACT:
 *
 *   The function, Transfer_File, implements error-free file transfer using
 *   CompuServe's "B" protocol.
 *
 *   It has been assumed that the start-of-packet sequence, DLE "B", has
 *   been detected and the next byte not received yet is the packet
 *   sequence number (an ASCII digit).
 *
 * ENVIRONMENT: Lattice "C", machine independent.
 *
 * AUTHOR: Steve Wilhite, CREATION DATE: 21-Jul-85
 *
 * REVISION HISTORY:
 * Jan 1-3 1988 - Charles B. Blish - Formatted source properly. IMHO.
 *                                   added 'short' specifier to vars,
 *                                   removing 32 bit default of lattice.
 *                           
 *
 **/

#include <lattice/stdio.h>
#include <lattice/fcntl.h>

/* External Functions */

extern Delay();                /* Sleep for "n" milliseconds */
extern Start_Timer();          /* Enable the timer for the specified number
                                  seconds */
extern int Timer_Expired();    /* Returns "true" if the timer has expired,
                                 "false" otherwise */
extern int Wants_To_Abort();   /* Returns "true" if the user wants to abort
                                  the file transfer, "false" otherwise */
extern int Read_Modem();       /* Read a character from the comm port.
                   Returns -1     if no character available */
extern int Write_Modem();      /* Send a character to the comm port. Returns
                                  "true" is successful, "false" otherwise */

extern int open(), creat();    /* standard I/O functions */
extern int read(), write();
extern int close();

#define ETX   0x03
#define ENQ   0x05
#define DLE   0x10
#define XON   0x11
#define XOFF  0x13
#define NAK   0x15
#define NUL   0x00

#define True          1
#define False         0
#define Success      -1
#define Failure       0
#define Packet_Size   512
#define Max_Errors    7
#define Max_Time      15
#define WACK          ';'      /* wait acknowledge */

/* Sender actions */

#define S_Send_Packet   0
#define S_Get_DLE       1
#define S_Get_Num       2
#define S_Get_Seq       3
#define S_Get_Data      4
#define S_Get_Checksum  5
#define S_Timed_Out     6
#define S_Send_NAK      7

/* Receiver actions */

#define R_Get_DLE        0
#define R_Get_B          1
#define R_Get_Seq        2
#define R_Get_Data       3
#define R_Get_Checksum   4
#define R_Send_NAK       5
#define R_Send_ACK       6


/* variables used by external routine that shows transfer status
 * in window - provided by user: CBB, Jan/88
 */
extern int datablocks,dataretrys,oursize;
/******************************************/

/* routines called for handling status window;
 * provided by user: CBB Jan/88
 */

extern int updl();            /* update download status window numbers */
extern int svcdl();           /* check for wants to abort by windowclose */
                              /* abort is forced by pushing ^C into the */
                              /* users kbd input */
extern int upul();            /* update upload status report window #'s */
extern int svcup();           /* check for wants to abort by windowclose */
                              /* abort is forced by pushing ^C into the */
                              /* users kbd input */
extern int showdownload();    /* opens window for status, calls recieve */
extern int showupload();      /* opens window for status, calls transmit */

/******************************************/

static short Checksum;

static short
Ch,
Seq_Num,
R_Size,            /* Size of receiver buffer */
XOFF_Flag,
Seen_ETX;

static unsigned char
S_Buffer[Packet_Size],      /* Sender buffer */
R_Buffer[Packet_Size];      /* Receiver buffer */

static Put_Msg(Text)

char *Text;
  {
    while (*Text != 0) Put_Char(*Text++);
    Put_Char('\015');
    Put_Char('\012');
    return(0);
  }



static Send_Byte(Ch)
int Ch;
  {
    short TCh;
    /* Listen for XOFF from the network */
    do
      {
        while ((TCh = Read_Modem()) >= 0)
          {
            if (TCh == XON)
              {
                XOFF_Flag = False;
              }
            else if (TCh == XOFF)
              {
                XOFF_Flag = True;
              }
          }
      } while (XOFF_Flag);
    while (!Write_Modem(Ch));
    return(0);
  }



static Send_Masked_Byte(Ch)
short Ch;
  {
    /* Mask any protocol or flow characters */
    /*PutChar(Ch);*/
    if (Ch == ETX || Ch == NUL || Ch == ENQ || Ch == DLE || Ch == NAK || Ch == XON || Ch == XOFF)
      {
        Send_Byte(DLE);
        Send_Byte(Ch + '@');
      }
    else
      {
        Send_Byte(Ch);
      }
    return(0);
  }



static Send_ACK()
  {
    Send_Byte(DLE);
    Send_Byte(Seq_Num + '0');
    return(0);
  }



static Read_Byte()
  {
    if ((Ch = Read_Modem()) < 0)
      {
        Start_Timer(Max_Time);
        do
          {
            if (Timer_Expired()) return Failure;
          } while ((Ch = Read_Modem()) < 0);
      }
    return Success;
  }



static Read_Masked_Byte()
  {
    Seen_ETX = False;
    if (Read_Byte() == Failure) return Failure;
    /*PutChar(Ch);*/
    if (Ch == DLE)
      {
        if (Read_Byte() == Failure) return Failure;
        Ch &= 0x1F;
      }
    else if (Ch == ETX) Seen_ETX = True;
    return Success;
  }




static Do_Checksum(Ch)
short Ch;
  {
    Checksum <<= 1;
    if (Checksum > 255)
      {
        Checksum = (Checksum & 0xFF) + 1;
      }
    Checksum += Ch;
    if (Checksum > 255)
      {
        Checksum = (Checksum & 0xFF) + 1;
      }
    return(0);
  }



static int Read_Packet(Action)
/**
 * Function:
 *   Receive a packet from the host.
 *
 * Inputs:
 *   Action -- the starting action
 *
 * Outputs:
 *   R_Buffer -- contains the packet just received
 *   R_Size -- length of the packet
 *
 * Returns:
 *   success/failure
 **/
int Action;
  {
    short Errors;
    short Next_Seq;

    Errors = 0;
    updl(); /* update download window */
    svcdl(); /* watch for user close... */
    while (Errors < Max_Errors)
      {
        switch (Action)
          {
            case R_Get_DLE:
              {
                if (Read_Byte() == Failure)
                  {
                    Action = R_Send_NAK;
                  }
                else if (Ch == DLE)
                  {
                    Action = R_Get_B;
                  }
                else if (Ch == ENQ)
                  {
                    Action = R_Send_ACK;
                  }
                break;
              }
            case R_Get_B:
              {
                if (Read_Byte() == Failure)
                  {
                    Action = R_Send_NAK;
                  }
                else if (Ch == 'B')
                  {
                    Action = R_Get_Seq;
                  }
                else
                  {
                    Action = R_Get_DLE;
                  }
                break;
              }
            case R_Get_Seq:
              {
                if (Read_Byte() == Failure)
                  {
                    Action = R_Send_NAK;
                  }
                else
                  {
                    Checksum = 0;
                    Next_Seq = Ch - '0';
                    Do_Checksum(Ch);
                    R_Size = 0;
                    Action = R_Get_Data;
                  }
                break;
              }
            case R_Get_Data:
              {
                if (Read_Masked_Byte() == Failure)
                  {
                    Action = R_Send_NAK;
                  }
                else if (Seen_ETX)
                  {
                    Action = R_Get_Checksum;
                  }
                else if (R_Size == Packet_Size)
                  {
                    Action = R_Send_NAK;
                  }
                else
                  {
                    R_Buffer[R_Size++] = Ch;
                    Do_Checksum(Ch);
                  }
                break;
              }
            case R_Get_Checksum:
              {
                Do_Checksum(ETX);
                if (Read_Masked_Byte() == Failure)
                  {
                    Action = R_Send_NAK;
                  }
                else if (Checksum != Ch)
                  {
                    Action = R_Send_NAK;
                  }
                else if (Next_Seq == Seq_Num)
                  {
                    Action = R_Send_ACK;   /* Ignore duplicate packet */
                  }
                else if (Next_Seq != (Seq_Num + 1) % 10)
                  {
                    Action = R_Send_NAK;
                  }
                else
                  {
                    Seq_Num = Next_Seq;
                    datablocks++;
                    return Success;
                  }
                break;
              }
            case R_Send_NAK:
              {
                Errors++;
                dataretrys++;
                Send_Byte(NAK);
                Action = R_Get_DLE;
                break;
                case R_Send_ACK:
                Send_ACK();
                Action = R_Get_DLE;
                break;
              }
          }
      }

    return Failure;
  }



static int Send_Packet(Size)
/**
 * Function:
 *   Send the specified packet to the host.
 *
 * Inputs:
 *   Size -- length of the packet
 *   S_Buffer -- the packet to send
 *
 * Outputs:
 *
 * Returns:
 *   success/failure
 **/
short Size;            /* size of packet to send */
  {
    short Action;
    short Next_Seq;
    short RCV_Num;
    short I;
    short Errors;
    Next_Seq = (Seq_Num + 1) % 10;
    Errors = 0;
    Action = S_Send_Packet;
    upul(); /* update download window */
    svcdl(); /* watch for user close... */
    while (Errors < Max_Errors)
      {
        switch (Action)
          {
            case S_Send_Packet:
              {
                Checksum = 0;
                Send_Byte(DLE);
                Send_Byte('B');
                Send_Byte(Next_Seq + '0');
                Do_Checksum(Next_Seq + '0');
                for (I = 0; I < Size; I++)
                  {
                    Send_Masked_Byte(S_Buffer[I]);
                    Do_Checksum(S_Buffer[I]);
                  }
                Send_Byte(ETX);
                Do_Checksum(ETX);
                Send_Masked_Byte(Checksum);
                Action = S_Get_DLE;
                break;
              }
            case S_Get_DLE:
              {
                if (Read_Byte() == Failure)
                  {
                    Action = S_Timed_Out;
                  }
                else if (Ch == DLE)
                  {
                    Action = S_Get_Num;
                  }
                else if (Ch == NAK)
                  {
                    Errors++;
                    dataretrys++;
                    Action = S_Send_Packet;
                  }
                break;
              }
            case S_Get_Num:
              {
                if (Read_Byte() == Failure)
                  {
                    Action = S_Timed_Out;
                  }
                else if (Ch >= '0' && Ch <= '9')
                  {
                    if (Ch == Seq_Num + '0')
                      {
                        Action = S_Get_DLE;   /* Ignore duplicate ACK */
                      }
                    else if (Ch == Next_Seq + '0')
                      {
                        /* Correct sequence number */
                        Seq_Num = Next_Seq;
                        return Success;
                      }
                    else
                      {
                        if (Errors == 0)
                          {
                            Action = S_Send_Packet;
                          }
                        else
                          {
                            Action = S_Get_DLE;
                          }
                      }
                  }
                else if (Ch == WACK)
                  {
                    /*Delay(50);    Sleep for 5 seconds */
                    Action = S_Get_DLE;
                  }
                else if (Ch == 'B')
                  {
                    Action = S_Get_Seq;
                  }
                else
                  {
                    Action = S_Get_DLE;
                  }
                break;
              }
            case S_Get_Seq:
              {
                /**
               * Start of a "B" protocol packet. The only packet that makes
               * any sense here is a failure packet.
               **/
                if (Read_Byte() == Failure)
                  {
                    Action = S_Send_NAK;
                  }
                else
                  {
                    Checksum = 0;
                    RCV_Num = Ch - '0';
                    Do_Checksum(Ch);
                    I = 0;
                    Action = S_Get_Data;
                  }
                break;
              }
            case S_Get_Data:
              {
                if (Read_Masked_Byte() == Failure)
                  {
                    Action = S_Send_NAK;
                  }
                else if (Seen_ETX)
                  {
                    Action = S_Get_Checksum;
                  }
                else if (I == Packet_Size)
                  {
                    Action = S_Send_NAK;
                  }
                else
                  {
                    R_Buffer[I++] = Ch;
                    Do_Checksum(Ch);
                  }
                break;
              }
            case S_Get_Checksum:
              {
                Do_Checksum(ETX);
                if (Read_Masked_Byte() == Failure)
                  {
                    Action = S_Send_NAK;
                  }
                else if (Checksum != Ch)
                  {
                    Action = S_Send_NAK;
                  }
                else if (RCV_Num != (Next_Seq + 1) % 10)
                  {
                    Action = S_Send_NAK;
                  }
                else
                  {
                    /**
                    * Assume the packet is failure packet. It makes no
                    * difference since any other type of packet would be
                    * invalid anyway. Return failure to caller.
                    **/
                    Errors = Max_Errors;
                  }
                break;
              }
            case S_Timed_Out:
              {
                Errors++;
                dataretrys++;
                Action = S_Get_DLE;
                break;
              }
            case S_Send_NAK:
              {
                Errors++;
                dataretrys++;
                Send_Byte(NAK);
                Action = S_Get_DLE;
                break;
              }
          }
      }
    toterm("");
    toterm("Too many Errors!");
    toterm("");
    return Failure;
  }



static Send_Failure(Code)
/**
 * Function:
 *   Send a failure packet to the host.
 *
 * Inputs:
 *   Code -- failure code
 *
 * Outputs:
 *
 * Returns:
 **/
char Code;
  {
    S_Buffer[0] = 'F';
    S_Buffer[1] = Code;
    Send_Packet(2);
    return(0);
  }



int Receive_File(Name)
/**
 * Function:
 *   Download the specified file from the host.
 *
 * Inputs:
 *   Name -- ptr to the file name string
 *
 * Outputs:
 *
 * Returns:
 *   success/failure
 **/
char *Name;
  {
    int Data_File;
    if ((Data_File = creat(Name, O_RAW)) == -1)
      {
        Put_Msg("Cannot create file");
        Send_Failure('E');
        return Failure;
      }
    Send_ACK();
    for (;;)
      {
        if (Read_Packet(R_Get_DLE) == Success)
          {
            switch (R_Buffer[0])
              {
                case 'N':      /* Data packet */
                  {
                    Send_ACK();
                    if (write(Data_File, &R_Buffer[1], R_Size - 1) != R_Size - 1)
                      {
                        /* Disk write error */
                        Put_Msg("Disk write error");
                        Send_Failure('E');
                        close(Data_File);
                        return Failure;
                      }
                    if (Wants_To_Abort())
                      {
                        /* The user wants to kill the transfer */
                        Send_Failure('A');
                        close(Data_File);
                        return Failure;
                      }
                    break;
                  }
                case 'T':      /* Transfer packet */
                  {
                    if (R_Buffer[1] == 'C') /* Close file */
                      {
                        Send_ACK();
                        close(Data_File);
                        return Success;
                      }
                    else
                      {
                        /**
                         * Unexpected "T" packet. Something is rotten on the
                         * other end. Send a failure packet to kill the
                         * transfer cleanly.
                         **/
                        Put_Msg("Unexpected packet type");
                        Send_Failure('E');
                        close(Data_File);
                        return Failure;
                      }
                  }
                case 'F':      /* Failure packet */
                  {
                    Send_ACK();
                    close(Data_File);
                    return Failure;
                  }
                default:
                  {
                    return Failure;
                  }
              }
          }
        else
          {
            close(Data_File);
            return Failure;
          }
      }
    return Failure;
  }



int Send_File(Name)
/**
 * Function:
 *   Send the specified file to the host.
 *
 * Inputs:
 *   Name -- ptr to the file name string
 *
 * Outputs:
 *
 * Returns:
 *   success/failure
 **/
char *Name;
  {
    int Data_File;
    short N;
    if ((Data_File = open(Name, O_RDONLY | O_RAW)) == -1)
      {
        Put_Msg("Cannot access that file");
        Send_Failure('E');
        return Failure;
      }
    do
      {
        S_Buffer[0] = 'N';
        N = read(Data_File, &S_Buffer[1], Packet_Size - 1);
        if (N > 0)
          {
            if (Send_Packet(N + 1) == Failure)
              {
                close(Data_File);
                return Failure;
              }
            if (Wants_To_Abort())
              {
                Send_Failure('A');
                close(Data_File);
                return Failure;
              }
            datablocks++;
          }
      } while (N > 0);
    if (N == 0)            /* end of file */
      {
        close(Data_File);
        S_Buffer[0] = 'T';
        S_Buffer[1] = 'C';
        return Send_Packet(2);
      }
    else
      {
        Put_Msg("Disk read error");
        Send_Failure('E');
        return Failure;
      }
    return Failure;
  }





/*
                               B PROTOCOL SUMMARY



Block Format:

     <DLE><B><blknum+48><command><contents><ETX><checksum>


Defined commands are:

 Remote Memory Reference

     B      Load memory
     G      Go execute at an address

 File Transfer Mode

     TE<file name>            Erase a file
     TR<present><0><new>      Rename a file
     TU<type><file name>      Transfer from Remote to Host
     TD<type><file name>      Transfer from Host to Remote
     N                        Next block of data
     TC                       Close file

          <type> is:
             A - ASCII
             B - Binary
             I - Image

 Failure Blocks

     FA Failure - Abort
     FC Failure - capacity problem
     FE Failure - error during processing of last block
     FN Failure - function not implemented
*/

int Transfer_File()
/**
 * Function:
 *   Transfer a file from/to the macro to/from the host.
 *
 * Inputs:
 *
 * Outputs:
 *
 * Returns:
 *   success/failure
 **/
  {
    short I, N,temp;
    char Name[64];

    XOFF_Flag = False;
    Seq_Num = 0;
    nowindow();
    if (Read_Packet(R_Get_Seq) == Success)
      {
        if (R_Buffer[0] == 'T')      /* transfer packet */
          {
            if (R_Buffer[1] != 'D' && R_Buffer[1] != 'U') /*Check drctchun*/
              {
                Send_Failure('N');              /* not UPLOAD or DOWNLOAD */
                return Failure;
              }
            if (R_Buffer[2] != 'A' && R_Buffer[2] != 'B')/*Check file type*/
              {
                Send_Failure('N');                 /* not ASCII or BINARY */
                return Failure;
              }
            if (R_Size - 3 > 63)              /* Get the file name length */
              {
                N = 63;
              }
            else
              {
                N = R_Size - 3;
              }
            for (I = 0; I < N; I++)    /* Now actually read the file name */
              {
                Name[I] = R_Buffer[I + 3];
              }
            Name[I] = 0;                     /* NULL terminate the string */
            if (R_Buffer[1] == 'U')                 /* Do Upload transfer */
              {
                oursize = findsize(Name);
                temp = showupload(Name);
              }
            else                                  /* Do Download transfer */
              {
                temp = showdownload(Name);
              }
            nowindow();
            return((int)temp);
          }
        else
          {
            Send_Failure('E');                    /* wrong type of packet */
            return Failure;
          }
      }
    else                               /* didn't read packet successfully */
      {
        return Failure;
      }
    return Failure;
  }


