/*+-------------------------------------------------------------------------
	zmodem.c - ZMODEM protocol primitives
    based on code by Chuck Forsberg
	tip adaptation by wht%n4hgf@emory.mathcs.emory.edu

  Entry point Functions:
	zsbhdr(type,hdr) send binary header
	zshhdr(type,hdr) send hex header
	zgethdr(hdr,eflag) receive header - binary or hex
	zsdata(buf,len,frameend) send data
	zrdata(buf,len) receive data
	stohdr(pos) store position data in Txhdr
	long rclhdr(hdr) recover position offset from header

  Defined functions:
	noxrd7()
	rclhdr(hdr)
	stohdr(pos)
	zdlread()
	zgeth1()
	zgethdr(hdr,eflag)
	zgethex()
	zputhex(c)
	zrbhdr(hdr)
	zrbhdr32(hdr)
	zrdat32(buf,length)
	zrdata(buf,length)
	zrhhdr(hdr)
	zsbh32(hdr,type)
	zsbhdr(type,hdr)
	zsda32(buf,length,frameend)
	zsdata(buf,length,frameend)
	zsendline(c)
	zshhdr(type,hdr)

--------------------------------------------------------------------------*/
/*+:EDITS:*/
/*:08-23-1990-13:48-wht@tridom-add mode 4 code */
/*:05-21-1990-16:00-wht@tridom-adapt ecu xfer protocols for tipwht */

#include "zmodem.h"			/* wht */
#include "zlint.h"

extern char s128[];				/* wht */
extern int Zctlesc;				/* wht */
extern int Zmodem;				/* wht */
extern long cr3tab[];			/* wht */
extern unsigned Baudrate;		/* wht */
extern unsigned short crctab[];	/* wht */

int Rxtimeout = 100;		/* Tenths of seconds to wait for something */

#if !defined(UNSL)
#define UNSL
#endif



static lastsent;	/* Last char we sent */
static evenp;		/* Even parity seen on header */

/* Globals used by ZMODEM functions */
char Attn[ZATTNLEN+1];	/* Attention string rx sends to tx on err */
char Rxhdr[4];		/* Received header */
char Txhdr[4];		/* Transmitted header */
int Crc32;		/* Display flag indicating 32 bit CRC being received */
int Crc32t;		/* Display flag indicating 32 bit CRC being sent */
int Rxcount;		/* Count of data bytes received */
int Rxframeind;		/* ZBIN ZBIN32,or ZHEX type of frame received */
int Rxtimeout;	/* Tenths of seconds to wait for something */
int Rxtype;		/* Type of header received */
int Txfcs32;		/* TURE means send binary frames with 32 bit FCS */
int Zrwindow;	/* RX window size (controls garbage count) */
long Rxpos;	/* Received file position */
long Txpos;	/* Transmitted file position */

char *frametypes[] = 
{
	"Carrier Lost",		/* -3 */
	"TIMEOUT",			/* -2 */
	"ERROR",			/* -1 */
/* #define FTOFFSET 3 moved to zmodem.h */
	"ZRQINIT",
	"ZRINIT",
	"ZSINIT",
	"ZACK ",
	"ZFILE",
	"ZSKIP",
	"ZNAK ",
	"ZABORT",
	"ZFIN ",
	"ZRPOS",
	"ZDATA",
	"ZEOF ",
	"ZFERR",
	"ZCRC ",
	"ZCHALLENGE",
	"ZCOMPL",
	"ZCAN ",
	"ZFREECNT",
	"ZCOMMAND",
	"ZSTDERR",
	"xxxxx"
#define FRTYPES 22	/* Total number of frame types in this array */
	/*  not including psuedo negative entries */
};

static char masked[] = "8 bit transparent path required";
static char badcrc[] = "Bad CRC";

/* Send ZMODEM binary header hdr of type type */
zsbhdr(type,hdr)
register unsigned char *hdr;
{
	register int n;
	register unsigned crc;

	report_tx_ind(1);
	sprintf(s128,"hdr %s %ld",frametypes[type+FTOFFSET],rclhdr(hdr));
	report_last_txhdr(s128,0);

	xsendline(ZPAD); 
	xsendline(ZDLE);

	if(Crc32t=Txfcs32)
		zsbh32(hdr,type);
	else 
	{
		xsendline(ZBIN); 
		zsendline(type); 
		crc = updcrc(type,0);

		for(n=4; --n >= 0; ++hdr)
		{
			zsendline(*hdr);
			crc = updcrc(*hdr,crc);
		}
		crc = updcrc(0,updcrc(0,crc));
		zsendline(crc>>8);
		zsendline(crc);
	}
	if(type != ZDATA)
		flushline();
	mode(4);
	report_tx_ind(0);
}


