/*
 *	C U 1 . C
 *
 * This file contains the core routines and the AST service 
 * handlers for cux. 
 *
 * In use, the program is functionally similar to Unix cu. The original design 
 * of cux was based on the Decus library program TALK, written by 
 * N. K. Bewtra, and modified by J. Yambor and Bob Turkelson.
 *
 * Besides providing adequate inter-system  communication and file transfer
 * capabilities, the program amply illustrates the convenient run-time
 * interface to the RSX-11M Executive's services, in particular AST support.
 *
 * Kindly forward copies of any  modifications, etc... to
 *
 *	Bob Donovan
 * 	Graef-Anhalt-Schloemer & Associates
 *	Milwaukee, WI  53216
 *	414-461-6900  ex 225
 */

#include <stdio.h>
#include "cu.h"

int	$$narg	= 1;		/* turn off csi			*/

/*
 * flags
 */
char	exit_flag = OFF;	/* exit main loop if true	*/
char	pt_overflow = OFF;	/* true if queue over flow	*/
char	tt_overflow = OFF;	/* true if queue over flow	*/
char	suspend = OFF;		/* true if host auto-suspeneded */
char	echo_flag = ON;		/* echo transmission		*/
char	recv_file_open = OFF;	/* on for redirected input	*/
char	cmd_flag = OFF;		/* on if building a command	*/
char	host_off = FALSE;	/* on if host manually suspended*/
int	x_file_open = OFF;	/* true if transfer documet open*/
int	abort_x = OFF;		/* on to abort transmission	*/
int	cmd_sent = OFF;		/* on if command enroute	*/
int	port_data = OFF;	/* on if port_queue receiving	*/
#ifndef GENERIC
char	auto_recv = OFF;	/* on if auto receive from host	*/
char	auto_x = OFF;		/* on if auto transfer to host	*/
#endif
int	halted = OFF;		/* to support xon-xoff protocol */

/*
 * system specific character set
 */
#ifdef	UNIX
char	eofchar = 'D'-0100;	/* EOT char on host system	*/
char	wait_for_me_char = 012;	/* host has record		*/
#endif
#ifdef	VAXVMS
char	eofchar = 'Z'-0100;	/* Control-Z			*/
char	wait_for_me_char = 012; /* host has record		*/
#endif
#ifdef	CYBERNET
char	wait_for_me_char = '?'; /* host requesting record	*/
#endif
char	xon = 'Q'-0100;		/* start character		*/
char	xoff= 'S'-0100;		/* stop character		*/
char 	cmdchar = 'W'-0100;	/* command flag			*/

/*
 * port data structures and parameters. The port buffer is 
 * implemented as a circular queue.
 */
char	pt_queue[QUEUE_SIZE];  	/* character queue		*/
char 	*pt_char;		/* address of port character	*/
char 	*pt_front = &pt_queue[0];/* front of queue		*/
char	*pt_rear = &pt_queue[0];/* rear of queue		*/
char 	*pt_end = &pt_queue[QUEUE_SIZE - 1]; /* end of queue	*/
int	pt_count = QUEUE_SIZE;	/* queue free count		*/
int	devpar[6];		/* for qio			*/

/*
 * tty data structures and parameters. The tty buffer is also
 * maintained as a circular queue.
 */
char	tt_queue[TT_SIZE];  	/* character queue		*/
char	*tt_char;		/* tty character		*/
char	*tt_front = &tt_queue[0];/* front of queue		*/
char	*tt_rear = &tt_queue[0];/* rear of queue		*/
char 	*tt_end = &tt_queue[TT_SIZE - 1]; /* end of queue	*/
int	tt_count = TT_SIZE; 	/* queue free count		*/
int	ttypar[6];		/* for tty qio's		*/

/*
 * file stuff and buffers
 */
