/* protz.c		Version 1.5, 92Apr24 */

/*
 * Doug Evans, dje@sspiff.UUCP or dje@ersys.edmonton.ab.ca
 *
 * This file provides the Zmodem protocol (by Chuck Forsberg) for
 * Ian Taylor's UUCP package.
 *
 * It was originally developed to establish a uucp link between myself and my
 * employer: Ivation Datasystems, Inc. of Ottawa. 
 *
 * My thanks to Ivation for letting me release this to the public. Given that
 * Zmodem is in the public domain, no additional copyrights have been added.
 *
 *****************************************************************************
 *
 * It's been difficult fitting Zmodem into the UUCP world. I have been guided
 * mostly by trying to plug it into Taylor UUCP. Where "the Zmodem way of doing
 * things" conflicted with "the UUCP way of doing things", I have err'd on the
 * side of UUCP. At the end of it all, I have achieved something that will plug
 * into Taylor UUCP very easily, but some might argue that I have corrupted Z
 * too much. At any rate, compatibility with sz/rz was sacrificed to achieve a
 * clean UUCP protocol. Given that, I took the opportunity to start from
 * scratch when defining protocol constants (EG: ZBIN).
 *
 * 1) I wasn't quite sure how to enhance Zmodem to handle send+receive in one
 *    session, so I added a 'g' protocol like initialization sequence. This
 *    also gets this stuff out of the way, in case we ever try to support
 *    full-duplex.
 *
 *	Caller			    Callee
 *	------			    ------
 *	ZINIT		-->	<-- ZINIT
 *	ZDATA (ZCRCF)	-->	<-- ZDATA (ZCRCF)
 *	ZACK		-->	<-- ZACK
 *	ZINITEND	-->	<-- ZINITEND
 *
 *    ZINIT is a combination of ZRINIT and ZSINIT and is intended to exchange
 *    simple protocol information (flags) and the protocol version number.
 *    ZDATA is intended to include window size information as well as the
 *    "Myattn" string (although at the moment it doesn't contain anything).
 *    ZDATA may contain at most 1k bytes of data and is sent out as one ZCRCF
 *    packet. Two ack's (ZACK + ZINITEND) are needed to ensure both sides have
 *    received ZDATA.
 *
 * 2) I've hardcoded several protocol parameters, like 32 bit CRC's for data.
 *    Others are not supported (we don't need them).
 *
 * 3) ZHEX headers use 32 bit CRC's.
 *
 * 4) Zmodem sends the ZFILE message "in one chunk". If there are errors, the
 *    entire string is resent. I have continued this practice. All UUCP
 *    commands are sent "in one chunk". This can be changed down the road if
 *    necessary.
 *
 * 5) The ZEOF message has been replaced with a new ZCRCx value: ZCRCF. ZCRCF
 *    is identical to ZCRCW except that it indicates the end of the message.
 *    The protocol here is *not* a file transfer protocol. It is an end to end
 *    transport protocol (that preserves message boundaries).
 *
 * 6) Zmodem handles restarting a file transfer, but as best as I can tell UUCP
 *    does not. At least Taylor UUCP doesn't. And if UUCP does start handling
 *    file restart, can it be plugged into the existing Zmodem way with zero
 *    changes? Beats me. Therefore I have removed this part of the code. One
 *    can always put it back in if and when UUCP handles it. Ditto for other
 *    pieces of removed code: there's no point in overly complicating this code
 *    when supporting all the bells and whistles requires enhancements to UUCP
 *    itself.
 *
 *    *** It is easier to put code back in in an upward compatible manner ***
 *    *** than it is to correct for misunderstood code or poorly merged   ***
 *    *** (Zmodem vs UUCP) code.                                          ***
 *
 * 7) For the character in the initial "protocol selection" sequence, I have
 *    chosen 'a'. I'm told 'z' is already in use for something that isn't
 *    Zmodem. It's entirely reasonable to believe that if Zmodem ever becomes a
 *    standard UUCP protocol, this won't be it (so I'll leave z/Z for them).
 *    Publicly, this is the 'a' protocol. Internally, it is refered to as 'z'.
 *    A little confusing, I know. Maybe in time I'll refer to it internally as
 *    'a', or maybe in time this will be *the* 'z' protocol.
 *
 * 8) Since we are writing a transport protocol, which isn't supposed to know
 *    anything about what is being transfered or where it is coming from, the
 *    header data value has changed meaning. It no longer means "file position"
 *    but instead means "window position". It is a running counter of the bytes
 *    transfered. Each "message" begins on a 1k boundary so the count isn't a
 *    precise byte count. The counter wraps every 4 gigabytes, although this
 *    wrapping isn't supported yet.
 *
 *    FIXME: At present the max data transfered per session is 4 gigabytes.
 *
 ****************************************************************************
 *
 * A typical message sequence is (master sending file to slave):
 *
 *      Master                          Slave
 *      ------                          -----
 *	ZDATA (S, ZCRCF)	-->
 *				<--	ZACK
 *				<--	ZDATA (SY, ZCRCF)
 *	ZACK			-->
 *	ZDATA			-->
 *                        ...	<--	ZACK/ZRPOS
 *	ZDATA (ZCRCF)		-->
 *				<--	ZACK
 *				<--	ZDATA (CY, ZCRCF)
 *	ZACK			-->
 *
 * A typical message sequence is (master receiving file from slave):
 *
 *	Master				Slave
 *	------				-----
 *	ZDATA (R, ZCRCF)	-->
 *				<--	ZACK
 *				<--	ZDATA (RY, ZCRCF)
 *	ZACK			-->
 *				<--	ZDATA
 *	ZACK/ZRPOS	...	-->
 *				<--	ZDATA (ZCRCF)
 *	ZACK			-->
 *	ZDATA (CY, ZCRCF)	-->
 *				<--	ZACK
 *
 *****************************************************************************
 *
 * Fitting this into Taylor UUCP:
 *
 * The protocol entry in <asProtocols> is:
 *
 *	{ 'a', FALSE, RELIABLE_EIGHT,
 *		asZproto_params, fzstart, fzshutdown, fzsendcmd, zzgetspace,
 *		fzsenddata, fzprocess, fzwait, fzfile },
 *
 * Additional entries for "prot.h":
 *
 *//* Prototypes for 'z' protocol functions.  */
/*
 * extern struct scmdtab asZproto_params[];
 * extern boolean fzstart P((boolean fmaster));
 * extern boolean fzshutdown P((void));
 * extern boolean fzsendcmd P((const char *z));
 * extern char *zzgetspace P((int *pcdata));
 * extern boolean fzsenddata P((char *z, int c));
 * extern boolean fzprocess P((boolean *pfexit, long *plredo));
 * extern boolean fzwait P((void));
 * extern boolean fzfile P((boolean fstart, boolean fsend, long *plredo,
 *			    long cbytes));
 *
 *****************************************************************************
 *
 * Notes:
 * 1) For future bidirectional concerns, keep packet types "unidirectional".
 *	Sender always uses:	ZDATA, ZNAK
 *	Receiver always uses:	ZRPOS, ZACK
 *	There is no intersection.
 *
 *    I'm not sure if this is necessary or even useful, but it seems to be.
 *
 * 2) I use to store the byte count / 32 in the data header. This left 5 bits
 *    unused for future concerns. I removed this because of the following
 *    situation when sending a file:
 *
 *	ZDATA (ZCRCG, xx bytes) - received ok
 *	ZDATA (ZCRCF, 0 bytes)  - corrupted
 *
 *    At this point the receiver would like to send back a ZRPOS with a value 
 *    of the size of the file. However, it can't because the value is divided
 *    by 32, and it would have to round up to the next multiple of 32. This
 *    seemed a little ugly, so I went with using the entire header to store
 *    the byte count.
 *
 *****************************************************************************
 *
 * Source version:
 * 
 * 1.1,2,3
 *	Protocol version 0
 *	Early attempts, completely rewritten later.
 *
 * 1.4	Protocol version 1
 *	Beta test sent to Ian for analysis 92Apr18.
 *
 * 1.5	Protocol version 1
 *	Released 92Apr24.
 *
 *****************************************************************************
 *
 * Protocol version:
 *
 * A version number is exchanged in the ZINIT message, so it is possible to
 * correct or enhance the protocol, without breaking existing versions.
 * The purpose of this section is to document these versions as they come out.
 * Remember, this is the protocol version, not the source version.
 *
 * 0	Initial version.
 *	Zmodem controlled file transfer. This was more of a "plug Z
 *	into UUCP as is" port.
 *
 * 1	Complete rewrite.
 *	Made Z more of a transport protocol. UUCP now controls transfer and Z
 *	is on the same footing as the other UUCP protocols.
 *	Theoretically, there will be little pain when UUCP goes bidirectional.
 */

#include "uucp.h"

#include <errno.h>
#include <string.h>

#include "prot.h"
#include "port.h"
#include "system.h"

#define ZPROTOCOL_VERSION	1

/*
 * Control message characters ...
 */

#define ZPAD	'*'	/* Padding character begins frames */
#define ZDLE	030	/* Ctrl-X Zmodem escape - `ala BISYNC DLE */
#define ZBIN	'A'	/* Binary frame indicator */
#define ZHEX	'B'	/* HEX frame indicator */

/*
 * Frame types (see array "frametypes" in zm.c) ...
 *
 * Note that the numbers here have been reorganized, as we don't support
 * all of them (nor do we need to).
 *
 * WARNING: The init sequence assumes ZINIT < ZDATA < ZACK < ZINITEND.
 */

