	NAME	msscom
; File MSSCOM.ASM
	include mssdef.h
;	Copyright (C) 1982, 1997, Trustees of Columbia University in the 
;	City of New York.  The MS-DOS Kermit software may not be, in whole 
;	or in part, licensed or sold for profit as a software product itself,
;	nor may it be included in or distributed with commercial products
;	or otherwise distributed by commercial concerns to their clients 
;	or customers without written permission of the Office of Kermit 
;	Development and Distribution, Columbia University.  This copyright 
;	notice must not be removed, altered, or obscured.
;
; Edit history
; 12 Jan 1995 version 3.14
; Last edit
; 12 Jan 1995
; 8 Dec 1992 version 3.13
; 6 Sept 1991 version 3.11
; 2 March 1991 version 3.10

	public	spack, rpack, spause, bufclr, pakptr, bufrel
	public	makebuf, getbuf, pakdup, chkwind, firstfree, windused
	public	rpacket, windlow, chkparflg, maxbufparas, peekreply
	public	winusedmax, krto, k_sa, k_sd, k_rto, tcp_rto, windgrow
	public	windshrink, cwindow, ssthresh

stat_suc equ	0		; success
stat_tmo equ	1		; timeout
stat_chk equ	2		; checksum mismatch
stat_ptl equ	4		; packet too long
stat_int equ	8		; user interrupt
stat_eol equ	10h		; eol char seen
stat_echo equ	20h		; echo of sent packet
stat_bad equ	80h		; packet is bad (premature EOL)
BIOSCLK	equ	046ch
HI_MAX	equ	018h
LO_MAX	equ     0b0h

data	segment
	extrn	flags:byte, trans:byte, fsta:word, ssta:word, fmtdsp:byte
	extrn	pktnum:byte, portval:word, denyflg:word, cardet:byte
	extrn	parmsk:byte

badpflag db	0		; flag to say have shown bad parity message
spmes	db	'Spack: $'
rpmes 	db	'Rpack: $'
crlf    db      cr,lf,'$'
msgstl	db	'Internal Error: send packet is too long',cr,lf,0,'$'
msgheader db	'<$'
msgtmo	db	'Timeout$'
msgchk	db	'Bad checksum$'
msgint	db	'Interrupted$'
msgptl	db	'Pkt too long$'
msgbad	db	'Early EOL$'
msgtail	db	'>',cr,lf,'$'
msgecho	db	cr,lf,'<Echo of sent packet>',cr,lf,'$'
msgbadsnd db	cr,lf,'<Error sending packet bytes>',cr,lf,'$'
msgbadpare db	'Unexpected Parity from host! Changing Parity to EVEN'
	db	cr,lf,0
msgbadparo db	'Unexpected Parity from host! Changing Parity to ODD'
	db	cr,lf,0
msgbadparm db	'Unexpected Parity from host! Changing Parity to MARK'
	db	cr,lf,0
tmp	db	0
spause	dw	0		; # millisec to wait before sending pkt
timeval	db	0		; active receive timeout value, seconds
prvtyp  db      0		; Type of last packet sent
prvlen	dw	0		; bytes sent, for echo suppression
chkparflg db	0		; non-zero to check parity on received pkts
chklength db	1		; active checksum length
prevchar db	0		; previous char from comms line (for ^C exit)
SOHchar	db	0		; start of packet char, local copy
lentyp	db	0		; packet length type, 3, 0, 1
debflg	db	0		; debug display, send/receive flag
timeit	db	0		; arm timeout counter
tcp_rto	dw	0		; rto (Bios ticks) from internal TCP/IP stack
k_rto	dw	0		; Kermit round trip timeout, Bios ticks
k_sa	dw	0		; Kermit smoothed avg round trip time
k_sd	dw	0		; Kermit std deviation of round trip time

				; sliding windows data structures
windlow	db	0		; lower border of window
windused db	0		; number of window slots in use
winusedmax db	0		; max used window slots
cwindow	db	0,0		; congestion window, fractions of slot
ssthresh db	0		; congestion threshold slot count
thirtytwo db	32		; divisor for window congestion counting
prolog  db	10 dup (0)	; prolog: SOH, LEN, SEQ, TYPE, xlen,...,null
epilog	db	10 dup (0)	; epilog: checksum, eol, handshake + null term
rbuf	db	128 dup (0)	; static packet buffer for replies
	even
pbufseg	dw	0		; segment of packet buffer memory block
maxbufparas dw	0		; paragraphs available in free memory
bufnum	dw	0		; number of buffers available now
buflist dw	maxwind dup (0) ; pointers to packet structures in pktlist
bufuse	dw	maxwind dup (0) ; in-use flag (0 = not in use)
pktlist	pktinfo maxwind dup (<>) ; pktinfo structured members (private)
rpacket	pktinfo <rbuf,0,length rbuf,0,0> ; reply pktinfo
	even
rtemp	dw	0		; address of pktinfo structure for rpack
stemp	dw	0		; address of pktinfo structure for spack
linecnt	db	0		; debug line width counter
colcnt	db	0
chksum	dw	0		; running checksum (two char)
chrcnt	dw	0		; number of bytes in data field of a packet
pktcnt	dw	0		; number of bytes sent/rcvd in this packet
status	dw	0		; status of packet receiver (0 = ok)
fairflg	dw	0		; fairness flag, for console/port reads
rptim	db	4 dup (0)	; read packet timeout slots
ninefive dw	95		; for mult/div with long packets
bias	db	' '		; ascii bias for checksum calculations
prolog_len dw	0		; length of prolog information, for crc
temp	dw	0
data	ends

code1	segment
	extrn	strlen:far, isdev:far, decout:far, dec2di:far
	assume	cs:code1
code1	ends

code	segment
	extrn	prtchr:near, outchr:near
	extrn	sppos:near, ermsg:near, clearl:near, rppos:near
	extrn	pktcpt:near, pcwait:far, peekcom:far

	assume 	cs:code, ds:data, es:nothing
prtchr1	proc	far		; near-far interface routines for code1 seg
	call	prtchr
	ret
prtchr1	endp
foutchr	proc	far
	call	outchr
	ret
foutchr	endp
rppos1	proc	far
	call	rppos
	ret
rppos1	endp
sppos1	proc	far
	call	sppos
	ret
sppos1	endp
ermsg1	proc	far
	call	ermsg
	ret
ermsg1	endp
clearl1	proc	far
	call	clearl
	ret
clearl1	endp
fpktcpt	proc	far
	call	pktcpt
	ret
fpktcpt	endp
code	ends

code1	segment
	assume 	cs:code1, ds:data, es:nothing

; Send_Packet
; This routine assembles a packet from the arguments given and sends it
; to the host.
;
; Expects the following:
;	SI = pointer to pktinfo structure, as
;	[SI].PKTYPE - Packet type letter
;	[SI].SEQNUM - Packet sequence number
;	[SI].DATLEN - Number of data characters
;	[SI].DATADR - DWord Address of data field for packet
; Returns: carry clear if success, carry set if failure.
; Packet construction areas:
; 	Prolog (8 bytes)			Data	 null  Epilog
;+----------------------------------------+---------------+---------------+
;| SOH,LEN,SEQ,TYPE,Xlen(2-3),Xlen chksum | packet's data | chksum,EOL,HS |
;+----------------------------------------+---------------+---------------+
; where Xlen is 2 byte (Long) or 3 byte (Extra Long) count of bytes to follow.
;
SPACK	PROC	FAR
	mov	stemp,si	; save pkt pointer
	mov	ah,[si].pktype
	mov	prvtyp,ah	; remember packet type
	mov	pktcnt,0	; number of bytes sent in this packet
	mov	prolog_len,0
	mov	ax,spause	; wait spause milliseconds before sending pkt
	or	ax,ax		; zero?
	jz	spac1		; z = yes
	call	pcwait		;   to let other side get ready
spac1:	mov	cl,trans.spad	; get the number of padding chars
	xor	ch,ch
	jcxz	spac4		; z = none
	xor	al,al
	xchg	al,trans.sdbl	; doubling char, stash and clear it
	push	ax
	mov	al,trans.spadch	; get padding char
spac2:	call	spkout		; send padding char
	jnc	spac3		; nc = success
	ret			; failed
spac3:	loop	spac2
	pop	ax		; recover doubling char
	xchg	trans.sdbl,al