/* Send ZMODEM binary header hdr of type type */
zsbh32(hdr,type)
register char *hdr;
{
	register int n;
	register UNSL long crc;

	report_tx_ind(1);
	xsendline(ZBIN32);  
	zsendline(type);
	crc = 0xFFFFFFFFL; 
	crc = UPDC32(type,crc);

	for(n=4; --n >= 0; ++hdr)
	{
		crc = UPDC32((0377 & *hdr),crc);
		zsendline(*hdr);
	}
	crc = ~crc;
	for(n=4; --n >= 0;)
	{
		zsendline((int)crc);
		crc >>= 8;
	}
	mode(4);
	report_tx_ind(0);
}

/* Send ZMODEM HEX header hdr of type type */
zshhdr(type,hdr)
register unsigned char *hdr;
{
	register int n;
	register unsigned short crc;

	report_tx_ind(1);

	sprintf(s128,"hdr %s %ld",frametypes[type+FTOFFSET],rclhdr(hdr));
	report_last_txhdr(s128,0);
	sendline(ZPAD); 
	sendline(ZPAD); 
	sendline(ZDLE); 
	sendline(ZHEX);
	zputhex(type);
	Crc32t = 0;

	crc = updcrc(type,0);
	for(n=4; --n >= 0; ++hdr)
	{
		zputhex(*hdr); 
		crc = updcrc(*hdr,crc);
/*		crc = updcrc((0377 & *hdr),crc);  original - wht */
	}
	crc = updcrc(0,updcrc(0,crc));
	zputhex(crc>>8); 
	zputhex(crc);

	/* Make it printable on remote machine */
	sendline(015); 
	sendline(012);
	/*
	 * Uncork the remote in case a fake XOFF has stopped data flow
	 */
	if(type != ZFIN && type != ZACK)
		sendline(021);
	flushline();
	mode(4);
	report_tx_ind(0);
}

/*
 * Send binary array buf of length length,with ending ZDLE sequence frameend
 */
static char *Zendnames[] = { "ZCRCE","ZCRCG","ZCRCQ","ZCRCW"};

zsdata(buf,length,frameend)
register unsigned char *buf;
{
	register unsigned short crc;

	report_tx_ind(1);

	sprintf(s128,"data %s %d bytes",Zendnames[frameend-ZCRCE&3],length);
	report_last_txhdr(s128,0);
	if(Crc32t)
		zsda32(buf,length,frameend);
	else 
	{
		crc = 0;
		for(;--length >= 0; ++buf)
		{
			zsendline(*buf); 
			crc = updcrc(*buf,crc);
		}
		xsendline(ZDLE); 
		xsendline(frameend);
		crc = updcrc(frameend,crc);

		crc = updcrc(0,updcrc(0,crc));
		zsendline(crc>>8); 
		zsendline(crc);
	}
	if(frameend == ZCRCW)
	{
		xsendline(XON);  
		flushline();
	}
	mode(4);
	report_tx_ind(0);

}

zsda32(buf,length,frameend)
register char *buf;
{
	register int c;
	register UNSL long crc;

	report_tx_ind(1);

	crc = 0xFFFFFFFFL;
	for(;--length >= 0; ++buf)
	{
		c = *buf & 0377;
		if(c & 0140)
			xsendline(lastsent = c);
		else
			zsendline(c);
		crc = UPDC32(c,crc);
	}
	xsendline(ZDLE); 
	xsendline(frameend);
	crc = UPDC32(frameend,crc);

	crc = ~crc;
	for(length=4; --length >= 0;)
	{
		zsendline((int)crc);  
		crc >>= 8;
	}
	mode(4);
	report_tx_ind(0);
}

