#include "fidoterm.h"
#include "mem.h"
#include <xfbuf.h>
#include <filexfer.h>
#include <ctype.h>
#include <time.h>

long sec2dos();

/* This is the default BERT pattern; the \377's are representations of NULs. */

#define FOX_NAME "FOX"

#ifdef TELECOM_CANADA
static char fox_pattern[]= "\02\07\r\n\377THE QUICK BROWN FOX JUMPS OVER THE "
	"LAZY DOG *****U1234567890 \377\377\377\377TELECOM CANADA TESTS!"
	"\03\07\377";
/* stx bell cr lf null THE sp QUICK sp BROWN sp FOX sp JUMPS sp OVER
 sp THE sp LAZY sp DOG sp *****U1234567890 sp null null null null
 TELECOM sp CANADA sp TESTS! etx bell null */

#else

static char fox_pattern[]= "\02\07\r\n\377THE QUICK BROWN FOX JUMPS OVER THE "
	"LAZY DOG *****U1234567890 \377\377\377\377"
	"\03\07\377";
/* stx bell cr lf null THE sp QUICK sp BROWN sp FOX sp JUMPS sp OVER
 sp THE sp LAZY sp DOG sp *****U1234567890 sp null null null null
 etx bell null */

#endif

static WINDOW box = {1,15,3,74,A_NORMAL,W_DOUBLEBOX | W_FULLBOX,
    "\r"
    "º         Pattern:                              Time:\r"
    "º            Mode:                           Elapsed:\r"
    "º           Speed:                          Duration:\r"
    "º                                                 CD:\r"
    "º\r"
    "ÌÍÍÍÍÍÍÍÍÍÍ Send ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ Receive ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ¹\r"
    "º      Total bits:            (NA)        Total bits:             (NA)\r"
    "º       Bytes/sec:            (NA)         Bytes/sec:             (NA)\r",
    "º Injected errors:            (NA)        Error bits:             (NA)\r"
    "º                                         Error rate:             (NA)\r"
    "º                                       Errored time:             (NA)\r"
    "º                                            Resyncs:             (NA)\r"
    "º                                             ERR BL:             (NA)\r"
};

#define PATTERN 1,19			/* pattern name */
#define MODE 2,19			/* BERT mode */
#define SPEED 3,19

#define SENDBITS 7,19			/* sent bits */
#define SBYTESEC 8,19			/* sent bytes/sec */
#define SENDERR 9,19			/* sent bad bits */

#define TIME 1,63			/* current time */
#define ELAPSED 2,59			/* elapsed time */
#define DURATION 3,59			/* time remaining */
#define CARRIER 4,68			/* carrier detect status */

#define RECVBITS 7,53			/* received bits */
#define RBYTESEC 8,56
#define RECVERR 9,56			/* receive errors */
#define RECVBER 10,57			/* receive bit error rate */
#define RECVFREE 11,62			/* receive error-free time */
#define RESYNCS 12,56			/* resyncs */
#define BLER 13,56			/* block error rate */

/* Characters counts are kept as dual counters; xxlo runs 0 - 999,999 and
xxhi counts millions. (Fast, accurate to 4,294,967,999,999, and simplifies 
display and BER calcs.) 

The normalization/rounding kludge only works when the scaler is the
square root of the counter modulo. */

#define MILLION (1000000L)		/* one million */
#define SCALER (1000L)			/* sqrt(MILLION) */
#define EXP (3)				/* exponent of SCALER */
#define MAX_MILLIONS (4294967L)		/* how many millions we can count */


#define INR_IN if (++inlo >= MILLION) {inlo= 0L; ++inhi;}
#define INR_OUT if (++outlo >= MILLION) {outlo= 0L; ++outhi;}

/* We will assume that there won't be > 4B errors per test... */

static long inlo,inhi,outlo,outhi;	/* char counts in and out */
static long in_errors;			/* recv bit errors */
static long inj_errors;			/* send bit errors */
static long block_errors;		/* errored blocks */
static time_t start_time = 0L;		/* xfer start time & flag */
static long elapsed_time;		/* elapsed time since start of test */