spac4:	call	snddeb		; do debug display (while it's still our turn)
	mov	al,trans.ssoh	; get the start of header char
	mov	prolog,al	; put SOH in the packet
	mov	si,stemp	; address of send pktinfo
	mov	al,[si].seqnum	; SEQ
	add	al,20h		; ascii bias
	mov	prolog+2,al	; store SEQ in packet
	xor	ah,ah
	mov	chksum,ax	; start checksum
	mov	al,prvtyp	; TYPE
	mov	word ptr prolog+3,ax ; store TYPE (and null terminator)
	add	chksum,ax	; add to checksum
;
; packet length type is directly governed here by length of header plus data
; field, [si].datlen, plus chksum: regular <= 94, long <= 9024, else X long.
;
	mov	ax,[si].datlen	; DATA length
	add	ax,2		; add SEQ, TYPE lengths
	cmp	trans.chklen,'B'-'0' ; B blank-free checksum?
	jne	spac12		; ne = no
	add	al,2		; B is a special two byte checksum
	jmp	short spac13
spac12:	add	al,trans.chklen	; add checksum length at the end
spac13:	adc	ah,0		; propagate carry, yields overall new length
	cmp	ax,[si].datsize	; too big?
	jle	spac14		; le = ok
	push	ax		; for carry-on-regardless after error
	push	dx		; tell user an internal error has occurred
	mov	dx,offset msgstl ; packet is too long
	call	ermsg1		; display message on error line
	call	captdol		; put into packet log
	pop	dx
	pop	ax
	jmp	short spac14	; carry-on-regardless
;	stc
;	ret			; return bad

spac14:	mov	lentyp,3	; assume regular packet
	cmp	ax,94		; longer than a regular?
	ja	spac15		; a = use Long
	add	al,20h		; convert length to ascii
	mov	prolog+1,al	; store LEN
	xor	ah,ah
	add	chksum,ax	; add LEN to checksum
	mov	bx,offset prolog+4 ; look at data field
	jmp	spac19		; do regular

				; use Long packets (type 0)
spac15:	sub	ax,2		; deduct SEQ and TYPE from above = data+chksum
	mov	lentyp,0	; assume type 0 packet
	cmp	ax,(95*95-1)	; longest type 0 Long packet (9024)
	jbe	spac16		; be = type 0
	mov	lentyp,1	; type 1 packet, Extra Long
spac16:	mov	cl,lentyp	; add new LEN field to checksum
	add	cl,20h		; ascii bias, tochar()
	xor	ch,ch
	add	chksum,cx	; add to running checksum
	mov	prolog+1,cl	; put LEN into packet
	mov	bx,offset prolog+4
	mov	cx,1		; a counter
	xor	dx,dx		; high order numerator of length
spac17:	div	ninefive	; divide ax by 95. quo = ax, rem = dx
	push	dx		; push remainder
	inc	cx		; count push depth
	cmp	ax,95		; quotient >= 95?
	jae	spac17		; ae = yes, recurse
	push	ax		; push for pop below
spac18:	pop	ax		; get a digit
	add	al,20h		; apply tochar()
	mov	[bx],al		; store in data field
	add	chksum,ax	; accumulate checksum for header
	inc	bx		; point to next data field byte
	loop	spac18		; get the rest
				;
	mov	ax,chksum	; current checksum
	shl	ax,1		; put two highest bits of al into ah
	shl	ax,1
	and	ah,3		; want just those two bits
	shr	al,1		; put al back in place
	shr	al,1
	add	al,ah		; add two high bits to earlier checksum
	and	al,03fh		; chop to lower 6 bits (mod 64)
	add	al,20h		; apply tochar()
	mov	[bx],al		; store that in length's header checksum
	inc	bx
	xor	ah,ah
	add	chksum,ax	; add that byte to running checksum
				; end of inserting Long pkt info

spac19:	mov	cx,bx		; where we stopped+1
	mov	bx,offset prolog ; place where prolog section starts
	sub	cx,bx
	mov	prolog_len,cx
	dec	prolog_len
	jcxz	spac22		; nothing
	mov	prvlen,cx	; count sent bytes for echo suppression
	sub	prvlen,3	;  minus LEN, SEQ, TYPE
spac20:	mov	al,[bx]		; prolog part
	or	al,al		; at the end?
	jz	spac22		; z = yes
	inc	bx
	call	spkout		; send byte to serial port
	jnc	spac21		; nc = good send
	jmp	spac28		; bad send
spac21:	loop	spac20		; do all prolog parts

spac22:	push	es
	mov	si,stemp	; address of pktinfo
	les	bx,[si].datadr	; select from given data buffer
	mov	dx,[si].datlen	; get the number of data bytes in packet
	add	prvlen,dx	; count bytes sent
spac23:	or	dx,dx		; any data chars remaining?
	jle	spac25		; le = no, finish up
	mov	al,es:[bx]	; get a data char
	inc	bx		; point to next char
spac24:	xor	ah,ah
	add	chksum,ax	; add the char to the checksum [umd]
	and	chksum,0fffh	; keep only low order 12 bits
	dec	dx		; say sending one character
	call	spkout		; send it
	jnc	spac23		; nc = success, get more data chars
	pop	es
	jmp	spac28		; bad send

spac25:	mov	byte ptr es:[bx],0	; terminator of data field
	pop	es
	mov	bx,offset epilog ; area for epilog
	mov	cx,chksum
	mov	bias,' '+1	; bias for checksum char (+1 for non-blank)
	cmp	trans.chklen,'B'-'0'; special non-blank checksum?
	je	spac27		; e = yes
	dec	bias		; use ' ' for regular packets
	cmp	trans.chklen,2	; what kind of checksum are we using?
	je	spac27		; e = 2 characters
	jg	spac26		; g = 3 characters
	mov	al,cl		; 1 char: get the character total
	mov	ch,cl		; save here too (need 'cl' for shift)
	and	al,0C0H		; turn off all but the two high order bits
	mov	cl,6
	shr	al,cl		; shift them into the low order position
	mov	cl,ch
	add	al,cl		; add it to the old bits
	and	al,3FH		; turn off the two high order bits.  (MOD 64)
	add	al,' '		; add a space so the number is printable
	mov	[bx],al		; put in the packet
	inc	bx		; point to next char
	jmp	short spac30

spac26:	mov	byte ptr[bx],0	; null, to determine end of buffer
	push	bx		; don't lose our place
	push	es
	mov	bx,ds		; set up es for crcclc
	mov	es,bx		; es:[bx] is src of data for crcclc
	mov	bx,offset prolog+1 ; first checksummed char, skip SOH
	mov	cx,prolog_len
	xor	dx,dx		; initial CRC value is 0
	call	crcclc		; calculate the CRC of prolog part, to cx
	mov	dx,cx		; first part of CRC returned in cx
	mov	si,stemp	; address of pktinfo
	mov	cx,[si].datlen
	les	bx,[si].datadr	; address of data
	call	crcclc		; do CRC of data, using current CRC in dx
	pop	es
	pop	bx		; recover place to store more debug info
	push	cx		; save the crc
	mov	ax,cx		; manipulate it here
	and	ax,0F000H	; get 4 highest bits
	mov	cl,4
	shr	ah,cl		; shift over 4 bits
	add	ah,' '		; make printable
	mov	[bx],ah		; add to buffer
	inc	bx
	pop	cx		; get back checksum value
spac27:	push	cx		; save it for now
	and	cx,0FC0H	; get bits 6-11
	mov	ax,cx
	mov	cl,6
	shr	ax,cl		; shift them bits over
	add	al,bias		; make printable
	mov	[bx],al		; add to buffer
	inc	bx
	pop	cx		; get back the original
	and	cx,003FH	; get bits 0-5
	add	cl,bias		; make printable
	mov	[bx],cl		; add to buffer
	inc	bx

spac30:	mov	al,trans.seol	; get the EOL the other host wants
	xor	ah,ah
	mov	[bx],ax		; put eol and terminator in buffer
	inc	bx
spac32:	xor	ch,ch
	mov	cl,trans.chklen	; checksum length
	cmp	cl,'B'-'0'	; special non-blank checksum?
	jne	spac32a		; ne = no
	mov	cl,2		; Blank checksum is a two byte affair
spac32a:add	prvlen,cx	; bytes sent
	inc	cx		; plus EOL char
	mov	bx,offset epilog; where to find data
spac33:	mov	al,[bx]
	inc	bx
	call	spkout		; send it
	jc	spac28		; c = failure to send
	loop	spac33
	cmp	debflg,0
	je	spac34
	mov	dx,offset crlf
	call	captdol
spac34:	mov	ax,pktcnt	; number of bytes sent in this packet
	add	fsta.psbyte,ax	; file total bytes sent
	adc	fsta.psbyte+2,0	; propagate carry to high word
	add	fsta.pspkt,1	; statistics, count a packet being sent
	adc	fsta.pspkt+2,0	;  ripple carry
	call	chkcon		; check console for user interrupts
	mov	si,stemp	; restore pkt pointer
	call	getbtime	; get Bios time of day to dx:ax
	mov	word ptr [si].sndtime,ax ; low order sent time
	mov	word ptr [si].sndtime+2,dx ; high order
	clc			; carry clear for success
	ret			; return successfully
spac28:	mov	dx,offset msgbadsnd ; say sending error in log
	call	captdol
	mov	ax,pktcnt	; number of bytes sent in this packet
	add	fsta.psbyte,ax	; file total bytes sent
	adc	fsta.psbyte+2,0	; propagate carry to high word
	add	fsta.pspkt,1	; statistics, count a packet being sent
	adc	fsta.pspkt+2,0	;  ripple carry
	mov	si,stemp	; restore pkt pointer
	stc			; carry set for failure
	ret			; bad send
SPACK	ENDP 

spkout	proc	near
	test	cardet,1	; Carrier detect, was on and is now off?
	jz	spkou4		; z = no
	stc			; fail now
	ret
spkou4:	cmp	al,255		; possible Telnet IAC char?
	jne	spkou2		; ne = no
	cmp	flags.comflg,'t' ; internal Telnet?
	je	spkou3		; e = yes, double the char
spkou2:	cmp	al,trans.sdbl	; double this char?
	jne	spkou1		; ne = no
spkou3:	call	spkou1		; do it once here and again via fall through
	jnc	spkou1		;  but again only if no failure
	ret			; return failure
spkou1:	push	bx		; send char in al out the serial port
	push	cx		; return carry clear if success
	push	dx
	push	es
	cmp	debflg,0	; recording material?
	je	spkour		; e = no
	call	captchr		; record in debugging log
spkour:	mov	ah,al		; foutchr wants char in ah
	inc	pktcnt		; count number of bytes sent in this packet
	call	foutchr		; serial port transmitter procedure
	pop	es
	pop	dx
	pop	cx
	pop	bx		; carry set by foutchr if failure to send
	ret
spkout	endp
     
; Calculate the CRC of the string whose address is in ES:BX, length CX bytes.
; Returns the CRC in CX.  Destroys BX and AX.
; The CRC is based on the SDLC polynomial: x**16 + x**12 + x**5 + 1.
; Original by Edgar Butt  28 Oct 1987 [ebb].
; Enter with initial CRC in DX (normally 0).
crcclc: push	dx
	jcxz	crc1
crc0:	push	cx
	mov	ah,es:[bx]		; get the next char of the string
        inc	bx
        xor	dl,ah			; XOR input with lo order byte of CRC
        mov	ah,dl			; copy it
	mov	cl,4			; load shift count
        shl	ah,cl			; shift copy
        xor	ah,dl			; XOR to get quotient byte in ah
        mov	dl,dh			; high byte of CRC becomes low byte
        mov	dh,ah			; initialize high byte with quotient
        xor	al,al
        shr	ax,cl			; shift quotient byte
        xor	dl,ah			; XOR (part of) it with CRC
        shr	ax,1			; shift it again
        xor	dx,ax			; XOR it again to finish up
	pop	cx
	loop	crc0
crc1:   mov	cx,dx			; return CRC in CX
        pop	dx
        ret

; Receive_Packet
; This routine waits for a packet arrive from the host. Two Control-C's in a
; row from the comms line will cause a Control-C interruption exit.
; Returns
;	SI = pointer to pktinfo structure, as
;	[SI].SEQNUM - Packet sequence number
;	[SI].DATLEN - Number of data characters
;	[SI].DATADR - Address of data field for packet
; Returns AH -  packet type (letter code)
; Returns: carry clear if success, carry set if failure.
; Packet construction areas:
; 	Prolog (8 bytes+2 nulls)	null	Data	null  Epilog   null
;+----------------------------------------+---------------+---------------+
;| SOH,LEN,SEQ,TYPE,Xlen(2-3),Xlen chksum | packet's data | chksum,EOL,HS |
;+----------------------------------------+---------------+---------------+
; where Xlen is 2 byte (Long) or 3 byte (Extra Long) count of bytes to follow.

RPACK	PROC	FAR
	mov	rtemp,si		; save pkt structure address
	xor	ax,ax			; get a zero
	mov	fairflg,ax		; set fairness flag
	mov	badpflag,al		; bad parity flag, clear it
	mov	prevchar,al		; clear previous recv'd char area
	mov	bias,al			; assume not using special B chksum
	mov	[si].pktype,'T'		; assume 'T' type packet (timeout)
	mov	[si].datlen,ax		; init to empty buffer
	mov	ax,word ptr [si].sndtime ; time at which pkt was sent
	or	ax,word ptr [si].sndtime+2
	jnz	rpack10			; nz = have a send time
	call	getbtime		; get Bios time of day to dx:ax
	sub	ax,2			; make two bios ticks ago
	sbb	dx,0
	mov	word ptr [si].sndtime,ax ; low order sent time
	mov	word ptr [si].sndtime+2,dx ; high order
rpack10:push	es
	les	bx,[si].datadr		; caller's data buffer
	mov	word ptr es:[bx],ax	; clear storage areas (asciiz)
	pop	es
	mov	word ptr prolog,ax
	mov	prolog_len,ax
	mov	word ptr epilog,ax
	mov	bx,offset prolog
	mov	pktcnt,ax		; number of bytes rcvd in packet
	mov	al,trans.rsoh		; start of packet char
	mov	SOHchar,al		; save, local copy is modified
	call	rcvdeb			; setup debug display
	mov	bx,seg prolog
	mov	es,bx
	mov	bx,offset prolog	; set es:bx for logging
	jmp	rpack0a

; Get here with unexpected char (SOH or before) and echos with printable SOH
rpack0:	push	ax			; save char which got us here
	mov	bx,rtemp		; pktinfo address
	mov	[bx].datlen,0		; say no data yet
	mov	[bx].seqnum,0ffh	; illegal value
	mov	[bx].pktype,0		; illegal value
	xor	ax,ax			; get a zero
	push	es
	les	bx,[bx].datadr	 	; point to data buffer
	mov	word ptr es:[bx],ax	; clear start of that buffer
	pop	es
	mov	bx,seg prolog
	mov	es,bx			; set buffer pointer
	mov	bx,offset prolog
	mov	word ptr prolog,ax	; clear prolog field
	mov	word ptr epilog,ax	; clear epilog field
	mov	pktcnt,ax		; count of chars
	mov	al,trans.rsoh		; start of packet char
	mov	SOHchar,al		; save, local copy is modified
	mov	al,trans.stime		; time to wait for start of packet
	mov	timeval,al		; local timer value, seconds
	pop	ax
	test	status,stat_int		; interrupted?
	jz	rpack0d			; z = no
	jmp	rpack60			; yes, exit now

rpack0d:mov	cx,status		; current status
	mov	status,stat_suc		; presume success
	test	cx,stat_echo		; doing echo processing?
	jnz	rpack0h			; nz = yes
	cmp	al,SOHchar		; read SOH?
	jne	rpack0a			; ne = no, get it
	jmp	rpack1			; go read LEN

rpack0h:cmp	trans.rsoh,' '		; printable SOH (special case)?
	jb	rpack0a			; b = no, normal, get normal SOH
	; For printable SOH we need to step over possible repetitions used in
	; the general data stream. Thus we wait the length of the sent pkt.
	; This will hopefully gobble up echos of the last sent packet.
	mov	cx,prvlen		; LEN field of last sent packet
	jcxz	rpack0a			; in case zero by some accident
	cmp	cx,256			; keep waiting bounded
	jbe	rpack0f			; be = not very large
	mov	cx,256			; this should be enough bytes to wait
rpack0f:push	cx
	call	inchr			; get and discard cx bytes
	pop	cx
	test	status,stat_tmo+stat_int ; timeout or user intervention?
	jz	rpack0g			; z = no
	jmp	rpack60			; timeout or user intervention, quit
rpack0g:loop	rpack0f			; keep discarding bytes
	mov	status,stat_suc		; assume success
	mov	cl,trans.stime		; time to wait for start of packet
	mov	timeval,cl		; local timer value, seconds
	call	inchr			; this reads the EOP byte echo, maybe
	jnc	rpack0b			; nc = not EOP, consider as SOP
					; else discard echoed EOP
rpack0a:mov	status,stat_suc		; assume success
	mov	cl,trans.stime		; time to wait for start of packet
	mov	timeval,cl		; local timer value, seconds
	call	inchr			; get a character. SOH
	jnc	rpack0b			; nc = got one
				; c=failure (eol, timeout, user intervention)
	test	status,stat_eol		; hit eol from prev packet?
	jnz	rpack0			; nz = yes, restart
	jmp	rpack60			; timeout or user intervention

rpack0b:mov	ah,al			; copy the char
	and	ah,7fh			; strip any parity bit, regardless
	cmp	ah,SOHchar		; start of header char?
	je	rpack0c			; e = yes, SOH
	jmp	rpack0			; ne = no, go until it is
rpack0c:xor	ah,ah			; clear the terminator byte
	cmp	SOHchar,' '		; printable SOHchar?
	jb	rpack1			; b = no (else start crippled mode)
	mov	SOHchar,ah		; yes, set it to null for no matches

rpack1:	cmp	flags.timflg,0		; are timeouts turned off?
	je	rpack1h			; e = no
	mov	timeval,2		; reduce local timer value to 2 secs
	cmp	flags.comflg,'t'	; internal Telnet?
	jne	rpack1h			; ne = no
	mov	timeval,20		; long timeout
rpack1h:call	inchr			; get a character. LEN
	jc	rpack1a			; failure
	and	al,7fh			; strip any parity bit
	cmp	al,SOHchar		; start of header char?
	jne	rpack1b			; ne = no
rpack1a:jmp	rpack0			; yes, start over (common jmp point)

rpack1b:mov	chksum,ax		; start the checksum
	sub	al,20h			; unchar(LEN) to binary
	jnc	rpack1e			; nc = legal (printable)
	or	status,stat_ptl		; set bad length status
	jmp	rpack40			; and quit
rpack1e:mov	si,rtemp
	mov	[si].datlen,ax		; save the data count (byte)
	call	inchr			; get a character. SEQ
	jc	rpack1a			; c = failure
	and	al,7fh			; strip any parity bit
	cmp	al,SOHchar		; SOH?
	je	rpack1a			; e = yes, then go start over
	add	chksum,ax
	sub	al,' '			; get the real packet number
	jnc	rpack1f			; nc = no overflow
	or	status,stat_ptl		; say bad status
	jmp	rpack40			; and exit now
rpack1f:mov	si,rtemp
	mov	[si].seqnum,al		; save the packet number. SEQ
	call	inchr			; get a character. TYPE
	jc	rpack1a			; c = failure
	and	al,7fh			; strip any parity bit
	cmp	al,SOHchar		; SOH?
	je	rpack1a			; e = yes, then go start over
	mov	[si].pktype,al		; save the message type
	cmp	al,prvtyp		; echo of sent packet?
	jne	rpack1g			; ne = no
	or	status,stat_echo	; status is echo processing
	mov	dx,offset msgecho 	; say echo in log
	call	captdol
	jmp	rpack0			; start over

rpack1g:add	chksum,ax		; add it to the checksum
	call	parchk			; check parity on protocol characters
	call	getlen		; get complicated data length (reg, lp, elp)
				; into [si].datlen and kind into byte lentyp. carry set if error
	jnc	rpack1c			; nc = packet is ok so far
	jmp	rpack40			; failure
rpack1c:
; Start of change.
; Now determine block check type for this packet.  Here we violate the layered
; nature of the protocol by inspecting the packet type in order to detect when
; the two sides get out of sync.  Two heuristics allow us to resync here:
;   a. I and S packets always have a type 1 checksum.
;   b. A NAK never contains data, so its block check type is len - 1.
	mov	si,rtemp		; pktinfo address
	mov	ch,trans.chklen		; current checksum length
	cmp	ch,'B'-'0'		; special non-blank kind?
	jne	rpk4			; ne = no
	mov	ch,2			; yes, it's a special 2-byte flavor
rpk4:	mov	ax,[si].datlen		; length of packet information
	mov	cl,[si].pktype		; packet type byte itself
	cmp	cl,'S'			; "S" packet?
	jne	rpk0			; ne = no
	mov	ch,1			; S packets use one byte checksums
	jmp	short rpk3
rpk0:	cmp	cl,'I'			; I packets are like S packets
	jne	rpk1
	mov	ch,1			; I packets use one byte checksums
	jmp	short rpk3
rpk1:	cmp	cl,'N'			; NAK?
	jne	rpk3			; ne = no
	cmp	ax,1			; NAK, get length of data + chklen
	jb	rpk1a			; b = impossible length
	cmp	ax,3			; longest NAK (3 char checksum)
	jbe	rpk2			; be = possible
rpk1a:	or	status,stat_ptl		; status = bad length
	jmp	rpack40			; return on impossible length
rpk2:	mov	trans.chklen,al		; remainder must be checksum type
	mov	ch,al
rpk3:	sub	al,ch			; minus checksum length, for all pkts
	sbb	ah,0			; propagate borrow
	mov	[si].datlen,ax		; store apparent length of data field
	mov	chklength,ch		; remember for checking below
; End of change.
; For long packets we start the real data (after the extended byte
; count 3 or 4 bytes).
	sub	bx,offset prolog+1	; compute length of prolog (skip SOH)
	mov	prolog_len,bx

	mov	si,rtemp
	mov	dx,[si].datlen		; length of data field, excl LP header
	mov	chrcnt,dx
	cmp	dx,[si].datsize		; material longer than data buffer?
	jbe	rpk8c			; be = no
;	ja	rpk8b			; a = yes, give up
;	mov	dx,trans.rlong		; longest packet we should receive
;	sub	dl,chklength		; minus checksum length
;	sbb	dh,0			; propagate borrow
;	cmp	dx,chrcnt		; is data too long?
;	jae	rpk8c			; ae = not too big
rpk8b:	or	status,stat_ptl		; failure status, packet too long
	jmp	rpack40			; too big, quit now
rpk8c:	les	bx,[si].datadr	 	; point to offset of data buffer
	mov	word ptr es:[bx],0	; clear start of that buffer
					; get DATA field characters
rpack2:	cmp	chrcnt,0		; any chars expected?
	jle	rpack3			; le = no, go do checksum
	call	inchr			; get a character into al. DATA
	jc	rpak2c			; c = Control-C, timeout, eol
	cmp	al,SOHchar		; start of header char?
	jne	rpak2b			; ne = no
	jmp	rpack0			; yes, then go start over
rpak2b:	add	chksum,ax		; inchr clears AH
	dec	chrcnt			; one less char expected
	jmp	short rpack2		; get another data character
rpak2c:	test	status,stat_eol		; bare EOL in data part?
	jz	rpak2d			; z = no
	and	status,not stat_eol	; turn off status bit
	jmp	short rpak2b		;  and carry on regardless
rpak2d:	jmp	rpack40			; Control-C, timeout

rpack3:	and	chksum,0fffh	; keep only lower 12 bits of current checksum
	mov	bx,offset epilog	; record debugging in epilog buffer
	mov	ax,seg epilog
	mov	es,ax
	mov	word ptr es:[bx],0
	call	inchr			; start Checksum bytes
	jc	rpack3b			; failed
	mov	ah,al
	and	ah,7fh			; strip high bit
	cmp	ah,SOHchar		; start of header char?
	jne	rpack3a			; ne = no
	jmp	rpack0			; yes, then go start over
rpack3a:sub	al,' '			; unchar() back to binary
	mov	cx,chksum		; current checksum
	cmp	chklength,2		; which checksum length is in use?
	je	rpack5			; e = Two character checksum
	ja	rpack4			; a = Three char CRC, else one char
	shl	cx,1			; put two highest digits of al into ah
	shl	cx,1
	and	ch,3			; want just those two bits
	shr	cl,1			; put al back in place
	shr	cl,1
	add	cl,ch			;add two high bits to earlier checksum
	and	cl,03fh			; chop to lower 6 bits (mod 64)
	cmp	cl,al		; computed vs received checksum byte (binary)
	je	rpack3b			; e = equal, so finish up
	or	status,stat_chk		; say checksum failure
rpack3b:jmp	rpack40

rpack4:	mov	tmp,al			; save value from packet here
	push	bx			; three character CRC
	push	es
	mov	bx,seg prolog
	mov	es,bx
	mov	bx,offset prolog+1	; where data for CRC is, skipping SOH
	mov	cx,prolog_len		; length of prolog field
	xor	dx,dx			; initial CRC is zero
	call	crcclc			; calculate the CRC and put into CX
	mov	dx,cx			; previous CRC
	mov	bx,rtemp
	mov	cx,[bx].datlen		; length of data field
	les	bx,[bx].datadr		; data field segment
	call	crcclc			; final CRC is in CX
	mov	chksum,cx		; save computed checksum
	pop	es
	pop	bx
	mov	ah,ch			; cx = 16 bit binary CRC of rcv'd data
	and	ah,0f0h			; manipulate it here
	shr	ah,1
	shr	ah,1			; get 4 highest bits
	shr	ah,1
	shr	ah,1			; shift right 4 bits
	cmp	ah,tmp			; is what we got == calculated?
	je	rpack4a			; e = yes
	or	status,stat_chk		; checksum failure
rpack4a:call	inchr			; get next character of checksum
	jc	rpack40			; c = failed
	and	al,7fh			; strip high bit
	cmp	al,SOHchar		; SOH?
	jne	rpack4b			; ne = no
	jmp	rpack0			; start over
rpack4b:sub	al,' '			; get back real value
					; two character checksum + CRC
rpack5:	mov	ch,al			; save last char here for now
	mov	ax,chksum
	and	ax,0fc0h		; get bits 11..6
	mov	cl,6
	shr	ax,cl			; shift bits
	cmp	al,ch			; equal?
	je	rpack5a			; e = yes
	mov	bias,1			; try adding bias
	inc	al			; try 'B' method of +1 on bias
	cmp	al,ch			; same?
	je	rpack5a			; matched
	or	status,stat_chk		; checksum failure
rpack5a:call	inchr			; get last character of checksum
	jc	rpack40			; c = failed
	and	al,7fh			; strip high bit
	cmp	al,SOHchar		; SOH?
	jne	rpack5b			; ne = no
	jmp	rpack0			; e = yes
rpack5b:sub	al,' '			; get back real value
	mov	cx,chksum
	and	cl,3FH			; get bits 0-5
	add	cl,bias			; try 'B' method of +1 on bias
	cmp	al,cl			; do the last chars match?
	je	rpack40			; e = yes
	or	status,stat_chk		; say checksum failure

rpack40:test	status,stat_tmo		; timeout?
	jz	rpack41			; z = no
	jmp	rpack60			; nz = yes
rpack41:test	status,stat_eol		; premature eol?
	jz	rpack42			; z = no
	or	status,stat_bad		; say bad packet overall
	jmp	short rpack45		; now try for handshake

rpack42:call	inchr			; get eol char
	jnc	rpack43			; nc = got regular character
	test	status,stat_int		; interrupted?
	jnz	rpack60			; nz = yes
rpack43:and	status,not stat_tmo	; ignore timeouts on EOL character
	test	status,stat_eol		; eol char?
	jnz	rpack44			; nz = yes, got the EOL char
	and	al,7fh			; strip high bit
	cmp	al,SOHchar		; SOH already?
	jne	rpack44			; ne = no
	jmp	rpack0			; yes, start over

rpack44:and	status,not stat_eol 	; desired eol is not an error
					; test for line turn char
rpack45:mov	bx,portval		;   if doing handshaking
	cmp	[bx].hndflg,0		; doing half duplex handshaking?
	je	rpack60			; e = no
	mov	ah,[bx].hands		; get desired handshake char
	mov	tmp,ah			; keep handshake char here
	mov	bx,seg epilog		; where to store character
	mov	es,bx
	mov	bx,offset epilog
rpack45b:call	inchr			; get handshake char
	jnc	rpack46			; nc = regular character
	test	status,stat_eol		; EOL char?
	jnz	rpack46			; nz = yes
	jmp	short rpack48		; timeout or user intervention
rpack46:and	status,not stat_eol	; ignore unexpected eol status here
	and	al,7fh			; strip high bit
	cmp	al,SOHchar		; SOH already?
	jne	rpack47			; ne = no
	jmp	rpack0			; yes, start over
rpack47:cmp	al,tmp			; compare received char with handshake
	jne	rpack45		; ne = not handshake, try again til timeout
rpack48:and	status,not stat_tmo	; ignore timeouts on handshake char

					; Perform logging and debugging now
rpack60:call	chkcon			; check console for user interrupt
	cmp	debflg,0		; recording packets?
	je	rpack66			; e = no
	mov	dx,offset crlf
	call	captdol			; end current display line
	cmp	status,stat_suc		; success?
	je	rpack66			; e = yes
	mov	dx,offset msgheader	; starting "<"
	call	captdol
	test	status,stat_tmo		; timeout?
	jz	rpack61			; no
	mov	dx,offset msgtmo 	; say timeout in log
	call	captdol
	mov	si,rtemp
	mov	ah,'T'			; return packet type in ah
	mov	[si].pktype,ah		; say 'T' type packet (timeout)
rpack61:test	status,stat_chk		; checksum bad?
	jz	rpack62			; z = no
	mov	dx,offset msgchk
	call	captdol
rpack62:test	status,stat_ptl		; packet too long?
	jz	rpack63			; z = no
	mov	dx,offset msgptl
	call	captdol
rpack63:test	status,stat_int		; user interruption?
	jz	rpack64			; z = no
	mov	dx,offset msgint
	call	captdol
rpack64:test	status,stat_bad		; premature EOL?
	jz	rpack65			; z = no
	mov	dx,offset msgbad
	call	captdol
rpack65:mov	dx,offset msgtail	; end of error cause field
	call	captdol

rpack66:mov	ax,pktcnt		; number of bytes received in packet
	add	fsta.prbyte,ax		; file total received pkt bytes
	adc	fsta.prbyte+2,0		; propagate carry to high word
	add	fsta.prpkt,1		; file received packets
	adc	fsta.prpkt+2,0		;  ripple carry
	mov	si,rtemp		; restore pkt pointer
	mov	ah,[si].pktype		; return packet TYPE in ah
	cmp	status,stat_suc		; successful so far?
	jne	rpack72			; ne = no
	cmp	chkparflg,0		; do parity checking?
	je	rpack71			; e = no
	mov	chkparflg,0		; do only once
	test	badpflag,80h		; get parity error flagging bit
	jz	rpack71			; z = no parity error
	mov	bx,portval
	mov	cl,badpflag		; get new parity plus flagging bit
	and	cl,7fh			; strip flagging bit
	mov	[bx].parflg,cl		; force new parity
rpack71:clc				; carry clear for success
	ret
rpack72:stc				; carry set for failure
	ret				; failure exit
RPACK	ENDP

; Get packet if enough bytes for minimal pkt and SOP has been seen. Returns
; carry set if unable, else use Rpack to deliver the packet. Call the same
; as Rpack.
peekreply proc	far
	call	peekcom			; get comms SOP count
	jnc	peekrp1			; nc = have some bytes
	ret				; return carry set for nothing to do
peekrp1:cmp	cx,6			; enough for basic NAK?
	jb	peekrp2			; be = no
	jmp	rpack			; go decode the packet
peekrp2:stc				; say nothing to do
	ret
peekreply endp

; Check Console (keyboard). Return carry setif "action" chars: cr for forced
; timeout, Control-E for force out Error packet, Control-C for quit work now.
; Return carry clear on Control-X and Control-Z as these are acted upon by
; higher layers. Consume and ignore anything else.
chkcon:	call	isdev		; is stdin a device and not a disk file?
	jnc	chkco5		; nc = no, a disk file so do not read here
	mov	dl,0ffh
	mov	ah,dconio		; read console
	int	dos
	jz	chkco5			; z = nothing there
	and	al,1fh			; make char a control code
	cmp	al,CR			; carriage return?
	je	chkco3			; e = yes, simulate timeout
	cmp	al,'C'-40h		; Control-C?
	je	chkco1			; e = yes
	cmp	al,'E'-40h		; Control-E?
	je	chkco1			; e = yes
	cmp	al,'X'-40h		; Control-X?
	je	chkco4			; e = yes
	cmp	al,'Z'-40h		; Control-Z?
	je	chkco4		; record it, take no immmediate action here
	cmp	al,'Q'-40h		; Control-Q?
	je	chkco6			; e = yes
	or	al,al			; scan code being returned?
	jnz	chkco5			; nz = no, ignore ascii char
	mov	ah,dconio		; read and discard second byte
	mov	dl,0ffh
	int	dos
	jmp	short chkco5		; else unknown, ignore
chkco1:	or	al,40h			; make Control-C-E printable
	mov	flags.cxzflg,al		; remember what we saw
chkco2:	or	status,stat_int		; interrupted
	stc
	ret				; act now
chkco3:	or	status,stat_tmo		; CR simulates timeout
	stc
	ret				; act now
chkco4:	or	al,40h			; make control-X-Z printable
	mov	flags.cxzflg,al		; put into flags
	clc				; do not act on them here
	ret
chkco5:	cmp	flags.cxzflg,'C'	; control-C intercepted elsewhere?
	je	chkco2			; e = yes
	clc				; else say no immediate action needed
	ret
chkco6:	xchg	ah,al			; put Control-Q in AH for transmission
	call	spkout			; send it now
	jmp	short chkco5

getlen	proc	near		; compute packet length for short & long types
				; returns length in [si].datlen and
				; length type (0, 1, 3) in local byte lentyp
				; returns length of  data + checksum
	mov 	si,rtemp
	mov	ax,[si].datlen	; get LEN byte value
	and	ax,7fh		; clear unused high byte and parity bit

	cmp	al,3		; regular packet has 3 or larger here
	jb	getln1		; b = long packet
	sub	[si].datlen,2	; minus SEQ and TYPE = DATA + CHKSUM
	mov	lentyp,3	; store assumed length type (3 = regular)
	clc			; clear carry for success
	ret

getln1:	push	cx		; counter for number of length bytes
	mov	lentyp,0	; store assumed length type 0 (long)
	mov	cx,2		; two base-95 digits
	or	al,al		; is this a type 0 (long packet)?
	jz	getln2		; z = yes, go find & check length data
	mov	lentyp,1	; store length type (1 = extra long)
	inc	cx		; three base 95 digits
	cmp	al,1		; is this a type 1 (extra long packet)?
	je	getln2		; e = yes, go find & check length data
	pop	cx
	or	status,stat_ptl	; say packet too long (an unknown len code)
	stc			; set carry bit to say error
	ret
getln2:				; chk header chksum and recover binary length
	push	dx		; save working reg
	xor	ax,ax		; clear length accumulator, low part
	mov	[si].datlen,ax	; clear final length too
getln3:	xor	dx,dx		; ditto, high part
	mov	ax,[si].datlen	; length to date
	mul	ninefive	; multiply accumulation (in ax) by 95
	mov	[si].datlen,ax	; save results
	push	cx
	call	inchr		; read another serial port char into al
	pop	cx
	jc	getln4		; c = failure
	add	chksum,ax
	sub	al,20h		; subtract space, apply unchar()
	mov	si,rtemp
	add	[si].datlen,ax	; add to overall length count
	loop	getln3		; cx preset earlier for type 0 or type 1
	mov	dx,chksum	; get running checksum
	shl	dx,1		; get two high order bits into dh
	shl	dx,1
	and	dh,3		; want just these two bits
	shr	dl,1		; put low order part back
	shr	dl,1
	add	dl,dh		; add low order byte to two high order bits
	and	dl,03fh		; chop to lower 6 bits (mod 64)
	add	dl,20h		; apply tochar()
	push	dx
	call	inchr		; read another serial port char
	pop	dx
	jc	getln4		; c = failure
	add	chksum,ax
	cmp	dl,al		; our vs their checksum, same?
	je	getln5		; e = checksums match, success
getln4:	or	status,stat_chk	; checksum failure
	pop	dx		; unsave regs (preserves flags)
	pop	cx
	stc			; else return carry set for error
	ret
getln5:	pop	dx		; unsave regs (preserves flags)
	pop	cx
	clc			; clear carry (say success)
	ret
getlen	endp

; Get char from serial port into al, with timeout and console check.
; Return carry set if timeout or console char or EOL seen,
; return carry clear and char in AL for other characters.
; Sets status of stat_eol if EOL seen.
; Fairflg allows occassional reads from console before looking at serial port.
inchr	proc	near
	mov	timeit,0	; reset timeout flag (do each char separately)
	push	es		; save debug buffer pointer es:bx
	push	bx
	cmp	fairflg,500	; look at console first every now and then
	jbe	inchr1		; be = not console's turn yet
	mov	fairflg,0	; reset fairness flag for next time
	call	chkcon		; check console
	jnc	inchr1		; nc = nothing to interrupt us
	pop	bx		; clean stack
	pop	es
	ret			; return failure for interruption

inchr1:	call	prtchr1		; read a serial port character
	jc	inchr2		; c = nothing there
	mov	timeit,0	; say not timing missing byte
	pop	bx		; here with char in al from port
	pop	es		; debug buffer pointer
	mov	ah,al		; copy char to temp place AH
	and	ah,7fh		; strip parity bit from work copy
	and	al,parmsk	; apply 7/8 bit parity mask
	cmp	debflg,0	; recording material?
	je	inchr1a		; e = no
	call	captchr		; log char in al
inchr1a:inc	pktcnt		; count received byte
	cmp	al,trans.rign	; char in al to be ignored?
	je	inchr		; e = yes, do so
	test	flags.remflg,dserver ; acting as a server?
	jz	inchr6		; z = no
	cmp	ah,'C'-40h	; Control-C from comms line?
	jne	inchr6		; ne = no
	cmp	ah,prevchar	; was previous char also Control-C?
	jne	inchr6		; ne = no
	cmp	ah,SOHchar	; could this also be an SOH?
	je	inchr6		; e = yes, do not exit
	cmp	ah,trans.reol	; could this also be an EOL?
	je	inchr6		; e = yes
	test	denyflg,finflg	; is FIN enabled?
	jnz	inchr6		; nz = no, ignore server exit cmd
	mov	flags.cxzflg,'C'; set Control-C flag
	or	status,stat_int+stat_eol ; say interrupted and End of Line
	mov	al,ah		; use non-parity version
	xor	ah,ah		; always return with high byte clear
	mov	es:[bx],ax	; store char and null in debugging buffer
	inc	bx
	stc			; exit failure
	ret
inchr6:	mov	prevchar,ah	; remember current as previous char
	cmp	ah,trans.reol	; eol char we want?
	je	inchr7		; e = yes, ret with carry set
	xor	ah,ah		; always return with high byte clear
	mov	es:[bx],ax	; store char and null in buffer
	inc	bx
	clc			; char is in al
	ret

inchr7:	or	status,stat_eol	; set status appropriately
	xor	ah,ah		; always return with high byte clear
	mov	es:[bx],ax	; store char and null in buffer
	inc	bx
	stc			; set carry to say eol seen
	ret			; and return qualified failure
	
inchr2:	push	bx
	mov	bx,portval
	cmp	[bx].portrdy,0	; is port not-ready?
	pop	bx
	jne	inchr2c		; ne = no, port is ready, just no char
	or	status,stat_int	; interrupted
;;;;;;;;;	mov	flags.cxzflg,'C'; set Control-C flag
	stc			; return failure (interruption)
	pop	bx
	pop	es
	ret

inchr2c:call	chkcon		; check console
	jnc	inchr2a		; nc = nothing to interrupt us
	pop	bx		; clean stack
	pop	es
	ret			; return failure for interruption

inchr2a:cmp	flags.timflg,0	; are timeouts turned off?
	je	inchr1		; e = yes, just check for more input
	cmp	timeval,0	; turned off running timeouts on receive?
	je	inchr1		; e = yes
	cmp	trans.stime,0	; doing time outs?
	jne	inchr2b		; ne = yes
	jmp	inchr1		; go check for more input
inchr2b:push	cx		; save regs
	push	dx
	cmp	timeit,0	; have we gotten time of day for first fail?
	jne	inchr4		; ne = yes, just compare times
	push	ax
	push	dx
	call	krto		; get current round trip timeout value
	call	getbtime	; get Bios time to dx:ax
	add	ax,k_rto	; add Bios ticks of timeout interval
	adc	dx,0
	mov	word ptr rptim,ax
	mov	word ptr rptim+2,dx
	pop	dx
	pop	ax
	mov	timeit,1	; say have tod of timeout
	jmp	short inchr4d

inchr4:	call	getbtime	; get Bios time
	cmp	dx,word ptr rptim+2 ; high order word
	ja	inchr4c		; a = we are late
	jb	inchr4d		; b = we are early
	cmp	ax,word ptr rptim ; low order word
	ja	inchr4c		; a = we are late
	jmp	inchr4d		; not timed out yet

inchr4c:or	status,stat_tmo	; say timeout
	pop	dx
	pop	cx
	pop	bx
	pop	es
	stc			; set carry bit
	ret			; failure
inchr4d:pop	dx
	pop	cx
	jmp	inchr1		; not timed out yet
inchr	endp

				; Packet Debug display routines
rcvdeb:	test	flags.debug,logpkt ; In debug mode?
	jnz	rcvde1		; nz = yes
	test	flags.capflg,logpkt ; log packets?
	jnz	rcvde1		; nz = yes
	ret			; no
rcvde1:	mov	debflg,'R'	; say receiving
	jmp	short deb1

snddeb:	test	flags.debug,logpkt ; In debug mode?
	jnz	sndde1		; nz = yes
	test	flags.capflg,logpkt ; log packets?
	jnz	sndde1		; yes
	ret			; no
sndde1:	mov	debflg,'S'	; say sending

deb1:	push	di		; Debug. Packet display
	test	flags.debug,logpkt	; is debug active (vs just logging)?
	jz	deb1e		; z = no, just logging
	cmp	fmtdsp,0	; non-formatted display?
	je	deb1d		; e = yes, skip extra line clearing
	cmp	debflg,'R'	; receiving?
	je	deb1a		; e = yes
	call	sppos1		; spack: cursor position
	jmp	short deb1b
deb1a:	call	rppos1		; rpack: cursor position
deb1b:	mov	cx,4		; clear 4 lines
deb1c:	push	cx
	call	clearl1		; clear the line
        mov	dx,offset crlf
        mov	ah,prstr	; display
        int	dos
	pop	cx
	loop	deb1c
	cmp	debflg,'R'	; receiving?
	je	deb1d		; e = yes
	call	sppos1		; reposition cursor for spack:
	jmp	short deb1e
deb1d:	call	rppos1		; reposition cursor for rpack:
deb1e:	mov	dx,offset spmes	; spack: message
	cmp	debflg,'R'
	jne	deb2		; ne = sending
	mov	dx,offset rpmes	; rpack: message
deb2:	mov	colcnt,0	; number of columns used so far
	mov	linecnt,0	; no lines completed yet
	call	captdol		; record dollar terminated string in Log file
	pop	di
	ret


captdol	proc	near		; write dollar sign terminated string in dx
				; to the capture file (Log file).
	push	ax		; save regs
	push	si
	push	es
	mov	si,dx		; point to start of string
	cld
captdo1:lodsb			; get a byte into al
	cmp	al,'$'		; at the end yet?
	je	captdo3		; e = yes
	or	al,al		; asciiz?
	jz	captdo3		; z = yes, this is also the end
	inc	colcnt
	cmp	al,lf		; new line?
	jne	captdo4		; ne = no
	inc	linecnt		; count displayed lines
	mov	colcnt,0	; start of new line
captdo4:test	flags.debug,logpkt ; debug display active?
	jz	captdo2		; z = no
	cmp	linecnt,4	; four lines used on screen?
	jae	captdo2		; ae = yes, omit more lines
	push	ax
	mov	dl,al
	mov	ah,conout
	int	dos		; display char in dl
	pop	ax
captdo2:test	flags.capflg,logpkt ; logging active?
	jz	captdo1		; z = no
	call	fpktcpt		; record the char, pktcpt is in msster.asm
	jmp	short captdo1	; repeat until dollar sign is encountered
captdo3:pop	es
	pop	si
	pop	ax
	ret
captdol	endp

captchr	proc	near		; record char in AL into the Log file
	test	flags.debug,logpkt ; debug display active?
	jnz	captch1		; nz = yes
	test	flags.capflg,logpkt ; logging active?
	jnz	captch1		; nz = yes
	ret
captch1:push	ax
	push	es
	test	al,80h		; high bit set?
	jz	captch2		; z = no
	push	ax
	mov	al,'~'
	call	captworker	; record the char
	pop	ax
	and	al,not 80h
captch2:cmp	al,DEL		; DEL?
	je	captch2a	; e = yes
	cmp	al,' '		; control char?
	jae	captch4		; ae = no
captch2a:add	al,40h		; uncontrollify the char
	push	ax		; save char in dl
	mov	al,5eh		; show caret before control code
	call	captworker	; record the char
	cmp	colcnt,70	; exhausted line count yet?
	jb	captch3		; b = not yet
	mov	al,CR
	call	captworker
	mov	al,LF
	call	captworker
captch3:pop	ax		; recover char in dl
captch4:call	captworker	; record the char
	cmp	colcnt,70
     	jb	captch5		; b = not yet
	mov	al,CR
	call	captworker
	mov	al,LF
	call	captworker
captch5:pop	es
	pop	ax
	ret
captchr	endp

captworker proc	near
	inc	colcnt		; count new character
	cmp	al,LF		; new line?
	jne	captw3		; ne = no
	inc	linecnt		; count displayed lines
	mov	colcnt,0	; start of new line
captw3:	test	flags.debug,logpkt ; debug display active?
	jz	captw1		; z = no
	cmp	linecnt,4	; four lines used on screen?
	jae	captw1		; ae = yes, omit more lines
	push	ax
	mov	dl,al
	mov	ah,conout
	int	dos
	pop	ax
captw1:	test	flags.capflg,logpkt ; logging active?
	jz	captw2		; z = no
	call	fpktcpt		; log to file
captw2:	ret
captworker endp

parchk	proc	near			; check parity of pkt prolog chars
	cmp	chkparflg,0		; ok to check parity?
	jne	parchk0			; ne = yes
	ret
parchk0:push	ax
	push	bx
	push	cx
	push	dx
	mov	ax,word ptr prolog	; first two prolog chars
	or	ax,word ptr prolog+2	; next two
	test	ax,8080h		; parity bit set?
	jz	parchk7			; z = no
	mov	parmsk,7fh		; set parity mask for 7 bits
	cmp	badpflag,0		; said bad parity once this packet?
	jne	parchk7			; ne = yes
	mov	cx,4			; do all four protocol characters
	xor	dx,dx			; dl=even parity cntr, dh=odd parity
	mov	bx,offset prolog
parchk1:mov	al,[bx]			; get a char
	inc	bx			; point to next char
	or	al,al			; sense parity
	jpo	parchk2			; po = odd parity
	inc	dl			; count even parity
	jmp	short parchk3
parchk2:inc	dh			; count odd parity
parchk3:loop	parchk1			; do all four chars
	cmp	dl,4			; got four even parity chars?
	jne	parchk4			; ne = no
	mov	badpflag,parevn+80h	; say even parity and flagging bit
	mov	dx,offset msgbadpare	; say using even parity
	jmp	short parchk6
parchk4:cmp	dh,4			; got four odd parity chars?
	jne	parchk5			; ne = no
	mov	badpflag,parodd+80h	; say odd parity and flagging bit
	mov	dx,offset msgbadparo	; say using odd parity
	jmp	short parchk6
parchk5:mov	badpflag,parmrk+80h	; say mark parity and flagging bit
	mov	dx,offset msgbadparm	; say using mark parity
parchk6:call	ermsg1
	call	captdol			; write in log file too
parchk7:pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
parchk	endp

; General packet buffer structure manipulation routines. The packet buffers
; consist of a arrays of words, bufuse and buflist, an array of pktinfo
; structure packet descriptors, and a malloc'd main buffer.
; Each pktinfo member describes a packet by holding the address (seg:offset 
; within segment data) of the data field of a packet (datadr), the length of 
; that field in bytes (datsize), the number of bytes currently occupying that 
; field (datlen), the packet sequence number, an ack-done flag byte, and the 
; number of retries of the packet.
; The data field requires a null terminator byte in packet routines rpack and
; spack. Trans.windo buffers are constructed by procedure makebuf.
; Bufuse is an array holding an in-use flag for each pktinfo member; 0 means
; the member is free, otherwise a caller has allocated the member via getbuf.
; Buflist holds the address (offset in segment 'data') of each pktinfo member,
; for rapid list searching.
;
; Packet structures are constructed and initialized by procedure makebuf.
; Other procedures below access the members in various ways. Details of
; buffer construction should remain local to these routines.
; Generally, SI is used to point to a pktinfo member and AL holds a packet
; sequence number (0 - 63 binary). BX and CX are used for some status reports.
;
;  bufuse	buflist		    pktlist (group of pktinfo members)
;  -------	-------		-------------------------------------------
; 0 for unused		      | datadr,datlen,datsize,seqnum,ackdone,numtry |
; 	   	pointers to ->+ datadr,datlen,datsize,seqnum,ackdone,numtry |
; 1 for used		      | datadr,datlen,datsize,seqnum,ackdone,numtry |
;						etc
;
; Construct new buffers, cleared, by computing the amount of DOS free memory,
; allocating as much as (window slots * desired packet length). When there
; is not enough memory shorten the packet length to yield the desired number
; of window slots. 
; This is called two ways: a protocol initialization stage with one normal
; length packet and a post negotiation stage with a stated length of packet.
; The first state finds the maximum available memory for packet buffers as
; well as allocating one regular packet from it. The second stage always
; follows spar() and that routine needs the maximum memory figure.
; Enter with trans.windo equal to the number of buffer slots and CX equal
; to the length of each buffer (we add one byte for null terminator here).
; Return word maxbufparas as number of paragraphs in free memory before
; allocations are done here.
makebuf	proc	far
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	mov	di,cx			; save real packet length
	mov	ax,pbufseg		; current buffer segment
	or	ax,ax			; segment allocated already?
	jz	makebu1			; z = no
	push	es
	mov	es,ax			; allocated segment
	mov	ah,freemem		; free it
	int	dos
	pop	es
makebu1:mov	bx,0ffffh		; allocate biggest chunk of memory
	mov	ah,alloc
	int	dos			; must fail, bx gets number paras free
	mov	maxbufparas,bx		; report max paragraphs available
	mov	al,trans.windo		; number of window slots
	xor	ah,ah
	or	ax,ax			; zero?
	jnz	makebu2			; nz = no
	inc	ax
	mov	trans.windo,al		; zero means one slot
makebu2:add	cx,15			; round, space for null at end of pkt
	and	cx,0fff0h		; and truncate
	mul	cx			; times pkt size per window slot
	mov	cx,4
makebu3:shr	dx,1			; divide double word by 16, to paras
	rcr	ax,1
	loop	makebu3			; ax gets paragraphs wanted
	cmp	ax,bx			; wanted versus available
	jae	makebu4			; ae = want more than available
	mov	bx,ax			; set bx to qty paragraphs wanted
makebu4:mov	cx,bx			; remember desired paragraphs
	mov	ah,alloc		; allocate a memory block
	int	dos
	mov	pbufseg,ax		; seg of memory area
	mov	ax,bx			; number paragraphs allocated
	mov	cl,trans.windo		; number of window slots
	xor	ch,ch
	mov	bufnum,cx		; number of buffers = window slots
	cmp	cx,1			; just one window slot?
	je	makebu5			; e = yes, save division
	xor	dx,dx
	div	cx			; paras per window slot to ax
makebu5:mov	dx,ax			; keep paragraphs per buffer in dx
;	mov	di,ax			; paragraphs per buffer
;	mov	cl,4
;	shl	di,cl			; bytes per buffer (9040 max)
	mov	cx,bufnum		; number of buffers wanted
	mov	ax,pbufseg		; seg where buffer starts
	mov	si,offset pktlist	; where pktinfo group starts
	xor	bx,bx			; index (words)
makebu6:mov	bufuse[bx],0		; say buffer slot is not used yet
	mov	buflist[bx],si		; pointer to pktinfo member
	mov	word ptr [si].datadr,0	 ; offset of data field
	mov	word ptr [si].datadr+2,ax ; segment of data field
	mov	[si].datsize,di		; data buffer size, bytes
	mov	[si].numtry,0		; clear number tries for this buffer
	mov	[si].ackdone,0		; not acked yet
	mov	[si].seqnum,0		; a dummy sequence number
	add	si,size pktinfo		; next pktinfo member
	add	ax,dx			; pointer to next buffer segment
	add	bx,2			; next buflist slot
	loop	makebu6			; make another structure member
	mov	windused,0		; no slots used yet
	mov	winusedmax,0		; max slots used
	mov	word ptr cwindow,1	; initial congestion window
	mov	ax,bufnum		; max slots availble
	mov	ssthresh,al		; save as congestion threshold
	clc				; success
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
makebuf	endp

; Allocate a buffer. Return carry clear and SI pointing at fresh pktinfo
; structure, or if failure return carry set and all regs preserved.
getbuf	proc	far
	push	ax
	push	cx
	push	si
	xor	si,si			; index
	mov	cx,bufnum		; number of buffers
	jcxz	getbuf2			; 0 means none, error
	mov	al,windused		; window slots in use now
	cmp	al,cwindow		; max slots allowed at this time
	jae	getbuf2			; ae = all in use, sorry

getbuf1:cmp	bufuse[si],0		; is this slot in use?
	je	getbuf3			; e = no, grab it
	add	si,2			; try next slot
	loop	getbuf1			; fall through on no free buffers
getbuf2:pop	si			; get here if all are in use
	pop	cx
	pop	ax
	stc				; return failure, si preserved
	ret

getbuf3:mov	bufuse[si],1		; mark buffer as being in use
	inc	windused		; one more slot in use
	mov	si,buflist[si]		; address of pktinfo member
	mov	al,pktnum		; next sequence number to be used
	mov	[si].seqnum,al		; use it as sequence number
	mov	[si].datlen,0		; no data in packet
	mov	[si].numtry,0		; clear number tries for this buffer
	mov	[si].ackdone,0		; not acked yet
	pop	cx			; discard originally saved si
	pop	cx
	pop	ax
	clc				; return success, buffer ptr in si
	ret
getbuf	endp

; Release all buffers (marks them as free, releases buffer memory).

bufclr	proc	far
	push	ax
	push	cx
	push	di
	push	es
	push	ds
	pop	es
	mov	cx,maxwind		; max number of buffers
	xor	ax,ax
	mov	di,offset bufuse	; buffer in-use list
	cld
	rep	stosw			; store zeros to clear the buffers
	mov	windused,0		; number now used (none)
	mov	ax,pbufseg		; segment of memory
	mov	es,ax			; es = segment affected
	mov	ah,freemem		; free it
	int	dos
	xor	ax,ax
	mov	pbufseg,ax		; say no buffer segment
	pop	es
	pop	di
	pop	cx
	pop	ax
	ret
bufclr	endp

; Release buffer whose pktinfo pointer is in SI.
; Return carry clear if success, or carry set if failure.
bufrel	proc	far
	push	bx
	push	cx
	mov	cx,bufnum		; number of buffers
	xor	bx,bx
bufrel1:cmp	buflist[bx],si		; compare addresses, match?
	je	bufrel2			; e = yes, found it
	add	bx,2
	loop	bufrel1
	pop	cx
	pop	bx
	stc				; no such buffer
	ret
bufrel2:mov	bufuse[bx],0		; say buffer is no longer in use
	dec	windused		; one less used buffer
	pop	cx
	pop	bx
	clc
	ret
bufrel	endp

; Grow sliding window slot count in accordance with Van Jacobson's
; TCP/IP congestion avoidance paper.
; An ACK has been received for a packet in the window.
; Inc cwindow by 1 if cwindow is below ssthresh, else inc by 1/cwindow.
; Keep fractions of a slot in byte cwindow+1.
; Limit cwindow to max slots in system, bufnum.
windgrow proc	far
	push	ax
	push	cx
	xor	ah,ah
	mov	al,cwindow	; congestion window, slots
	cmp	al,ssthresh 	; above congestion avoidance threshold?
	jae	windgr1		; ae = yes, grow slowly
	inc	cwindow		; fast opening, add one window slot
	jmp	short windgr2
windgr1:mov	al,thirtytwo	; congestion avoidance slow growth
	xor	ah,ah
	div	cwindow 	; 32 / cwindow, al=quo, ah=rem
	add	cwindow+1,al 	; increment qty of 32nds of slots
	mov	al,cwindow+1
	mov	cl,5		; divide cwindow+1 by 32
	shr	al,cl		; get whole number of window slots
	jz	windgr2		; z = none
	inc	cwindow		; increment cwindow
	mov	cwindow+1,0	; clear fraction
windgr2:mov	al,cwindow	; limit cwindow to max slots (bufnum)
	cmp	al,byte ptr bufnum ; exceeds max number of window slots?
	jbe	windgr3		; be = no
	mov	al,byte ptr bufnum ; limit
windgr3:mov	cwindow,al	; max window slots allowed at this time
	pop	cx
	pop	ax
	ret
windgrow endp

; Shrink sliding window slots. ssthresh <- cwindow / 2, cwindow <- 1.
windshrink proc far
	push	ax
	mov	al,cwindow	; current congestion window, slots
	shr	al,1		; divide by two
	jnz	windsh1		; nz = not too small
	mov	al,1		; must have at least one slot available
windsh1:mov	ssthresh,al	; new congestion threshold
	mov	word ptr cwindow,1 ; back to slow start
	pop	ax
	ret
windshrink endp

; Returns in BX the "packet pointer" for the buffer with the same seqnum as
; provided in AL. Returns carry set if no match found. Modifies BX.
pakptr	proc	far
	push	cx
	push	di
	mov	cx,bufnum		; number of buffers
	xor	di,di			; buffer index for tests
pakptr1:cmp	bufuse[di],0		; is buffer vacant?
	je	pakptr2			; e = yes, ignore
	mov	bx,buflist[di]		; bx = address of pktinfo member
	cmp	al,[bx].seqnum		; is this the desired sequence number?
	je	pakptr3			; e = yes
pakptr2:add	di,2			; next buffer index
	loop	pakptr1			; do next test
	xor	bx,bx			; say no pointer
	stc				; set carry for failure
	pop	di
	pop	cx
	ret
pakptr3:clc				; success, BX has buffer pointer
	pop	di
	pop	cx
	ret
pakptr	endp

; Returns in AH count of packets with a given sequence number supplied in AL
; and returns in BX the packet pointer of the last matching entry.
; Used to detect duplicated packets.
pakdup	proc	far
	push	cx
	push	dx
	push	di
	mov	cx,bufnum		; number of buffers
	xor	di,di			; buffer index for tests
	xor	ah,ah			; number of pkts with seqnum in al
	mov	dx,-1			; a bad pointer
pakdup1:cmp	bufuse[di],0		; is buffer vacant?
	je	pakdup2			; e = yes, ignore
	mov	bx,buflist[di]		; bx = address of pktinfo member
	cmp	al,[bx].seqnum		; is this the desired sequence number?
	jne	pakdup2			; ne = no
	mov	dx,bx			; yes, remember last pointer
	inc	ah			; count a found packet
pakdup2:add	di,2			; next buffer index
	loop	pakdup1			; do next test
	mov	bx,dx			; return last matching member's ptr
	pop	di
	pop	dx
	pop	cx
	or	ah,ah			; any found?
	jz	pakdup3			; z = no
	clc				; return success
	ret
pakdup3:stc				; return failure
	ret
pakdup	endp
	
; Find sequence number of first free window slot and return it in AL,
; Return carry set and al = windlow if window is full (no free slots).
firstfree proc	far
	mov	al,windlow		; start looking at windlow
	mov	ah,al
	add	ah,trans.windo
	and	ah,3fh			; ah = 1+top window seq number, mod 64
firstf1:push	bx
	call	pakptr			; buffer in use for seqnum in AL?
	pop	bx
	jc	firstf2			; c = no, seq number in not in use
	inc	al			; next sequence number
	and	al,3fh			; modulo 64
	cmp	al,ah			; done all yet?
	jne	firstf1			; ne = no, do more
	mov	al,windlow		; a safety measure
	stc				; carry set to say no free slots
	ret
firstf2:clc				; success, al has first free seqnum
	ret
firstfree endp

; Check sequence number for lying in the current or previous window or
; outside either window.
; Enter with sequence number of received packet in [si].seqnum.
; Returns:
;	carry clear and cx =  0 if [si].seqnum is within the current window,
;	carry set   and cx = -1 if [si].seqnum is inside previous window,
;	carry set   and cx = +1 if [si].seqnum is outside any window.
chkwind	proc	far
	mov	ch,[si].seqnum		; current packet sequence number
	mov	cl,trans.windo		; number of window slots
	sub	ch,windlow		; ch = distance from windlow
	jc	chkwin1			; c = negative result
	cmp	ch,cl			; span greater than # window slots?
	jb	chkwinz			; b = no, in current window
	sub	ch,64			; distance measured the other way
	neg	ch
	cmp	ch,cl			; more than window size?
	ja	chkwinp			; a = yes, outside any window
	jmp	short chkwinm		; else in previous window

					; sequence number less than windlow
chkwin1:neg	ch			; distance, positive, cl >= ch
	cmp	ch,cl			; more than window size?
	ja	chkwin2			; a = yes, maybe this window
	jmp	short chkwinm		; no, in previous window

chkwin2:sub	ch,64			; distance measured the other way
	neg	ch
	cmp	ch,cl			; greater than window size?
	jb	chkwinz			; b = no, in current window
					; else outside any window

chkwinp:mov	cx,1			; outside any window
	stc				; carry set for outside current window
	ret
chkwinz:xor	cx,cx			; inside current window
	clc				; carry clear, inside current window
	ret
chkwinm:mov	cx,-1			; in previous window
	stc				; carry set for outside current window
	ret
chkwind	endp

; Return Bios time of day in dx:ax
getbtime proc	far
	push	es
	push	bx
	push	cx
	xor     ax,ax
        mov     es,ax
getbt1:
	mov	cx,es:[biosclk+0]
	mov	dx,es:[biosclk+2]
	in	al,61h			; pause
	in	al,61h
	mov	ax,es:[biosclk+0]
	mov	bx,es:[biosclk+2]
	cmp	ax,cx
	jne	getbt1			; ne = time jumped
	cmp	bx,dx
	jne	getbt1			; ne = time jumped
	mov	ax,[bp+4+0]
	cwd				; sign extend ax to dx
        add     ax,cx
        adc     dx,bx
	pop	cx			; end critical section
	pop	bx
	pop	es
	ret
getbtime endp

; Compute Kermit round trip timeout, k_rto, in Bios clock ticks.
; Enter with SI pointing to packet structure of sent packet
krto	proc	far
	cmp	flags.comflg,'t'	; internal Telnet?
	je	krto5			; e = yes, get rto from it
	push	ax
	push	dx
	call	getbtime		; get current Bios time to dx:ax
	sub	ax,word ptr [si].sndtime ; minus time at which pkt was sent
	sbb	dx,word ptr [si].sndtime+2
	jc	krto4			; c = negative elapsed time (midnight)
	; assume rtt Bios ticks fits into 13 bits (in AX), 450 minutes
	shl	ax,1
	shl	ax,1
	shl	ax,1			; rtt * 8
	sub	ax,k_sa			; minus 8 * smoothed average rtt
	mov	dx,ax			; dx = 8 * rtt_error
	sar	ax,1
	sar	ax,1
	sar	ax,1			; ax = rtt_error
	add	k_sa,ax			; k_sa += rtt_error
	or	ax,ax			; negative?
	jge	krto1			; ge = no
	neg	ax			; make error positve
krto1:	mov	dx,k_sd			; 8 * std dev
	shr	dx,1
	shr	dx,1			; k_sd >> 2
	sub	ax,dx			; rtt_error -= (k_sd >> 2)
	add	k_sd,ax			; k_sd += rtt_error
	mov	ax,k_sa
	shr	ax,1
	shr	ax,1
	add	ax,k_sd
	shr	ax,1			; k_rto = ((k_sa >> 2) + k_sd) >> 1
	cmp	ax,60 * 18 * 3		; more than 3 * 60 seconds?
	jle	krto2			; le = no
	mov	ax,60 * 18 * 3		; clamp at 3 * 60 seconds
krto2:	cmp	ax,9			; below floor of 9 Bios clock ticks
	jge	krto3			; ge = no
	mov	ax,9			; set floor
krto3:	mov	k_rto,ax		; predicted round trip time out, ticks
krto4:	pop	dx
	pop	ax
	ret
krto5:	push	ax			; Internal TCP/IP stack provides rto
	mov	ax,tcp_rto		; Bios ticks from TCP/IP stack
	add	ax,18			; bias up by one second
	mov	k_rto,ax
	pop	ax
	ret
krto	endp

code1	ends
	end
