#include <zmodem.h>
#include <zmem.h>
#include <xfbuf.h>
#include <filexfer.h>

/*
	Tom Jennings
	Fido Software
	30 Apr 91

	Zmodem send

	Derived from the Zmodem protocol files
	Chuck Forsberg, Omen Technology Inc
	05-09-88
*/

/* Transmit one or more files. The list fn contains one or more file names;
if explicit is true, then they should be used as is else the system path
name should be used. */

far zsend(fn,namelist,listlen)
char *fn;		/* filename for XMODEM */
char *namelist;		/* list of sent names */
int listlen;		/* max. len of the list */
{
char fspec[SS];		/* full file to search for */
char fname[SS];		/* full filename to send */
char sendname[SS];	/* name to transmit */
char *cp;
int i,f;
int error;
struct _xfbuf xfbuf;

	totl_process= totl_files= totl_errors= totl_recoveries= 0;
	totl_bytes= 0L;
	blklen= 512;				/* block size for init stuff */
	rxbuflen= 512;				/* assume Rx buffer size */
	wantfcs32= 1;				/* yes we want 32 bit CRC */
	lzconv= 0;				/* file conversion: none */
	lzmanag= 0;				/* file mgt: none */
	lztrans= 0;				/* file xport: none */
	zctlesc= 0;				/* dont need control chars escaped */
	cantovio= 0;				/* assume Rx can overlap disk & serial I/O */
	zmabort= 0;
	txwindow= (zmttype != 0);		/* mark if a window is set */
	xferstat(0,0L,0,"");			/* start the display, */

	rxtimeout= 200;				/* Rx timeout, 100ths */
	for (i= 6000 / rxtimeout; i--; ) {	/* one minute to start */
		xferstat(2,0L + i,0,"Waiting to start");
		if (zabort()) {
			error= ABORT;			/* manual abort */
			goto done;
		}
		stohdr(0L);
		zshhdr(ZRQINIT,txhdr);			/* request Rx init */
		f= getzrxinit();			/* get Rx response */
		if (f != TIMEOUT) break;		/* stop on error/response */
	}
	if (f != OK) {
		xferstat(3,0L,0,"Receiver not ready");
		error= TIMEOUT;
		goto done;				/* return error code */
	}
	rxtimeout= 1000;				/* real receive timeout, 1/100ths */
	error= OK;					/* for no-files case */
	while (*fn) {
		cpyarg(fspec,fn);			/* get an atom, */
		fn= next_arg(fn);			/* for next time */
		*sendname= '\0';			/* no rename yet, */
		for (cp= fspec; *cp; ++cp) {	
			if (*cp == '=') break;		/* look for "NAME=NAME" */
		}
		if (*cp == '=') {
			*cp++= '\0';			/* remove "=NAME" */
			strcpy(sendname,cp);		/* save 2nd name */
		}
		sendname[13]= '\0';			/* force legal */
		i= 0; xfbuf.s_attrib= 0;
		while (_find(fspec,i,&xfbuf)) {
			++i;
			strip_path(fname,fspec);	/* get the prefix, */
			strcat(fname,xfbuf.name);	/* add the filename */

			f= open(fname,0);		/* open it, */
			if (f == -1) {
				xferstat(3,xfbuf.fsize,0,"Can't open file \"%s\"",fname);
				error= OK;
				continue;
			} 
			if (*sendname) {
				xferstat(1,xfbuf.fsize,0,"Transmitting \"%s\" as \"%s\"",fname,sendname);
				strcpy(xfbuf.name,sendname);

			} else xferstat(1,xfbuf.fsize,0,fname);/* display it, */

			totl_process= 1;		/* file in progress */
			error= zsendf(f,&xfbuf);
			close(f);			/* close the file, */
			totl_process= 0;		/* file complete, one way or another */
			if (zabort()) aborttx();	/* cancel transmission */
			if (error == ZSKIP) {
				xferstat(3,0L,0,"File Skipped by Recvr");
				error= OK;		/* in case no more files */
				continue;		/* skip this file */
			}
			if (error != OK) break;		/* check errors */
			totl_bytes += txpos;		/* if OK tally byte count */

			if (strlen(fname) < listlen) {	/* keep a list */
				strcat(namelist,fname);
				strcat(namelist," ");
				listlen -= strlen(fname);
				--listlen;
			}
			++totl_files;			/* count another */
		}
		if (! i) xferstat(3,0L,0,"No file(s) match \"%s\"",fspec);
		if (error != OK) break;
	}
	if ((error != OK) || zmabort) ++totl_errors;	/* something happened */
	if (!zmabort) sendend();			/* end session unless aborted earlier */

	xferstat(3,totl_bytes,0,"%d files sent",totl_files);
done:
	xferstat(3,0L,0,"Protocol Complete");
	return(error);
}

