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

#include "\lc\stdio.h"
#include "\lc\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 True		1
#define False		0
#define Success		-1
#define Failure		0
#define Packet_Size	512
#define Max_Errors	10
#define Max_Time	10
#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

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

static 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');
}

static Send_Byte(Ch)
    int Ch;
{
    int 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));
}

static Send_Masked_Byte(Ch)
    int Ch;
{
    /* Mask any protocol or flow characters */

    if (Ch == ETX || Ch == ENQ || Ch == DLE || Ch == NAK || Ch == XON || Ch == XOFF)
	{
	Send_Byte(DLE);
	Send_Byte(Ch + '@');
	}
    else Send_Byte(Ch);
}

static Send_ACK()
{
    Send_Byte(DLE);
    Send_Byte(Seq_Num + '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;

    if (Ch == DLE)
	{
	if (Read_Byte() == Failure) return Failure;
	Ch &= 0x1F;
	}
    else if (Ch == ETX) Seen_ETX = True;

    return Success;
}


static Do_Checksum(Ch)
    int Ch;
{
    Checksum <<= 1;
    if (Checksum > 255) Checksum = (Checksum & 0xFF) + 1;
    Checksum += Ch;
    if (Checksum > 255) Checksum = (Checksum & 0xFF) + 1;
}

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;
{
    int Errors;
    int Next_Seq;

    Errors = 0;

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

		break;

	    case R_Send_NAK:
		Put_Char('-');
		Errors++;
		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
 **/
    int Size;				/* size of packet to send */
{
    int Action;
    int Next_Seq;
    int RCV_Num;
    int I;
    int Errors;

    Next_Seq = (Seq_Num + 1) % 10;
    Errors = 0;
    Action = S_Send_Packet;

    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++;
		    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(5000);	/* 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++;
		Action = S_Get_DLE;
		break;

	    case S_Send_NAK:
		Put_Char('-');
		Errors++;
		Send_Byte(NAK);
		Action = S_Get_DLE;
		break;
	    }

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

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

		    Send_ACK();
		    Put_Char('+');
		    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;
		}
	else
	    {
	    close(Data_File);
	    return Failure;
	    }
}

static 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;
    int 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;
		}

	    Put_Char('+');
	    }
	}
    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;
	}
}

int Transfer_File()
/**
 * Function:
 *	Transfer a file from/to the macro to/from the host.
 *
 * Inputs:
 *
 * Outputs:
 *
 * Returns:
 *	success/failure
 **/
{
    int I, N;
    char Name[64];

    XOFF_Flag = False;
    Seq_Num = 0;

    if (Read_Packet(R_Get_Seq) == Success)
	{
	if (R_Buffer[0] == 'T')		/* transfer packet */
	    {
	    /* Check the direction */

	    if (R_Buffer[1] != 'D' && R_Buffer[1] != 'U')
		{
		Send_Failure('N');	/* not implemented */
		return Failure;
		}

	    /* Check the file type */

	    if (R_Buffer[2] != 'A' && R_Buffer[2] != 'B')
		{
		Send_Failure('N');
		return Failure;
		}

	    /* Collect the file name */

	    if (R_Size - 3 > 63) N = 63;
	    else N = R_Size - 3;

	    for (I = 0; I < N; I++)
		Name[I] = R_Buffer[I + 3];

	    Name[I] = 0;

	    /* Do the transfer */

	    if (R_Buffer[1] == 'U') return Send_File(Name);
	    else return Receive_File(Name);
	    }
	else
	    {
	    Send_Failure('E');		/* wrong type of packet */
	    return Failure;
	    }
	}
    else return Failure;
}