/* Errored time is used to calc error-free time; each second that has
a bit error is "errored" and subtracted from the error-free time. (multiple
bad bits within the same second count as only one errored second.) */

static long errored_second;		/* mS counter */
static long errored_secs;		/* time of last bit error */

/* These are used to generate the display and log file. Some of these are
formatted text, to avoid doing it twice. */

static int last_type;			/* type of test */
static int last_rate;			/* baud rate */
static char last_time[20];		/* curent time */
static char last_elapsed[20];		/* elapsed time */
static char last_send_bits[20];		/* send bit count */
static char last_recv_bits[20];		/* recv bit count */
static char last_in_errors[20];		/* recv errors */
static char last_ber[20];		/* bit error rate */
static char last_errored_secs[20];	/* errored seconds */
static char last_inj_errors[20];	/* recv errors */
static char last_recv_bytes_sec[20];	/* receive bytes/sec */
static char last_send_bytes_sec[20];	/* send bytes/sec */
static char last_block_errors[20];	/* block errors */
static char last_resyncs[20];		/* resyncs */

static long duration;			/* time, in seconds, to run BERT */

static char pattern_name[16];		/* name of current pattern */
static char pattern[256];		/* where the pattern goes */
static char *inpatt;			/* ptr into the pattern */
static char *outpatt;			/* pattern output pointer */
static char in_sync;			/* BER receive sync state machine: 
					   0 == not in sync yet (initial or
					   fell out of sync due to errors);
					   1 == in sync and testing;
					   > 1 == counting sequential bad bytes,
					   >= RESYNC_COUNT force resync (too
					   many errors) */

static char control_x = 0;		/* count of manually entered Control-X's */
static int logfile = -1;		/* BERT log file */
static long loginterval = 60L;		/* log interval, in seconds */
static int logsecs = 0;			/* secs since last log */
static long resyncs = 0;		/* number of resyncs */
static char bit_error;			/* flagged error in block */
static char carrier_lost;		/* 1 == carrier detect disappeared */

#define RESYNC_COUNT 8			/* bad bytes in a row to force resync */
#define CONTROLX_COUNT 4		/* ZMODEM-like abort sequence */
#define HUMAN_TIME (2000)		/* keyboard/screen/log attention time */

/* type: 
	1 == send 
	2 == receive
	3 == bidirectional
	4 == loopback
*/

#define SEND 1
#define RECV 2
#define LOOPBACK 4

bert(type)
int type;
{
int h1;
char *cp;

	get_duration();				/* get time to execute */
	if (! duration) return;

	strcpy(pattern,fox_pattern);		/* assume this as default */
	strcpy(pattern_name,FOX_NAME);

	h1= open("fidoterm.btp",OPEN_RO);	/* see if there's */
	if (h1 != -1) {				/* a pattern file */
		rline(h1,pattern,sizeof(pattern));
		pattern[sizeof(pattern_name)]= NUL; /* truncate to fit */
		strcpy(pattern_name,pattern);	/* copy in name */
		pattern[read(h1,pattern,sizeof(pattern))]= NUL;
		close(h1);
	}
	start_time= time(NULL);
	errored_secs= 0L;			/* no bad time yet */
	h1= set_tick(&errored_second);		/* set errored-second counter */
	in_errors= inj_errors= 0L;
	inlo= inhi= outlo= outhi= 0L;
	last_type= type;
	last_rate= rate;
	draw_box();				/* draw the initial box */

	outpatt= pattern;			/* output is easy */
	inpatt= pattern;
	bglockout= 1;				/* prevent background polling */
	in_sync= 0;				/* not in sync yet */
	control_x= 0;				/* clear abort counter */
	resyncs= 0L;				/* no resyncs yet */
	block_errors= 0L;			/* no recv block errors */
	bit_error= 0;
	errored_second= 0L;			/* reset errored-sec window */
	carrier_lost= cd(cd_bit);		/* terminate if CD false */

/* These next three initialize display and calc data for the display and
logging. */

	calc_time();				/* start clock, etc */
	calc_recv();				/* init recv strings */
	calc_send();				/* ditto send */
	logstart();				/* possibly start logging */

/* NOTE: for speed, we treat 'millisec' as a short, not a long, since
we only need 2000 mS max anyways. Clear it here once. */

	millisec= 0L + HUMAN_TIME;		/* first time through */

	switch (type) {
		case RECV: recv_only(); break;
		case SEND: send_only(); break;
		case SEND+RECV: bidir(); break;
		case LOOPBACK: loopback(); break;
	}
	logend();
	end_bert();				/* terminate session */
	flush(0);				/* flush garbage */
	clr_tick(h1);
	bglockout= 0;
}

