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

long lstonum();

/*
	Tom Jennings
	Fido Software
	10 June 91

	Zmodem receive

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

This will accept up to 8192 byte data packets, generated by
BinkleyTerm and Opus.

*/

static int zfile;		/* file we write to */
static long zfiletime;		/* saves DOS time/date */
static char fpath[SS];		/* pathname for receive */
static char fname[SS];		/* complete filename */
static long bytesleft;		/* bytes left to transfer */


/*
 * Process incoming file information header
 */
static procheader(bp)
char *bp;
{
char buff[SS];

	cpyarg(buff,strip_path(buff,bp));	/* copy of the name only */
	if (badname(buff)) buff[1]= '$';	/* make acceptable */

	strcpy(fname,fpath);			/* receive pathname */
	strcat(fname,buff);			/* add filename */
	stoupper(fname);			/* make all upper case */

	if (fexist(fname)) {
		xferstat(3,0L,0,"File \"%s\" already exists, skipping it",fname);
		return ZSKIP;			/* skip it */
	}
	if (zmanag == ZMAPND) {			/* if appending, */
		zfile= open(fname,1);		/* open for writing, */
		if (zfile == -1) {		/* cant! report an error */
			xferstat(3,0L,0,"Can't append to non-existent file \"%s\"!",fname);
			return BADFILE;
		}
		lseek(zfile,0L,2);		/* seek EOF */

	} else zfile= creat(fname,1);		/* create a new one */

	totl_process= 1;			/* this file in process */
	if (zfile == -1) {
		xferstat(3,0L,0,"Can't create file \"%s\"",fname);
		return ERROR;
	}
	eofseen= 0;				/* not EOF yet */
	bytesleft= (-1L >> 1L);			/* default tremendous file */
	zfiletime= -1L;				/* and invalid time */
	while (*bp++);				/* add'l info follows filename */

	if (*bp) bytesleft= stonum(bp,10);	/* file size follows (decimal) */
	bp= next_arg(bp);			/* file time follows that */
	if (*bp) {				/* if a time was given */
		zfiletime= stonum(bp,8);	/* use it (octal) */
		zfiletime= sec2dos(zfiletime);	/* convert to DOS format */
	}
	xferstat(1,bytesleft,0,fname);		/* (possibly) update display */
	xferstat(2,0L,0,"Rec'v Data");
	return OK;
}

/* Return the number of bytes free on the disk. */

static long
getfree() {
int i[4],n;
long n1,n2;

	n= 0;					/* default drive number, */
	if ((strlen(fpath) >= 2) && (fpath[1] == ':')) n= toupper(fpath[0]) - 'A' + 1;
	_free(n,i);				/* get disk stuff, */
	n1= i[3]; n1*= i[2]; n1*= i[0];		/* total clust * bytes/sec * secs/clust */
	n2= i[1]; n2*= i[2]; n2*= i[0];		/* free clust * bytes/sec * secs/clust */
	return(n2);
}

/* Receive one or more files using ZMODEM. */

far zrecv(fn,nl,ll)
char *fn;		/* path to receive to */
char *nl;		/* created list of received files */
unsigned ll;		/* its maximum size */
{
int error;

	totl_process= totl_files= totl_errors= totl_recoveries= 0;
	totl_bytes= 0L;
	strip_path(fpath,fn);		/* get a copy of the pathname, */
	namelist= nl; listlen= ll;	/* where to keep the list */
	*namelist= '\0';		/* list is empty */

	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;			/* Rx can overlap disk & serial I/O */
	zmabort= 0;
	zfile= -1;			/* no file open */
	rxtimeout= 1000;		/* real receive timeout, 1/100ths */
	strcpy(attn,"");		/* Rx attention string */
	xferstat(0,0L,0,"");		/* start the display, */

	error= zrecvf();			/* start receiving */
	if (error == ZFILE) error= zrecvf2();	/* do the rest, */
	if (zfile != -1) close(zfile);		/* (in case error/aborted) */
	if (zabort()) error= ABORT;		/* so we can switch() it */
	switch (error) {			/* see what happened */
		case ERROR:			/* outright failure */
		case TIMEOUT:			/* someone fell asleep */
		case DISKFULL:			/* not our fault */
		case ABORT:			/* someone is to blame */
			aborttx();		/* cancel the transfer */
			++totl_errors;		/* count the error */
			break;
	}
	xferstat(3,0L,0,"Protocol Complete");
	return(error);
}