/* Send a single file. */

static zsendf(f,xf)
int f;			/* open file */
struct _xfbuf *xf;	/* file info */
{
int i;
char *p;
long ftime;

	for (i= zmblkmax, p= zmbuff; --i;) 	/* clear out the block, */
		*p++= '\0';

	p= zmbuff;				/* point to start of buffer, */
	eofseen= 0;				/* not EOF yet */
	cpyarg(p,xf-> name);			/* add the filename */
	stolower(p);				/* make it lower case */
	while (*p++);				/* point to AFTER the null, */
	ftime= dos2sec(xf-> time,xf-> date);	/* convert time/date */

	itoav(xf-> fsize,p,10);			/* add file size, (decimal) */
	while (*p) ++p;
	*p++= ' ';				/* intervening space, */
	itoav(ftime,p,8);			/* add file date, (octal) */
	while (*p++);				/* point to after the null */

	bytcnt= txpos= rxpos= 0;		/* assumed starting position */
	lastsync= -1;				/* avoid initial ZCRCW */

	return zsendfile(f,zmbuff,p - zmbuff);	/* to calc buff length */
}

/* Convert a long to an ASCII string in the specified base. Base must
be 10 or less. */

static itoav(n,s,base)
long n;
char s[];
unsigned base;
{
int i,j;
char c;

	i= 0; 
	do {					/* generate digits */
		s[i++]= n % base + '0';		/* in reverse */
	} while ((n /= base) > 0);
	s[i]= '\0';

	for (j= 0, --i; j < i; i--, j++) {	/* reverse the string */
		c= s[j];
		s[j]= s[i];
		s[i]= c;
	}
}

/* Fill buffer with blklen chars */

static zfilbuf(f)
int f;		/* open file */
{
int n;

	n= read(f,zmbuff,blklen);
	if (n < blklen) eofseen= 1;
	return(n);
}

/*
 * Get the receiver's init parameters
 */

static getzrxinit() {
int n;
char buff[80];

	switch (zgethdr(rxhdr,1)) {

		case ZCHALLENGE:	/* Echo receiver's challenge number */
			xferstat(2,rxpos,0,"Challenge; responding");
			stohdr(rxpos);
			zshhdr(ZACK,txhdr);
			break;

		case ZCOMMAND:		/* They didn't see our ZRQINIT */
			xferstat(2,0L,0,"COMMAND; ignored");
			stohdr(0L);	/* send another */
			zshhdr(ZRQINIT,txhdr);
			break;

		case ZRINIT:
			strcpy(buff,"Receiver Ready");
			rxflags= 0xff & rxhdr[ZF0];		/* set Rx flags */
			txfcs32= (wantfcs32 && (rxflags & CANFC32));
			zctlesc |= rxflags & TESCCTL;
			cantovio= !(rxflags & CANOVIO);		/* ZCRCW if Rx cant overlap */
			if ( !(rxflags & CANFDX)) txwindow= 0;
			rxbuflen= (0xff & rxhdr[ZP0]) + ((0xff & rxhdr[ZP1]) << 8);

			if (txfcs32) strcat(buff,": CRC32");
			if (zctlesc) strcat(buff,"; Escaping Control Chars");
			if (cantovio) strcat(buff,"; Can't overlap I/O");
			xferstat(3,0L + rxbuflen,0,buff);

			/* Set initial subpacket length */

			blklen= zmblkst;			/* set output buffer size */
			if (baudrate() < 4800) blklen /= 2;	/* (512 at 2400) */
			if (baudrate() < 2400) blklen /= 2;	/* (256 at 1200) */
			if (blklen < 64) blklen= 64;		/* minimum size */

			if (rxbuflen && (blklen > rxbuflen)) 	/* Rx's max. */
				blklen= rxbuflen;		/* overrides all */
			return (sendzsinit());

		case ZCAN:
			xferstat(3,0L,0,"Cancel");
			return ERROR;

		case TIMEOUT:
			xferstat(3,0L,0,"Timeout");
			return TIMEOUT;

		case ZRQINIT:
			if (rxhdr[ZF0] == ZCOMMAND) break;
		default:		/** fall through */
			zshhdr(ZNAK,txhdr);
			break;
	}
	return(TIMEOUT);
}