#define ZINIT		0	/* Init (contains protocol version, flags) */
#define ZDATA		1	/* Data packet(s) follow */
#define ZRPOS		2	/* Resume data trans at this position */
#define ZACK		3	/* ACK to above */
#define ZNAK		4	/* Last packet was garbled */
#define Zreserved	5	/* reserved (for future concerns) */
#define ZINITEND	6	/* end of init sequence */
#define ZFIN		7	/* Finish session */

/*
 * ZDLE sequences ...
 *
 * Note addition of ZCRCF: "end of message".
 */

#define ZCRCE 'h'	/* CRC next, frame ends, header packet follows */
#define ZCRCG 'i'	/* CRC next, frame continues nonstop */
#define ZCRCQ 'j'	/* CRC next, frame continues, ZACK expected */
#define ZCRCW 'k'	/* CRC next, ZACK expected, end of frame */
#define ZCRCF 'l'	/* CRC next, ZACK expected, end of message */

#define ZRUB0 'm'	/* Translate to rubout 0177 */
#define ZRUB1 'n'	/* Translate to rubout 0377 */


/*
 * zdlread return values (internal) ...
 * Other values are ZM_ERROR, ZM_TIMEOUT, ZM_RCDO.
 */

#define GOTOR	0400
#define GOTCRCE (ZCRCE | GOTOR)	/* ZDLE-ZCRCE received */
#define GOTCRCG (ZCRCG | GOTOR)	/* ZDLE-ZCRCG received */
#define GOTCRCQ (ZCRCQ | GOTOR)	/* ZDLE-ZCRCQ received */
#define GOTCRCW (ZCRCW | GOTOR)	/* ZDLE-ZCRCW received */
#define GOTCRCF (ZCRCF | GOTOR)	/* ZDLE-ZCRCF received */

/*
 * Byte positions within header array ...
 */

#define ZF0	3	/* First flags byte */
#define ZF1	2
#define ZF2	1
#define ZF3	0

#define ZP0	0	/* Low order 8 bits of position */
#define ZP1	1
#define ZP2	2
#define ZP3	3	/* High order 8 bits of position */

/*
 * Bit Masks for ZRQINIT flags byte ZF0 ...
 */

#define TX_ESCCTL	1	/* Tx will escape control chars */

/*
 * Possible errors when running ZMODEM ...
 */

#define	ZM_ERROR	(-1)	/* crc error, etc. */
#define ZM_TIMEOUT	(-2)
#define ZM_RCDO		(-3)	/* Carrier Lost */

/*
 * ASCII characters ...
 */

#define LF		012
#define CR		015
#define XON		021
#define XOFF		023

#define XON_WAIT	10	/* seconds */

/*
 * Packet sizes ...
 *
 * FIXME: CPACKETSIZE is hardcoded in a lot of places.
 *	It's not clear to me whether changing it's value would be a
 *	"good thing" or not. But of course that doesn't excuse the hardcoding.
 */

#define CPACKETSIZE		1024	/* max packet size (data only) */
#define CFRAMELEN		12	/* header size */
#define CSUFFIXLEN		10	/* suffix at end of data packets */
#define CEXCHANGE_INIT_RETRIES	4

/*
 * Data types ...
 */

typedef unsigned char achdrval_t[4];
typedef unsigned long hdrval_t;
typedef unsigned long winpos_t;

/*
 * Configurable parms ...
 *
 * FIXME: <cZrx_buf_len> isn't used yet. It may not be needed.
 */

#define CTIMEOUT		10
#define CRETRIES		10
#define CSTARTUP_RETRIES	4
#define CGARBAGE		2400
#define CSEND_WINDOW		16384
#define FESCAPE_CONTROL		FALSE

static int cZtimeout = CTIMEOUT;	/* (seconds) */
static int cZretries = CRETRIES;
static int cZstartup_retries = CSTARTUP_RETRIES;
static int cZmax_garbage = CGARBAGE;		/* max garbage before header */
static int cZtx_window = CSEND_WINDOW;		/* our transmission window */
static int cZrx_buf_len = 0;			/* our receiver's buffer size */
static boolean fZesc_ctl = FESCAPE_CONTROL;	/* escape control chars */

struct scmdtab asZproto_params[] =
{
	{"timeout", CMDTABTYPE_INT, (pointer) & cZtimeout, NULL},
	{"retries", CMDTABTYPE_INT, (pointer) & cZretries, NULL},
	{"startup-retries", CMDTABTYPE_INT, (pointer) & cZstartup_retries, NULL},
	{"garbage", CMDTABTYPE_INT, (pointer) & cZmax_garbage, NULL},
	{"send-window", CMDTABTYPE_INT, (pointer) & cZtx_window, NULL},
	{"escape-control", CMDTABTYPE_BOOLEAN, (pointer) & fZesc_ctl, NULL},
	{NULL, 0, NULL, NULL}
};

/*
 * Variables for statistic gathering ...
 *
 * We use <wpZtxpos, wpZrxbytes> to record the number of "packets"
 * sent/received. Packets is in double quotes because some of them aren't full.
 */

static unsigned long cZheaders_sent;
static unsigned long cZheaders_received;
static unsigned long cZbytes_resent;
static unsigned long cZtimeouts;
static unsigned long cZerrors;

/*
 * Data buffers ...
 */

static char *zZtx_buf;		/* transmit buffer */

static char *zZtx_packet_buf;	/* raw outgoing packet data */
static char *zZrx_packet_buf;	/* raw incoming packet data */

/*
 * Transmitter state variables ...
 */

static unsigned cZblklen;	/* data length in sent/received packets */
static unsigned cZtxwspac;	/* spacing between ZCRCQ requests */
/*static unsigned cZblklen_override;*//* override value for <cZblklen> */
static unsigned cZtxwcnt;	/* counter used to space ack requests */
static unsigned cZrxwcnt;	/* counter used to watch receiver's buf size */
static winpos_t wpZtxstart;	/* <wpZtxpos> when message started */
static winpos_t wpZtxpos;	/* transmitter position */
static winpos_t wpZlastsync;	/* last offset to which we got a ZRPOS */
static winpos_t wpZlrxpos;	/* receiver's last reported offset */
static winpos_t wpZrxpos;	/* receiver file position */

static int iZlast_tx_data_packet; /* type of last ZDATA packet (ZCRCx) */
static int iZjunk_count;	/* amount of garbage characters received */
static int iZtleft;		/* for dynamic packet resizing */

static int iZbeenhereb4;	/* times we've been ZRPOS'd to same place */

/*
 * Receiver state variables ...
 */

static winpos_t wpZrxbytes;	/* receiver byte count */
static int iZlast_rx_data_packet; /* last successfully received ZCRCx packet */

/*
 * Misc. globals ...
 */

static char xon = XON;

#ifdef DJE_TESTING
int uucptest = -1;
int uucptest2;
int uucptestseed;
#endif

/*
 * Kludge!!!
 * See fzfinish_tx(). Basically the next two globals are used to record the
 * fact that we got a ZDATA, but aren't quite ready to process it.
 */

static int iZpkt_rcvd_kludge;			/* -1 if not valid */
static hdrval_t hvZpkt_hdrval_kludge;

/*
 * Packet types ...
 */

static const char *azZframe_types[] = {
	"Carrier Lost",		/* -3 */
	"Timeout",		/* -2 */
	"Error",		/* -1 */
#define FTOFFSET 3
	"ZINIT",
	"ZDATA",
	"ZRPOS",
	"ZACK",
	"ZNAK",
	"Zreserved",
	"ZINITEND",
	"ZFIN",
	"UNKNOWN!!!"
};
#define FTNUMBER	(sizeof(azZframe_types) / sizeof(char *))

#ifndef min
#define min(a, b)	((a) < (b) ? (a) : (b))
#endif
#define ZZHEADER_NAME(itype) \
		azZframe_types[min((itype) + FTOFFSET, FTNUMBER - 1)]

/*
 * Local functions ...
 */

static boolean fzsend_data P((char *zdata, int cdata, boolean fendofmessage));
static boolean fzstart_proto P((boolean fmaster));
static int izexchange_init P((boolean fmaster, int send_type, achdrval_t send_val, achdrval_t recv_val));
static boolean fzshutdown_proto P((void));
static boolean fzstart_tx P((void));
static boolean fzfinish_tx P((long *plredo));
static boolean fzstart_rx P((void));
static boolean fzfinish_rx P((void));
static boolean fzsend_hdr P((int ipkttype, int ihdrtype, hdrval_t hdrval, boolean fcheckreceive));
static boolean fzsend_data_packet P((char *zdata, int cdata, int frameend, boolean fcheckreceive));
static int czbuild_header P((char *zresult, int ipkttype, int ihdrtype, hdrval_t hdrval));
static int czbuild_data_packet P((char *zresult, const char *zdata, int cdata, int frameend));
/*
 * The rest of the functions do not follow Ian's naming style. I have left
 * the names the same as the original zm source. Over time, they may change.
 */
static int izrecv_hdr P((achdrval_t hdr));	/* IE: zgethdr() */
static int zrbhdr32 P((achdrval_t hdr));
static int zrhhdr P((achdrval_t hdr));
static int zrdat32 P((char *buf, int length, int *iprxcount));
static int getinsync P((boolean flag));
static char *zputhex P((char *p, int ch));
static char *zputchar P((char *p, int ch));
static int zgethex P((void));
static int zdlread P((void));
static int noxrd7 P((void));
static int realreadchar P((int timeout));
static boolean fzreceive_ready P((void));
static void stohdr P((hdrval_t pos, achdrval_t hdr));
static hdrval_t rclhdr P((achdrval_t hdr));
static hdrval_t hvzencode_data_hdr P((winpos_t cbytes));
static void zdecode_data_hdr P((hdrval_t hdrval, winpos_t *pcbytes));
static winpos_t lzupdate_rxpos P((achdrval_t rx_hdr, winpos_t rxpos, winpos_t lrxpos, winpos_t txpos));

