diff --git a/src/sbbs3/sexyz.c b/src/sbbs3/sexyz.c
old mode 100644
new mode 100755
index 67e2339efbcf097f9f14d4d0ab9c0d4f3f34f0f2..0ea352680022246729e2c73ee7d2bf21ff8f0431
--- a/src/sbbs3/sexyz.c
+++ b/src/sbbs3/sexyz.c
@@ -18,7 +18,7 @@
  *																			*
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
  ****************************************************************************/
-/* 
+/*
  * ZMODEM code based on zmtx/zmrx v1.02 (C) Mattheij Computer Service 1994
  * by Jacques Mattheij
  *
@@ -87,7 +87,7 @@
 /* Global Vars */
 /***************/
 long	mode=0;							/* Program mode 					*/
-long	zmode=0L;						/* Zmodem mode						*/
+long	zmode=0L;						/* ZMODEM mode						*/
 uchar	block[XMODEM_MAX_BLOCK_SIZE];	/* Block buffer 					*/
 ulong	block_num;						/* Block number 					*/
 char*	dszlog;
@@ -106,7 +106,7 @@ FILE*	errfp;
 FILE*	statfp;
 FILE*	logfp=NULL;
 
-char	revision[16];
+const char*	revision = "3.0";
 
 SOCKET	sock=INVALID_SOCKET;
 