/*
 * Initialize for Zmodem receive attempt, try to activate Zmodem sender
 *  Handles ZSINIT frame
 *  Return ZFILE if Zmodem filename received, -1 on error,
 *   ZCOMPL if transaction finished, else 0
 */

static zrecvf() {
int i,c,n;

	tryzhdrtype= ZRINIT;

	for (n= 60; n--; ) {
		if ((zmrtype == 1) || (zmblkmax < BLKSIZE))
			stohdr(0L+zmblkmax);	/* say how big our buffer is */
		else stohdr(0L);		/* else full streaming */

		txhdr[ZF0]= CANFC32 | CANFDX;
		if (zmrtype == 0) txhdr[ZF0] |= CANOVIO;
		if (zctlesc) txhdr[ZF0] |= TESCCTL;
		zshhdr(tryzhdrtype, txhdr);
		if (tryzhdrtype == ZSKIP)	/* Don't skip too far */
			tryzhdrtype= ZRINIT;	/* CAF 8-21-87 */

again:		if (zabort()) return ERROR;	/* manual intervention */

		c= zgethdr(rxhdr, 0);
		switch (c) {
			case ZFILE:
				zconv= rxhdr[ZF0];
				zmanag= rxhdr[ZF1];
				ztrans= rxhdr[ZF2];
				tryzhdrtype= ZRINIT;
				c= zrdata(zmbuff,zmblkmax); /* get file info */
				if (c == GOTCRCW) return ZFILE;
				zshhdr(ZNAK,txhdr);
				goto again;

			case ZSINIT:
				zctlesc= TESCCTL & rxhdr[ZF0];
				if (zrdata(attn,ZATTNLEN) == GOTCRCW) {
					stohdr(1L);
					zshhdr(ZACK,txhdr);
					goto again;
				}
				zshhdr(ZNAK,txhdr);
				goto again;

			case ZFREECNT:
				stohdr(getfree());
				zshhdr(ZACK,txhdr);
				goto again;

			case ZCOMPL:
				goto again;

			case ZFIN:
				stohdr(0L);
				for (i= 3; --i >= 0; ) {
					xferstat(2,0L,0,"Session Complete");
					mflush();		/* flush anything */
					zshhdr(ZFIN,txhdr);	/* send ZFIN */
					c= modin(100);		/* get response */
					if (c == 'O') {		/* Got 'O' */
						xferstat(2,0L,0,"Over & Out");
						modin(10);	/* eat 2nd 'O' */
						return ZCOMPL;	/* success */
					}
				}
				return ZCOMPL;

			case ZCOMMAND:
			case ZCAN:
				return ERROR;

			case ZRQINIT:
			case ZEOF:
			case TIMEOUT:
			default:
				continue;
		}
	}
	return 0;
}

/*
 * Receive 1 or more files with ZMODEM protocol
 */

static zrecvf2() {
int tries,c;

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

		switch (c= zrecvf3()) {
			case ZEOF:
			case ZSKIP:
				switch (zrecvf()) {
					case ZCOMPL: return OK;
					case ZFILE: break;
					default: return ERROR;
				}
				tries= 10;	/* not an error, reset */
				continue;

			case ERROR: return ERROR;
			default: return c;
		}
	}
	xferstat(3,rxbytes,0,"ZRecvF: Too many retries!");
	return ERROR;
}

/*
 * Receive a file with ZMODEM protocol
 *  Assumes file name frame is in zmbuff
 */