/*
 * This macro replaces readchar() because it achieves a noticable speed up. The
 * readchar() function has been renamed realreadchar(). Thanks to Ian for
 * running this stuff through a profiler to find this out. Ian suggests further
 * speed ups may be obtained by doing a similar thing in zrdat32().
 */

/* Assign the next character to b. */
#define READCHAR(b, i) \
  (iPrecstart != iPrecend \
   ? ((b) = BUCHAR (abPrecbuf[iPrecstart]), \
      iPrecstart = (iPrecstart + 1) % CRECBUFLEN) \
   : ((b) = realreadchar (i)))

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

/*
 * Start the protocol ...
 */

boolean
fzstart(fmaster)
boolean fmaster;
{
	if (!fport_set (PORTSETTING_EIGHT))
		return FALSE;

	ulog (LOG_NORMAL, "Protocol 'a' starting: %d, %d, %d, %d, %d, %d",
		cZtimeout, cZretries, cZstartup_retries,
		cZmax_garbage, cZtx_window, fZesc_ctl);

	/*
	 * For now, we place tight restrictions on the size of the transmit
	 * window. This might be relaxed in the future. If it is relaxed,
	 * some of these tests will stay, some will go. That is why it is
	 * coded like it is.
	 */

	if (cZtx_window % 1024 != 0 ||
		cZtx_window < 4096 || cZtx_window > 65536 ||
		65536 % cZtx_window != 0
	) {
		ulog (LOG_ERROR, "fzstart: cZtx_window not one of 4096, 8192, 16384, 32768, 65536");
		return FALSE;
	}

	zZtx_buf = (char *) xmalloc (CPACKETSIZE);

	zZtx_packet_buf = (char *) xmalloc (CFRAMELEN + 2 * CPACKETSIZE + CSUFFIXLEN + 42 /*slop*/);
	zZrx_packet_buf = (char *) xmalloc (CFRAMELEN + 2 * CPACKETSIZE + CSUFFIXLEN + 42 /*slop*/);

	iZlast_tx_data_packet = -1;
	iZlast_rx_data_packet = -1;

	wpZtxpos = wpZlrxpos = wpZrxpos = wpZrxbytes = 0;
	cZtxwspac = cZtx_window / 4;

	cZheaders_sent = cZheaders_received = cZbytes_resent = 0;
	cZtimeouts = cZerrors = 0;

	iZpkt_rcvd_kludge = -1;

#if 0
	/*
	 * We ensure <cZtx_window> is at least 4k, so the following is
	 * unnecessary. It can be put back in later if needed.
	 */
	if (cZblklen_override > cZtxwspac || (!cZblklen_override && cZtxwspac < 1024))
		cZblklen_override = cZtxwspac;
#endif

#ifdef DJE_TESTING
	{
		extern int uucptest,uucptest2,uucptestseed;
		FILE *f;

		if (uucptest == -1) {
			if ((f = fopen ("/usr/local/src/bin/uucp/uucptest", "r")) != NULL) {
				fscanf (f, "%d %d %d",
					&uucptestseed, &uucptest, &uucptest2);
				fclose (f);
			}
			srand (uucptestseed);
		}
	}
#endif

	/*
	 * Fire up the protocol (exchange init messages) ...
	 */

	if (!fzstart_proto (fmaster))
		return FALSE;

	return TRUE;
}

/*
 * Stop the protocol ...
 */

boolean
fzshutdown()
{
	(void) fzshutdown_proto ();

	xfree ((pointer) zZtx_buf);
	xfree ((pointer) zZtx_packet_buf);
	xfree ((pointer) zZrx_packet_buf);
	zZtx_buf = NULL;
	zZtx_packet_buf = NULL;
	zZrx_packet_buf = NULL;

	/*
	 * Print some informative statistics ...
	 *
	 * I use the word "messages" here instead of "headers" because the
	 * latter is jargonese.
	 */

	ulog (LOG_NORMAL,
		"Protocol 'a' messages: sent %lu, received %lu",
		cZheaders_sent, cZheaders_received);
	ulog (LOG_NORMAL,
		"Protocol 'a' packets: sent %lu, received %lu",
		wpZtxpos / 1024, wpZrxbytes / 1024);
	if (cZbytes_resent != 0 || cZtimeouts != 0 || cZerrors != 0)
		ulog (LOG_NORMAL,
			"Protocol 'a' errors: bytes resent %lu, timeouts %lu, errors %lu",
			cZbytes_resent, cZtimeouts, cZerrors);

	/*
	 * Reset all the parameters to their default values, so that the
	 * protocol parameters used for this connection do not affect the
	 * next one.
	 */

	cZtimeout = CTIMEOUT;
	cZretries = CRETRIES;
	cZstartup_retries = CSTARTUP_RETRIES;
	cZmax_garbage = CGARBAGE;
	cZtx_window = CSEND_WINDOW;
	fZesc_ctl = FESCAPE_CONTROL;

	cZheaders_sent = cZheaders_received = cZbytes_resent = 0;
	cZtimeouts = cZerrors = 0;

	return TRUE;
}

/*
 * Send a command string ...
 * We send everything up to and including the null byte.
 *
 * We assume the command will fit in the outgoing data buffer.
 * FIXME: A valid assumption?
 */

boolean
fzsendcmd(z)
const char *z;
{
	int n,clen;
	long lredo;
	char *zbuf;

	clen = strlen (z) + 1;

	DEBUG_MESSAGE1 (DEBUG_PROTO, "fzsendcmd: sending command %s", z);

	if (!fzstart_tx ())	/* must be called before zzgetspace() */
		return FALSE;

	if ((zbuf = zzgetspace (&n)) == NULL)
		return FALSE;

#if DEBUG > 0
	if (clen > n)
		ulog (LOG_FATAL, "fzsendcmd: clen > n");
#endif

	strcpy (zbuf, z);

	/*
	 * Send it out ...
	 */

	do {
		if (!fzsend_data (zbuf, clen, TRUE))
			return FALSE;
		if (!fzfinish_tx (&lredo))
			return FALSE;
	} while (lredo >= 0);

	return TRUE;
}

/*
 * Allocate a packet to send out ...
 *
 * Note that 'z' has dynamic packet resizing and that <cZblklen> will range
 * from 32 to 1024, in multiples of 2.
 */

char *
zzgetspace(pclen)
int *pclen;
{
	*pclen = cZblklen;
	return zZtx_buf;
}

/*
 * Send a block of data ...
 *
 * If (cdata == 0) then the end of the file has been reached.
 */

boolean
fzsenddata(zdata, cdata)
char *zdata;
int cdata;
{
	DEBUG_MESSAGE1 (DEBUG_PROTO, "fzsenddata: %d bytes", cdata);

	return fzsend_data (zdata, cdata, cdata == 0);
}

/*
 * Send a block of data (command or file) ...
 */

static boolean
fzsend_data(zdata, cdata, fendofmessage)
char *zdata;
int cdata;
boolean fendofmessage;
{
	int n;

	if (iZlast_tx_data_packet == -1 || iZlast_tx_data_packet == ZCRCW) {
		cZtxwcnt = cZrxwcnt = 0;
		iZjunk_count = 0;
		if (!fzsend_hdr (ZBIN, ZDATA, hvzencode_data_hdr (wpZtxpos), TRUE))
			return FALSE;
	}

	n = cdata;

	if (fendofmessage)
		iZlast_tx_data_packet = ZCRCF;
	else if (iZjunk_count > 3)
		iZlast_tx_data_packet = ZCRCW;
	else if (wpZtxpos == wpZlastsync)
		iZlast_tx_data_packet = ZCRCW;
	else if (cZrx_buf_len && (cZrxwcnt += n) >= cZrx_buf_len)
		iZlast_tx_data_packet = ZCRCW;
	else if ((cZtxwcnt += n) >= cZtxwspac) {
		iZlast_tx_data_packet = ZCRCQ;
		cZtxwcnt = 0;
	} else
		iZlast_tx_data_packet = ZCRCG;

	if (++iZtleft > 3) {
		iZtleft = 0;
		if (cZblklen < 1024)
			cZblklen *= 2;
#if 0	/* <cZblklen_override> is currently unnecessary */
		if (cZblklen_override && cZblklen > cZblklen_override)
			cZblklen = cZblklen_override;
#endif
		if (cZblklen > 1024)
			cZblklen = 1024;
		if (cZrx_buf_len && cZblklen > cZrx_buf_len)
			cZblklen = cZrx_buf_len;
	}

#if DEBUG > 0
	if (FDEBUGGING(DEBUG_PROTO)) {
		const char *type;

		switch (iZlast_tx_data_packet) {
		case ZCRCW: type = "ZCRCW"; break;
		case ZCRCG: type = "ZCRCG"; break;
		case ZCRCQ: type = "ZCRCQ"; break;
		case ZCRCE: type = "ZCRCE"; break;
		case ZCRCF: type = "ZCRCF"; break;
		default : type = "UNKNOWN!!!"; break;
		}
		ulog (LOG_DEBUG, "fzsend_data: %s, pos 0x%lx, %d bytes",
			type, wpZtxpos, n);
	}
#endif

	if (!fzsend_data_packet (zdata, n, iZlast_tx_data_packet, TRUE))
		return FALSE;

	wpZtxpos += n;

	if (iZlast_tx_data_packet == ZCRCW) {
		/*
		 * FIXME: Ideally this would be done in fzprocess. However, it
		 *	is only called if there is data pending which there
		 *	may not be yet. I could have patched fploop() a bit but
		 *	for now, I've done it like this.
		 */
		switch (getinsync (FALSE)) {
		case ZACK:
			break;
		case ZRPOS:
			iZlast_tx_data_packet = -1; /* trigger ZDATA */
			ffileclearerror (eSendfile);
			if (!ffileseek (eSendfile, wpZrxpos - wpZtxstart, 0 /*SEEK_SET*/)) {
				ulog (LOG_ERROR, "seek: %s", strerror (errno));
				usendfile_error ();
				return FALSE;
			}
			break;
		default:
			return FALSE;
		}
		return TRUE;
	}

	/*
	 * If we've reached the maximum transmit window size, let the
	 * receiver catch up ...
	 *
	 * I use (cZtx_window - 2048) to play it safe.
	 */

	while (wpZtxpos - wpZlrxpos >= cZtx_window - 2048) {
		if (iZlast_tx_data_packet != ZCRCQ) {
			if (!fzsend_data_packet (zdata, 0,
					iZlast_tx_data_packet = ZCRCQ, TRUE))
				return FALSE;
		}
		/*
		 * FIXME: I'd rather not call ffileseek() in this file. When we
		 *	start buffering the outgoing data, the following
		 *	ffileseek() will disappear.
		 */
		switch (getinsync (TRUE)) {
		case ZACK:
			break;
		case ZRPOS:
			iZlast_tx_data_packet = -1; /* trigger ZDATA */
			ffileclearerror (eSendfile);
			if (!ffileseek (eSendfile, wpZrxpos - wpZtxstart, 0 /*SEEK_SET*/)) {
				ulog (LOG_ERROR, "seek: %s", strerror (errno));
				usendfile_error ();
				return FALSE;
			}
			break;
		default:
			return FALSE;
		}
	}

	return TRUE;
}