/* Inform the remote that a BERT session is about to start (give specifics.) */

static sendstart(s)
char *s;
{
char buff[40];

	mputs("\025\r\nFidoTerm Auto-BERT sequence:\r\n"); /* standard text */
	sprintf(buff,"%s %lu:%02ld %ld\r\n",
	    s,					/* test type, */
	    duration / 60L, duration % 60L, 	/* duration, min:sec */
	    loginterval / 60L);			/* log interval, minutes */
	mputs(buff);
}

/* BERT: Receive Only. */

static recv_only() {
int i;
char *cp;

	cp= "BERT in progress: \004ESC\005 key to interrupt";
	msg(cp);
	sendstart("R");					/* inform remote */
	while (1) {
		if (carrier_lost= !cd(cd_bit)) break;	/* stop if CD false */

		for (i= 1000; i-- && _mconstat();) {
			if (recv_char(_mconin()))	/* process input */
				INR_IN;			/* count chars */
		}
		if ((int) millisec >= HUMAN_TIME) {	/* kbd/display/log */
			calc_time();			/* time & elapsed time */
			calc_recv();			/* recv statistics */
			update_recv();			/* recv display */
			if ((logsecs += (HUMAN_TIME / 1000L)) >= loginterval) {
				logdata("=");		/* record log info */
				logsecs= 0;
			}
			if (control_x >= CONTROLX_COUNT) break;
			if (keydisp(cp) == -1) break;	/* manual intervention */
			(int) millisec= 0;		/* reset check timer */
			control_x= 0;			/* reset the counter */
		}
	}
	calc_time();				/* for final display */
	calc_recv();
	logdata("-");
	update_recv();
}

/* BERT: Send Only */

static send_only() {
int c,i;
char *cp;

	cp= "BERT in progress: \004ESC\005 interrupt, \004I\005nject bit error";
	msg(cp);
	sendstart("T");					/* inform remote */

	while (1) {
		if (carrier_lost= !cd(cd_bit)) break;	/* stop if CD false */

		while (_mconstat()) {		/* Control-X counting */
			if (_mconin() == CAN) ++control_x;
		}
		for (i= 23; i--;) {			/* output the pattern */
			if ((c= *outpatt++) == '\377') _mconout(NUL);
			else _mconout(c);
			if (! *outpatt) outpatt= pattern;
			INR_OUT;
		}
		if ((int) millisec >= HUMAN_TIME) {/* keyboard and display */
			calc_time();
			calc_send();
			update_send();
			if ((logsecs += (HUMAN_TIME / 1000L)) >= loginterval) {
				logdata("=");
				logsecs= 0;
			}
			if (control_x >= CONTROLX_COUNT) break;

/* Check the keyboard for ESCape abort, or to cause a bit error. If the latter,
output the first bytes of the pattern with the bad bit. */

			c= keydisp(cp);
			if (c == -1) break;
			if (c) _mconout(c ^ *outpatt++);

			(int) millisec= 0;
			control_x= 0;
		}
	}
	calc_time();				/* final time */
	calc_send();				/* and send/recv stats */
	logdata("-");
	update_send();
}

/* BERT: Bidirectional. To avoid getting ahead of ourselves too far, read as
many input characters as are available, or 100 max (so we can do control 
and display). */