char	cmd_buf[80];		/* general purpose buffer */
char	x_buff[BUFF_SIZE + 1];	/* transmit file buffer		*/
char	buff[BUFF_SIZE + 1];	/* receive file buffer		*/
char	*buff_ptr = &buff[0]; 	/* buff pointer			*/
char	*buff_end = &buff[BUFF_SIZE]; /* end of buffer address	*/
FILE	*rcvfd;			/* receive file iov		*/
FILE	*xfd;			/* transmit file iov		*/

/*
 * diagnostics
 */
char	*mesg1 = "\rcux: port queue overflow - character lost\n";
char	*mesg2 = "\rcux: terminal queue overflow - character lost\n";


main ()
/*
 * Attach devices, initiate communication,
 * and cleanup.
 */
{	
	/*
	 * verbosity
	 */
#ifdef	UNIX
	printf("cux:  UNIX version\n");
#endif
#ifdef	VAXVMS
	printf("cux:  VAX/VMS version\n");
#endif
#ifdef  GENERIC
#ifdef	CYBERNET
	printf("cux:  Cybernet NOS version\n");
#else
	printf("cux:  Generic version\n");
#endif
#endif
	attach_ports();		/* assign and attach tty and host ports */
	printf("ready\n");	/* verbosity */
	control();		/* main loop to manage conversation */
	detach_ports();		/* detach devices from program */
	printf("\rdisconnected\n");
}

pt_enqueue ()
/*
 * AST service handler. Trap unsolicited input from the remote system
 * and enqueue it into the port's character buffer, respresented as a 
 * circular queue.
 */
{
	astset();			/* required ast setup routine */
	if (pt_count == 0) 		/* if port full signal overflow */
		pt_overflow = ON;
	else {
		*pt_rear++ = (char) gtdp(0); /* get trapped input */
		if (pt_rear > pt_end)	/* wrap around pointer to rear */
			pt_rear = pt_queue; 
		pt_count--;		/* reduce free count by one */

#ifndef	CYBERNET
		/*
		 * test if port queue is getting full. If so,
		 * suspend host transmission to allow program 
		 * catch up.
		 */
		if (pt_count <= LOW_SIZE){
			put_to_sleep();	/* transmit 'stop' character */
			suspend = ON;	/* set flag */
		}
#endif
	}
	port_data = ON;			/* signal data received	*/
	astx (1);			/* required ast exit routine */
}

tt_enqueue ()
/*
 * AST service handler. Take unsolicited input from tty and 
 * enqueue it into terminal's character buffer, represented
 * as a circular queue.
 */
{
	astset();			/* ast setup routine */
	if (tt_count == 0) 		/* signal queue is full */
		tt_overflow = ON;
	else {
		*tt_rear++ = (char) gtdp(0); /* remove trapped character */
		if (tt_rear > tt_end)
			tt_rear = tt_queue; /* wrap around */
		tt_count--;		/* reduce free count by one */
	}
	astx (1);			/* ast exit routine */
}

pt_dequeue ()
/*
 * dequeue a character from the port attached to the remote system.
 */
{
	pt_char = pt_front++;		/* get character address */
	if (pt_front > pt_end)
		pt_front = pt_queue; 	/* wrap around pointer	*/

	*pt_char = (*pt_char & ~AST_MASK); /* remove parity bit	*/

	pt_count++;			/* step queue free count */
	port_data = OFF;		/* signal port data taken */
}

tt_dequeue ()
/*
 * dequeue a character from terminal queue.
 */
{
	tt_char = tt_front++;
	if (tt_front > tt_end)
		tt_front = tt_queue; 	/* wrap around pointer 	*/

	*tt_char = (*tt_char & ~AST_MASK);  /* keep 7 bit character */

	tt_count++;			/* step queue free count */
}

