/*************************************************************************
**   File:  bprot.c
**      contains routines to perform CompuServe's B-protocol file transfers.
**	
**	Code originally downloaded from Compuserve -- See following
**	'original' remarks.  Modified heavily by Bob Rakosky to fit in
**	with the rest of TermPlus -- June, 1986
**
**   Functions:
**	Send_Byte() - send character to remote, honoring XON/XOFF from host
**	Send_Masked_Byte() - send character to remote, translating control
**		characters to DLE sequences
**	Send_ACK() - send positive response to remote, including current
**		sequence number
**	Read_Byte() - timed read of single character from remote
**	Read_Masked_Byte() - read of character from remote, translating DLE
**		sequence to original control character
**	Do_Checksum() = calculate/update checksum based on single character,
**		conforming with B-protocol algorithm
**	Read_Packet() - receive a packet from remote host
**	Send_Packet() - send a packet to remote host
**	Send_Failure() - send a Failure packet to remote host
**	Receive_File() - do download of file from remote host
**	Send_File() - do upload of file to remote host
**	B_prot_xfer() - do file transfer (either way, as directed by host)
**
*************************************************************************/
#include "term.h"
/**
 * BP.C
 * 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:
 *
 *	Steve Wilhite, 17-Jan-86
 *	- included a virtual file interface.
 **/

/* External Functions */

extern int emit();		/* Write a character to the display */
extern int start_timer();	/* Enable the timer for the specified number
    				   seconds */
extern int test_timer();	/* Returns "true" if the timer has expired,
    				   "false" otherwise */
extern int test_kbd_abort();	/* Returns "true" if the user wants to abort
    				   the file transfer, "false" otherwise */
extern int sendchar();		/* Send a character to the comm port. Returns
    				   "true" is successful, "false" otherwise */
extern int timeout;
extern char rs_in[];
extern struct IOExtSer *Read_Request;
extern struct timerequest *timer;

#define NUL	0x00
#define ETX	0x03
#define XON	0x11
#define XOFF	0x13

#define True		1
#define False		0
#define Success		-1
#define Failure		0
#define Packet_Size	512
#define Max_Errors	5
#define Max_Time	10
#define Max_Xoff_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
#define S_Send_ACK	8

/* 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,
	Seen_ENQ;

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



static Send_Byte(Ch)
int Ch;
{
	int TCh;

	/* Listen for XOFF from the network */

	start_timer(Max_Xoff_Time);
	do
	   {
		while ((TCh = cond_read()) >= 0)
			if (TCh == XON)
				XOFF_Flag = False;
			else if (TCh == XOFF)
			{
				XOFF_Flag = True;
				kill_timer();
				start_timer(Max_Xoff_Time);
			}
	}
	while (XOFF_Flag && !test_timer());
	if (!timeout) kill_timer();

	sendchar(Ch);
}


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

	if (Ch == NUL || 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 = cond_read()) < 0)
	{
		start_timer(Max_Time);

		do
		   {
			if (test_timer()) {
				return Failure;
			}
		}
		while ((Ch = cond_read()) < 0);
		kill_timer();
	}

	return Success;
}


static Read_Masked_Byte()
{
	Seen_ETX = False;
	Seen_ENQ = False;

	if (Read_Byte() == Failure)
		return Failure;

	if (Ch == DLE)
	{
		if (Read_Byte() == Failure)
			return Failure;

		Ch -= '@';
	}
	else if (Ch == ETX)
		Seen_ETX = True;
	else if (Ch == ENQ)
		Seen_ENQ = True;

	return Success;
}