/* Send send-init information */

static sendzsinit() {
char c;
int n;

	if (!zctlesc || (rxflags & TESCCTL)) return OK;

	for (n= 6000 / rxtimeout; n--;) {
		xferstat(2,0L + errors,0,"Sender Information");
		if (zabort()) return ERROR;	/* manual intervention */

		stohdr(0L);
		if (zctlesc) {
			txhdr[ZF0] |= TESCCTL; 
			zshhdr(ZSINIT,txhdr);

		} else zsbhdr(ZSINIT,txhdr);

		zsdata("",0,ZCRCW);		/* wait for response */
		c= zgethdr(rxhdr,1);
		switch (c) {
			case ZCAN: return ERROR;
			case ZACK: return OK;
		}
	}
	return ERROR;
}

/* Send file name and related info */

static zsendfile(f,buf,blen)
int f;			/* open file */
char *buf;		/* buffer to send */
unsigned blen;		/* how big it is */
{
int tries,n,c;
long crc;

/**/	for (tries= 10;;) {		/* test is at 'again' */
		xferstat(2,0L + blen,0,"Send File Info");
		if (zabort()) return ERROR; /* manual intervention */

		txhdr[ZF0]= lzconv;	/* file conversion request */
		txhdr[ZF1]= lzmanag;	/* file management request */
		txhdr[ZF2]= lztrans;	/* file transport request */
		txhdr[ZF3]= 0;
		zsbhdr(ZFILE,txhdr);
		zsdata(buf,blen,ZCRCW);

again:		if (--tries <= 0) break; /* count errors here */

		c= zgethdr(rxhdr,1);
		switch (c) {
		case ZRINIT:
			while ((c= modin(50)) != TIMEOUT) {
				if (c == ZPAD) goto again;
			}
			break;

		case TIMEOUT: c= ZTIMEOUT;	/* for report only */
		case ZCAN:
		case ZABORT:
		case ZFIN:
			xferstat(3,0L + blen,0,"ZSendFile: %s",framenames[c]);
			return ERROR;

		case ZCRC:			/* generate & send CRC */
			crc= 0xFFFFFFFFL;
			while (read(f,&c,1) && --rxpos)
				crc= UPDC32(c,crc);
			crc= ~crc;
			stohdr(crc);
			zsbhdr(ZCRC,txhdr);
			errors= 0;		/* reset error counter */
			lseek(f,0L,0);
			goto again;

		case ZSKIP:
			return ZSKIP;

		case ZRPOS:
			/*
			 * Suppress zcrcw request otherwise triggered by
			 * lastsync==bytcnt
			 */
			lseek(f,rxpos,0);
			lastsync= (bytcnt= txpos= rxpos) - 1;
			return zsendfdata(f);
		}
	}
	xferstat(3,rxbytes,0,"ZSendFile: Too many retries!");
	return ERROR;
}

/* Send the data in the file */