/*
 * Process existing data ...
 * This function is only called when sending a file.
 *
 * Set *pfexit to TRUE if a file or a command has been
 * completely received. (FIXME: not supported at present).
 *
 * If the protocol needs uucp to fseek() to some point, *plredo is set to
 * that point. Otherwise it is -1L.
 */

boolean
fzprocess(pfexit, plredo)
boolean *pfexit;
long *plredo;
{
	int c,ch;

	*pfexit = FALSE;
	*plredo = -1;

	while (fzreceive_ready ()) {
		READCHAR (ch, 1);
		switch (ch) {
		case ZPAD:
			/* see if we're detecting ZRPOS packets quickly */
			DEBUG_MESSAGE0 (DEBUG_PROTO, "fzprocess: possible ZRPOS packet");
			c = getinsync (TRUE);
			if (c == ZACK)
				break;
			/* FIXME: sz does a TCFLSH here */
#if 0	/* FIXME: Not sure if this is needed, or where to put it. */
			/* ZCRCE - dinna wanna starta ping-pong game */
			if (!fzsend_data_packet (zZtx_packet_buf, 0, ZCRCE, TRUE))
				return FALSE;
#endif
			if (c == ZRPOS) {
				*plredo = wpZrxpos - wpZtxstart;
				iZlast_tx_data_packet = -1; /* trigger ZDATA */
				break;	/* not returning is intentional */
			}
			return FALSE;
		case XOFF:
		case XOFF | 0200:
			READCHAR (ch, XON_WAIT);
			break;
		case CR:
			break;
		default:
			iZjunk_count++;
			break;
		}
	}

	return TRUE;
}

/*
 * Wait for data to come in.
 *
 * This continues processing until a complete file or command has been
 * received.
 */

boolean
fzwait()
{
	int c,cerr,rxcount;
	boolean fexit;
	achdrval_t rx_hdr;

	if (!fzstart_rx ())
		return FALSE;

	cerr = cZretries;

	goto nxthdr;

	for (;;) {
		if (!fzsend_hdr (ZHEX, ZRPOS, hvzencode_data_hdr (wpZrxbytes), FALSE))
			return FALSE;
nxthdr:
		c = izrecv_hdr (rx_hdr);

		switch (c) {
		case ZM_TIMEOUT:
		case ZNAK:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "fzwait: retries exhausted");
				return FALSE;
			}
			continue;
		case ZM_ERROR:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "fzwait: retries exhausted");
				return FALSE;
			}
			/*fport_break ();*/
			continue;
		case ZM_RCDO:
		case ZFIN:
			return FALSE;
		case ZRPOS:
		case ZACK:
			goto nxthdr;	/* ignore, partner is out of sync */
		case ZDATA: {
			winpos_t rx_bytes;

			zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes);
			DEBUG_MESSAGE2 (DEBUG_PROTO,
				"fzwait: bytes(us,them) 0x%lx,0x%lx",
				wpZrxbytes, rx_bytes);
			if (rx_bytes != wpZrxbytes) {
				if (--cerr < 0) {
					ulog (LOG_ERROR, "fzwait: retries exhausted");
					return FALSE;
				}
				(void) zrdat32 (zZrx_packet_buf, 1024, &rxcount);
				/*fport_break ();*/
				/*
				 * FIXME: Seems to me we should ignore this one
				 *	and go for a timeout, the theory being
				 *	that the appropriate ZRPOS has already
				 *	been sent. We're obviously out of sync.
				 *	/dje 92Mar10
				 */
				continue;	/* goto nxthdr? */
			}
moredata:
			/*
			 * Do not call fgot_data() with (rxcount == 0) if it's
			 * not ZCRCF. fgot_data() will erroneously think this
			 * is the end of the message.
			 */
			c = zrdat32 (zZrx_packet_buf, 1024, &rxcount);
#if DEBUG > 0
			if (FDEBUGGING(DEBUG_PROTO)) {
				const char *msg;

				if (c < 0) {
					msg = ZZHEADER_NAME(c);
				} else {
					switch (c) {
					case GOTCRCW: msg = "ZCRCW"; break;
					case GOTCRCG: msg = "ZCRCG"; break;
					case GOTCRCQ: msg = "ZCRCQ"; break;
					case GOTCRCE: msg = "ZCRCE"; break;
					case GOTCRCF: msg = "ZCRCF"; break;
					default : msg = NULL; break;
					}
				}
				if (msg != NULL)
					ulog (LOG_DEBUG, "fzwait: zrdat32: %s, %d bytes", msg, rxcount);
				else
					ulog (LOG_DEBUG, "fzwait: zrdat32: %d, %d bytes", c, rxcount);
			}
#endif
			switch (c) {
			case ZM_ERROR:	/* CRC error */
				cZerrors++;
				if (--cerr < 0) {
					ulog (LOG_ERROR, "fzwait: retries exhausted");
					return FALSE;
				}
				/*fport_break ();*/
				continue;
			case ZM_TIMEOUT:
				cZtimeouts++;
				if (--cerr < 0) {
					ulog (LOG_ERROR, "fzwait: retries exhausted");
					return FALSE;
				}
				continue;
			case ZM_RCDO:
				return FALSE;
			case GOTCRCW:
				iZlast_rx_data_packet = ZCRCW;
				cerr = cZretries;
				if (rxcount != 0 && !fgot_data (zZrx_packet_buf, rxcount, FALSE, FALSE, &fexit))
					return FALSE;
				wpZrxbytes += rxcount;
				if (!fzsend_hdr (ZHEX, ZACK, hvzencode_data_hdr (wpZrxbytes), FALSE))
					return FALSE;
				fsend_data (&xon, 1, FALSE);
				goto nxthdr;
			case GOTCRCQ:
				iZlast_rx_data_packet = ZCRCQ;
				cerr = cZretries;
				if (rxcount != 0 && !fgot_data (zZrx_packet_buf, rxcount, FALSE, FALSE, &fexit))
					return FALSE;
				wpZrxbytes += rxcount;
				if (!fzsend_hdr (ZHEX, ZACK, hvzencode_data_hdr (wpZrxbytes), FALSE))
					return FALSE;
				goto moredata;
			case GOTCRCG:
				iZlast_rx_data_packet = ZCRCG;
				cerr = cZretries;
				if (rxcount != 0 && !fgot_data (zZrx_packet_buf, rxcount, FALSE, FALSE, &fexit))
					return FALSE;
				wpZrxbytes += rxcount;
				goto moredata;
			case GOTCRCE:
				iZlast_rx_data_packet = ZCRCE;
				cerr = cZretries;
				if (rxcount != 0 && !fgot_data (zZrx_packet_buf, rxcount, FALSE, FALSE, &fexit))
					return FALSE;
				wpZrxbytes += rxcount;
				goto nxthdr;
			case GOTCRCF:
				iZlast_rx_data_packet = ZCRCF;
				/*
				 * fzfinish_rx() must be called before
				 * fgot_data() because fgot_data() will send
				 * out a UUCP-command but the sender won't be
				 * ready for it until it receives our final
				 * ZACK.
				 */
				cerr = cZretries;
				wpZrxbytes += rxcount;
				if (!fzfinish_rx ())
					return FALSE;
				if (!fgot_data (zZrx_packet_buf, rxcount, FALSE, FALSE, &fexit))
					return FALSE;
				/* FIXME: Examine <fexit>? Or maybe ensure it's TRUE? */
				return TRUE;
			}
			return FALSE;
		}
		default:
			ulog (LOG_FATAL, "fzwait: received header %s",
				ZZHEADER_NAME(c));
			return FALSE;
		}
	}

	return TRUE;
}