static zrecvf3() {
int tries,c;
long lastack;

	switch (procheader(zmbuff)) {
		case OK: break;					/* alls well */
		case ZSKIP:					/* duplicate */
		case BADFILE: return(tryzhdrtype= ZSKIP);	/* bad filename? */
		case ERROR: return(ERROR);			/* somethings wrong */
	}
	rxbytes= 0L;
	tries= 10;

	for (;;) {
		stohdr(lastack= rxbytes);
		zshhdr(ZRPOS,txhdr);			/* last good position */

nxthdr:		if (zabort()) return ERROR;		/* manual intervention */
		switch (c= zgethdr(rxhdr, 0)) {
			case ZNAK:
			case TIMEOUT:
				if ( --tries < 0) {
					xferstat(3,rxbytes,0,"File rec'v failed!");
					return ERROR;
				}
				/* fall through ... */

			case ZFILE:
				zrdata(zmbuff,zmblkmax);
				continue;

			case ZEOF:
				if (rclhdr(rxhdr) != rxbytes) {
					/*
					 * Ignore eof if it's at wrong place - force
					 *  a timeout because the eof might have gone
					 *  out before we sent our zrpos.
					 */
					goto nxthdr;
				}
				if (zfiletime != -1) ftime(1,zfile,&zfiletime);	/* set file time */
				close(zfile); zfile= -1;		/* close, mark it closed */
				totl_process= 0;			/* file completed */
				if (strlen(fname) < listlen) {		/* keep a list */
					strcat(namelist,fname);
					strcat(namelist," ");
					listlen -= strlen(fname);
				}
				++totl_files;				/* another ... */
				totl_bytes += rxbytes;
				xferstat(2,rxbytes,0,"File complete");
				return c;

			case ERROR:
				if ( --tries < 0) {
					xferstat(3,rxbytes,0,"Excess line noise!");
					return ERROR;
				}
				continue;

			case ZSKIP:
				close(zfile); zfile= -1;
				totl_process= 0;
				xferstat(3,0L,0,"File skipped");
				return c;

			case ZDATA:
				if (rclhdr(rxhdr) != rxbytes) {
					if ( --tries < 0) {
						xferstat(3,rxbytes,0,"ZRecvF3a: Too many retries!");
						return ERROR;
					}
					rxattn(); continue;
				}

/* Loop here for all data blocks. */

moredata:		if (zabort()) return ERROR;	/* manual intervention */
			switch (c= zrdata(zmbuff,zmblkmax)) {
					case ZCAN:
						xferstat(3,rxbytes,0,"Sender cancelled!");
						return ERROR;

					case ERROR:	/* CRC error */
						if ( --tries < 0) {
							xferstat(3,rxbytes,0,"ZRecvFb: Too many retries!");
							return ERROR;
						}
						rxattn();
						continue;

					case TIMEOUT:
						if ( --tries < 0) {
							xferstat(3,rxbytes,0,"Sender stopped");
							return ERROR;
						}
						continue;

					case GOTCRCW:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						xferstat(2,rxbytes,0,"Rec'v Data (%s)",Zendnames[c - GOTCRCE & 3]);
						stohdr(lastack= rxbytes);
						zshhdr(ZACK, txhdr);
						modout(XON);
						goto nxthdr;

					case GOTCRCQ:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						xferstat(2,rxbytes,0,"Rec'v Data (%s)",Zendnames[c - GOTCRCE & 3]);
sendack:;					stohdr(lastack= rxbytes);
						zshhdr(ZACK,txhdr);
						goto moredata;

					case GOTCRCG:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						xferstat(2,rxbytes,0,"Rec'v Data (%s)",Zendnames[c - GOTCRCE & 3]);
						goto moredata;

					case GOTCRCE:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						xferstat(2,rxbytes,0,"Rec'v Data (%s)",Zendnames[c - GOTCRCE & 3]);
						goto nxthdr;
				}

			default:
				return ERROR;
		}
	}
	return ERROR;
}

/* Output the attention string (if there is one). */

static rxattn() {

char *cp;

	for (cp= attn; *cp;) modout(*cp++);
}

/* Write out a buffer full. */

static putsec(buff,count)
char *buff;
unsigned count;
{
	if (write(zfile,buff,count) != count) {
		xferstat(3,rxbytes,0,"DISK FULL!");
		return ERROR;
	}
	return OK;
}

/* Convert a string of ASCII digits to a long number, in the 
specified base. Works only for bases 1 - 10 (mainly: dec. and octal) */

/*********
static long
lstonum(s,base)
char *s;
int base;
{
int i;
long n;

	n= 0L;
	while ((*s >= '0') && (*s <= '9')) 
		n= (base * n) + (*s++ - '0');
	return(n);
}

***********/