static bidir() {
int c,i;
char *cp;

	cp= "BERT in progress: \004ESC\005 interrupt, \004I\005nject bit error";
	msg(cp);
	sendstart("B");					/* inform remote */

	while (1) {
		if (carrier_lost= !cd(cd_bit)) break;	/* stop if CD false */

		for (i= 23; i--;) {			/* output the pattern */
			if ((c= *outpatt++) == '\377') _mconout(NUL);
			else _mconout(c);
			if (! *outpatt) outpatt= pattern;
			INR_OUT;
		}
		for (i= 529; i-- && _mconstat(); ) {	/* (23 squared) */
			c= _mconin();
			if (recv_char(c))
				INR_IN;
		}
		if ((int) millisec >= HUMAN_TIME) {	/* keyboard and display */
			calc_time();
			calc_recv(); calc_send();
			update_send(); update_recv();
			if ((logsecs += (HUMAN_TIME / 1000L)) >= loginterval) {
				logdata("=");
				logsecs= 0;
			}
			if (control_x >= CONTROLX_COUNT) break;

/* Check the keyboard for ESCape abort, or to inject a bit error. If the latter,
output the first byte of the pattern with the bad bit. (Let's hope the first 
byte is not \377...) */

			c= keydisp(cp);
			if (c == -1) break;
			if (c) _mconout(c ^ *outpatt++);

			(int) millisec= 0;
			control_x= 0;
		}
	}
	calc_time();
	calc_recv();
	calc_send();
	logdata("-");
	update_send();
	update_recv();
}

/* BERT: Loopback. */

static loopback() {
register c,i;
char *cp;

	cp= "BERT in progress: \004ESC\005 key to interrupt";
	msg(cp);
	sendstart("L");					/* inform remote */

	while (1) {
/*		if (carrier_lost= !cd(cd_bit)) break; */

		for (i= 1000; i-- && _mconstat() && _mconostat();) {
			if ((c= _mconin()) == CAN) ++control_x;
			_mconout(c);		/* echo everything */
			INR_IN; INR_OUT;
		}
		if ((int) millisec >= HUMAN_TIME) {/* keyboard and display */
			(int) millisec= 0;
			calc_time();
			update_loopback();
			if (control_x >= CONTROLX_COUNT) break;
			if (keydisp(cp) == -1) break;
			control_x= 0;
		}
	}
	calc_time();
	update_loopback();
}

/* Process receive characters. This counts Control-X characters for aborting. */

static recv_char(c)
char c;
{
char d;

	if (! c) c= '\377';			/* NULs are \377 in our string */

	d= c;					/* save a copy */
	c ^= *inpatt++;				/* match, diff. in c */
	if (! *inpatt) {
		inpatt= pattern;		/* advance ptr */
		block_errors += bit_error;	/* count errored blocks */
		bit_error= 0;			/* reset error flag */
	}
	if (in_sync) {
		if (! c) return(in_sync= 1);	/* s'OK */

/* Bad bit(s). If it's been > one second since the last error, we have
another "errored second". */

		bit_error= 1;			/* flag (any) error in block */
		if (errored_second > 1000L)	/* if outside window */
			++errored_secs;		/* count bad time */
		errored_second= 0L;

/* Count bad bits. If we get RESYNC_COUNT bad bytes in a row, require a 
resync. */
		if (++in_sync < RESYNC_COUNT) {
			in_errors += bits_in_byte(c);
			return(in_sync);

		} else ++resyncs;		/* count another resync */
	}
	if (! c) return(in_sync= 1);		/* now in sync */

/* Out of sequence; a good time to count Control-X's. */

	if (d == CAN) ++control_x;

	inpatt= pattern;
	return(in_sync= 0);			/* require resync */
}

/* Get current time and calc elapsed time. */

static calc_time() {
long t;
unsigned p;
time_t ltime;

	time(&ltime);				/* get current time */
	p= sec2dos(ltime - timezone);		/* (cvt to int) */
	sprintf(last_time,"%2u:%02u:%02u",
	    p >> 11,(p >> 5) & 0x3f,(p & 0x1f) << 1);

	elapsed_time= ltime - start_time;	/* elapsed time */
	sprintf(last_elapsed,"%9,lu:%02lu",elapsed_time / 60L,elapsed_time % 60L);
}

/* Calculate send stats. */