/*
 * File level routine. Called when initiating/terminating file transfers.
 *
 * When starting to send a file:	(TRUE, TRUE, NULL, cbytes)
 * When starting to receive a file:	(TRUE, FALSE, NULL, -1)
 * When send EOF, check resend:		(FALSE, TRUE, &lredo, -1)
 * When receive EOF, check re-receive:	(FALSE, FALSE, &fredo, -1)
 */

boolean
fzfile(fstart, fsend, plredo, cbytes)
boolean fstart;
boolean fsend;
long *plredo;
long cbytes;
{
	DEBUG_MESSAGE2 (DEBUG_PROTO, "fzfile: fstart=%d, fsend=%d", fstart, fsend);
	if (plredo != NULL)
		*plredo = -1;

	if (fsend) {
		if (fstart)
			return fzstart_tx ();
		return fzfinish_tx (plredo);
	}

	return TRUE;
}

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

#if 0	/* not used, we only use 32 bit crc's */
/*
 * crctab calculated by Mark G. Mendel, Network Systems Corporation
 */

static unsigned short crctab[256] = {
    0x0000,  0x1021,  0x2042,  0x3063,  0x4084,  0x50a5,  0x60c6,  0x70e7,
    0x8108,  0x9129,  0xa14a,  0xb16b,  0xc18c,  0xd1ad,  0xe1ce,  0xf1ef,
    0x1231,  0x0210,  0x3273,  0x2252,  0x52b5,  0x4294,  0x72f7,  0x62d6,
    0x9339,  0x8318,  0xb37b,  0xa35a,  0xd3bd,  0xc39c,  0xf3ff,  0xe3de,
    0x2462,  0x3443,  0x0420,  0x1401,  0x64e6,  0x74c7,  0x44a4,  0x5485,
    0xa56a,  0xb54b,  0x8528,  0x9509,  0xe5ee,  0xf5cf,  0xc5ac,  0xd58d,
    0x3653,  0x2672,  0x1611,  0x0630,  0x76d7,  0x66f6,  0x5695,  0x46b4,
    0xb75b,  0xa77a,  0x9719,  0x8738,  0xf7df,  0xe7fe,  0xd79d,  0xc7bc,
    0x48c4,  0x58e5,  0x6886,  0x78a7,  0x0840,  0x1861,  0x2802,  0x3823,
    0xc9cc,  0xd9ed,  0xe98e,  0xf9af,  0x8948,  0x9969,  0xa90a,  0xb92b,
    0x5af5,  0x4ad4,  0x7ab7,  0x6a96,  0x1a71,  0x0a50,  0x3a33,  0x2a12,
    0xdbfd,  0xcbdc,  0xfbbf,  0xeb9e,  0x9b79,  0x8b58,  0xbb3b,  0xab1a,
    0x6ca6,  0x7c87,  0x4ce4,  0x5cc5,  0x2c22,  0x3c03,  0x0c60,  0x1c41,
    0xedae,  0xfd8f,  0xcdec,  0xddcd,  0xad2a,  0xbd0b,  0x8d68,  0x9d49,
    0x7e97,  0x6eb6,  0x5ed5,  0x4ef4,  0x3e13,  0x2e32,  0x1e51,  0x0e70,
    0xff9f,  0xefbe,  0xdfdd,  0xcffc,  0xbf1b,  0xaf3a,  0x9f59,  0x8f78,
    0x9188,  0x81a9,  0xb1ca,  0xa1eb,  0xd10c,  0xc12d,  0xf14e,  0xe16f,
    0x1080,  0x00a1,  0x30c2,  0x20e3,  0x5004,  0x4025,  0x7046,  0x6067,
    0x83b9,  0x9398,  0xa3fb,  0xb3da,  0xc33d,  0xd31c,  0xe37f,  0xf35e,
    0x02b1,  0x1290,  0x22f3,  0x32d2,  0x4235,  0x5214,  0x6277,  0x7256,
    0xb5ea,  0xa5cb,  0x95a8,  0x8589,  0xf56e,  0xe54f,  0xd52c,  0xc50d,
    0x34e2,  0x24c3,  0x14a0,  0x0481,  0x7466,  0x6447,  0x5424,  0x4405,
    0xa7db,  0xb7fa,  0x8799,  0x97b8,  0xe75f,  0xf77e,  0xc71d,  0xd73c,
    0x26d3,  0x36f2,  0x0691,  0x16b0,  0x6657,  0x7676,  0x4615,  0x5634,
    0xd94c,  0xc96d,  0xf90e,  0xe92f,  0x99c8,  0x89e9,  0xb98a,  0xa9ab,
    0x5844,  0x4865,  0x7806,  0x6827,  0x18c0,  0x08e1,  0x3882,  0x28a3,
    0xcb7d,  0xdb5c,  0xeb3f,  0xfb1e,  0x8bf9,  0x9bd8,  0xabbb,  0xbb9a,
    0x4a75,  0x5a54,  0x6a37,  0x7a16,  0x0af1,  0x1ad0,  0x2ab3,  0x3a92,
    0xfd2e,  0xed0f,  0xdd6c,  0xcd4d,  0xbdaa,  0xad8b,  0x9de8,  0x8dc9,
    0x7c26,  0x6c07,  0x5c64,  0x4c45,  0x3ca2,  0x2c83,  0x1ce0,  0x0cc1,
    0xef1f,  0xff3e,  0xcf5d,  0xdf7c,  0xaf9b,  0xbfba,  0x8fd9,  0x9ff8,
    0x6e17,  0x7e36,  0x4e55,  0x5e74,  0x2e93,  0x3eb2,  0x0ed1,  0x1ef0
};
#endif	/* crctab */

/*
 * Copyright (C) 1986 Gary S. Brown.  You may use this program, or
 * code or tables extracted from it, as desired without restriction.
 */

/* First, the polynomial itself and its table of feedback terms.  The  */
/* polynomial is                                                       */
/* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */
/* Note that we take it "backwards" and put the highest-order term in  */
/* the lowest-order bit.  The X^32 term is "implied"; the LSB is the   */
/* X^31 term, etc.  The X^0 term (usually shown as "+1") results in    */
/* the MSB being 1.                                                    */

/* Note that the usual hardware shift register implementation, which   */
/* is what we're using (we're merely optimizing it by doing eight-bit  */
/* chunks at a time) shifts bits into the lowest-order term.  In our   */
/* implementation, that means shifting towards the right.  Why do we   */
/* do it this way?  Because the calculated CRC must be transmitted in  */
/* order from highest-order term to lowest-order term.  UARTs transmit */
/* characters in order from LSB to MSB.  By storing the CRC this way,  */
/* we hand it to the UART in the order low-byte to high-byte; the UART */
/* sends each low-bit to hight-bit; and the result is transmission bit */
/* by bit from highest- to lowest-order term without requiring any bit */
/* shuffling on our part.  Reception works similarly.                  */

/* The feedback terms table consists of 256, 32-bit entries.  Notes:   */
/*                                                                     */
/*     The table can be generated at runtime if desired; code to do so */
/*     is shown later.  It might not be obvious, but the feedback      */
/*     terms simply represent the results of eight shift/xor opera-    */
/*     tions for all combinations of data and CRC register values.     */
/*                                                                     */
/*     The values must be right-shifted by eight bits by the "updcrc"  */
/*     logic; the shift must be unsigned (bring in zeroes).  On some   */
/*     hardware you could probably optimize the shift in assembler by  */
/*     using byte-swap instructions.                                   */

static long crc_32_tab[] = { /* CRC polynomial 0xedb88320 */
0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L,
0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L,
0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L,
0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L,
0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L,
0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL,
0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL,
0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L,
0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L,
0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L,
0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL,
0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L,
0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL,
0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L,
0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L,
0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L,
0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL,
0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L,
0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL,
0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL,
0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L,
0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L,
0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L,
0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL,
0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL,
0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL
};

/*
 * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell. 
 *  NOTE: First argument must be in range 0 to 255.
 *        Second argument is referenced twice.
 * 
 * Programmers may incorporate any or all code into their programs, 
 * giving proper credit within the source. Publication of the 
 * source routines is permitted so long as proper credit is given 
 * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg, 
 * Omen Technology.
 */

#define updcrc(cp, crc) (crctab[((crc >> 8) & 255)] ^ (crc << 8) ^ cp)
#define UPDC32(b, crc) (crc_32_tab[((int) (crc) ^ (b)) & 0xff] ^ (((crc) >> 8) & 0x00ffffffL))

/****************************************************************************/

/*
 * This section contains the guts of the Zmodem protocol. The intention
 * is to leave as much of it alone as possible at the start. Overtime it
 * will be cleaned up (EG: I'd like to clean up the naming of the globals).
 * Also, Zmodem has a different coding style. Over time this will be converted
 * to the Taylor UUCP coding style.
 */

/*
 * Start the protocol (exchange init packets) ...
 *
 * UUCP can transfer files in both directions in one session. Therefore the
 * init sequence is a little different.
 *
 * 1) ZINIT packets are exchanged
 *    - contains protocol version and protocol flags
 * 2) ZDATA packets are exchanged
 *    - is intended to contain various numeric and string information
 * 3) ZACK packets are exchanged
 * 4) ZINITEND packets are exchanged
 *
 * As with 'g', we ignore <fmaster>.
 */