control()
/*
 * Manage an interactive conversation between two systems.
 * control is an infinite loop that constantly monitors and 
 * maintains the character buffers attached to the modem port 
 * and tty.
 */
{
	for (;;){
	/*
	 * this loop constantly monitors the character queues
	 * maintained by the ast service handlers
	 */
		if (pt_count != QUEUE_SIZE){
			/* 
			 * process port character
			 */
			if (pt_overflow){
				/* 
				 * report queue overflow and reset flag
				 */
				printf("%s", mesg1);
				pt_overflow = OFF;
			}
			else{
				pt_dequeue();	
				pt_process();
			}
		}
		while (tt_count != TT_SIZE){
			/*
			 * process terminal queue until empty
			 */
			if (tt_overflow){
				/* 
				 * report queue overflow and reset flag
				 */
				printf("%s", mesg2);
				tt_overflow = OFF;
			}
			tt_dequeue();
			tt_process();
			if (exit_flag)		/* exit loop */
				goto end_loop;
		}
		/*
		 * continue to process port queue while not empty.
		 */
		if (pt_count != QUEUE_SIZE)
			continue;
#ifndef	CYBERNET
		/*
		 * if host transmission was temporarily suspended due
		 * to low port free count, send xon character to resume
		 * host transmission.
		 */
		if (suspend){
			wake_up();
			suspend = OFF;
		}
		else 
#endif
	    	if (x_file_open && !halted)
			/* 
			 * if a file is open for transmission to host, 
			 * and we haven't been stopped by the remote
			 * system, send the next record. 
			 */
			send_next_record();
	} /* end loop */

	/*
	 * Perform some general cleanup before exiting. 
	 * Process any remaining characters from the port queue and
 	 * close any opened files.
	 */
end_loop:
	if (suspend || host_off)
		wake_up();
	while (pt_count != QUEUE_SIZE){
		/* 
		 * process any remaining port characters
		 */
		pt_dequeue();
		pt_process();
   	} 
	/*
	 * close any opened files
	 */
	if (recv_file_open)
		fclose(rcvfd);
	if (x_file_open)
		fclose(xfd);
}


wake_up()
/*
 * send 'start' character to host to resume transmission
 */
{
	devpar[0] = &xon;
	devpar[1] = 1;
	qio(IO_WAL,LUN,3,NULL,NULL,devpar);
}

put_to_sleep()
/*
 * send x-off character to suspend host transmission
 */
{
	devpar[0] = &xoff;
	devpar[1] = 1;
	qio(IO_WAL,LUN,3,NULL,NULL,devpar);
}



#ifdef	GENERIC
#ifndef	CYBERNET
#define PURELY_GENERIC	1
#endif
#endif

pt_process ()
/*
 * Process characters received from the remote system.
 */
{
	register char ch;

	if ((ch = *pt_char) == 0)		/* ignore NULLs */
		goto end_process;

#ifdef	PROTOCOL
	/*
 	 * the following recognizes xon-xoff protocol from host.
 	 */
	if (ch == xon || ch == xoff){
		halted ^= ON;			/* toggle flag */
		goto end_process;		/* premature exit */
	}
#endif
	/*
	 * echo character to tty
	 */
	if (echo_flag){
		ttypar[0] = pt_char;		/* address of char */
		ttypar[1] = 1;
		qiow(IO_WAL, TTYLUN, 4, 0, 0, ttypar);
	}

	/*
	 * test if a receive file is opened for redirected output
	 * or 'take'.
	 */
	if (recv_file_open){
		/*
		 * ignore DEL, LF, and CR when dumping input to file.
		 */
		if (ch != 0177 && ch != '\n' && ch != '\r')
			*buff_ptr++ = ch;	/* put to file buffer	*/
		/*
		 * write buffer when <CR> received or when buffer becomes
		 * full.
		 */
		if (ch == '\r' || buff_ptr > buff_end) {
			*buff_ptr = '\0';	/* append null		*/
			fputss (buff, rcvfd);	/* write buffer		*/
			buff_ptr = buff;	/* reset buff pointer	*/
		}
#ifndef	GENERIC
		/* 
		 * if auto-receive and port queue is empty, assume
		 * eof.
		 */
	      if (auto_recv && pt_count == QUEUE_SIZE && !suspend && !host_off)
			end_receive();
#endif
	}

#ifndef	PURELY_GENERIC
	/*
	 * To prevent a locally initiated command from being
	 * written (echoed) to the receive file, or to allow the host 
	 * time to 
	 * set up to receive a document, wait until the command has been
	 * sent back before signaling to program that the receive or
	 * transmit file is open.
	 */
	else if (cmd_sent && ch == wait_for_me_char){
		cmd_sent = OFF;
#ifdef CYBERNET
		x_file_open = ON;		/* send next record to NOS */
#else
		if (auto_recv){			/* 'take' in effect */
			recv_file_open = ON;	/* signal file open */
			while (!port_data);	/* wait for port data	*/
		}
		else if (auto_x){		/* 'put' in effect */
			x_file_open = ON;	/* signal local file open */
			sleep(2);		/* sleep a second or two */
		}
#endif
	}
#endif
end_process: ;
}