static zsendfdata(f)
int f;			/* open file */
{
int c,e,n;
int junkcount;			/* Counts garbage chars received by Tx */
FLAG did_zcrcq;
int goodinarow;
int usedmaxblock,maxblock;

	lrxpos= 0;		/* rx's last reported offset (none) */
	junkcount= 0;		/* extraneous characters */
	beenhereb4= 0;		/* same-bad-place counter */
	goodinarow= 0;		/* blocks in a row with no errors */
	usedmaxblock= 0;	/* 1 == sending at max. block size */

	maxblock= zmblkmax;	/* local largest-block size */

/* Look for asynchronous error packets. */

somemore:
	while (modstat()) {
		switch (modin(0)) {
			case CAN:
			case ZPAD:
waitack:			junkcount= 0;
				c= getinsync(f,1);
gotack:				switch (c) {
					case ZACK:		/* we want this */
					case ZRPOS: break;	/* we want this */
					case ZSKIP: return c;
					case ZRINIT: return OK;
					case ZCAN:
					default: 
						if ((c < 0) || (c > ZTIMEOUT)) c= ZTIMEOUT;
						xferstat(3,0L + txpos,0,"ZSendFData: %s",framenames[c]);
						return ERROR;
				}
				break;

			case XOFF:			/* Wait a while for an XON */
			case XOFF|0200: modin(1000);
			default:
				++junkcount;
				goodinarow= 0;
				break;
		}
	}
	stohdr(txpos);
	zsbhdr(ZDATA,txhdr);				/* start a new frame */

	if (zmttype <= 64) txwsize= blklen * zmttype; 	/* go this many blocks */
	else txwsize= zmttype & ~1023;			/* or Ks */
	txwspace= txwsize >> 1;				/* without ACK */
	did_zcrcq= 0;					/* windowing: ZCRCQ once */

	do {

/* If many good blocks in a row, increase the block size. The flag 
'usedmaxblock' is set when we increase the block size up to the maximum;
if it remains set (ie. no errors) and we get another 20 good ones, increase
the maximum block size itself. The flag is set only when the block size is
actually increased; any error clears the flag. */

		if (++goodinarow >= 20) {		/* if many good blocks */
			if (usedmaxblock) {		/* if good error rate */
				n= maxblock;		/* increase ceiling */
				maxblock *= 2;		/* half max. block size */
				maxblock= min(maxblock,zmblkmax);
				if (n != maxblock) 
					xferstat(3,txpos,0,"Good error rate: %d byte ceiling",maxblock);
			}
			n= blklen;			/* (save for report) */
			blklen += blklen; 		/* double block size */
			blklen= min(blklen,BLKSIZE);	/* Zmodem spec maximum */
			blklen= min(blklen,maxblock);	/* our local buffer */
			if (rxbuflen != 0) 		/* if Rx gave buffer size */
				blklen= min(blklen,rxbuflen); /* use that */

			if (blklen != n) {		/* increased block size */
				++usedmaxblock;
				xferstat(3,txpos,0,"%d byte blocks",blklen);
				if (zmttype <= 64) {	/* if block based */
					txwsize= blklen * zmttype; /* calc Ks */
					txwspace= txwsize >> 1;	/* window size */
				}
			}
			goodinarow= 0;			/* reset it */
		}

/* If an error, drop the block size. If the error occurred with 'usedmaxblock'
set (meaning: we just increased to the max. block) decrease the maximum
block size. This will not happen on runs of errors, only after a block 
increase. */

		if (beenhereb4 > 1) {			/* shorten block size */
			if (usedmaxblock) {		/* if we errored using */
				n= maxblock;		/* max block, */
				maxblock /= 2;		/* half max. block size */
				maxblock= min(maxblock,256); /* 256 min max block size */
				if (n != maxblock) 
					xferstat(3,txpos,0,"Bad error rate: %d byte ceiling",maxblock);
			}
			n= blklen;			/* shorten current */
			blklen /= 2;			/* block size */
			if (blklen < 64) blklen= 64;
			if (blklen != n) {		/* report block size */
				xferstat(3,txpos,0,"%d byte blocks",blklen);
			}
			usedmaxblock= 0;		/* obviously we did not */
		}

/* Now we finally get around to sending some data. */

		n= zfilbuf(f);				/* read some file data */
		if (eofseen) e= ZCRCE;			/* will be last block */
		else if (junkcount > 3) e= ZCRCW;	/* demand a response */
		else if (bytcnt == lastsync) e= ZCRCW;	/* ditto (expect EOF) */
		else if (rxbuflen) e= ZCRCW;		/* receiver has a small buffer */
		else if (cantovio) e= ZCRCW;		/* cant overlap disks & chars */
		else if (txwindow && (txpos - lrxpos >= txwspace)) {
			e= did_zcrcq++ ? ZCRCG : ZCRCQ;

		} else e= ZCRCG;

		xferstat(2,txpos,0,"Send Data (%s)",Zendnames[e - ZCRCE & 3]);
		if (zabort()) return ERROR;		/* manual intervention */

		zsdata(zmbuff,n,e);			/* send data & type */
		bytcnt= (txpos += n);			/* we sent this much */
		if (e == ZCRCW) goto waitack;		/* now go wait */

/* Look for error packets */

		while (modstat()) {			/* check for any response */
			switch (modin(0)) {
				case CAN:
				case ZPAD:		/* some sort of error */
					c= getinsync(f,1);
					if (c == ZACK) break;
					goodinarow= 0;	/* oops got a bad 'un */
					xferstat(3,lrxpos,0,"Error recovery");
					++totl_recoveries;
					if (c != ZRPOS) zsdata("",0,ZCRCE);
					goto gotack;

				case XOFF:		/* Wait a while for an XON */
				case XOFF|0200: 	/* Fall through */
					modin(rxtimeout);
				default:
					++junkcount;
					goodinarow= 0;	/* be conservative */
					break;
			}
		}

/* If we have a window in force, wait for ZACK or ZRPOS before continuing; 
send empty data packets to fill time. */

		if (txwindow) {
			while (txpos - lrxpos >= txwsize) {
				xferstat(2,txpos,0,"Window Sync");
				if (e != ZCRCQ) zsdata("",0,e= ZCRCQ);

/* NOTE: Since the modem and system might have a lot of in-transit data in it, 
we might have to wait a while for a response. If we get a TIMEOUT, try for at
least 60 seconds. */
				for (n= 6000 / rxtimeout; n--;) {
					c= getinsync(f,1);	/* wait for anything */
					if (c != TIMEOUT) break;/* but TIMEOUT */
				}
				if (c != ZACK) {
					xferstat(3,lrxpos,0,"Window error recovery");
					++totl_recoveries;
					zsdata("",0,ZCRCE);
					goodinarow= 0;
					goto gotack;
				}
			}
		}
	} while (!eofseen);

/* End of file; say so and wait for a response. */

	for (n= 10; n--;) {
		stohdr(txpos);				/* send total Tx byte count */
		zsbhdr(ZEOF,txhdr);
		switch (getinsync(f,0)) {
			case ZACK: break;		/* old ACKs? */
			case ZRPOS: goto somemore;	/* oops, need to retransmit */
			case ZSKIP: return c;		/* skip this file */
			case ZRINIT: return OK;		/* huh? */
			default: return ERROR;		/* what the fuck */
		}
		if (zabort()) break;
	}
	return ERROR;
}