static boolean
fzstart_proto(fmaster)
boolean fmaster;
{
	int i,n;
	achdrval_t tx_hdr,rx_hdr;

	for (i = 0; i < cZstartup_retries; i++) {
		stohdr (0L, tx_hdr);
		tx_hdr[ZF0] = ZPROTOCOL_VERSION;
		if (fZesc_ctl)
			tx_hdr[ZF1] |= TX_ESCCTL;
		switch (izexchange_init (fmaster, ZINIT, tx_hdr, rx_hdr)) {
		case -1: return FALSE;
		case 0:  continue;
		case 1:  break;
		}
#if 0	/* can't work, but kept for documentation */
		if (rx_hdr[ZF0] == 0) {
			ulog (LOG_ERROR, "Old protocol version, init failed");
			return FALSE;
		}
#endif
		fZesc_ctl = fZesc_ctl || (rx_hdr[ZF1] & TX_ESCCTL) != 0;

		stohdr (0L, tx_hdr);
		switch (izexchange_init (fmaster, ZDATA, tx_hdr, rx_hdr)) {
		case -1: return FALSE;
		case 0:  continue;
		case 1:  break;
		}

		stohdr (0L, tx_hdr);
		switch (izexchange_init (fmaster, ZACK, tx_hdr, rx_hdr)) {
		case -1: return FALSE;
		case 0:  continue;
		case 1:  break;
		}

		stohdr (0L, tx_hdr);
		switch (izexchange_init (fmaster, ZINITEND, tx_hdr, rx_hdr)) {
		case -1: return FALSE;
		case 0:  continue;
		case 1:  break;
		}

		DEBUG_MESSAGE0 (DEBUG_PROTO, "fzstart_proto: Protocol started");
		return TRUE;

		/* FIXME: see protg.c regarding sequencing here. */
	}

	ulog (LOG_ERROR, "Protocol init failed");
	return FALSE;
}

/*
 * Exchange init messages. This is based on 'g'.
 * See the comments concerning fgexchange_init() in protg.c.
 *
 * We return 1 for success, 0 for restart, -1 for comm failure (terminate).
 */

static int
izexchange_init(fmaster, send_type, send_val, recv_val)
boolean fmaster;
int send_type;
achdrval_t send_val;
achdrval_t recv_val;
{
	int i,recv_type,count;

	for (i = 0; i < CEXCHANGE_INIT_RETRIES; i++) {
		if (!fzsend_hdr (send_type == ZDATA ? ZBIN : ZHEX,
				send_type, rclhdr (send_val), FALSE))
			return -1;

		/*
		 * The ZDATA packet is intended to contain the <Attn> string
		 * (eventually, if it's ever usable) and allow for anything
		 * else that will need to be thrown in.
		 */

		if (send_type == ZDATA) {
			count = czbuild_data_packet (zZtx_packet_buf, "", 1, ZCRCF);
			if (!fsend_data (zZtx_packet_buf, count, FALSE))
				return -1;
		}

		recv_type = izrecv_hdr (recv_val);

		switch (recv_type) {
		case ZM_TIMEOUT:
		case ZM_ERROR:
			continue;
		case ZM_RCDO:
		case ZFIN:
			return -1;
		case ZINIT:
		case ZACK:
		case ZINITEND:
			break;
		case ZDATA:
			if (zrdat32 (zZrx_packet_buf, 1024, &count) == GOTCRCF)
				break;
			continue;
		default:
			continue;
		}

		if (recv_type == send_type)
			return 1;

		/*
		 * If the other side is farther along than we are, we have lost
		 * a packet.  Fall immediately back to ZINIT (but don't fail
		 * if we are already doing ZINIT, since that would count
		 * against cStart_retries more than it should).
		 *
		 * FIXME: The ">" test is "<" in protg.c. Check who's right.
		 */

		if (recv_type > send_type && send_type != ZINIT)
			return 0;

		/*
		 * If we are sending ZINITEND and we receive an ZINIT, the
		 * other side has falled back (we know this because we have
		 * seen a ZINIT from them).  Fall back ourselves to start
		 * the whole handshake over again.
		 */

		if (recv_type == ZINIT && send_type == ZINITEND)
			return 0;
	}

	return 0;
}

/*
 * Shut down the protocol ...
 */

static boolean
fzshutdown_proto()
{
	(void) fzsend_hdr (ZHEX, ZFIN, 0L, FALSE);
	return TRUE;
}

/*
 * Reset the transmitter side for sending a new message ...
 */

static boolean
fzstart_tx()
{
	iZlast_tx_data_packet = -1;

	/*
	 * <wpZlastsync> is set to -1L to suppress ZCRCW request otherwise
	 * triggered by (wpZlastsync == wpZtxpos).
	 */

	cZblklen = 1024;
	wpZlastsync = -1L;
	iZbeenhereb4 = 0;
	iZtleft = 0;
	iZjunk_count = 0;

	wpZtxpos = wpZtxpos + 1024L & ~1023L;	/* next packet boundary */
	wpZlrxpos = wpZrxpos = wpZtxpos;

	wpZtxstart = wpZtxpos;	/* so we can compute the "file offset" */

	return TRUE;
}

/*
 * Finish the sending of a message ...
 *
 * Basically, we wait for some indication that the receiver received our last
 * message. If the receiver tells us to restart from some point, we set
 * *plredo to that point.
 *
 * FIXME: This function is a major kludge at the moment. It is taken from
 *	getinsync(). It is necessary because I don't yet buffer outgoing data.
 *	It will go away when we do (buffer outgoing data).
 */

static boolean
fzfinish_tx(long *plredo)
{
	int c,cerr,ctimeouts;
	achdrval_t rx_hdr;
	winpos_t rx_bytes;

	*plredo = -1;
	cerr = cZretries;
	ctimeouts = 0;

	DEBUG_MESSAGE4 (DEBUG_PROTO,
		"fzfinish_tx: txpos=0x%lx, rxpos=0x%lx, lrxpos=0x%lx, rxbytes=0x%lx",
		wpZtxpos, wpZrxpos, wpZlrxpos, wpZrxbytes);

	for (;;) {
		c = izrecv_hdr (rx_hdr);

		switch (c) {
		case ZRPOS:
			wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, wpZlrxpos, wpZtxpos);
			/*
			 * If the receiver sends a ZRPOS for the 1k block after
			 * the one we're currently at, we lost the final ZACK.
			 * We cheat and ignore this ZRPOS. Remember: the theory
			 * is that this entire function will go away when we
			 * begin buffering the outgoing data. Of course, one
			 * can reword the protocol definition and say this
			 * isn't cheating at all.
			 */
			if ((wpZtxpos + 1024 & ~1023) == wpZrxpos)
				return TRUE;
			cZbytes_resent += wpZtxpos - wpZrxpos;
			wpZlrxpos = wpZtxpos = wpZrxpos;
			if (wpZlastsync == wpZrxpos) {
				if (++iZbeenhereb4 > 4)
					if (cZblklen > 32)
						cZblklen /= 2;
				/* FIXME: shouldn't we reset iZbeenhereb4? */
			}
			wpZlastsync = wpZrxpos;
			iZlast_tx_data_packet = ZCRCW; /* force a timeout */
			*plredo = wpZrxpos - wpZtxstart;
			return TRUE;
		case ZACK:
			wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, wpZlrxpos, wpZtxpos);
			wpZlrxpos = wpZrxpos;
			if (wpZtxpos == wpZrxpos)	/* is this the ACK we want? */
				return TRUE;
			break;
		case ZDATA:
			/*
			 * We cheat here and take advantage of UUCP's current
			 * half duplex nature. If we get a ZDATA starting on
			 * the next 1k boundary, we lost the ZACK. We cheat and
			 * tuck it away so that izrecv_hdr() can later detect
			 * it. Remember: see above.
			 */
			zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes);
			if ((wpZrxbytes + 1024L & ~1023L) == rx_bytes) {
				iZpkt_rcvd_kludge = ZDATA;
				hvZpkt_hdrval_kludge = rclhdr (rx_hdr);
				return TRUE;
			}
			break;	/* ignore, out of sync (old) */
		case ZNAK:
			/*
			 * We cheat here and take advantage of UUCP's current
			 * half duplex nature. If we get a ZNAK starting on
			 * the next 1k boundary, we lost the ZACK. We cheat and
			 * throw the ZNAK away. Remember: see above.
			 *
			 * On the other hand, if (rx_bytes == wpZrxbytes) then
			 * the other side is also in fzfinish_tx(). He must
			 * have lost our ZACK, so we send him another.
			 */
			zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes);
			if ((wpZrxbytes + 1024L & ~1023L) == rx_bytes)
				return TRUE;
			if (rx_bytes == wpZrxbytes) {
				if (!fzsend_hdr (ZHEX, ZACK, hvzencode_data_hdr (wpZrxbytes), TRUE))
					return FALSE;
			}
			break;	/* ignore, out of sync (old) */
		case ZFIN:
		case ZM_RCDO:
			return FALSE;
		case ZM_TIMEOUT:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "fzfinish_tx: retries exhausted");
				return FALSE;
			}
			/*
			 * Normally the sender doesn't send NAK's for timeouts.
			 * We have to here because of the following scenario:
			 *
			 * - We send ZDATA/ZCRCF
			 * - They send ZACK (corrupted)
			 * - They send ZDATA/ZCRCF (corrupted)
			 *
			 * At this point, both sides are in fzfinish_tx().
			 * We only send ZNAK every second timeout to increase
			 * our timeout delay vs. our partner. This tries to
			 * avoid ZRPOS and ZNAK "passing in transit".
			 */
			if (++ctimeouts % 2 == 0)
				if (!fzsend_hdr (ZHEX, ZNAK, hvzencode_data_hdr (wpZtxpos), TRUE))
					return FALSE;
			break;
		case ZM_ERROR:
		default:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "fzfinish_tx: retries exhausted");
				return FALSE;
			}
			if (!fzsend_hdr (ZHEX, ZNAK, hvzencode_data_hdr (wpZtxpos), TRUE))
				return FALSE;
			break;
		}
	}
}