static calc_send() {
long e,hi,lo;
/**/ long t,x;
/**/ char *cp;

/* Convert byte count to bit count. Multiply by 8 and propagate overflow (which
may be > 1). */

	lo= outlo << 3;
	hi= outhi << 3;				/* count *= 3 */
	hi += lo / MILLION;			/* propagate overflow */
	lo %= MILLION;				/* bound low count */

	sprintf(last_inj_errors,"%15,lu",inj_errors); /* injected errors */

	if (hi >= MAX_MILLIONS) {		/* check for out of range */
		outhi= MAX_MILLIONS;		/* stick it there */
		outlo= 0L;
		strcpy(last_send_bits,">4,294,967,999,999");
		strcpy(last_send_bytes_sec,"***************");
		return;
	}
	if (hi > 0L) sprintf(last_send_bits,"%7;lu,%07;lu",hi,lo);
	else sprintf(last_send_bits,"%15,lu",lo);

/* Bytes/sec is slightly tricky; because of the MILLIONS scaling, it
is possible for elapsed to go to zero after scaling; in this case, we
scale bytes up instead of scaling elapsed_time down. */

	lo= outlo;
	e= elapsed_time;
/**/	if (outhi > 0L) {			/* if millions overflow, */
		lo /= SCALER;			/* scale byte counter */
		lo += outhi * SCALER;
		lo *= SCALER;			/* scale bytes up not time down */
	}
	if (e > 0L) sprintf(last_send_bytes_sec,"%15,lu",(long)(lo / e));
	else strcpy(last_send_bytes_sec,"              0");
}

/* Calculate receive-bits stats for display. */

static calc_recv() {
int n;
long o,bits;			/* total receive bits */
long e;				/* total bit errors */
int exp;			/* exponent */

/* Calc recv bytes/sec first */

	o= inlo;
	e= elapsed_time;

/* Bytes/sec is slightly tricky; because of the MILLIONS scaling, it
is possible for elapsed to go to zero after scaling; in this case, we
scale bytes up instead of scaling elapsed_time down. */

	if (inhi > 0L) {
		o /= SCALER;
		o += outhi * SCALER;
		o *= SCALER;
	}
	if (e > 0L) sprintf(last_recv_bytes_sec,"%15,lu",(long)(o / e));
	else strcpy(last_recv_bytes_sec,"              0");

/* Calc # bits (in bits & e) to the number of receive bits. It is also 
used below in BER calcs. */

	bits= inlo << 3;			/* byte count to bit count */
	o= inhi << 3;				/* times 8 */
	o += bits / MILLION;			/* propagate overflow */
	bits %= MILLION;			/* bound low count */

	sprintf(last_in_errors,"%15,lu",in_errors);
	sprintf(last_block_errors,"%15,lu",block_errors);
	sprintf(last_resyncs,"%15,lu",resyncs);

	sprintf(last_errored_secs,"%6,lu:%02lu",errored_secs / 60L,errored_secs % 60L);

/* If we reach maximum count flag the display. */

	if (o >= MAX_MILLIONS) {		/* out of range! */
		inhi= MAX_MILLIONS;		/* stick it there */
		inlo= 0L;
		strcpy(last_recv_bits,">4,294,967,999,999");
		strcpy(last_ber,"*************"); /* can't calc this */
		return;
	}
	if (o > 0L) sprintf(last_recv_bits,"%10;lu,%07;lu",o,bits);
	else sprintf(last_recv_bits,"%18,lu",bits);

/* Calculate bit error rate, expressed as a negative exponent. The exponent 
should never be greater than 1/E bits (ie. 1E6 bits, BER should display as 
1E-6 or more.)

Bit Error Rate is (errors / bits); for maximum accuracy the dividend (errors)
is normalized with the radix point on the left. Both are done within the
same loop since we only need the result once. */

	exp= 0;
	if (o > 0L) {
		bits /= SCALER; 		/* drop precision */
		bits += (o * SCALER);		/* bits / SCALER */
		exp= EXP;			/* exponent so far */
	}
	if (bits < 100L) goto no_ber;		/* be reasonable */

	e= in_errors * 1000L;			/* prescale for precision */

/* If there are no errors, simply determine the correct exponent for 
displaying "less than 0.01 errors in 10*E(exp) bits". */

	if (e == 0L) {				/* if no errors, */
		while (bits > 1000L) {
			bits /= 10L;
			++exp;			/* find exponent */
		}
no_ber:;	sprintf(last_ber," < 1.00*10E-%02d",++exp);
		return;
	}

/* Errors: calc bit error rate. */

	while ((n= e / bits) < 100) {		/* calc & scale */
		e *= 10L;			/* for 3 digits worth */
		++exp;
	}
	sprintf(last_ber,"%4d.%02d*10E-%02d",n / 100,n % 100,++exp);
}

