#include "zmodem.h"
#include "zmem.h"
#include <filexfer.h>
#include <fastfile.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 */

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

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 */
	znulls= 0;			/* don't null pad */
	zmabort= 0;
	zfile= -1;			/* no file open */
	rxtimeout= 1000;		/* real receive timeout, 1/100ths */
	strcpy(attn,"");		/* Rx attention string */
	xferstat(XFER_START,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 */
			_mflush();
			++totl_errors;		/* count the error */
			break;
	}
	xferstat(XFER_END,totl_bytes,0,"Protocol complete");
	return(error);
}

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

	cpyarg(buff,strip_path(buff,bp));	/* copy of the name only */
	fixname(buff);				/* check filename legality */
	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(XFER_ERROR,0L,0,"File \"%s\" already exists, skipping it",fname);
		return ZSKIP;			/* skip it */
	}
	if (zmanag == ZMAPND) {			/* if appending, */
		zfile= open(fname,OPEN_WO);	/* open for writing, */
		if (zfile == -1) {		/* cant! report an error */
			xferstat(XFER_ERROR,0L,0,"Can't append to non-existent file \"%s\"!",fname);
			return BADFILE;
		}
		lseek(zfile,0L,2);		/* seek EOF */

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

	totl_process= 1;			/* this file in process */
	if (zfile == -1) {
		xferstat(XFER_ERROR,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= lstonum(bp,10);	/* file size follows (decimal) */
	bp= next_arg(bp);			/* file time follows that */
	if (*bp) {				/* if a time was given */
		zfiletime= lstonum(bp,8);	/* use it (octal) */
		zfiletime= sec2dos(zfiletime);	/* convert to DOS format */
	}
	xferstat(XFER_FILE,bytesleft,0,fname);	/* (possibly) update display */
	xferstat(XFER_DATA,0L,0,"Receive data");
	return OK;
}

/* Force a filename to conform to MSDOS specs (length and legal characters).
If the total characters not including dots is < 8, we simply use them all.
If greater than 8, we use the first four and the last four. The extention is 
always the first three characters after the last dot. Examples:

a.ex.c				aex.c			(total < 8)
wais.documents.examples.txt	waisples.txt		(total > 8)
 
*/

static fixname(op)
char *op;
{
char buff[80],*cp,n,*last_dot,dots;
						/* op= where we put filename */
	for (cp= op, dots= 0; *cp; ++cp) {	/* check filename first */
		if (*cp == '.') {
			++dots;			/* count dots, */
			last_dot= cp;		/* remember the last one */
		}
	}
	if (dots == 0) last_dot= cp;		/* none; point to end of string */

	for (cp= op, n= 0; *cp && n < sizeof(buff); ++cp) { /* make filename text */
		if (cp == last_dot) break;	/* stop at end of name portion */
		if (*cp == '.') continue;	/* without dots */
		buff[n++]= fnc(*cp);		/* whack illegal chars */
	}
	buff[n]= '\0';				/* null-terminate it */

	if (n < 1) strcpy(buff,"$FT-NAME");	/* all bad chars?! */
	else if (n > 8) {			/* > 8 char filename */
		strncpy(&buff[4],&buff[n - 4],4); /* first 4, plus last 4 */
		buff[n= 8]= '\0';		/* strncpy() doesnt do this! */
	}

	last_dot[4]= '\0';			/* ".EXT" is 4 chars max */
	while (*last_dot) {			/* possibly add extention */
		buff[n++]= fnc(*last_dot++);	/* fix bad characters */
	}
	buff[n]= '\0';				/* force null terminate */
	strcpy(op,buff);			/* output name */
}

/* Fix this filename character */

static fnc(c)
char c;
{
	switch (c) {
		case '\0':			/* never wreck this... */
			return(c);

		case '"': case '/': 
		case '\\': case '[': case ']': case ':':
		case '|': case '<': case '>': case '+':
		case '=': case ';': case '\'':
		case '?': case '*':
			return('$');

		default: if (c <= ' ') return('$');
			return(c);
	}
}

/* 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);
}

/*
 * 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 (zabort()) return ERROR;	/* manual intervention */

		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(XFER_STATUS,totl_bytes,0,"Session complete");
					flush(0);		/* flush anything */
					zshhdr(ZFIN,txhdr);	/* send ZFIN */
					c= modin(100);		/* get response */
					if (c == 'O') {		/* Got 'O' */
						xferstat(XFER_STATUS,totl_bytes,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(XFER_ERROR,rxbytes,0,"Too many retries! (ZRecvF)");
	return ERROR;
}

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

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

	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(XFER_ERROR,rxbytes,0,"File receive 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(XFER_EOF,rxbytes,0,"File complete");
				return c;

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

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

			case ZDATA:
				if (rclhdr(rxhdr) != rxbytes) {
					if ( --tries < 0) {
						xferstat(XFER_ERROR,rxbytes,0,"Too many retries! (ZRecvF3a)");
						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(XFER_ERROR,rxbytes,0,"Sender cancelled!");
						return ERROR;

					case GOTCRCW:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						switch (c) {				/* faster than ... */
							case ZCRCE: cp= "Receive data (ZCRCE)"; break;
							case ZCRCG: cp= "Receive data (ZCRCG)"; break;
							case ZCRCQ: cp= "Receive data (ZCRCQ)"; break;
							case ZCRCW: cp= "Receive data (ZCRCW)"; break;
							default: cp= "Receive data"; break;						}
						xferstat(XFER_DATA,rxbytes,0,cp);
						stohdr(lastack= rxbytes);
						zshhdr(ZACK, txhdr);
						modout(XON);
						goto nxthdr;

					case GOTCRCQ:
						tries= 10;
						if (putsec(zmbuff,rxcount)) return ERROR;
						rxbytes += rxcount;
						xferstat(XFER_DATA,rxbytes,0,"Receive 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(XFER_DATA,rxbytes,0,"Receive Data (%s)",Zendnames[c - GOTCRCE & 3]);
						goto moredata;

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

/* Receive error; send a NAK at the last good position. */

					case TIMEOUT:	/* no/lost response */
						if ( --tries < 0) {
							xferstat(XFER_ERROR,rxbytes,0,"Sender timed out");
							return ERROR;
						}
						goto sendnak;

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

					default:
sendnak:;					rxattn();
						stohdr(lastack= rxbytes);
						zshhdr(ZNAK,txhdr);
						goto moredata;
				}

			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(XFER_ERROR,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);
}