/*
 * Initialize the receiver ...
 */

static boolean
fzstart_rx()
{
	wpZrxbytes = wpZrxbytes + 1024L & ~1023L; /* next packet boundary */

	return TRUE;
}

/*
 * Terminate the receiver ...
 *
 * Acknowledge the last packet received.
 */

static boolean
fzfinish_rx()
{
	DEBUG_MESSAGE0 (DEBUG_PROTO, "fzfinish_rx: message/file received");

	return fzsend_hdr (ZHEX, ZACK, hvzencode_data_hdr (wpZrxbytes), FALSE);
}

/*
 * Send a Zmodem header to our partner ...
 */

static boolean
fzsend_hdr(ipkttype, ihdrtype, hdrval, fcheckreceive)
int ipkttype;
int ihdrtype;
hdrval_t hdrval;
boolean fcheckreceive;
{
	int cpacketlen;

	DEBUG_MESSAGE2 (DEBUG_PROTO, "fzsend_hdr: %s, data = 0x%lx",
		ZZHEADER_NAME(ihdrtype), hdrval);

	cpacketlen = czbuild_header (zZtx_packet_buf, ipkttype, ihdrtype, hdrval);

#ifdef DJE_TESTING
#if 0
	if (ihdrtype == ZACK && rand () % 100 < uucptest2) {
		cZheaders_sent++;
		return TRUE;
	}
#else
	if (ihdrtype == ZACK || ihdrtype == ZDATA) {
		boolean fresult;
		int old;
		extern int uucptest,uucptest2;

		old = uucptest;
		uucptest = uucptest2;
		cZheaders_sent++;
		fresult = fsend_data (zZtx_packet_buf, cpacketlen, fcheckreceive);
		uucptest = old;
		return fresult;
	}
#endif
#endif
	cZheaders_sent++;
	return fsend_data (zZtx_packet_buf, cpacketlen, fcheckreceive);
}

/*
 * Send a data packet to our partner ...
 * <frameend> is one of ZCRCx.
 */

static boolean
fzsend_data_packet(zdata, cdata, frameend, fcheckreceive)
char *zdata;
int cdata;
int frameend;
boolean fcheckreceive;
{
	int cpacketlen;

	cpacketlen = czbuild_data_packet (zZtx_packet_buf, zdata, cdata, frameend);

	return fsend_data (zZtx_packet_buf, cpacketlen, fcheckreceive);
}

/*
 * Build Zmodem headers ...
 *
 * Note that we use 32 bit CRC's for ZHEX headers.
 *
 * This function is a combination of zm fns: zsbhdr(), zsbh32(), and zshhdr().
 */

static int
czbuild_header(zresult, ipkttype, ihdrtype, hdrval)
char *zresult;
int ipkttype;
int ihdrtype;
hdrval_t hdrval;
{
	char *p;
	int i;
	long crc;
	achdrval_t achdrval;

	p = zresult;

	switch (ipkttype) {
	case ZBIN:
		*p++ = ZPAD;
		*p++ = ZDLE;
		*p++ = ZBIN;
		p = zputchar (p, ihdrtype);
		crc = 0xffffffffL;
		crc = UPDC32 (ihdrtype, crc);
		stohdr (hdrval, achdrval);
		for (i = 0; i < 4; i++) {
			p = zputchar (p, achdrval[i]);
			crc = UPDC32 (achdrval[i], crc);
		}
		crc = ~crc;
		for (i = 0; i < 4; i++) {
			p = zputchar (p, (char) crc);
			crc >>= 8;
		}
		break;
	case ZHEX: 	/* build hex header */
		*p++ = ZPAD;
		*p++ = ZPAD;
		*p++ = ZDLE;
		*p++ = ZHEX;
		p = zputhex (p, ihdrtype);
		crc = 0xffffffffL;
		crc = UPDC32 (ihdrtype, crc);
		stohdr (hdrval, achdrval);
		for (i = 0; i < 4; i++) {
			p = zputhex (p, achdrval[i]);
			crc = UPDC32 (achdrval[i], crc);
		}
		crc = ~crc;
		for (i = 0; i < 4; i++) {
			p = zputhex (p, (char) crc);
			crc >>= 8;
		}
		*p++ = CR;
		/*
		 * Uncork the remote in case a fake XOFF has stopped data flow.
		 */
		if (ihdrtype != ZFIN && ihdrtype != ZACK) /* FIXME: why? */
			*p++ = XON;
		break;
	default:
		ulog (LOG_FATAL, "czbuild_header: ipkttype == %d", ipkttype);
		break;
	}

	return p - zresult;
}

/*
 * Build Zmodem data packets ...
 *
 * This function is zsdata() and zsda32() from the zm source.
 */

static int
czbuild_data_packet(zresult, zdata, cdata, frameend)
char *zresult;
const char *zdata;
int cdata;
int frameend;
{
	char *p;
	long crc;

	p = zresult;

	crc = 0xffffffffL;
	for ( ; --cdata >= 0; zdata++) {
		char c;

		c = *zdata;
		if (c & 0140)
			*p++ = c;
		else
			p = zputchar (p, c);
		crc = UPDC32 ((unsigned char) c, crc);
	}
	*p++ = ZDLE;
	*p++ = frameend;
	crc = UPDC32 (frameend, crc);
	crc = ~crc;
	for (cdata = 0; cdata < 4; cdata++) {
		p = zputchar (p, (char) crc);
		crc >>= 8;
	}
	if (frameend == ZCRCW || frameend == ZCRCE || frameend == ZCRCF) {
		*p++ = CR;
		*p++ = XON;
	}

	return p - zresult;
}

/*
 * Read in a header ...
 *
 * This is function zgethdr() from the Zmodem source.
 */

static int
izrecv_hdr(hdr)
achdrval_t hdr;
{
	int c,cerr;

	/*
	 * Kludge alert! If another part of the program received a packet but
	 * wasn't ready to handle it, it is tucked away for us to handle now.
	 */

	if (iZpkt_rcvd_kludge != -1) {
		c = iZpkt_rcvd_kludge;
		iZpkt_rcvd_kludge = -1;
		stohdr (hvZpkt_hdrval_kludge, hdr);
		DEBUG_MESSAGE2 (DEBUG_PROTO,
			"izrecv_hdr: queued %s, data = 0x%lx",
			ZZHEADER_NAME(c), rclhdr (hdr));
		cZheaders_received++;
		return c;
	}

	cerr = cZmax_garbage;	/* Max bytes before start of frame */

again:
	switch (c = noxrd7 ()) {
	case ZM_TIMEOUT:
	case ZM_ERROR:
	case ZM_RCDO:
		goto fifi;
	case ZPAD:		/* This is what we want */
		break;
	case CR:		/* padding at end of previous header */
	default:
		if (--cerr < 0) {
			c = ZM_ERROR;
			goto fifi;
		}
		goto again;
	}

splat:
	switch (c = noxrd7 ()) {
	case ZPAD:
		if (--cerr < 0) {
			c = ZM_ERROR;
			goto fifi;
		}
		goto splat;
	case ZM_TIMEOUT:
	case ZM_RCDO:
		goto fifi;
	case ZDLE:		/* This is what we want */
		break;
	default:
		if (--cerr < 0) {
			c = ZM_ERROR;
			goto fifi;
		}
		goto again;
	}

	switch (c = noxrd7 ()) {
	case ZM_TIMEOUT:
	case ZM_RCDO:
		goto fifi;
	case ZBIN:
		c = zrbhdr32 (hdr);
		break;
	case ZHEX:
		c = zrhhdr (hdr);
		break;
	default:
		if (--cerr < 0) {
			c = ZM_ERROR;
			goto fifi;
		}
		goto again;
	}

fifi:
	switch (c) {
	case ZM_TIMEOUT:
		cZtimeouts++;
		break;
	case ZM_ERROR:
		cZerrors++;
		break;
	case ZM_RCDO:
		break;
	default:
		cZheaders_received++;
		break;
	}
	DEBUG_MESSAGE2 (DEBUG_PROTO, "izrecv_hdr: %s, data = 0x%x",
		ZZHEADER_NAME(c), rclhdr (hdr));

	return c;
}

/*
 * Receive a binary style header (type and position) with 32 bit FCS ...
 */

static int
zrbhdr32(hdr)
achdrval_t hdr;
{
	int c,i,type;
	long crc;

	if ((c = zdlread ()) & ~0377)
		return c;
	type = c;
	crc = 0xffffffffL;
	crc = UPDC32 (c, crc);

	for (i = 0; i < 4; i++) {
		if ((c = zdlread ()) & ~0377)
			return c;
		crc = UPDC32 (c, crc);
		hdr[i] = (char) c;
	}
	for (i = 0; i < 4; i++) {
		if ((c = zdlread ()) & ~0377)
			return c;
		crc = UPDC32 (c, crc);
	}
	if (crc != 0xDEBB20E3L)
		return ZM_ERROR;

	return type;
}

/*
 * Receive a hex style header (type and position) ...
 */

static int
zrhhdr(hdr)
achdrval_t hdr;
{
	int c,i,type;
	long crc;

	if ((c = zgethex ()) < 0)
		return c;
	type = c;
	crc = 0xffffffffL;
	crc = UPDC32 (c, crc);

	for (i = 0; i < 4; i++) {
		if ((c = zgethex ()) < 0)
			return c;
		crc = UPDC32 (c, crc);
		hdr[i] = (char) c;
	}
	for (i = 0; i < 4; i++) {
		if ((c = zgethex ()) < 0)
			return c;
		crc = UPDC32 (c, crc);
	}
	if (crc != 0xDEBB20E3L)
		return ZM_ERROR;

	return type;
}