#ifdef HELP
static 	char *cuhelp =
"\r\n\n        cux - communications utility\r\n\n\
     .			exit cux\r\n\
     %take from [to]	copy file 'from' (on the remote system) to\r\n\
			file 'to' on the local system.\r\n\
     %put from [to]	copy file 'from' (on local system) to file 'to'\r\n\
			on remote system.\r\n\
     a  		abort document transmission\r\n\
     >[>][:]file 	redirect host output to the named file.\r\n\
     >  		terminate ouptut redirection.\r\n\
     <file 		read from the named file, as though typed at ter-\r\n\
			minal.\r\n\
     x  		toggle host transmission (xoff - xon)\r\n\
     h			display this same help text\r\n\n";
#endif
#ifdef	CYBERNET
static char *erase = "\b \b";
#endif

tt_process ()
/*
 * Process the character taken from tty. Filter out commands to the
 * program. Everything else goes out the other port.
 */
{
	if (cmd_flag) 			/* get command character */
		cmd_process();		/* ...and process it */
	else if (*tt_char == cmdchar)	/* enter command mode	*/
		cmd_flag = ON;		/* set flag */
	else {				/* transmit character to host	*/
#ifdef	CYBERNET
		if (*tt_char == 0177){	/* map DEL into backspace */
			ttypar[0] = erase; /* Simulate erase */
			ttypar[1] = 3;
			qiow(IO_WAL, TTYLUN, 4, 0, 0, ttypar);
			*tt_char = '\b'; /* map char */
		}
		else {			/* provide full duplex support */
			ttypar[0] = tt_char;
			ttypar[1] = 1;
			qiow(IO_WAL, TTYLUN, 4, 0, 0, ttypar);
		}			
#endif
		devpar[0] = tt_char;
		devpar[1] = 1;
		qio(IO_WAL, LUN, 3, 0, 0, devpar);
	}
}


cmd_process()
/*
 * Process a command character taken from tty following ^W
 */
{
	register char	ch;		/* local character storage */

	cmd_flag = OFF;			/* reset flag */

	ch = (char) tolower(*tt_char);	/* process command character */

	if (ch == 'Z'-0100 || ch == '.') /* exit program */
		exit_flag = ON;		/* ^Z wants exit */
	else if (ch == 'e') 
		echo_flag ^= ON;	/* toggle local echo */
	else if (ch == 'x') {		/* send xoff <-> on */
		if (host_off)
			wake_up();	/* send x-on to host */
		else put_to_sleep();	/* send x-off */
		host_off ^= ON;		/* toggle flag */
	}
	else if (ch == '<' && !x_file_open)
		dvt_from();		/* divert input */
	else if (ch == '>' && (pt_count == QUEUE_SIZE || host_off))
		divert_to();		/* redirect output */
#ifdef	HELP
	else if (ch == 'h' && (pt_count == QUEUE_SIZE || host_off))
		printf("%s", cuhelp);
#endif
	else if (ch == 'a')
		abort_x = ON;
#ifndef	GENERIC
	else if (ch == '%' && pt_count == QUEUE_SIZE && !host_off)
		take_or_put();
#endif
	else if (ch == '!' && (pt_count == QUEUE_SIZE || host_off))
		/*
		 * escape to local shell
		 */
		escape();
	else if (ch == cmdchar){	/* send command character */
		devpar[0] = tt_char;
		devpar[1] = 1;
		qio(IO_WAL, LUN, 3, 0, 0, devpar);
	}
}