/* Check the keyboard and update the display. Allow the user to cause
a send bit error. */

static keydisp(m)
char *m;
{
int c;

	switch (c= keyhit()) {
		case NUL: break;		/* no key hit */

		case 'i': case 'I':
			c= 1;			/* cause one bit error */
			++inj_errors;		/* count as output error */
			break;

		default:
			if (c == cmdchar) {	/* manual abort */
				bglockout= 1;	/* prevent background polling */
				calc_time();	/* get current time */
				logthis("  OPERATOR INTERRUPTION START\r\n");
				if (ask("Abort BERT test")) {
					stop_script();
					c= -1;
				}
				bglockout= 0;
				cursoroff();
				msg(m); 	/* restore the message */
				calc_time();	/* get current time */
				logthis("  OPERATOR INTERRUPTION END\r\n");
			}
	}
	if (elapsed_time >= duration) c= -1;	/* check elapsed time */
	return(c);
}

/* File transfer complete -- clean up the screen, etc */

static end_bert() {

	msg("BERT complete -- hit any key to continue");
	cursoroff();
	for (millisec= 0L; millisec < 20000L; ) {
		if (unattended()) break;	/* no sense waiting! */
		if (keyhit()) break;
	}
	removewindow(&box);			/* restore the screen */
	signon();				/* re-arm standard prompt */
}

/* Draw the display box and fill in the basics. */

static draw_box() {
char *cp;

	cursoroff();
	init_window(&box);			/* clear status display */
	w_pos(&box,SPEED);
	w_printf(&box,"%,u",last_rate);		/* baud rate */

	switch (last_type) {
		case SEND: cp= "Send Only"; break;
		case RECV: cp= "Recv Only"; break;
		case SEND+RECV: cp= "Bidirectional"; break;
		case LOOPBACK: cp= "Loopback (no statistics)"; break;
	}
	w_pos(&box,PATTERN);			/* pattern name */
	w_string(&box,pattern_name);

	w_pos(&box,MODE);			/* display mode */
	w_string(&box,cp);

	w_pos(&box,DURATION);			/* display duration (once) */
	w_printf(&box,"%9,lu:%02lu",duration / 60L,duration % 60L);
}

/* Update the send stats. Note the abuse of decent arithmetic. */

static update_send() {

	w_pos(&box,TIME);			/* (time in low 16) */
	w_string(&box,last_time);
	w_pos(&box,ELAPSED);			/* elapsed time */
	w_string(&box,last_elapsed);
	w_pos(&box,SENDERR);
	w_string(&box,last_inj_errors);
	w_pos(&box,SENDBITS);
	w_string(&box,last_send_bits);
	w_pos(&box,SBYTESEC);
	w_string(&box,last_send_bytes_sec);
	w_pos(&box,CARRIER);
	w_string(&box,(carrier_lost ? "Off" : " On"));
}

/* Display recv status. */

static update_recv() {

	w_pos(&box,TIME);			/* (time in low 16) */
	w_string(&box,last_time);
	w_pos(&box,ELAPSED);			/* elapsed time */
	w_string(&box,last_elapsed);
	w_pos(&box,RECVERR);			/* bad bits */
	w_string(&box,last_in_errors);
	w_pos(&box,RBYTESEC);			/* bytes/sec */
	w_string(&box,last_recv_bytes_sec);
	w_pos(&box,BLER);			/* block errors */
	w_string(&box,last_block_errors);
	w_pos(&box,RECVFREE);			/* error time */
	w_string(&box,last_errored_secs);
	w_pos(&box,RESYNCS);			/* resyncs */
	w_string(&box,last_resyncs);
	w_pos(&box,RECVBITS);			/* receive bits */
	w_string(&box,last_recv_bits);
	w_pos(&box,RECVBER);
	w_string(&box,last_ber);
	w_pos(&box,CARRIER);
	w_string(&box,(carrier_lost ? "Off" : " On"));
}