/*
 * Receive a data packet ...
 */

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

	crc = 0xffffffffL;
	*iprxcount = 0;
	end = buf + length;
	while (buf <= end) {
		if ((c = zdlread ()) & ~0377) {
crcfoo:
			switch (c) {
			case GOTCRCE:
			case GOTCRCG:
			case GOTCRCQ:
			case GOTCRCW:
			case GOTCRCF:
				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 != 0xDEBB20E3L)
					return ZM_ERROR;
				*iprxcount = length - (end - buf);
				return d;
			case ZM_TIMEOUT:
			case ZM_RCDO:
				return c;
			default:
				return ZM_ERROR;
			}
		}
		*buf++ = (char) c;
		crc = UPDC32 (c, crc);
	}

	return ZM_ERROR;	/* bad packet, too long */
}

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

static int
getinsync(flag)
boolean flag;
{
	int c,cerr;
	achdrval_t rx_hdr;

	cerr = cZretries;

	for (;;) {
		c = izrecv_hdr (rx_hdr);

		switch (c) {
		case ZRPOS:
			wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, wpZlrxpos, wpZtxpos);
			cZbytes_resent += wpZtxpos - wpZrxpos;
			wpZlrxpos = wpZtxpos = wpZrxpos;
			if (wpZlastsync == wpZrxpos) {
				if (++iZbeenhereb4 > 4)
					if (cZblklen > 32)
						cZblklen /= 2;
				/* FIXME: shouldn't we reset iZbeenhereb4? */
			}
			wpZlastsync = wpZrxpos;
			return ZRPOS;
		case ZACK:
			wpZrxpos = lzupdate_rxpos (rx_hdr, wpZrxpos, wpZlrxpos, wpZtxpos);
			wpZlrxpos = wpZrxpos;
			if (flag || wpZtxpos == wpZrxpos)
				return ZACK;
			break;
		case ZNAK: {
			winpos_t rx_bytes;
			/*
			 * Our partner is in fzfinish_tx() and is waiting
			 * for ZACK ...
			 */
			zdecode_data_hdr (rclhdr (rx_hdr), &rx_bytes);
			if (rx_bytes == wpZrxbytes) {
				if (!fzsend_hdr (ZHEX, ZACK, hvzencode_data_hdr (wpZrxbytes), TRUE))
					return FALSE;
			}
			break;
		}
		case ZFIN:
		case ZM_RCDO:
			return c;
		case ZM_TIMEOUT:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "getinsync: retries exhausted");
				return ZM_ERROR;
			}
			break;	/* sender doesn't send ZNAK for timeout */
		case ZM_ERROR:
		default:
			if (--cerr < 0) {
				ulog (LOG_ERROR, "getinsync: retries exhausted");
				return ZM_ERROR;
			}
			if (!fzsend_hdr (ZHEX, ZNAK, hvzencode_data_hdr (wpZtxpos), TRUE))
				return ZM_ERROR;
			break;
		}
	}
}

/*
 * Send a byte as two hex digits ...
 */

static char *
zputhex(p, ch)
char *p;
int ch;
{
	static char digits[] = "0123456789abcdef";

	*p++ = digits[(ch & 0xF0) >> 4];
	*p++ = digits[ch & 0xF];
	return p;
}

/*
 * Send character c with ZMODEM escape sequence encoding ...
 *
 * Escape XON, XOFF.
 * FIXME: Escape CR following @ (Telenet net escape) ... disabled for now
 *	Will need to put back references to <lastsent>.
 */

static char *
zputchar(p, ch)
char *p;
int ch;
{
	char c = ch;

	/* Quick check for non control characters */

	if (c & 0140) {
		*p++ = c;
	} else {
		switch (c & 0377) {
		case ZDLE:
			*p++ = ZDLE;
			*p++ = c ^ 0100;
			break;
		case CR:
#if 0
			if (!fZesc_ctl && (lastsent & 0177) != '@')
				goto sendit;
#endif
			/* fall through */
		case 020:	/* ^P */
		case XON:
		case XOFF:
			*p++ = ZDLE;
			c ^= 0100;
/*sendit:*/
			*p++ = c;
			break;
		default:
			if (fZesc_ctl && !(c & 0140)) {
				*p++ = ZDLE;
				c ^= 0100;
			}
			*p++ = c;
			break;
		}
	}

	return p;
}

/*
 * Decode two lower case hex digits into an 8 bit byte value ...
 */

static int
zgethex()
{
	int c,n;

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

	return c;
}

/*
 * Read a byte, checking for ZMODEM escape encoding ...
 */

static int
zdlread()
{
	int c;

again:
	READCHAR (c, cZtimeout);
	if (c < 0)
		return c;
	if (c & 0140)		/* quick check for non control characters */
		return c;
	switch (c) {
	case ZDLE:
		break;
	case XON:
		goto again;
	case XOFF:
		READCHAR (c, XON_WAIT);
		goto again;
	default:
		if (fZesc_ctl && !(c & 0140))
			goto again;
		return c;
	}

again2:
	READCHAR (c, cZtimeout);
	if (c < 0)
		return c;
	switch (c) {
	case ZCRCE:
	case ZCRCG:
	case ZCRCQ:
	case ZCRCW:
	case ZCRCF:
		return c | GOTOR;
	case ZRUB0:			/* FIXME: This is never generated. */
		return 0177;
	case ZRUB1:			/* FIXME: This is never generated. */
		return 0377;
	case XON:
		goto again2;
	case XOFF:
		READCHAR (c, XON_WAIT);
		goto again2;
	default:
		if (fZesc_ctl && !(c & 0140))
			goto again2;		/* FIXME: why again2? */
		if ((c & 0140) == 0100)
			return c ^ 0100;
		break;
	}

	return ZM_ERROR;
}

/*
 * Read a character from the modem line with timeout ...
 * Eat parity bit, XON and XOFF characters.
 */

static int
noxrd7()
{
	int c;

	for (;;) {
		READCHAR (c, cZtimeout);
		if (c < 0)
			return c;
		switch (c &= 0177) {
		case XON:
			continue;
		case XOFF:
			READCHAR (c, XON_WAIT);
			continue;
		case CR:
		case ZDLE:
			return c;
		default:
			if (fZesc_ctl && !(c & 0140))
				continue;
			return c;
		}
	}
}

/*
 * Read a character from the receive buffer, or from the line if empty ...
 *
 * <timeout> is in seconds (maybe make it tenths of seconds like in Zmodem?)
 */

static int
realreadchar(timeout)
int timeout;
{
	int c;

	if ((c = breceive_char (timeout, TRUE)) >= 0)
		return c;

	switch (c) {
	case -1:
		return ZM_TIMEOUT;
	case -2:
		return ZM_RCDO;
	}

	ulog (LOG_FATAL, "realreadchar: breceive_char() returned %d", c);
	return ZM_ERROR;
}

/*
 * Check if the receive channel has any characters in it.
 *
 * At present we can only test the receive buffer. No mechanism is available
 * to go to the hardware. This should not be a problem though, as long as all
 * appropriate calls to fsend_data() set <fdoread> to TRUE.
 */

static boolean
fzreceive_ready()
{
	return iPrecstart != iPrecend;
}

/*
 * Store integer value in an achdrval_t ...
 */

static void
stohdr(val, hdr)
hdrval_t val;
achdrval_t hdr;
{
	hdr[ZP0] = (char) val;
	hdr[ZP1] = (char) (val >> 8);
	hdr[ZP2] = (char) (val >> 16);
	hdr[ZP3] = (char) (val >> 24);
}

/*
 * Recover an integer from a header ...
 */

static hdrval_t
rclhdr(hdr)
achdrval_t hdr;
{
	hdrval_t v;

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

	return v;
}

/*
 * Encode a <hdrval_t> from the byte count ...
 *
 * We use to store the byte count / 32 and a message sequence number which
 * made this function very useful. Don't remove it.
 * FIXME: Well, maybe remove it later.
 */

static hdrval_t
hvzencode_data_hdr(cbytes)
winpos_t cbytes;
{
	return (hdrval_t) cbytes;
}

/*
 * Decode a <hdrval_t> into a byte count ...
 *
 * We use to store the byte count / 32 and a message sequence number which
 * made this function very useful. Don't remove it.
 * FIXME: Well, maybe remove it later.
 */

static void
zdecode_data_hdr(hdrval, pcbytes)
hdrval_t hdrval;
winpos_t *pcbytes;
{
	*pcbytes = hdrval;
}

/*
 * Update <wpZrxpos> from the received data header value ...
 *
 * FIXME: Here is where we'd handle wrapping around at 4 gigabytes.
 */

static winpos_t
lzupdate_rxpos(rx_hdr, rxpos, lrxpos, txpos)
achdrval_t rx_hdr;
winpos_t rxpos,lrxpos,txpos;
{
	winpos_t rx_pktpos;

	zdecode_data_hdr (rclhdr (rx_hdr), &rx_pktpos);

	DEBUG_MESSAGE4 (DEBUG_PROTO,
		"lzupdate_rxpos: rx_pktpos=0x%lx, rxpos=0x%lx, lrxpos=0x%lx, txpos=0x%lx",
		rx_pktpos, rxpos, lrxpos, txpos);

	/*
	 * Check if <rx_pktpos> valid. It could be old.
	 */

	if (rx_pktpos < wpZlrxpos || rx_pktpos > (wpZtxpos + 1024L & ~1023L))
		return rxpos;

	return rx_pktpos;
}