/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */
zrdata(buf,length)
register char *buf;
{
	register int c;
	register unsigned short crc;
	register char *end;
	register int d;

	report_rx_ind(1);

	if(Rxframeind == ZBIN32)
	{
		report_rx_ind(0);
		return(zrdat32(buf,length));
	}

	crc = Rxcount = 0;  
	end = buf + length;
	while(buf <= end)
	{
		if((c = zdlread()) & ~0377)
		{
crcfoo:
			switch(c)
			{
			case GOTCRCE:
			case GOTCRCG:
			case GOTCRCQ:
			case GOTCRCW:
				crc = updcrc(((d=c)&0377),crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = updcrc(c,crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = updcrc(c,crc);
				if(crc & 0xFFFF)
				{
					report_str(badcrc,0);
					report_rx_ind(0);
					return(ERROR);
				}
				Rxcount = length - (end - buf);
				report_rxblklen(Rxcount);
				sprintf(s128,"data %s %d bytes",
					Zendnames[d-GOTCRCE&3],Rxcount);
				report_last_rxhdr(s128,0);
				report_rx_ind(0);
				return(d);
			case GOTCAN:
				report_str("Sender Cancelled",1);
				report_rx_ind(0);
				return(ZCAN);
			case TIMEOUT:
				report_str("TIMEOUT",1);
				report_rx_ind(0);
				return(c);
			default:
				report_str("Bad data subpacket",1);
				report_rx_ind(0);
				return(c);
			}
		}
		*buf++ = c;
		crc = updcrc(c,crc);
	}
	report_str("Data subpacket too long",1);
	report_rx_ind(0);
	return(ERROR);
}

zrdat32(buf,length)
register char *buf;
{
	register int c;
	register UNSL long crc;
	register char *end;
	register int d;

	report_rx_ind(1);
	crc = 0xFFFFFFFFL;  
	Rxcount = 0;  
	end = buf + length;
	while(buf <= end)
	{
		if((c = zdlread()) & ~0377)
		{
crcfoo:
			switch(c)
			{
			case GOTCRCE:
			case GOTCRCG:
			case GOTCRCQ:
			case GOTCRCW:
				d = c;  
				c &= 0377;
				crc = UPDC32(c,crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = UPDC32(c,crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = UPDC32(c,crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = UPDC32(c,crc);
				if((c = zdlread()) & ~0377)
					goto crcfoo;
				crc = UPDC32(c,crc);
				if(crc != 0xDEBB20E3)
				{
					report_str(badcrc,0);
					report_rx_ind(0);
					return(ERROR);
				}
				Rxcount = length - (end - buf);
				report_rxblklen(Rxcount);
				sprintf(s128,"data %s %d bytes",
					Zendnames[d-GOTCRCE&3],Rxcount);
				report_last_rxhdr(s128,0);
				report_rx_ind(0);
				return(d);
			case GOTCAN:
				report_str("Sender Canceled",1);
				report_rx_ind(0);
				return(ZCAN);
			case TIMEOUT:
				report_str("TIMEOUT",1);
				report_rx_ind(0);
				return(c);
			default:
				report_str("Bad data subpacket",1);
				report_rx_ind(0);
				return(c);
			}
		}
		*buf++ = c;
		crc = UPDC32(c,crc);
	}
	report_str("Data subpacket too long",1);
	report_rx_ind(0);
	return(ERROR);
}


/*
 * Read a ZMODEM header to hdr,either binary or hex.
 *  eflag controls local display of non zmodem characters:
 *	0:  no display
 *	1:  display printing characters only
 *	2:  display all non ZMODEM characters
 *  On success,set Zmodem to 1,set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence,for fast error recovery.
 */
zgethdr(hdr,eflag)
char *hdr;
{
register int c,n,cancount;

	report_rx_ind(1);
	n = Zrwindow + Baudrate;	/* Max bytes before start of frame */
	Rxframeind = Rxtype = 0;

startover:
	cancount = 5;
again:
	/* Return immediate ERROR if ZCRCW sequence seen */
	switch(c = readline(Rxtimeout))
	{
	case RCDO:
	case TIMEOUT:
		goto fifi;
	case CAN:
gotcan:
		if(--cancount <= 0)
		{
			c = ZCAN; 
			goto fifi;
		}
		switch(c = readline(1))
		{
		case TIMEOUT:
			goto again;
		case ZCRCW:
			c = ERROR;
			/* **** FALL THRU TO **** */
		case RCDO:
			goto fifi;
		default:
			break;
		case CAN:
			if(--cancount <= 0)
			{
				c = ZCAN; 
				goto fifi;
			}
			goto again;
		}
		/* **** FALL THRU TO **** */
	default:
agn2:
		if( --n == 0)
		{
			report_str("Garbage count exceeded",1);
			report_last_rxhdr("Noise",0);
			report_rx_ind(0);
			return(ERROR);
		}
		goto startover;
	case ZPAD|0200:		/* This is what we want. */
	case ZPAD:		/* This is what we want. */
		evenp = c & 0200;
		break;
	}
	cancount = 5;
splat:
	switch(c = noxrd7())
	{
	case ZPAD:
		goto splat;
	case RCDO:
	case TIMEOUT:
		goto fifi;
	default:
		goto agn2;
	case ZDLE:		/* This is what we want. */
		break;
	}

	switch(c = noxrd7())
	{
	case RCDO:
	case TIMEOUT:
		goto fifi;
	case ZBIN:
		Rxframeind = ZBIN;  
		Crc32 = FALSE;
		c =  zrbhdr(hdr);
		break;
	case ZBIN32:
		Crc32 = Rxframeind = ZBIN32;
		c =  zrbhdr32(hdr);
		break;
	case ZHEX:
		Rxframeind = ZHEX;  
		Crc32 = FALSE;
		c =  zrhhdr(hdr);
		break;
	case CAN:
		goto gotcan;
	default:
		goto agn2;
	}
	Rxpos = hdr[ZP3] & 0377;
	Rxpos = (Rxpos<<8) + (hdr[ZP2] & 0377);
	Rxpos = (Rxpos<<8) + (hdr[ZP1] & 0377);
	Rxpos = (Rxpos<<8) + (hdr[ZP0] & 0377);
fifi:
	switch(c)
	{
	case GOTCAN:
		c = ZCAN;
		/* **** FALL THRU TO **** */
	case ZNAK:
	case ZCAN:
	case ERROR:
	case TIMEOUT:
	case RCDO:
		sprintf(s128,"Got %s",frametypes[c+FTOFFSET]);
		report_str(s128,1);
		/* **** FALL THRU TO **** */
	default:
		if(c >= -3 && c <= FRTYPES)
			sprintf(s128,"hdr %s %ld",frametypes[c+FTOFFSET],Rxpos);
		else
			sprintf(s128,"hdr 0x%02x? %ld",c,Rxpos);
		report_last_rxhdr(s128,0);
	}
	report_rx_ind(0);
	return(c);
}

/* Receive a binary style header (type and position) */
zrbhdr(hdr)
register char *hdr;
{
	register int c,n;
	register unsigned short crc;

	if((c = zdlread()) & ~0377)
		return(c);
	Rxtype = c;
	crc = updcrc(c,0);

	for(n=4; --n >= 0; ++hdr)
	{
		if((c = zdlread()) & ~0377)
			return(c);
		crc = updcrc(c,crc);
		*hdr = c;
	}
	if((c = zdlread()) & ~0377)
		return(c);
	crc = updcrc(c,crc);
	if((c = zdlread()) & ~0377)
		return(c);
	crc = updcrc(c,crc);
	if(crc & 0xFFFF)
	{
		if(evenp)
			report_str(masked,1);
		report_str(badcrc,0);
		return(ERROR);
	}
#if defined(ZMODEM)
	Protocol = ZMODEM;
#endif
	Zmodem = 1;
	return(Rxtype);
}

/* Receive a binary style header (type and position) with 32 bit FCS */
zrbhdr32(hdr)
register char *hdr;
{
	register int c,n;
	register UNSL long crc;

	if((c = zdlread()) & ~0377)
		return(c);
	Rxtype = c;
	crc = 0xFFFFFFFFL; 
	crc = UPDC32(c,crc);

	for(n=4; --n >= 0; ++hdr)
	{
		if((c = zdlread()) & ~0377)
			return(c);
		crc = UPDC32(c,crc);
		*hdr = c;
	}
	for(n=4; --n >= 0;)
	{
		if((c = zdlread()) & ~0377)
			return(c);
		crc = UPDC32(c,crc);
	}
	if(crc != 0xDEBB20E3)
	{
		if(evenp)
			report_str(masked,1);
		report_str(badcrc,0);
		return(ERROR);
	}
#if defined(ZMODEM)
	Protocol = ZMODEM;
#endif
	Zmodem = 1;
	return(Rxtype);
}


/* Receive a hex style header (type and position) */
zrhhdr(hdr)
char *hdr;
{
	register int c;
	register unsigned short crc;
	register int n;

	if((c = zgethex()) < 0)
		return(c);
	Rxtype = c;
	crc = updcrc(c,0);

	for(n=4; --n >= 0; ++hdr)
	{
		if((c = zgethex()) < 0)
			return(c);
		crc = updcrc(c,crc);
		*hdr = c;
	}
	if((c = zgethex()) < 0)
		return(c);
	crc = updcrc(c,crc);
	if((c = zgethex()) < 0)
		return(c);
	crc = updcrc(c,crc);
	if(crc & 0xFFFF)
	{
		report_str(badcrc,0); 
		return(ERROR);
	}
	if(readline(1) == '\r')	/* Throw away possible cr/lf */
		readline(1);
#if defined(ZMODEM)
	Protocol = ZMODEM;
#endif
	Zmodem = 1; 
	return(Rxtype);
}

/* Send a byte as two hex digits */
zputhex(c)
register int c;
{
	static char digits[]	= "0123456789abcdef";

	sendline(digits[(c&0xF0)>>4]);
	sendline(digits[(c)&0xF]);
}

/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON,XOFF. Escape CR following @ (Telenet net escape)
 */
zsendline(c)
{

	/* Quick check for non control characters */
	if(c & 0140)
		xsendline(lastsent = c);
	else 
	{
		switch(c &= 0377)
		{
		case ZDLE:
			xsendline(ZDLE);
			xsendline(lastsent = (c ^= 0100));
			break;
		case 015:
		case 0215:
			if(!Zctlesc && (lastsent & 0177) != '@')
				goto sendit;
			/* **** FALL THRU TO **** */
		case 020:
		case 021:
		case 023:
		case 0220:
		case 0221:
		case 0223:
			xsendline(ZDLE);
			c ^= 0100;
sendit:
			xsendline(lastsent = c);
			break;
		default:
			if(Zctlesc && ! (c & 0140))
			{
				xsendline(ZDLE);
				c ^= 0100;
			}
			xsendline(lastsent = c);
		}
	}
}

/* Decode two lower case hex digits into an 8 bit byte value */
zgethex()
{
	register int c;

	c = zgeth1();
	return(c);
}
zgeth1()
{
	register int c,n;

	if((c = noxrd7()) < 0)
		return(c);
	n = c - '0';
	if(n > 9)
		n -= ('a' - ':');
	if(n & ~0xF)
		return(ERROR);
	if((c = noxrd7()) < 0)
		return(c);
	c -= '0';
	if(c > 9)
		c -= ('a' - ':');
	if(c & ~0xF)
		return(ERROR);
	c += (n<<4);
	return(c);
}

/*
 * Read a byte,checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */
zdlread()
{
	register int c;

again:
	/* Quick check for non control characters */
	if((c = readline(Rxtimeout)) & 0140)
		return(c);
	switch(c)
	{
	case ZDLE:
		break;
	case 023:
	case 0223:
	case 021:
	case 0221:
		goto again;
	default:
		if(Zctlesc && !(c & 0140))
		{
			goto again;
		}
		return(c);
	}
again2:
	if((c = readline(Rxtimeout)) < 0)
		return(c);
	if(c == CAN && (c = readline(Rxtimeout)) < 0)
		return(c);
	if(c == CAN && (c = readline(Rxtimeout)) < 0)
		return(c);
	if(c == CAN && (c = readline(Rxtimeout)) < 0)
		return(c);
	switch(c)
	{
	case CAN:
		return(GOTCAN);
	case ZCRCE:
	case ZCRCG:
	case ZCRCQ:
	case ZCRCW:
		return(c | GOTOR);
	case ZRUB0:
		return(0177);
	case ZRUB1:
		return(0377);
	case 023:
	case 0223:
	case 021:
	case 0221:
		goto again2;
	default:
		if(Zctlesc && ! (c & 0140))
		{
			goto again2;
		}
		if((c & 0140) ==  0100)
			return(c ^ 0100);
		break;
	}
	sprintf(s128,"Bad escape sequence %x",c);
	report_str(s128,1);
	return(ERROR);
}

/*
 * Read a character from the modem line with timeout.
 *  Eat parity,XON and XOFF characters.
 */
noxrd7()
{
	register int c;

	for(;;)
	{
		if((c = readline(Rxtimeout)) < 0)
			return(c);
		switch(c &= 0177)
		{
		case XON:
		case XOFF:
			continue;
		default:
			if(Zctlesc && !(c & 0140))
				continue;
		case '\r':
		case '\n':
		case ZDLE:
			return(c);
		}
	}
}

/* Store long integer pos in Txhdr */
stohdr(pos)
long pos;
{
	Txhdr[ZP0] = pos;
	Txhdr[ZP1] = pos>>8;
	Txhdr[ZP2] = pos>>16;
	Txhdr[ZP3] = pos>>24;
}

/* Recover a long integer from a header */
long
rclhdr(hdr)
register char *hdr;
{
	register long l;

	l = (hdr[ZP3] & 0377);
	l = (l << 8) | (hdr[ZP2] & 0377);
	l = (l << 8) | (hdr[ZP1] & 0377);
	l = (l << 8) | (hdr[ZP0] & 0377);
	return(l);
}

/* end of zmodem.c */
/* vi: set tabstop=4 shiftwidth=4: */