/*
 * Respond to receiver's complaint, get back in sync with receiver
 */

static getinsync(f,flag)
int f;		/* open file */
int flag;
{
int tries,n,c;

/**/	for (tries= 10; tries--;) {
		if (zabort()) return ERROR;		/* manual intervention */

		c= zgethdr(rxhdr,0);
		switch (c) {

		case ZCAN:
		case ZABORT:
		case ZFIN:
			xferstat(3,0L + lrxpos,0,"GetInSync: %s",framenames[c]);
			return ERROR;

		case ZRPOS:				/* (flush modem output buffer here) */
			lseek(f,rxpos,0);		/* reseek */
			eofseen= 0;			/* rediscover EOF later, maybe */
			bytcnt= lrxpos= txpos= rxpos;	/* OK, start from here */
			if (lastsync == rxpos)		/* detect repeated tries */
				++beenhereb4;		/* flag repeats */
			else beenhereb4= 0;
			lastsync= rxpos;
			return c;

		case ZACK:
			lrxpos= rxpos;
			if (flag || txpos == rxpos) return ZACK;
			continue;

		case ZRINIT:
		case ZSKIP:
			return c;

		case ERROR:
		default:
			zsbhdr(ZNAK,txhdr);
			continue;
		}
	}
	xferstat(3,rxbytes,0,"GetInSync: Too many retries!");
	return ERROR;
}

/* End the transmit session, try to do it cleanly */

static sendend() {
int tries;

/**/	for (tries= 10; tries--;) {
		if (zabort()) return ERROR; /* manual intervention */

		stohdr(0L);		/* CAF Was zsbhdr - minor change */
		zshhdr(ZFIN,txhdr);	/*  to make debugging easier */

		switch (zgethdr(rxhdr,0)) {
			case ZFIN:
				modout('O'); modout('O'); flush(0);

			case ZCAN:
			case TIMEOUT: return;
		}
	}
	xferstat(3,rxbytes,0,"SendEnd: Too many retries!");
	return ERROR;
}