/* Update loopback. */

static update_loopback() {

	w_pos(&box,TIME);			/* (time in low 16) */
	w_string(&box,last_time);
	w_pos(&box,ELAPSED);			/* elapsed time */
	w_string(&box,last_elapsed);
}

/* Regenerate the latest file transfer status display. */

bert_status() {
char *cp;

	if (start_time != 0L) {
		draw_box();			/* draw the previous display */
		switch (last_type) {
			case SEND: update_send(); break;
			case RECV: update_recv(); break;
			case SEND+RECV: update_recv(); update_send(); break;
		}
		msg("Previous BERT status shown -- hit any key to continue");

	} else msg("There is no previous BERT status to show --  hit any key to continue");

	cursoroff();				/* no cursor */
	while (!keyhit()) {			/* wait for a key */
		if (unattended()) break;	/* no sense waiting! */
	}
	removewindow(&box);			/* remove box */
	signon();				/* restore commandline */
}

/* Generate a file transfer text log entry. */

static logstart() {
unsigned n;
char buff[80],*cp;

	logfile= -1;				/* flag as not-open */
	if (!testlog[0]) return;		/* skip if no logfile name */

	logfile= open(testlog,OPEN_RW);		/* open or create log file */
	if (logfile == -1) logfile= creat(testlog,CREAT_RW);
	else lseek(logfile,0L,2);		/* append to the end */
	if (logfile == -1) return;		/* oops */

	n= sec2dos(start_time - timezone) >> 16L;	/* DOS date in upper 16 */
	sprintf(buff,"\r\n+START: %s (%2u %s %2u)\r\n",
	    last_time,					/* start time */
	    n & 0x1f,_months[(n >> 5) & 0x0f],((n >> 9) & 0x3f) + 80);
	logstring(buff);

	logstring("+MODE: ");
	switch (last_type) {
		case RECV: logstring("Receive-only\r\n"); break;
		case SEND: logstring("Transmit-only\r\n"); break;
		case SEND+RECV: logstring("Bidirectional\r\n"); break;
		case LOOPBACK: logstring("Loopback\r\n"); break;
	}
	logstring("+PATTERN: "); logstring(pattern_name); logstring("\r\n");
	sprintf(buff,"+SPEED: %,u\r\n",last_rate); logstring(buff);
	sprintf(buff,"+DURATION:     %9,lu:%02lu\r\n",duration / 60L,duration % 60L);
	    logstring(buff);
	sprintf(buff,"+LOG INTERVAL: %9,lu:%02lu\r\n",loginterval / 60L,loginterval % 60L);
	    logstring(buff);
	logstring("         ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ Bits ÄÄÄÄÄÄÄÄÄÄÄÄ¿ÚÄÄÄÄÄÄÄÄÄÄ Errored ÄÄÄÄÄÄÄÄÄÄÄ¿          Inj.\r\n");
	logstring("Time                Sent             Rec'd           Bits  Blocks  Seconds Resyncs  Errs\r\n");
}

/* Log test data. */

static logdata(prefix)
char *prefix;
{

	if (logfile == -1) return;

	logstring(prefix);
	logstring(last_time);
	logstring(last_send_bits);
	logstring(last_recv_bits);

	logstring(last_in_errors);
	logstring(&last_block_errors[7]);	/* truncate some */
	logstring(last_errored_secs);
	logstring(&last_resyncs[7]);		/* to fit our log table */
	logstring(&last_inj_errors[9]);
	logstring("\r\n");
}

/* Log an operator interrupt. */

static logthis(s)
char *s;
{

	logstring("=");
	logstring(last_time);
	logstring(s);
}

/* Write a string to the logfile. */

static logstring(cp)
char *cp;
{
	while (*cp) write(logfile,cp++,1);
}