static Do_Checksum(Ch)
int Ch;
{
	Checksum <<= 1;

	if (Checksum > 255)
		Checksum = (Checksum & 0xFF) + 1;

	Checksum += Ch & 0xFF;

	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,
		Next_Seq;

	Errors = 0;

	while (Errors < Max_Errors)
		switch (Action)
		{
		case R_Get_DLE:
			if (Read_Byte() == Failure) {
				emits("Timeout-get_dle\n");
				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) {
				emits("Timeout-get_B\n");
				Action = R_Send_NAK;
			}
			else if (Ch == 'B')
				Action = R_Get_Seq;
			else {
				emits("Not xfer packet\n");
			   Action = R_Get_DLE;
			}

			break;

		case R_Get_Seq:
			if (Read_Byte() == Failure) {
				emits("Timeout-get_seq\n");
				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) {
				emits("Timeout-get_data\n");
				Action = R_Send_NAK;
			}
			else if (Seen_ETX)
				Action = R_Get_Checksum;
			else if (Seen_ENQ)
				Action = R_Send_ACK;
			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) {
				emits("Timeout-get_checksum\n");
				Action = R_Send_NAK;
			}
			else if (Checksum != Ch) {
				emits("--Checksum Error\n");
				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) {
				emits("--Sequence error\n");
				Action = R_Send_NAK;
			}
			else
			   {
				Seq_Num = Next_Seq;
				return Success;
			}

			break;

		case R_Send_NAK:
			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;
{
	int	Action,
		Next_Seq,
		RCV_Num,
		I,
		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 == ENQ)
				Action = S_Send_ACK;
			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(5);	/* 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 (Seen_ENQ)
				Action = S_Send_ACK;
			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:
			Errors++;
			Send_Byte(NAK);
			Action = S_Get_DLE;
			break;

		case S_Send_ACK:
			Send_ACK();
			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;			/* file descriptor */
	char line[80];
	int Real_Seq = 0;

	if ((Data_File = creat(Name, 0)) == -1)
	{
		emits("Cannot create file\n");
		Send_Failure('E');
		close_time();
		return Failure;
	}

	sprintf(line,"\nReceiving File: %s\n\n",Name);
	emits(line);
	
	Send_ACK();

	for (;;) {
		sprintf(line,"\rReceiving Packet %d   ",Real_Seq);
		emits(line);
		if (Read_Packet(R_Get_DLE) == Success) {
			Real_Seq++;
			switch (R_Buffer[0])
			{
			case 'N':		/* Data packet */

				if (write(Data_File, &R_Buffer[1], R_Size - 1) != R_Size - 1)
				{
					/* Disk write error */

					emits("Disk write error\n");
					Send_Failure('E');
					close(Data_File);
					close_time();
					return Failure;
				}

				if (test_kbd_abort())
				{
					/* The user wants to kill the transfer */

					Send_Failure('A');
					close(Data_File);
					close_time();
					return Failure;
				}

				Send_ACK();
				emit('+');
				break;

			case 'T':		/* Transfer packet */

				if (R_Buffer[1] == 'C') /* Close file */
				{
					Send_ACK();
					close(Data_File);
					close_time();
					return Success;
				}
				else
				   {
					/**
			 * Unexpected "T" packet. Something is rotten on the
								 * other end. Send a failure packet to kill the
								 * transfer cleanly.
								 **/

					emits("Unexpected packet type\n");
					Send_Failure('E');
					close(Data_File);
					close_time();
					return Failure;
				}

			case 'F':		/* Failure packet */
				Send_ACK();
				close(Data_File);
				close_time();
				return Failure;
			}
		}
		else
		   {
			Send_Failure('E');
			close(Data_File);
			close_time();
			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 N;
	int Data_File;			/* file descriptor */
	char line[80];
	int Real_Seq = 0;

	if ((Data_File = open(Name,O_RDONLY)) == -1)
	{
		emits("Cannot access that file\n");
		Send_Failure('E');
		close_time();
		return Failure;
	}

	sprintf(line,"\nSending File: %s\n\n",Name);
	emits(line);
	
	do
	   {
		S_Buffer[0] = 'N';
		N = read(Data_File, &S_Buffer[1], Packet_Size - 1);

		if (N > 0)
		{
			sprintf(line,"\rSending Packet %d   ",Real_Seq);
			emits(line);
			if (Send_Packet(N + 1) == Failure)
			{
				close(Data_File);
				close_time();
				return Failure;
			}
			
			Real_Seq++;

			if (test_kbd_abort())
			{
				Send_Failure('A');
				close(Data_File);
				close_time();
				return Failure;
			}
		}
	}
	while (N > 0);

	if (N == 0)				/* end of file */
	{
		close(Data_File);
		S_Buffer[0] = 'T';
		S_Buffer[1] = 'C';
		N = Send_Packet(2);
		close_time();
		emits("\n\nFile Sent\n\n");
		return(N); 
	}
	else
	   {
		emits("Disk read error\n");
		Send_Failure('E');
		close_time();
		return Failure;
	}
}

int B_prot_xfer()
/**
 * Function:
 *	Transfer a file from/to the micro to/from the host.
 *
 * Inputs:
 *
 * Outputs:
 *
 * Returns:
 *	success/failure
 **/
{
	int I, N;
	char Name[64];			/* holds the file name */
	if (!open_time()) {	/* open timer device */
		return Failure;
	}

	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 */
				close_time();
				return Failure;
			}

			/* Check the file type */

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

			/* Collect the file name */

			N = R_Size - 3 > 63 ? 63 : 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 */
			close_time();
			return Failure;
		}
	}
	else {
	   return Failure;
	}
}