@@ -604,7 +604,7 @@ int send_byte(void* unused, uchar ch, unsigned timeout)
 		fprintf(statfp,"\b\b\b\b    \b\b\b\b");
 		if(result!=WAIT_OBJECT_0) {
 			lprintf(LOG_WARNING
-				,"!TIMEOUT (%d) waiting for output buffer to flush (%u seconds, %u bytes)\n"
+				,"TIMEOUT (%d) waiting for output buffer to flush (%u seconds, %u bytes)"
 				,result, timeout, RingBufFull(&outbuf));
 			fprintf(statfp
 				,"\n!TIMEOUT (%d) waiting for output buffer to flush (%u seconds, %u bytes)\n"
@@ -972,7 +972,7 @@ static int send_files(char** fname, uint fnames)
 
 			if(mode&ZMODEM)
 				success=zmodem_send_file(&zm, path, fp, /* ZRQINIT? */fnum==0, &startfile, &sent_bytes);
-			else	/* X/Ymodem */
+			else	/* X/YMODEM */
 				success=xmodem_send_file(&xm, path, fp, &startfile, &sent_bytes);
 
 			fclose(fp);
@@ -1138,7 +1138,7 @@ static int receive_files(char** fname_list, int fnames)
 				lprintf(LOG_DEBUG,"YMODEM header (%u fields): %s", i, block+strlen((char*)block)+1);
 				SAFECOPY(fname,(char*)block);
 
-			} else {	/* Zmodem */
+			} else {	/* ZMODEM */
 				lprintf(LOG_INFO,"Waiting for ZMODEM sender...");
 
 				i=zmodem_recv_init(&zm);
@@ -1415,17 +1415,18 @@ static const char* usage=
 #endif
 	"\n"
 	"opts   = -y  allow overwriting of existing files when receiving\n"
-	"         -o  disable Zmodem CRC-32 mode (use CRC-16)\n"
-	"         -s  disable Zmodem streaming (Slow Zmodem)\n"
-	"         -k  enable X/Ymodem-1K send mode\n"
-    "         -c  enable Xmodem-CRC receive mode\n"
-	"         -g  enable X/Ymodem-G receive mode (no error recovery)\n"
-	"         -2  set maximum Zmodem block size to 2K\n"
-	"         -4  set maximum Zmodem block size to 4K\n"
-	"         -8  set maximum Zmodem block size to 8K (ZedZap)\n"
-	"         -m# set maximum receive file size to # bytes (0=unlimited, default=%u)\n"
-	"         -!  to pause after abnormal exit (error)\n"
+	"         -k  enable X/YMODEM-1K send mode\n"
+	"         -c  enable XMODEM-CRC receive mode\n"
+	"         -g  enable X/YMODEM-G receive mode (no error recovery)\n"
+	"         -o  disable ZMODEM CRC-32 mode (use CRC-16)\n"
+	"         -s  use segmented ZMODEM (disable streaming)\n"
+	"         -2  set maximum ZMODEM block size to 2K\n"
+	"         -4  set maximum ZMODEM block size to 4K\n"
+	"         -8  set maximum ZMODEM block size to 8K (ZedZap)\n"
+	"         -w# set maximum ZMODEM transmit window size (default=0, unlimited)\n"
+	"         -m# set maximum receive file size to # bytes (default=0, unlimited)\n"
 	"         -l  lowercase received filenames\n"
+	"         -!  to pause after abnormal exit (error)\n"
 #ifdef __unix__
 	"         -telnet to enable Telnet mode (the default except in stdio mode)\n"
 #else
@@ -1434,11 +1435,11 @@ static const char* usage=
 	"         -rlogin or -ssh or -raw to disable Telnet mode\n"
 	"\n"
 	"cmd    = v  to display detailed version information\n"
-	"         sx to send Xmodem     rx to receive Xmodem\n"
-	"         sX to send Xmodem-1K  rc to receive Xmodem-CRC\n"
-	"         sy to send Ymodem     ry to receive Ymodem\n"
-	"         sY to send Ymodem-1K  rg to receive Ymodem-G\n"
-	"         sz to send Zmodem     rz to receive Zmodem\n"
+	"         sx to send XMODEM     rx to receive XMODEM\n"
+	"         sX to send XMODEM-1K  rc to receive XMODEM-CRC\n"
+	"         sy to send YMODEM     ry to receive YMODEM\n"
+	"         sY to send YMODEM-1K  rg to receive YMODEM-G\n"
+	"         sz to send ZMODEM     rz to receive ZMODEM\n"
 	"\n"
 	"file   = filename to send or receive\n"
 	"path   = directory to receive files into\n"
@@ -1508,7 +1509,6 @@ int main(int argc, char **argv)
 	int		retval;
 	uint	fnames=0;
 	FILE*	fp;
-	BOOL	tcp_nodelay;
 	char	compiler[32];
 	BOOL	telnet_requested=FALSE;
 	str_list_t ini = strListInit();
@@ -1525,13 +1525,10 @@ int main(int argc, char **argv)
 	statfp=stdout;
 #endif
 
-	sscanf("$Revision: 2.10 $", "%*s %s", revision);
-
 	fprintf(statfp,"\nSynchronet External X/Y/ZMODEM  v%s-%s"
-		"  Copyright %s Rob Swindell\n\n"
+		"  Copyright Rob Swindell\n\n"
 		,revision
 		,PLATFORM_DESC
-		,&__DATE__[7]
 		);
 
 	xmodem_init(&xm,NULL,&mode,lputs,xmodem_progress,send_byte,recv_byte,is_connected,NULL,flush);
@@ -1555,8 +1552,6 @@ int main(int argc, char **argv)
 		fclose(fp);
 	}
 
-	tcp_nodelay				=iniGetBool(ini, ROOT_SECTION,"TCP_NODELAY",TRUE);
-
 	telnet					=iniGetBool(ini, ROOT_SECTION,"Telnet",TRUE);
 	debug_tx				=iniGetBool(ini, ROOT_SECTION,"DebugTx",FALSE);
 	debug_rx				=iniGetBool(ini, ROOT_SECTION,"DebugRx",FALSE);
@@ -1578,36 +1573,42 @@ int main(int argc, char **argv)
 	if(iniGetBool(ini, ROOT_SECTION,"Debug",FALSE))
 		log_level=LOG_DEBUG;
 
-	xm.send_timeout			=iniGetInteger(ini, "Xmodem","SendTimeout",xm.send_timeout);	/* seconds */
-	xm.recv_timeout			=iniGetInteger(ini, "Xmodem","RecvTimeout",xm.recv_timeout);	/* seconds */
-	xm.byte_timeout			=iniGetInteger(ini, "Xmodem","ByteTimeout",xm.byte_timeout);	/* seconds */
-	xm.ack_timeout			=iniGetInteger(ini, "Xmodem","AckTimeout",xm.ack_timeout);	/* seconds */
-	xm.block_size			=(ulong)iniGetBytes(ini, "Xmodem","BlockSize",1,xm.block_size);			/* 128 or 1024 */
-	xm.max_block_size		=(ulong)iniGetBytes(ini, "Xmodem","MaxBlockSize",1,xm.max_block_size);	/* 128 or 1024 */
-	xm.max_errors			=iniGetInteger(ini, "Xmodem","MaxErrors",xm.max_errors);
-	xm.g_delay				=iniGetInteger(ini, "Xmodem","G_Delay",xm.g_delay);
-	xm.crc_mode_supported	=iniGetBool(ini, "Xmodem","SendCRC",xm.crc_mode_supported);
-	xm.g_mode_supported		=iniGetBool(ini, "Xmodem","SendG",xm.g_mode_supported);
-
-	xm.fallback_to_xmodem	=iniGetInteger(ini, "Ymodem","FallbackToXmodem", xm.fallback_to_xmodem);
-
-	zm.init_timeout			=iniGetInteger(ini, "Zmodem","InitTimeout",zm.init_timeout);	/* seconds */
-	zm.send_timeout			=iniGetInteger(ini, "Zmodem","SendTimeout",zm.send_timeout);	/* seconds */
-	zm.recv_timeout			=iniGetInteger(ini, "Zmodem","RecvTimeout",zm.recv_timeout);	/* seconds */
-	zm.crc_timeout			=iniGetInteger(ini, "Zmodem","CrcTimeout",zm.crc_timeout);	/* seconds */
-	zm.block_size			=(ulong)iniGetBytes(ini, "Zmodem","BlockSize",1,zm.block_size);	/* 1024  */
-	zm.max_block_size		=(ulong)iniGetBytes(ini, "Zmodem","MaxBlockSize",1,zm.max_block_size); /* 1024 or 8192 */
-	zm.max_errors			=iniGetInteger(ini, "Zmodem","MaxErrors",zm.max_errors);
-	zm.recv_bufsize			=(ulong)iniGetBytes(ini, "Zmodem","RecvBufSize",1,0);
-	zm.no_streaming			=!iniGetBool(ini, "Zmodem","Streaming",TRUE);
-	zm.want_fcs_16			=!iniGetBool(ini, "Zmodem","CRC32",TRUE);
-	zm.escape_telnet_iac	=iniGetBool(ini, "Zmodem","EscapeTelnetIAC",TRUE);
-	zm.escape_8th_bit		=iniGetBool(ini, "Zmodem","Escape8thBit",FALSE);
-	zm.escape_ctrl_chars	=iniGetBool(ini, "Zmodem","EscapeCtrlChars",FALSE);
-
-	dszlog_path				=iniGetBool(ini, "DSZLOG","Path",TRUE);
-	dszlog_short			=iniGetBool(ini, "DSZLOG","Short",FALSE);
-	dszlog_quotes			=iniGetBool(ini, "DSZLOG","Quotes",FALSE);
+	const char* section = "XMODEM";
+	xm.send_timeout			=iniGetInteger(ini, section,"SendTimeout",xm.send_timeout);	/* seconds */
+	xm.recv_timeout			=iniGetInteger(ini, section,"RecvTimeout",xm.recv_timeout);	/* seconds */
+	xm.byte_timeout			=iniGetInteger(ini, section,"ByteTimeout",xm.byte_timeout);	/* seconds */
+	xm.ack_timeout			=iniGetInteger(ini, section,"AckTimeout",xm.ack_timeout);	/* seconds */
+	xm.block_size			=(ulong)iniGetBytes(ini, section,"BlockSize",1,xm.block_size);			/* 128 or 1024 */
+	xm.max_block_size		=(ulong)iniGetBytes(ini, section,"MaxBlockSize",1,xm.max_block_size);	/* 128 or 1024 */
+	xm.max_errors			=iniGetInteger(ini, section,"MaxErrors",xm.max_errors);
+	xm.g_delay				=iniGetInteger(ini, section,"G_Delay",xm.g_delay);
+	xm.crc_mode_supported	=iniGetBool(ini, section,"SendCRC",xm.crc_mode_supported);
+	xm.g_mode_supported		=iniGetBool(ini, section,"SendG",xm.g_mode_supported);
+
+	xm.fallback_to_xmodem	=iniGetInteger(ini, "YMODEM","FallbackToXmodem", xm.fallback_to_xmodem);
+
+	section = "ZMODEM";
+	zm.init_timeout			=iniGetInteger(ini, section,"InitTimeout",zm.init_timeout);	/* seconds */
+	zm.send_timeout			=iniGetInteger(ini, section,"SendTimeout",zm.send_timeout);	/* seconds */
+	zm.recv_timeout			=iniGetInteger(ini, section,"RecvTimeout",zm.recv_timeout);	/* seconds */
+	zm.crc_timeout			=iniGetInteger(ini, section,"CrcTimeout",zm.crc_timeout);	/* seconds */
+	zm.block_size			=(ulong)iniGetBytes(ini, section,"BlockSize",1,zm.block_size);	/* 1024  */
+	zm.max_block_size		=(ulong)iniGetBytes(ini, section,"MaxBlockSize",1,zm.max_block_size); /* 1024 or 8192 */
+	zm.max_errors			=iniGetInteger(ini, section,"MaxErrors",zm.max_errors);
+	zm.recv_bufsize			=(ulong)iniGetBytes(ini, section,"RecvBufSize",1,0);
+	zm.no_streaming			=!iniGetBool(ini, section,"Streaming",TRUE);
+	zm.want_fcs_16			=!iniGetBool(ini, section,"CRC32",TRUE);
+	zm.can_full_duplex		=iniGetBool(ini, section,"FullDuplex",TRUE);
+	zm.escape_telnet_iac	=iniGetBool(ini, section,"EscapeTelnetIAC",TRUE);
+	zm.escape_8th_bit		=iniGetBool(ini, section,"Escape8thBit",FALSE);
+	zm.escape_ctrl_chars	=iniGetBool(ini, section,"EscapeCtrlChars",FALSE);
+	zm.max_window_size		=(uint32_t)iniGetBytes(ini, section,"MaxWindowSize",1,0);
+	zm.target_window_size	=iniGetDuration(ini, section,"TargetWindowSize",0);
+
+	section = "DSZLOG";
+	dszlog_path				=iniGetBool(ini, section, "Path",TRUE);
+	dszlog_short			=iniGetBool(ini, section, "Short",FALSE);
+	dszlog_quotes			=iniGetBool(ini, section, "Quotes",FALSE);
 
 	if(zm.recv_bufsize > 0xffff)
 		zm.recv_bufsize = 0xffff;
@@ -1669,7 +1670,7 @@ int main(int argc, char **argv)
 					case 'Y':
 						mode|=(YMODEM|CRC);
 						break;
-					case 'k':	/* Ymodem-Checksum for debug/test purposes only */
+					case 'k':	/* YMODEM-Checksum for debug/test purposes only */
 						mode|=YMODEM;
 						break;
 					case 'g':
@@ -1682,7 +1683,7 @@ int main(int argc, char **argv)
 						break;
 					default:
 						fprintf(statfp,"Unrecognized command '%s'\n\n",argv[i]);
-						fprintf(statfp,usage,MAX_FILE_SIZE);
+						fprintf(statfp,usage);
 						bail(1); 
 						return -1;
 				} 