/* End the log session. */

static logend() {

	if (logfile == -1) return;

	logstring("-END: "); logstring(last_time); logstring("\r\n");
	logstring("-REASON: ");
	if (elapsed_time < duration) {
		if (control_x >= CONTROLX_COUNT) logstring("REMOTE ABORT\r\n");
		else if (carrier_lost && cd(cd_bit)) logstring("CARRIER DETECT DROPOUT\r\n");
		else if (carrier_lost) logstring("CARRIER DETECT OFF\r\n");
		else logstring("OPERATOR ABORT\r\n");

	} else logstring("NORMAL\r\n");

	logstring("-DURATION:             "); logstring(last_elapsed); logstring("\r\n");
	logstring("-SEND BYTES/SEC:    "); logstring(last_send_bytes_sec); logstring("\r\n");
	logstring("-RECEIVE BYTES/SEC: "); logstring(last_recv_bytes_sec); logstring("\r\n");
	logstring("-BER:                "); logstring(last_ber); logstring("\r\n");
	close(logfile);
}

/* Input the BERT test duration; accept seconds or MMMM:SS. Return 0 if
no input. */

static get_duration() {
char *cp;
long n;
long atol();
long getminsec();

	logsecs= 0;

	cp= input("ENTER","BER test duration, in MMM:SS or minutes");
	duration= getminsec(&cp);		/* get specified duration */
	if (duration == 0L) return;		/* nothing or nonsense entered */

	loginterval= duration / 10;		/* log interval is 1/10th duration */
	if (n= getminsec(&cp)) 			/* if a log interval specified */
		loginterval= n;			/* set it instead of default */

	logsecs= loginterval;			/* force initial log record */
}

/* Convert MMM or MMM:SS into a long and return it as seconds. Advance
the string pointer to the next ASCII digit or NUL. */

static long getminsec(sp)
char **sp;
{
char *cp;
long n;

	cp= *sp;				/* copy of the ptr */
	n= atol(cp);				/* get a number */
	n *= 60L;				/* make that minutes */
	while (isdigit(*cp)) ++cp;		/* check for :SS */
	if (*cp++ == ':') {
		n += atol(cp);			/* add in seconds */
		while (isdigit(*cp)) ++cp;	/* skip over SS digits */
	}
	while (*cp && !isdigit(*cp)) ++cp;	/* skip to next number or NUL */
	*sp= cp;
	return(n);
}
static long
sec2dos(sec70)
long sec70;
{
long time;
int dayofyear,leapyear;
int year,month,day;
int hour,mins,secs;

	time= sec70 % 86400L;			/* time only */
	sec70 /= 86400L;			/* date only */
	sec70 += 25203L;			/* add days 1900 - 1970 */
	leapyear= 2;

	year= ((sec70 - (sec70 / 1461) + 364) / 365);	/* make year, */
	dayofyear= sec70 - ((long)(year - 1) * 1461) / 4;/* day in current year */
	if (year % 4 == 0) leapyear= 1;
	if (dayofyear > 59 && ((dayofyear > 60) || (leapyear == 2)))
		dayofyear += leapyear;
	month= (269 + (dayofyear * 9)) / 275;
	day= dayofyear + 30 - ((275 * month) / 9);

/*	cprintf("\r\nsec2dos: %lu == %02d/%02d/%02d %02d:%02d:%02d\r\n",x,year,month,day,time / 3600L,(time % 3600L) / 60L,(time % 3600L) % 60L);
*/
	sec70= 0L;
	sec70 |= ((year - 80) & 0x3f) << 9;	/* pack the date (starts 1980) */
	sec70 |= (month & 0x0f) << 5;		/* into lower 16 bits */
	sec70 |= day & 0x1f;
	sec70 <<= 16L;				/* shift to upper 16 bits */

	sec70 |= (hour= ((time / 3600L) & 0x1f)) << 11;	/* then pack the time */
	time %= 3600L;				/* hours, */
	sec70 |= (mins= time / 60L) << 5;	/* minutes, */
	sec70 |= (secs= time % 60L) >> 1;	/* seconds. */
	return(sec70);
}