#ifndef	GENERIC

send_cmd ()
/*
 * transmit the given command string to the host
 *
 */
{
	printf("\r");			/* line feed to clear line	*/
	devpar[0] = cmd_buf;		/* character address		*/
	devpar[1] = strlen(cmd_buf);	/* string length		*/
	qiow(IO_WAL,LUN,3,NULL,NULL,devpar);
}
#endif


send_next_record()
/*
 * Read the next record from the opened transmit file and
 * send it to the host. If eof, close file and reset flags.
 */
{
	register int	recsiz;			/* record size		*/
	register char	*s;
#ifdef	CYBERNET
	register char	ch;
#endif

	if (!abort_x && (s = fgets(x_buff, BUFF_SIZE, xfd)) != NULL){
		/*
		 * Transmit local record to host.
		 */
		recsiz = strlen(s);
		*(s + recsiz -1) = '\r';	/* map newline into <CR>*/
#ifdef	CYBERNET
		/*
		 * The NOS cybernet system runs half duplex and has no 
		 * typeahead buffering.
		 * After each record is sent, we set the 'cmd_sent' flag.
		 * This forces us to wait for xedit's '?' prompt
		 * before sending the next record. 
		 */
 		while (*s != '\0'){
			/*
			 * transmit a single character at a time.
			 */
			devpar[0] = s++;
			devpar[1] = 1;
			qiow(IO_WAL, LUN, 3, NULL, NULL, devpar);
		}
		ch = getc(xfd);			/* test for EOF */
		if (feof(xfd))
			x_file_open = ON;	/* catch EOF on next pass */
		else{
			ungetc(ch, xfd);	/* return char to stream */
			cmd_sent = ON;		/* force wait for '?' prompt */
			x_file_open = OFF;	/* before sending next record*/
		}
#else
		devpar[0] = s;			/* address of gotten line */
		devpar[1] = recsiz;		/* length */
		qiow(IO_WAL, LUN, 3, NULL, NULL, devpar);
#endif
	}
	else {					/* eof or error		*/
		/*
	 	 * Either eof or 'abort' requested. 
 		 * Close file and reset flags
		 */
		fclose (xfd);
		x_file_open = OFF;
#ifdef	CYBERNET
		if (!echo_flag)
			printf("\rcux: file transfer complete\n");
#endif
		echo_flag = ON;
		abort_x = OFF;
#ifndef	GENERIC
		/*
	 	 * if auto transfer of local file to remote system,
		 * transmit eof character to close remote document.
		 */
		if (auto_x) {
			devpar[0] = &eofchar;
			devpar[1] = 1;
			qio(IO_WAL, LUN, 3, 0, 0, devpar);
			auto_x = OFF;
		}
#endif
	}
}


#ifndef	GENERIC

end_receive ()
/*
 * Terminate auto-receive diversion of host file.
 */
{
	if (recv_file_open) {
		fclose (rcvfd);		/* close input file	*/
		recv_file_open = OFF;	/* reset flag		*/
		auto_recv = OFF;	/* end of auto-transfer	*/
		echo_flag = ON;		/* in case it was off	*/
		/*
 		 * print any partially filled file buffer 
		 * to terminal. It should be the remote 
		 * host monitor's prompt.
		 */
		if (buff_ptr != buff) {
			buff_ptr = '\0';
			ttypar[0] = buff;
			ttypar[1] = (buff_ptr - buff);
			qiow(IO_WAL,TTYLUN,4,NULL,NULL,ttypar);
		}
	}
}
#endif