@@ -1729,11 +1730,11 @@ int main(int argc, char **argv)
 					dszlog_quotes=TRUE;
 					continue;
 				}
-				switch(toupper(*arg)) {
-					case 'K':	/* sz/rz compatible */
+				switch(*arg) {
+					case 'k':	/* sz/rz compatible */
 						xm.block_size=XMODEM_MAX_BLOCK_SIZE;
 						break;
-					case 'C':	/* sz/rz compatible */
+					case 'c':	/* sz/rz compatible */
 						mode|=CRC;
 						break;
 					case '2':
@@ -1745,25 +1746,28 @@ int main(int argc, char **argv)
 					case '8':	/* ZedZap */
 						zm.max_block_size=8192;
 						break;
-					case 'O':	/* disable Zmodem CRC-32 */
+					case 'o':	/* disable ZMODEM CRC-32 */
 						zm.want_fcs_16=TRUE;
 						break;
-					case 'S':	/* disable Zmodem streaming */
+					case 's':	/* disable ZMODEM streaming */
 						zm.no_streaming=TRUE;
 						break;
-					case 'G':	/* Ymodem-G or Xmodem-G (a.k.a. Qmodem-G) */
+					case 'w':	/* Max ZMODEM Transmit Window Size */
+						zm.max_window_size = parse_byte_count(arg + 1, /* units: */1);
+						break;
+					case 'g':	/* YMODEM-G or XMODEM-G (a.k.a. Qmodem-G) */
 						mode|=(GMODE|CRC);
 						break;
-					case 'Y':
+					case 'y':
 						mode|=OVERWRITE;
 						break;
 					case '!':
 						pause_on_abend=TRUE;
 						break;
-					case 'M':	/* MaxFileSize */
-						max_file_size=strtoul(arg + 1,NULL,0);	/* TODO: use strtoull() ? */
+					case 'm':	/* MaxFileSize */
+						max_file_size = parse_byte_count(arg + 1, /* units: */1);
 						break;
-					case 'L':	/* Lowercase received filenames */
+					case 'l':	/* Lowercase received filenames */
 						lc_filenames=TRUE;
 						break;
 				}
@@ -1819,14 +1823,14 @@ int main(int argc, char **argv)
 
 	if(!(mode&(SEND|RECV))) {
 		fprintf(statfp,"!No command specified\n\n");
-		fprintf(statfp,usage,MAX_FILE_SIZE);
+		fprintf(statfp,usage);
 		bail(1); 
 		return -1;
 	}
 
-	if(mode&(SEND|XMODEM) && !fnames) { /* Sending with any or recv w/Xmodem */
+	if(mode&(SEND|XMODEM) && !fnames) { /* Sending with any or recv w/XMODEM */
 		fprintf(statfp,"!Must specify filename or filelist\n\n");
-		fprintf(statfp,usage,MAX_FILE_SIZE);
+		fprintf(statfp,usage);
 		bail(1); 
 		return -1;
 	}
@@ -1845,7 +1849,7 @@ int main(int argc, char **argv)
 		init_stdio();
 #else
 		fprintf(statfp,"!No socket descriptor specified\n\n");
-		fprintf(errfp,usage,MAX_FILE_SIZE);
+		fprintf(errfp,usage);
 		bail(1);
 		return -1;
 #endif
@@ -1875,6 +1879,19 @@ int main(int argc, char **argv)
 		lprintf(LOG_DEBUG, "Setting socket options");
 		if(iniGetSocketOptions(ini, "sockopts", sock, error, sizeof(error)) != 0)
 			lprintf(LOG_ERR, "ERROR %s", error);
+		int value = 0;
+		socklen_t len = sizeof(value);
+		if(getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&value, &len) == 0)
+			lprintf(LOG_DEBUG, "Socket send buffer length: %d bytes", value);
+		value = 0;
+		len = sizeof(value);
+		if(getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&value, &len) == 0)
+			lprintf(LOG_DEBUG, "Socket receive buffer length: %d bytes", value);
+		value = 0;
+		len = sizeof(value);
+		if(getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char*)&value, &len) == 0)
+			lprintf(LOG_DEBUG, "Socket TCP_NODELAY: %d", value);
+
 #ifdef __unix__
 	}
 #endif
diff --git a/src/sbbs3/zmodem.c b/src/sbbs3/zmodem.c
index 1270f77d2ab23971e1906c9cb43bc391873cfabf..f7ee7433eefe525867f16842fc87e8910a6fecaa 100755
--- a/src/sbbs3/zmodem.c
+++ b/src/sbbs3/zmodem.c
@@ -2,8 +2,6 @@
 
 /* Synchronet ZMODEM Functions */
 
-/* $Id: zmodem.c,v 1.124 2019/08/25 03:05:34 rswindell Exp $ */
-
 /******************************************************************************/
 /* Project : Unite!       File : zmodem general        Version : 1.02         */
 /*                                                                            */
@@ -62,6 +60,8 @@
 
 #define BADSUBPKT	0x80
 
+#define SEND_SUCCESS	0
+
 #define HDRLEN     5	/* size of a zmodem header */
 
 static int lprintf(zmodem_t* zm, int level, const char *fmt, ...)
@@ -96,7 +96,7 @@ static BOOL is_cancelled(zmodem_t* zm)
 	return(zm->cancelled);
 }
 
-int zmodem_data_waiting(zmodem_t* zm, unsigned timeout)
+static BOOL is_data_waiting(zmodem_t* zm, unsigned timeout)
 {
 	if(zm->data_waiting)
 		return(zm->data_waiting(zm->cbdata, timeout));
@@ -111,7 +111,7 @@ static char *chr(int ch)
 		case TIMEOUT:			return("TIMEOUT");
 		case ABORTED:			return("ABORTED");
 		case SUBPKTOVERFLOW:	return "Subpacket Overflow";
-		case CRCFAILED:			return "CRC Failure";
+		case CRCFAILED:			return "CRC ERROR";
 		case INVALIDSUBPKT:		return "Invalid Subpacket";
 		case ZRQINIT:			return("ZRQINIT");
 		case ZRINIT:			return("ZRINIT");
@@ -241,7 +241,7 @@ int zmodem_send_raw(zmodem_t* zm, unsigned char ch)
 {
 	int	result;
 
-	if((result = zm->send_byte(zm->cbdata, ch, zm->send_timeout)) != 0)
+	if((result = zm->send_byte(zm->cbdata, ch, zm->send_timeout)) != SEND_SUCCESS)
 		lprintf(zm, LOG_ERR, "%s ERROR: %d", __FUNCTION__, result);
 	else
 		zm->last_sent = ch;
@@ -257,7 +257,7 @@ int zmodem_send_esc(zmodem_t* zm, unsigned char c)
 {
 	int	result;
 
-	if((result = zmodem_send_raw(zm, ZDLE)) != 0) {
+	if((result = zmodem_send_raw(zm, ZDLE)) != SEND_SUCCESS) {
 		lprintf(zm, LOG_ERR, "%s ERROR: %d", __FUNCTION__, result);
 		return result;
 	}
@@ -291,7 +291,7 @@ int zmodem_tx(zmodem_t* zm, unsigned char c)
 			break;
 		case TELNET_IAC:
 			if(zm->escape_telnet_iac) {
-				if((result=zmodem_send_raw(zm, ZDLE))!=0)
+				if((result=zmodem_send_raw(zm, ZDLE)) != SEND_SUCCESS)
 					return(result);
 				return zmodem_send_raw(zm, ZRUB1);
 			}
@@ -317,7 +317,7 @@ int zmodem_send_hex(zmodem_t* zm, uchar val)
 
 //	lprintf(zm, LOG_DEBUG, __FUNCTION__ " %02X",val);
 
-	if((result=zmodem_send_raw(zm, xdigit[val>>4]))!=0)
+	if((result=zmodem_send_raw(zm, xdigit[val>>4])) != SEND_SUCCESS)
 		return result;
 	return zmodem_send_raw(zm, xdigit[val&0xf]);
 }
@@ -326,9 +326,9 @@ int zmodem_send_padded_zdle(zmodem_t* zm)
 {
 	int result;
 
-	if((result=zmodem_send_raw(zm, ZPAD))!=0)
+	if((result=zmodem_send_raw(zm, ZPAD)) != SEND_SUCCESS)
 		return result;
-	if((result=zmodem_send_raw(zm, ZPAD))!=0)
+	if((result=zmodem_send_raw(zm, ZPAD)) != SEND_SUCCESS)
 		return result;
 	return zmodem_send_raw(zm, ZDLE);
 }
@@ -347,10 +347,10 @@ int zmodem_send_hex_header(zmodem_t* zm, unsigned char * p)
 
 //	lprintf(zm, LOG_DEBUG, __FUNCTION__ " %s", chr(type));
 
-	if((result=zmodem_send_padded_zdle(zm))!=0)
+	if((result=zmodem_send_padded_zdle(zm)) != SEND_SUCCESS)
 		return result;
 
-	if((result=zmodem_send_raw(zm, ZHEX))!=0)
+	if((result=zmodem_send_raw(zm, ZHEX)) != SEND_SUCCESS)
 		return result;
 
 	/*
@@ -364,7 +364,7 @@ int zmodem_send_hex_header(zmodem_t* zm, unsigned char * p)
 	 */
 
 	for(i=0;i<HDRLEN;i++) {
-		if((result=zmodem_send_hex(zm, *p))!=0)
+		if((result=zmodem_send_hex(zm, *p)) != SEND_SUCCESS)
 			return result;
 		crc = ucrc16(*p, crc);
 		p++;
@@ -378,18 +378,18 @@ int zmodem_send_hex_header(zmodem_t* zm, unsigned char * p)
 	 * transmit the crc
 	 */
 
-	if((result=zmodem_send_hex(zm, (uchar)(crc>>8)))!=0)
+	if((result=zmodem_send_hex(zm, (uchar)(crc>>8))) != SEND_SUCCESS)
 		return result;
-	if((result=zmodem_send_hex(zm, (uchar)(crc&0xff)))!=0)
+	if((result=zmodem_send_hex(zm, (uchar)(crc&0xff))) != SEND_SUCCESS)
 		return result;
 
 	/*
 	 * end of line sequence
 	 */
 
-	if((result=zmodem_send_raw(zm, '\r'))!=0)
+	if((result=zmodem_send_raw(zm, '\r')) != SEND_SUCCESS)
 		return result;
-	if((result=zmodem_send_raw(zm, '\n'))!=0)	/* FDSZ sends 0x8a instead of 0x0a */
+	if((result=zmodem_send_raw(zm, '\n')) != SEND_SUCCESS)	/* FDSZ sends 0x8a instead of 0x0a */
 		return result;
 
 	if(type!=ZACK && type!=ZFIN)
@@ -412,27 +412,27 @@ int zmodem_send_bin32_header(zmodem_t* zm, unsigned char * p)
 
 //	lprintf(zm, LOG_DEBUG, __FUNCTION__ " %s", chr(*p));
 
-	if((result=zmodem_send_padded_zdle(zm))!=0)
+	if((result=zmodem_send_padded_zdle(zm)) != SEND_SUCCESS)
 		return result;
 
-	if((result=zmodem_send_raw(zm, ZBIN32))!=0)
+	if((result=zmodem_send_raw(zm, ZBIN32)) != SEND_SUCCESS)
 		return result;
 
 	crc = 0xffffffffL;
 
 	for(i=0;i<HDRLEN;i++) {
 		crc = ucrc32(*p,crc);
-		if((result=zmodem_tx(zm, *p++))!=0)
+		if((result=zmodem_tx(zm, *p++)) != SEND_SUCCESS)
 			return result;
 	}
 
 	crc = ~crc;
 
-	if((result=	zmodem_tx(zm, (uchar)((crc      ) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar)((crc      ) & 0xff))) != SEND_SUCCESS)
 		return result;
-	if((result=	zmodem_tx(zm, (uchar)((crc >>  8) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar)((crc >>  8) & 0xff))) != SEND_SUCCESS)
 		return result;
-	if((result=	zmodem_tx(zm, (uchar)((crc >> 16) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar)((crc >> 16) & 0xff))) != SEND_SUCCESS)
 		return result;
 	return		zmodem_tx(zm, (uchar)((crc >> 24) & 0xff));
 }
@@ -445,21 +445,21 @@ int zmodem_send_bin16_header(zmodem_t* zm, unsigned char * p)
 
 //	lprintf(zm, LOG_DEBUG, __FUNCTION__ " %s", chr(*p));
 
-	if((result=zmodem_send_padded_zdle(zm))!=0)
+	if((result=zmodem_send_padded_zdle(zm)) != SEND_SUCCESS)
 		return result;
 
-	if((result=zmodem_send_raw(zm, ZBIN))!=0)
+	if((result=zmodem_send_raw(zm, ZBIN)) != SEND_SUCCESS)
 		return result;
 
 	crc = 0;
 
 	for(i=0;i<HDRLEN;i++) {
 		crc = ucrc16(*p,crc);
-		if((result=zmodem_tx(zm, *p++))!=0)
+		if((result=zmodem_tx(zm, *p++)) != SEND_SUCCESS)
 			return result;
 	}
 
-	if((result=	zmodem_tx(zm, (uchar)(crc >> 8)))!=0)
+	if((result=	zmodem_tx(zm, (uchar)(crc >> 8))) != SEND_SUCCESS)
 		return result;
 	return		zmodem_tx(zm, (uchar)(crc&0xff));
 }
@@ -494,25 +494,25 @@ int zmodem_send_data32(zmodem_t* zm, uchar subpkt_type, unsigned char * p, size_
 
 	while(l > 0) {
 		crc = ucrc32(*p,crc);
-		if((result=zmodem_tx(zm, *p++))!=0)
+		if((result=zmodem_tx(zm, *p++)) != SEND_SUCCESS)
 			return result;
 		l--;
 	}
 
 	crc = ucrc32(subpkt_type, crc);
 
-	if((result=zmodem_send_raw(zm, ZDLE))!=0)
+	if((result=zmodem_send_raw(zm, ZDLE)) != SEND_SUCCESS)
 		return result;
-	if((result=zmodem_send_raw(zm, subpkt_type))!=0)
+	if((result=zmodem_send_raw(zm, subpkt_type)) != SEND_SUCCESS)
 		return result;
 
 	crc = ~crc;
 
-	if((result=	zmodem_tx(zm, (uchar) ((crc      ) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar) ((crc      ) & 0xff))) != SEND_SUCCESS)
 		return result;
-	if((result=	zmodem_tx(zm, (uchar) ((crc >> 8 ) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar) ((crc >> 8 ) & 0xff))) != SEND_SUCCESS)
 		return result;
-	if((result=	zmodem_tx(zm, (uchar) ((crc >> 16) & 0xff)))!=0)
+	if((result=	zmodem_tx(zm, (uchar) ((crc >> 16) & 0xff))) != SEND_SUCCESS)
 		return result;
 	return		zmodem_tx(zm, (uchar) ((crc >> 24) & 0xff));
 }
@@ -528,42 +528,47 @@ int zmodem_send_data16(zmodem_t* zm, uchar subpkt_type,unsigned char * p, size_t
 
 	while(l > 0) {
 		crc = ucrc16(*p,crc);
-		if((result=zmodem_tx(zm, *p++))!=0)
+		if((result=zmodem_tx(zm, *p++)) != SEND_SUCCESS)
 			return result;
 		l--;
 	}
 
 	crc = ucrc16(subpkt_type,crc);
 
-	if((result=zmodem_send_raw(zm, ZDLE))!=0)
+	if((result=zmodem_send_raw(zm, ZDLE)) != SEND_SUCCESS)
 		return result;
-	if((result=zmodem_send_raw(zm, subpkt_type))!=0)
+	if((result=zmodem_send_raw(zm, subpkt_type)) != SEND_SUCCESS)
 		return result;
 
-	if((result=	zmodem_tx(zm, (uchar)(crc >> 8)))!=0)
+	if((result=	zmodem_tx(zm, (uchar)(crc >> 8))) != SEND_SUCCESS)
 		return result;
 	return		zmodem_tx(zm, (uchar)(crc&0xff));
 }
 
+BOOL zmodem_end_of_frame(int subpkt_type)
+{
+	return subpkt_type == ZCRCW || subpkt_type == ZCRCE;
+}
+
 /*
  * send a data subpacket using crc 16 or crc 32 as desired by the receiver
  */
 
-int zmodem_send_data_subpkt(zmodem_t* zm, uchar subpkt_type, unsigned char * p, size_t l)
+int zmodem_send_data_subpkt(zmodem_t* zm, uchar subpkt_type, unsigned char* data, size_t len)
 {
 	int result;
 
-	if(subpkt_type == ZCRCW || subpkt_type == ZCRCE)	/* subpacket indicating 'end-of-frame' */
+	if(zmodem_end_of_frame(subpkt_type))
 		zm->frame_in_transit=FALSE;
 	else	/* other subpacket (mid-frame) */
 		zm->frame_in_transit=TRUE;
 
 	if(!zm->want_fcs_16 && zm->can_fcs_32) {
-		if((result=zmodem_send_data32(zm, subpkt_type,p,l))!=0)
+		if((result=zmodem_send_data32(zm, subpkt_type, data, len)) != SEND_SUCCESS)
 			return result;
 	}
 	else {
-		if((result=zmodem_send_data16(zm, subpkt_type,p,l))!=0)
+		if((result=zmodem_send_data16(zm, subpkt_type, data, len)) != SEND_SUCCESS)
 			return result;
 	}
 
@@ -575,7 +580,7 @@ int zmodem_send_data_subpkt(zmodem_t* zm, uchar subpkt_type, unsigned char * p,
 	return result;
 }
 
-int zmodem_send_data(zmodem_t* zm, uchar subpkt_type, unsigned char * p, size_t len)
+int zmodem_send_data(zmodem_t* zm, uchar subpkt_type, unsigned char* data, size_t len)
 {
 	if(!zm->frame_in_transit)	{ /* Start of frame, include ZDATA header */
 		lprintf(zm, LOG_DEBUG, "%lu %s Start of frame: %s"
@@ -583,7 +588,7 @@ int zmodem_send_data(zmodem_t* zm, uchar subpkt_type, unsigned char * p, size_t
 		zmodem_send_pos_header(zm, ZDATA, (uint32_t)zm->current_file_pos, /* Hex? */ FALSE);
 	}
 
-	return zmodem_send_data_subpkt(zm, subpkt_type, p, len);
+	return zmodem_send_data_subpkt(zm, subpkt_type, data, len);
 }
 
 int zmodem_send_pos_header(zmodem_t* zm, int type, int32_t pos, BOOL hex)
@@ -862,7 +867,7 @@ int zmodem_recv_data32(zmodem_t* zm, unsigned char * p, unsigned maxlen, unsigne
 	rxd_crc |= zmodem_rx(zm) << 24;
 
 	if(rxd_crc != crc) {
-		lprintf(zm,LOG_WARNING, "%lu %s CRC ERROR (%08lX, expected: %08lX) Bytes=%u, subpacket type=%s"
+		lprintf(zm, LOG_DEBUG, "%lu %s CRC ERROR (%08lX, expected: %08lX) Bytes=%u, subpacket type=%s"
 			,(ulong)zm->ack_file_pos, __FUNCTION__, rxd_crc, crc, *len, chr(subpkt_type));
 		return CRCFAILED;
 	}
@@ -911,7 +916,7 @@ int zmodem_recv_data16(zmodem_t* zm, register unsigned char* p, unsigned maxlen,
 	rxd_crc |= zmodem_rx(zm);
 
 	if(rxd_crc != crc) {
-		lprintf(zm,LOG_WARNING, "%lu %s CRC ERROR (%04hX, expected: %04hX) Bytes=%u, subpacket type=%s"
+		lprintf(zm, LOG_DEBUG, "%lu %s CRC ERROR (%04hX, expected: %04hX) Bytes=%u, subpacket type=%s"
 			,(ulong)zm->ack_file_pos, __FUNCTION__, rxd_crc, crc, *len, chr(subpkt_type));
 		return CRCFAILED;
 	}
@@ -923,7 +928,7 @@ int zmodem_recv_data16(zmodem_t* zm, register unsigned char* p, unsigned maxlen,
 	return subpkt_type;
 }
 
-int zmodem_recv_data(zmodem_t* zm, unsigned char* p, size_t maxlen, unsigned* len, BOOL ack, int* type)
+int zmodem_recv_data(zmodem_t* zm, unsigned char* buf, size_t maxlen, unsigned* len, BOOL ack, int* type)
 {
 	int subpkt_type;
 	unsigned n=0;
@@ -940,15 +945,15 @@ int zmodem_recv_data(zmodem_t* zm, unsigned char* p, size_t maxlen, unsigned* le
 	*len = 0;
 
 	if(zm->receive_32bit_data) {
-		subpkt_type = zmodem_recv_data32(zm, p, maxlen, len, type);
+		subpkt_type = zmodem_recv_data32(zm, buf, maxlen, len, type);
 	}
 	else {
-		subpkt_type = zmodem_recv_data16(zm, p, maxlen, len, type);
+		subpkt_type = zmodem_recv_data16(zm, buf, maxlen, len, type);
 	}
 
 	if(subpkt_type <= 0) {	/* e.g. TIMEOUT, SUBPKTOVERFLOW, CRCFAILED */
-		lprintf(zm, LOG_WARNING, "%lu %s ERROR: %s (after %u bytes)"
-			,(ulong)zm->ack_file_pos, __FUNCTION__, chr(subpkt_type), *len);
+		lprintf(zm, LOG_WARNING, "%s data subpacket (%u bytes) %s"
+			,chr(*type), *len, chr(subpkt_type));
 		return(subpkt_type);
 	}
 
@@ -995,7 +1000,7 @@ BOOL zmodem_recv_subpacket(zmodem_t* zm, BOOL ack, int* type)
 
 	result = zmodem_recv_data(zm,zm->rx_data_subpacket, sizeof(zm->rx_data_subpacket), &len, ack, type);
 	if(result != FRAMEOK && result != ENDOFFRAME) {
-		lprintf(zm, LOG_ERR, "%lu %s ERROR: %s (subpacket type: %s, %u bytes)"
+		lprintf(zm, LOG_DEBUG, "%lu %s ERROR: %s (subpacket type: %s, %u bytes)"
 			,(ulong)zm->ack_file_pos, __FUNCTION__, chr(result), chr(*type), len);
 		zmodem_send_znak(zm);
 		return(FALSE);
@@ -1100,7 +1105,7 @@ BOOL zmodem_recv_bin16_header(zmodem_t* zm)
 //	lprintf(zm,LOG_DEBUG, "%lu %s GOOD CRC: %04hX", __FUNCTION__
 //		,(ulong)zm->ack_file_pos, __FUNCTION__, crc);
 
-	zm->rxd_header_len = 5;
+	zm->rxd_header_len = n;
 
 	return(TRUE);
 }
@@ -1143,7 +1148,7 @@ BOOL zmodem_recv_hex_header(zmodem_t* zm)
 
 	if(rxd_crc == crc) {
 //		lprintf(zm,LOG_DEBUG, "%s GOOD CRC: %04hX", __FUNCTION__, crc);
-		zm->rxd_header_len = 5;
+		zm->rxd_header_len = i;
 	}
 	else {
 		lprintf(zm,LOG_WARNING, "%s CRC ERROR: 0x%hX, expected: 0x%hX"
@@ -1203,7 +1208,7 @@ BOOL zmodem_recv_bin32_header(zmodem_t* zm)
 	}
 //	lprintf(zm,LOG_DEBUG, "%lu %s GOOD CRC: %08lX", (ulong)zm->ack_file_pos, __FUNCTION__, crc);
 
-	zm->rxd_header_len = 5;
+	zm->rxd_header_len = n;
 	return(TRUE);
 }
 
@@ -1369,7 +1374,7 @@ BOOL zmodem_recv_crc(zmodem_t* zm, uint32_t* crc)
 {
 	int type;
 
-	if(!zmodem_data_waiting(zm,zm->crc_timeout)) {
+	if(!is_data_waiting(zm,zm->crc_timeout)) {
 		lprintf(zm,LOG_ERR, "%lu %s Timeout waiting for response (%u seconds)"
 			,(ulong)zm->current_file_pos, __FUNCTION__, zm->crc_timeout);
 		return(FALSE);
@@ -1425,7 +1430,7 @@ int zmodem_get_zrinit(zmodem_t* zm)
 	zmodem_send_raw(zm,'\r');
 	zmodem_send_hex_header(zm,zrqinit_header);
 
-	if(!zmodem_data_waiting(zm,zm->init_timeout))
+	if(!is_data_waiting(zm,zm->init_timeout))
 		return(TIMEOUT);
 	return zmodem_recv_header(zm);
 }
@@ -1434,7 +1439,8 @@ int zmodem_send_zrinit(zmodem_t* zm)
 {
 	unsigned char zrinit_header[] = { ZRINIT, 0, 0, 0, 0 };
 
-	zrinit_header[ZF0] = ZF0_CANFDX;
+	if(zm->can_full_duplex)
+		zrinit_header[ZF0] = ZF0_CANFDX;
 
 	if(!zm->no_streaming)
 		zrinit_header[ZF0] |= ZF0_CANOVIO;
@@ -1452,7 +1458,7 @@ int zmodem_send_zrinit(zmodem_t* zm)
 		zrinit_header[ZF0] |= ZF0_ESC8;
 
 	if(zm->no_streaming && zm->recv_bufsize==0)
-		zm->recv_bufsize = sizeof(zm->rx_data_subpacket);
+		zm->recv_bufsize = zm->max_block_size;
 
 	zrinit_header[ZP0] = zm->recv_bufsize & 0xff;
 	zrinit_header[ZP1] = zm->recv_bufsize >> 8;
@@ -1474,7 +1480,7 @@ int zmodem_get_zfin(zmodem_t* zm)
 			result = zmodem_send_zabort(zm);
 		else
 			result = zmodem_send_zfin(zm);
-		if(result != 0)
+		if(result != SEND_SUCCESS)
 			return result;
 		if((type = zmodem_recv_header(zm)) == ZFIN)
 			break;
@@ -1496,9 +1502,10 @@ int zmodem_get_zfin(zmodem_t* zm)
 
 BOOL zmodem_handle_zrpos(zmodem_t* zm, uint64_t* pos)
 {
-	if(zm->rxd_header_pos <= zm->current_file_size) {
+	if(zm->rxd_header_pos < zm->current_file_size) {
 		if(*pos != zm->rxd_header_pos) {
 			*pos = zm->rxd_header_pos;
+			zm->ack_file_pos = *pos;
 			lprintf(zm, LOG_INFO, "%lu Resuming transfer from offset: %"PRIu64
 				,(ulong)zm->current_file_pos, *pos);
 		}
@@ -1510,10 +1517,11 @@ BOOL zmodem_handle_zrpos(zmodem_t* zm, uint64_t* pos)
 	return FALSE;
 }
 
-BOOL zmodem_handle_zack(zmodem_t* zm)
+BOOL zmodem_handle_zack(zmodem_t* zm, uint32_t min, uint32_t max)
 {
-	if(zm->rxd_header_pos == zm->current_file_pos) {
+	if(zm->rxd_header_pos >= min && zm->rxd_header_pos <= max) {
 		lprintf(zm, LOG_DEBUG, "%lu Received valid ZACK", zm->rxd_header_pos);
+		zm->ack_file_pos = zm->rxd_header_pos;
 		return TRUE;
 	}
 	lprintf(zm, LOG_WARNING, "%lu Received INVALID ZACK, offset: %lu"
@@ -1521,6 +1529,17 @@ BOOL zmodem_handle_zack(zmodem_t* zm)
 	return FALSE;
 }
 
+static unsigned new_window_size(zmodem_t* zm, time_t start, unsigned pos)
+{
+	time_t elapsed = time(NULL) - start;
+	if(elapsed < 1)
+		elapsed = 1;
+	unsigned cps = pos / elapsed;
+	if(cps < 1)
+		cps = 1;
+	return cps * zm->target_window_size;
+}
+
 /*
  * send from the current position in the file
  * all the way to end of file or until something goes wrong.
@@ -1530,22 +1549,24 @@ BOOL zmodem_handle_zack(zmodem_t* zm)
 
 int zmodem_send_from(zmodem_t* zm, FILE* fp, uint64_t pos, uint64_t* sent)
 {
-	size_t n;
-	uchar type;
-	unsigned buf_sent=0;
-	unsigned subpkts_sent=0;
-
+	size_t len;
+	uchar tx_type;
+	unsigned buf_sent = 0;
+	unsigned subpkts_sent = 0;
+	unsigned backchannel_wait = 0;
+	time_t start = time(NULL);
+
+	lprintf(zm, LOG_DEBUG, "%lu %s", (ulong)pos, __FUNCTION__); 
 	if(sent!=NULL)
 		*sent=0;
 
-	if(fseeko(fp,(off_t)pos,SEEK_SET)!=0) {
+	if(fseeko(fp,(off_t)pos,SEEK_SET) != 0) {
 		lprintf(zm, LOG_ERR, "%s ERROR %d seeking to file offset %"PRIu64
 			,__FUNCTION__, errno, pos);
 		zmodem_send_pos_header(zm, ZFERR, (uint32_t)pos, /* Hex? */ TRUE);
 		return ZFERR;
 	}
-	zm->current_file_pos=pos;
-
+	zm->current_file_pos = pos;
 
 	/*
 	 * send the data in the file
@@ -1553,16 +1574,61 @@ int zmodem_send_from(zmodem_t* zm, FILE* fp, uint64_t pos, uint64_t* sent)
 
 	while(is_connected(zm)) {
 
+		/*
+		 * characters from the other side
+		 * check out that header
+		 */
+		if(zm->consecutive_errors && backchannel_wait == 0)
+			backchannel_wait = 1;
+		while(is_data_waiting(zm, backchannel_wait) && !is_cancelled(zm) && is_connected(zm)) {
+			int rx_type;
+			int c;
+			lprintf(zm, LOG_DEBUG, "Back-channel traffic detected");
+			if((c = zmodem_recv_raw(zm)) < 0) {
+				lprintf(zm, LOG_ERR, "Back-channel receive ERROR: %s", chr(c));
+				return(c);
+			}
+			if(c == ZPAD) {
+				rx_type = zmodem_recv_header(zm);
+				lprintf(zm,LOG_DEBUG, "Received back-channel data: %s", chr(rx_type));
+				if(rx_type == ZACK && zmodem_handle_zack(zm, zm->ack_file_pos, zm->current_file_pos)) {
+					zm->current_window_size = zm->current_file_pos - zm->ack_file_pos;
+					lprintf(zm, LOG_DEBUG, "%lu Asynchronous acknowledgment (ZACK) of %lu bytes, new window: %lu"
+						,(ulong)zm->current_file_pos, (ulong)zm->ack_file_pos, (ulong)zm->current_window_size);
+					if(zm->max_window_size && zm->target_window_size) {
+						zm->max_window_size = new_window_size(zm, start, zm->rxd_header_pos);
+						lprintf(zm, LOG_DEBUG, "%lu New window size: %lu (%u seconds of data)"
+							,(ulong)zm->current_file_pos, (ulong)zm->max_window_size, zm->target_window_size);
+					}
+					continue;
+				}
+				else if(rx_type >= 0) {
+					zmodem_send_data(zm, ZCRCE, /* data: */NULL, /* len: */0);
+					return rx_type;
+				}
+			} else
+				lprintf(zm,LOG_INFO, "Unexpected back-channel data received: %s", chr(c));
+		}
+		if(is_cancelled(zm))
+			return(ZCAN);
+
 		/*
 		 * read a block from the file
 		 */
 		pos = zm->current_file_pos;
-		n = fread(zm->tx_data_subpacket,sizeof(BYTE),zm->block_size,fp);
+		if(zm->max_window_size && zm->current_window_size >= zm->max_window_size) {
+			lprintf(zm, LOG_WARNING, "%lu Transmit-Window management: %lu >= %lu"
+				,(ulong)zm->current_file_pos, (ulong)zm->current_window_size, (ulong)zm->max_window_size);
+			backchannel_wait = 1;
+			continue;
+		}
+
+		len = fread(zm->tx_data_subpacket,sizeof(BYTE),zm->block_size,fp);
 
 		if(zm->progress!=NULL)
 			zm->progress(zm->cbdata, ftello(fp));
 
-		type = ZCRCW;
+		tx_type = ZCRCW;
 
 		/** ZMODEM.DOC:
 			ZCRCW data subpackets expect a response before the next frame is sent.
@@ -1571,36 +1637,43 @@ int zmodem_send_from(zmodem_t* zm, FILE* fp, uint64_t pos, uint64_t* sent)
 			the receiver to write its buffer before sending more data.
 		***/
 		/* Note: we always use ZCRCW for the first frame */
-		if(subpkts_sent || n < zm->block_size) {
+		if(subpkts_sent || len < zm->block_size) {
 			/*  ZMODEM.DOC:
 				In the absence of fatal error, the sender eventually encounters end of
 				file.  If the end of file is encountered within a frame, the frame is
 				closed with a ZCRCE data subpacket which does not elicit a response
 				except in case of error.
 			*/
-			if(n < zm->block_size)
-				type = ZCRCE;
+			if(len < zm->block_size || zm->consecutive_errors)
+				tx_type = ZCRCE;
 			else {
-				if(zm->can_overlap_io && !zm->no_streaming && (zm->recv_bufsize==0 || buf_sent+n < zm->recv_bufsize))
-					type = ZCRCG;
+				if(zm->can_overlap_io && !zm->no_streaming && (zm->recv_bufsize==0 || buf_sent + len < zm->recv_bufsize)) {
+					if(zm->can_full_duplex && zm->max_window_size)
+						tx_type = (subpkts_sent % (zm->max_window_size / zm->block_size / 4)) == 0 ? ZCRCQ : ZCRCG;
+					else
+						tx_type = ZCRCG;
+				}
 				else	/* Send a ZCRCW frame */
 					buf_sent = 0;
 			}
 		}
 
-		/* Note: No support for sending ZCRCQ data subpackets here */
-
-		if(zmodem_send_data(zm, type, zm->tx_data_subpacket, n) != 0)
-			return(TIMEOUT);
+		lprintf(zm, LOG_DEBUG, "%lu Sending %s data subpacket (%u bytes) window: %lu / %lu"
+			,(ulong)pos, chr(tx_type), len, (ulong)zm->current_window_size, (ulong)zm->max_window_size);
+		if(zmodem_send_data(zm, tx_type, zm->tx_data_subpacket, len) != SEND_SUCCESS) {
+			zm->consecutive_errors++;
+			continue;
+		}
 
-		zm->current_file_pos += n;
+		zm->current_window_size += len;
+		zm->current_file_pos += len;
 		if(zm->current_file_pos > zm->current_file_size)
 			zm->current_file_size = zm->current_file_pos;
 		subpkts_sent++;
 
-		if(type == ZCRCW || type == ZCRCE) {
-			lprintf(zm, LOG_DEBUG, "%lu Sent end-of-frame (%s subpacket)",(ulong)pos, chr(type));
-			if(type == ZCRCW) {	/* ZACK expected */
+		if(zmodem_end_of_frame(tx_type)) {
+			lprintf(zm, LOG_DEBUG, "%lu Sent end-of-frame (%s subpacket)", (ulong)pos, chr(tx_type));
+			if(tx_type == ZCRCW) {	/* ZACK expected */
 				lprintf(zm, LOG_DEBUG, "%lu Waiting for ZACK", (ulong)pos);
 				while(is_connected(zm)) {
 					int ack;
@@ -1610,54 +1683,25 @@ int zmodem_send_from(zmodem_t* zm, FILE* fp, uint64_t pos, uint64_t* sent)
 					if(is_cancelled(zm))
 						return(ZCAN);
 
-					if(zmodem_handle_zack(zm))
+					if(zmodem_handle_zack(zm, zm->current_file_pos, zm->current_file_pos)) {
+						zm->current_window_size = 0;
 						break;
+					}
 				}
 			}
 		}
 
 		if(sent!=NULL)
-			*sent+=n;
+			*sent += len;
 
-		buf_sent+=n;
+		buf_sent += len;
 
-		if(n < zm->block_size) {
-			lprintf(zm,LOG_DEBUG, "%lu End of file (or read error) reached", (ulong)zm->current_file_pos);
+		if(len < zm->block_size) {
+			lprintf(zm, LOG_DEBUG, "%lu End of file (or read error) reached", (ulong)zm->current_file_pos);
 			zmodem_send_zeof(zm);
 			return zmodem_recv_header(zm);	/* If this is ZRINIT, Success */
 		}
-
-		/*
-		 * characters from the other side
-		 * check out that header
-		 */
-
-		while(zmodem_data_waiting(zm, zm->consecutive_errors ? 1:0)
-			&& !is_cancelled(zm) && is_connected(zm)) {
-			int rx_type;
-			int c;
-			lprintf(zm, LOG_DEBUG, "Back-channel traffic detected");
-			if((c = zmodem_recv_raw(zm)) < 0) {
-				lprintf(zm, LOG_ERR, "Back-channel receive ERROR: %s", chr(c));
-				return(c);
-			}
-			if(c == ZPAD) {
-				/* ZMODEM.DOC:
-					FULL STREAMING WITH SAMPLING
-					If one of these characters (CAN or ZPAD) is seen, an
-					empty ZCRCE data subpacket is sent.
-				*/
-				zmodem_send_data(zm, ZCRCE, NULL, 0);
-				rx_type = zmodem_recv_header(zm);
-				lprintf(zm,LOG_DEBUG, "Received back-channel data: %s", chr(rx_type));
-				if(rx_type >= 0) {
-					return rx_type;
-				}
-			} else
-				lprintf(zm,LOG_INFO, "Unexpected back-channel data received: %s", chr(c));
-		}
-		if(is_cancelled(zm))
-			return(ZCAN);
+		backchannel_wait = 0;
 
 		zm->consecutive_errors = 0;
 
@@ -1820,11 +1864,11 @@ BOOL zmodem_send_file(zmodem_t* zm, char* fname, FILE* fp, BOOL request_init, ti
 		lprintf(zm,LOG_DEBUG,"Sending ZFILE frame: '%s'"
 			,zm->tx_data_subpacket+strlen((char*)zm->tx_data_subpacket)+1);
 
-		if((i=zmodem_send_bin_header(zm,zfile_frame))!=0) {
+		if((i=zmodem_send_bin_header(zm,zfile_frame)) != SEND_SUCCESS) {
 			lprintf(zm,LOG_DEBUG,"zmodem_send_bin_header returned %d",i);
 			continue;
 		}
-		if((i=zmodem_send_data_subpkt(zm,ZCRCW,zm->tx_data_subpacket,p - zm->tx_data_subpacket))!=0) {
+		if((i=zmodem_send_data_subpkt(zm,ZCRCW,zm->tx_data_subpacket,p - zm->tx_data_subpacket)) != SEND_SUCCESS) {
 			lprintf(zm,LOG_DEBUG,"zmodem_send_data_subpkt returned %d",i);
 			continue;
 		}
@@ -1905,15 +1949,16 @@ BOOL zmodem_send_file(zmodem_t* zm, char* fname, FILE* fp, BOOL request_init, ti
 		if(type == ZRINIT)
 			return(TRUE);	/* Success */
 
-		if(type == ZACK && zmodem_handle_zack(zm)) {
-			pos += sent_bytes;
+		pos = zm->ack_file_pos;
+
+		if(type == ZACK && zmodem_handle_zack(zm, pos, zm->current_file_pos)) {
 			continue;
 		}
 
 		/* Error of some kind */
 
 		zm->errors++;
-		lprintf(zm, LOG_ERR, "%lu ERROR #%d: Received %s", (ulong)zm->current_file_pos, zm->errors, chr(type));
+		lprintf(zm, LOG_ERR, "%lu ERROR #%d: %s", (ulong)zm->current_file_pos, zm->errors, chr(type));
 
 		if(zm->block_size == zm->max_block_size && zm->max_block_size > ZBLOCKLEN)
 			zm->max_block_size /= 2;
@@ -2202,7 +2247,7 @@ unsigned zmodem_recv_file_data(zmodem_t* zm, FILE* fp, int64_t offset)
 	zm->transfer_start_pos=offset;
 	zm->transfer_start_time=time(NULL);
 
-	if(fseeko(fp, pos, SEEK_SET)!=0) {
+	if(fseeko(fp, pos, SEEK_SET) != 0) {
 		lprintf(zm,LOG_ERR, "%s ERROR %d seeking to file offset %"PRId64
 			,__FUNCTION__, errno, offset);
 		zmodem_send_pos_header(zm, ZFERR, (uint32_t)pos, /* Hex? */ TRUE);
@@ -2224,7 +2269,7 @@ unsigned zmodem_recv_file_data(zmodem_t* zm, FILE* fp, int64_t offset)
 		if(pos > zm->current_file_size)
 			zm->current_file_size = pos;
 
-		if(zm->max_file_size!=0 && pos >= zm->max_file_size) {
+		if(zm->max_file_size != 0 && pos >= zm->max_file_size) {
 			lprintf(zm, LOG_ERR, "%lu Specified maximum file size (%"PRId64" bytes) reached"
 				,(ulong)pos, zm->max_file_size);
 			zmodem_send_pos_header(zm, ZFERR, (uint32_t)pos, /* Hex? */ TRUE);
@@ -2338,9 +2383,7 @@ const char* zmodem_source(void)
 
 char* zmodem_ver(char *buf)
 {
-	sscanf("$Revision: 1.124 $", "%*s %s", buf);
-
-	return(buf);
+	return strcpy(buf, "2.0");
 }
 
 void zmodem_init(zmodem_t* zm, void* cbdata
@@ -2363,6 +2406,7 @@ void zmodem_init(zmodem_t* zm, void* cbdata
 	zm->block_size=ZBLOCKLEN;
 	zm->max_block_size=ZBLOCKLEN;
 	zm->max_errors=9;
+	zm->can_full_duplex=TRUE;
 
 	zm->cbdata=cbdata;
 	zm->lputs=lputs;
diff --git a/src/sbbs3/zmodem.h b/src/sbbs3/zmodem.h
index 6549e12f32c4721e1c31c7f2cbc3cb95eb839465..e4c46773ed1795bdac142c966deb7eae16d58067 100755
--- a/src/sbbs3/zmodem.h
+++ b/src/sbbs3/zmodem.h
@@ -281,6 +281,7 @@ typedef struct {
 	int32_t		crc_request;
 	unsigned	errors;
 	unsigned	consecutive_errors;
+	int64_t		current_window_size;	/* "current transmitter file offset - last reported receiver file offset" */
 
 	/* Configuration */
 	BOOL		escape_telnet_iac;
@@ -292,6 +293,8 @@ typedef struct {
 	unsigned	block_size;
 	unsigned	max_block_size;
 	int64_t		max_file_size;		/* 0 = unlimited */
+	int64_t		max_window_size;	/* 0 = unlimited */
+	unsigned	target_window_size;	/* Target Transmit Window Size, in Seconds, 0 = no-auto-adjustment of window size */
 	int			*log_level;
 
 	/* Callbacks */