	NAME	mssrcv
; File MSSRCV.ASM
	include mssdef.h
;  Copyright (C) 1985, 1993, Trustees of Columbia University in the 
;  City of New York.  Permission is granted to any individual or institution
;  to use this software as long as it is not sold for profit.  This copyright
;  notice must be retained.  This software may not be included in commercial
;  products without written permission of Columbia University.
;
; Edit history
; 27 August 1992 version 3.13
; 6 Sept 1991 version 3.11
; Last edit 14 April 1993
; Sliding Windows

	public	read2, read, rrinit, ackpak, nakpak, rstate

setattr equ	57h			; DOS get/set file's date and time

data 	segment
	extrn	encbuf:byte, decbuf:byte, fmtdsp:byte, flags:byte, trans:byte
	extrn	dtrans:byte, sstate:byte, diskio:byte, auxfile:byte
	extrn	maxtry:byte, fsta:word, errlev:byte, kstatus:word
	extrn	rpacket:byte, wrpmsg:byte, numpkt:word, windlow:byte
	extrn	charids:word, windflag:byte, denyflg:word, chkparflg:byte
	extrn	tfilsz:word

cemsg	db	'User intervention',0
erms11	db	'Not enough disk space for file',0
erms13	db	'Unable to send reply',0
erms14  db	'No response from the host',0
erms15	db	'Error. No buffers in receive routine',0
erms29	db	'Rejecting file: ',0
erms30	db	'File size',0
erms31	db	'Date/time',0
erms32	db	'Mailer request',0
erms33	db	'File Type',0
erms34	db	'Transfer Char-set',0
erms36	db	'Unknown reason',0
infms1  db	cr,'           Receiving: In progress',cr,lf,'$'
infms3  db      'Completed',cr,lf,'$'
infms4  db      'Failed',cr,lf,'$'
infms6  db      'Interrupted',cr,lf,'$'
infms7	db	'Discarding $'
filhlp2 db      ' Local path or filename or carriage return$'
ender	db	bell,bell,'$'
crlf	db	cr,lf,'$'
badrcv	db	0		; local retry counter
filopn	db	0		; non-zero if disk file is open
ftime	db	0,0		; file time (defaults to 00:00:00)
fdate	db	0,0		; file date (defaults to 1 Jan 1980)
attrib	db	0		; attribute code causing file rejection
rstate	db	0		; state of automata
permchrset dw	0		; permanent file character set holder
temp	dw	0
data	ends

code1	segment
	extrn bufclr:far, pakptr:far, bufrel:far, makebuf:far, chkwind:far
	extrn firstfree:far, getbuf:far, pakdup:far
	extrn rpack:far, spack:far, fcsrtype:far
code1	ends

code	segment
	extrn	gofil:near, comnd:near, cntretry:near, perpr:near
	extrn	serini:near, spar:near, rpar:near
	extrn	init:near, cxmsg:near, cxerr:near, perpos:near
	extrn	ptchr:near, ermsg:near, winpr:near, dskspace:near
	extrn	stpos:near, rprpos:near, packlen:near
	extrn	dodec:near, doenc:near, errpack:near, intmsg:near
	extrn	ihostr:near, prtasz:near, begtim:near
	extrn	endtim:near, pktsize:near,strlen:near,strcpy:near
	extrn	msgmsg:near, clrbuf:near, pcwait:near, goopen:near
	extrn	filekind:near, filecps:near

	assume  cs:code, ds:data, es:nothing

; Data structures comments.
; Received packet material is placed in buffers pointed at by [si].bufadr;
; SI is typically used as a pointer to a pktinfo packet structure.
; Sent packet material (typically ACK/NAKs) is placed in a standard packet
; structure named rpacket.
; Rpack and Spack expect a pointer in SI to the pktinfo structure for the
; packet.

; RECEIVE command
 
READ	PROC	NEAR		
	mov	bx,offset filhlp2	; help message
	mov	dx,offset auxfile	; local file name string
	mov	byte ptr auxfile,0	; clear it first
	mov	ah,cmword		; local override filename/path
	call	comnd		
	jc	read1a			; c = failure
	mov	ah,cmeol		; get a confirm
	call	comnd
	jc	read1a			; c = failure
	mov	rstate,'R'		; set state to receive initiate
	mov	flags.xflg,0
	call	serini			; initialize serial port
	jnc	read1b			; nc = success
	or	errlev,ksrecv		; set DOS error level
	or	fsta.xstatus,ksrecv	; set status, failed
	or	kstatus,ksrecv		; global status
	test	flags.remflg,dquiet	; quiet display mode?
	jnz	read1a			; nz = yes. Don't write to screen
	mov	ah,prstr
	mov	dx,offset infms4	; Failed message
	int	dos
	stc
read1a:	ret				; return failure

read1b:	call	rrinit			; init variables for read
	call	clrbuf			; clear serial port buffer
	call	ihostr			; initialize the host
	cmp	flags.destflg,2		; destination is screen?
	je	read2			; e = yes
	call	init			; setup display form
	
					; Called by GET & SRVSND, display ok
READ2:	mov	kstatus,kssuc		; global status, success
	mov	windflag,0		; init windows in use display flag
	mov	numpkt,0		; set the number of packets to zero
	mov	badrcv,0		; local retry counter
	mov	fsta.pretry,0		; clear total retry counter
	mov	flags.cxzflg,0		; reset ^X/^Z flag
	mov	ax,flags.chrset		; permanent character set (Code Page)
	mov	permchrset,ax		; remember here around attributes ptks
	cmp	fmtdsp,0		; formatted display?
	je	read2a			; e = no
	call	stpos
	mov	ah,prstr		; Receiving in progress msg
	mov	dx,offset infms1
	int	dos
read2a:	jmp	dispatch
READ	ENDP

; Call the appropriate action routines for each state of the protocol machine.
; State is held in byte rstate. Enter at label dispatch.

dispatch proc	near			; dispatch on state variable rstate
	mov	ah,rstate		; get current state
	cmp	ah,'R'			; Receive initiate state?
	jne	dispat2			; ne = no
	call	rinit
	jmp	short dispatch

dispat2:cmp	ah,'F'			; File header receive state?
	jne	dispat3
	call	rfile			; receive file header
	jmp	short dispatch

dispat3:cmp	ah,'D'			; Data receive state?
	jne	dispat4
	call	rdata			; get data packets
	jmp	short dispatch

dispat4:cmp	ah,'Z'			; EOF?
	jne	dispat5
	call	reof			; do EOF wrapup
	jmp	short dispatch

dispat5:cmp	ah,'E'			; ^C or ^E abort?
	jne	dispat6			; ne = no
	mov	bx,offset cemsg		; user intervention message
	call	errpack			; send error message
	call	intmsg			; show interrupt msg for Control-C-E

					; Receive Complete state processor
dispat6:cmp	rstate,'C'		; completed normally?
	jne	dispat6a		; ne = no
	cmp	flags.cxzflg,0		; interrupted?
	je	dispat7			; e = no, ended normally
dispat6a:or	errlev,ksrecv		; set DOS error level
	or	fsta.xstatus,ksrecv+ksuser ; set status, failed + intervention
	or	kstatus,ksrecv+ksuser	; global status
dispat7:xor	ax,ax		; tell statistics this is a receive operation
	call	endtim			; stop file statistics accumulator
	call	filecps			; show file chars/sec
	call	bufclr			; release all buffers
	mov	windlow,0
	mov	ax,permchrset		; permanent character set (Code Page)
	mov	flags.chrset,ax		; restore external version
	cmp	rstate,'C'		; receive complete state?
	je	dispat8			; e = yes
	or	errlev,ksrecv		; Failed, set DOS error level
	or	fsta.xstatus,ksrecv	; set status, failed
	or	kstatus,ksrecv		; global status
	call	fileclose		; close output file
	call	filedel			; delete incomplete file

dispat8:cmp	flags.destflg,2		; receiving to screen?
	je	dispa11			; e = yes, nothing to clean up
	test	flags.remflg,dquiet+dserial ; quiet or serial display mode?
	jnz	dispa11			; nz = yes, keep going
	mov	al,1			; underline cursor
	call	fcsrtype		; set IBM-PC cursor to underline
	cmp	flags.xflg,0		; writing to the screen?
	jne	dispa11			; ne = yes
	call	stpos			; position cursor to status line
	mov	dx,offset infms3	; completed message
	cmp	rstate,'C'		; receive complete state?
	je	dispa10			; e = yes
	mov	dx,offset infms4	; failed message
	cmp	flags.cxzflg,0		; interrupted?
	je	dispa10			; e = no, ended normally
	mov	dx,offset infms6	; interrupted message
dispa10:mov	ah,prstr
	int	dos
	cmp	flags.belflg,0		; bell desired?
	je	dispa11			; e = no
	mov	ah,prstr
	mov	dx,offset ender		; ring the bell
	int	dos
dispa11:call	rprpos			; put cursor at reprompt position
	mov	flags.cxzflg,0		; clear flag for next command
	mov	auxfile,0		; clear receive-as filename buffer
	mov	flags.xflg,0		; clear to-screen flag
	mov	diskio.string,0		; clear active filename buffer
	mov	fsta.xname,0		; clear statistics external name
	clc				; return to ultimate caller, success
	ret
dispatch endp

;	Receive routines
 
; Receive initiate packet (tolerates E F M S X Y types)
RINIT	PROC	NEAR
	mov	windlow,0		; lowest acceptable packet number
	mov	trans.chklen,1		; Use 1 char for init packet
	mov	chkparflg,1		; check for unexpected parity
	call	rcvpak			; get a packet
	jnc	rinit2			; nc = success
	ret

rinit2:	mov	ah,[si].pktype		; examine packet type
	cmp	ah,'S'			; Send initiate packet?
	je	rinit6			; e = yes, process 'S' packet
	cmp	ah,'M'			; Message packet?
	jne	rinit4			; ne = no
	call	msgmsg			; display message
	mov	trans.chklen,1		; send Init checksum is always 1 char
	call	ackpak0			; ack and release packet
	ret

rinit4:	cmp	ah,'F'			; File receive?
	je	rinit5			; e = yes
	cmp	ah,'X'			; File receive to screen?
	je	rinit5			; e = yes
	cmp	ah,'Y'			; ACK to a REMOTE command?
	jne	rinit4a			; ne = no
	call	msgmsg			; show any message in the ACK
	mov	rstate,'C'		; Completed state
	ret

rinit4a:call	bufrel			; release this packet buffer
	ret				;  and ignore it

rinit5:	mov	rstate,'F'		; File header receive state
	ret

					; 'S' packet received
rinit6:	call	spar			; negotiate parameters
	push	si
	mov	si,offset rpacket	; build response in this packet
	call	rpar			; report negotiated parameters
	pop	si
	mov	ah,trans.chklen		; negotiated checksum length
	push	ax			; save it
	mov	trans.chklen,1		; use 1 char for init packet reply
	mov	rstate,'F'		; set state to file header
	call	ackpak			; ack with negotiated data
	pop	ax			; recover working checksum
	mov	trans.chklen,ah
	mov	cx,trans.rlong		; negotiated length of received pkts
	call	makebuf			; remake buffering for new windowing
	call	packlen			; compute packet length
	ret
RINIT	ENDP
 

; Receive file header (tolerates E F M X Z types)
 
RFILE	PROC	NEAR
	call	rcvpak			; receive next packet
	jnc	rfile1			; nc = success
	ret

rfile1: cmp	[si].pktype,'Z'		; EOF?
	jne	rfile2			; ne = no, try next type
	mov	rstate,'Z'		; change to EOF state, SI is valid pkt
	ret

rfile2: cmp	[si].pktype,'F'		; file header (F or X packet)?
	je	rfil3a			; e = yes, 'F' pkt
	cmp	[si].pktype,'X'		; visual display header?
	jne	rfile5			; ne = neither one

rfile3:	mov	flags.xflg,1		; 'X', say receiving to the screen
rfil3a:	mov	filopn,0		; assume not writing to a disk file
	call	dodec			; decode packet
	call	cxmsg			; clear Last Message line
	xor	al,al			; say starting receive operation
	call	begtim			; start statistics gathering
	mov	al,dtrans.xchset	; reset Transmission char set
	mov	trans.xchset,al		;  to the current user default
	mov	al,dtrans.xtype		; ditto for File Type
	mov	trans.xtype,al
	mov	ax,permchrset		; permanent character set (Code Page)
	mov	flags.chrset,ax		; active character set
	call	gofil			; open the output file
	jnc	rfile4			; nc = success
	jmp	giveup			; failure, dx has message pointer

rfile4:	push	si
	push	di
	mov	si,offset decbuf	; local filename is here
	mov	di,offset encbuf	; destination is encoding buffer
	mov	byte ptr [di],' '	; leave space for protocol char
	inc	di			;  so other Kermits do not react
	call	strcpy			; copy it, to echo local name to host
	dec	di
	mov	dx,di
	call	strlen			; get length to cx for doenc
	mov	si,offset rpacket	; use this packet buffer
	call	doenc			; encode buffer, cx gets length
	pop	di
	pop	si
	mov	rstate,'D'		; set the state to data receive
	call	filekind		; report Text/Bin, char set
	jmp	ackpak			; ack the packet, with filename

rfile5:	mov	ah,[si].pktype		; get reponse packet type
	cmp	ah,'B'			; 'B' End Of Transmission?
	jne	rfile6			; ne = no
	mov	rstate,'C'		; set state to Complete
	jmp	ackpak0			; ack the packet

rfile6:	cmp	ah,'M'			; Message packet?
	jne	rfile7			; ne = no
	call	msgmsg			; display message
	jmp	ackpak0			; ack packet, stay in this state

rfile7:	call	bufrel			; release buffer
	ret				;  and ignore unknown packet
RFILE	ENDP

; Get file attributes from packet
; Recognize file size in bytes and kilobytes (used if bytes missing),
; file time and date. Reject Mail commands. Return carry clear for success,
; carry set for failure. If rejecting place reason code in byte attrib.

GETATT	PROC	NEAR
	push	es
	les	bx,[si].datadr		; pointer to data field
getat0:	push	bx
	sub	bx,word ptr [si].datadr ; bx = length to examine
	cmp	bx,[si].datlen		; are we beyond end of data?
	pop	bx
	jl	getat1			; l = not yet
	pop	es
	clc
	ret				; has carry clear for success

getat1:	cmp	byte ptr es:[bx],'1'	; Byte length field?
	jne	getat2			; ne = no
	test	flags.attflg,attlen	; allowed to examine file length?
	jnz	getat1a			; nz = yes
	jmp	getatunk		; z = no, ignore
getat1a:mov	al,es:[bx]		; remember attribute
	mov	attrib,al
	inc	bx			; pointer
	push	si
	call	getas			; get file size
	call	spchk			; check available disk space
	pop	si
	jnc	getat0			; nc = have enough space for file
	pop	es
	ret				; return failure

getat2:	cmp	byte ptr es:[bx],'!'	; Kilobyte length field?
	jne	getat3			; ne = no
	test	flags.attflg,attlen	; allowed to examine file length?
	jnz	getat2b			; nz = yes
getat2a:jmp	getatunk		; z = no, ignore
getat2b:mov	al,es:[bx]		; remember attribute
	mov	attrib,al
	inc	bx			; pointer
	call	getak			; get file size
	jc	getat2a			; carry means decode rejected
	push	si
	call	spchk			; check available disk space
	pop	si
	jnc	short getat0
	pop	es
	ret				; return failure

getat3:	cmp	byte ptr es:[bx],'#'	; date field?
	jne	getat4			; ne = no
	mov	word ptr ftime,0	; clear time and date fields
	mov	word ptr fdate,0
	test	flags.attflg,attdate	; allowed to update file date/time?
	jnz	getat3a			; nz = yes
	jmp	getatunk		; z = no, ignore
getat3a:mov	al,es:[bx]		; remember attribute
	mov	attrib,al
	inc	bx
	call	getatd			; get file date
	jmp	short getat0

getat4:	cmp	byte ptr es:[bx],'+'	; Disposition?
	jne	getat5			; ne = no
	mov	al,es:[bx]		; remember attribute
	mov	attrib,al
	cmp	byte ptr es:[bx+2],'M'	; Mail indicator?
	je	getat4c			; e = yes, fail
	cmp	byte ptr es:[bx+2],'P'	; REMOTE PRINT?
	jne	getat4b			; ne = no, ignore field
	test	flags.remflg,dserver	; acting as a server now?
	jz	getat4a			; z = no
	test	denyflg,prtflg		; is this server command disabled?
	jnz	getat4c			; nz = yes, disabled
getat4a:mov	word ptr diskio.string,'RP'	; output to PRN
	mov	word ptr diskio.string+2,'N' 	; ignore options
getat4b:jmp	getatunk		; ignore field
getat4c:stc				; set carry for failure
	pop	es
	ret

getat5:	cmp	byte ptr es:[bx],'"'	; File Type?
	jne	getat6			; ne = no
       	test	flags.attflg,atttype	; allowed to examine file type?
	jnz	getat5a			; nz = yes
	jmp	getatunk		; z = no, ignore
getat5a:mov	attrib,'"'		; remember attribute
	inc	bx			; length field
	xor	ch,ch
	mov	cl,es:[bx]		; get length
	inc	bx
	sub	cl,20h			; remove ascii bias
	jc	getat5d			; c = error in length, fail
	cmp	byte ptr es:[bx],'A'	; Type letter (A, B, I), Ascii?
	jne	getat5b			; ne = no
	mov	trans.xtype,0		; say Ascii/Text file type
	add	bx,cx			; step to next field
	jmp	getat0			; next item please
getat5b:cmp	byte ptr es:[bx],'B'	; "B" Binary?
	jne	getat5d			; ne = no, fail
	cmp	cl,2			; full "B8"?
	jb	getat5c			; b = no, just "B"
	cmp	byte ptr es:[bx+1],'8'	; proper length?
	jne	getat5d			; ne = no
getat5c:mov	trans.xtype,1		; say Binary
	add	bx,cx			; step to next field
	jmp	getat0			; next item please
getat5d:stc				; set carry for rejection
	pop	es
	ret

getat6:	cmp	byte ptr es:[bx],'*'	; character set usage?
	jne	getat6d			; ne = no
	test	flags.attflg,attchr	; allowed to examine char-set?
	jnz	getat6a			; nz = yes
getat6d:jmp	getatunk		; z = no, ignore
getat6a:mov	attrib,'*'		; remember attribute
	inc	bx			; length field
	xor	ch,ch
	mov	cl,es:[bx]		; get length
	inc	bx
	sub	cl,20h			; remove ascii bias
	js	getat6c			; c = length error, fail
	mov	trans.xchset,0		; assume Transparent Transfer char-set
	cmp	byte ptr es:[bx],'A'	; Normal Transparent?
	jne	getat6b			; be = not Transparent
	add	bx,cx			; point at next field
	clc
	jmp	getat0
getat6b:cmp	byte ptr es:[bx],'C'	; character set?
	je	getat7			; e = yes
getat6c:stc				; set carry for rejection
	pop	es
	ret
getat7:	push	di			; examine transfer character set
	push	si
	mov	di,bx			; point at first data character
	add	bx,cx			; point bx beyond the text
	dec	cx			; deduct leading 'C' char from count
	inc	di			; skip the 'C'
	push	bx			; save bx
	mov	bx,offset charids	; point to array of char set info
	mov	ax,[bx]			; number of members
	mov	temp,ax			; loop counter
	mov	trans.xchset,xfr_xparent ; assume xfer char set Transparent
getat7a:add	bx,2			; point to a member's address
	mov	si,[bx]			; point at member [length, string]
	cmp	cl,[si]			; string lengths the same?
	jne	getat7b			; ne = no, try the next member
	inc	si			; point at ident string
	cld
	push	cx			; save incoming count
	push	di			; save incoming string pointer
	repe	cmpsb			; compare cx characters
	pop	di
	pop	cx
	je	getat7d			; e = idents match
getat7b:inc	trans.xchset		; try next set
	dec	temp			; one less member to consider
	jnz	getat7a			; nz = more members to try
	pop	bx			; failure to find a match
	pop	si
	pop	di
	mov	trans.xchset,xfr_xparent; use Transparent for unknown char set
	cmp	flags.unkchs,0		; keep the file?
	je	getat7c			; e = yes, regardless of unk char set
	pop	es
	stc				; set carry for rejection
	ret
getat7c:jmp	getat0			; report success anyway

getat7d:pop	bx			; a match, use current trans.xchset
	pop	si
	pop	di
	cmp	trans.xchset,xfr_cyrillic ; using Transfer Char Set Cyrillic?
	jne	getat7e			; ne = no
	mov	flags.chrset,866	; force CP866 (required by Cyrillic)
	clc
	jmp	getat0
getat7e:cmp	trans.xchset,xfr_japanese ; using Trans Char Set Japanese-EUC?
	jne	getat7f			; ne = no
	mov	flags.chrset,932	; force Shift-JIS
	clc
	jmp	getat0			; success
getat7f:cmp	trans.xchset,xfr_latin2	; using Trans Char Set Latin-2?
	jne	getat7g			; ne = no
	mov	flags.chrset,852	; force CP852
	clc
	jmp	getat0			; success
getat7g:cmp	trans.xchset,xfr_hebiso	; using Hebrew-ISO?
	jne	getat7h			; ne = no
	mov	flags.chrset,862	; force CP862
getat7h:clc				; success
	jmp	getat0

					; workers for above
getatunk:inc	bx			; Unknown. Look at length field
	mov	al,es:[bx]
	sub	al,' '			; remove ascii bias
	xor	ah,ah
	inc	ax			; include length field byte
	add	bx,ax			; skip to next attribute
	jmp	getat0
					; Decode File length (Byte) field
getas:	mov	cl,es:[bx]		; length of file size field
	inc	bx			; point at file size data
	sub	cl,' '			; remove ascii bias
	xor	ch,ch
	xor	ax,ax			; current length, bytes
	xor	dx,dx
	jcxz	getas3			; z = empty field
getas2:	push	cx
	shl	dx,1			; high word of size, times two
	mov	di,dx			; save
	shl	dx,1
	shl	dx,1			; times 8
	add	dx,di			; yields dx * 10
	mov	di,dx			; save dx
	xor	dx,dx
	mov	cx,10			; also clears ch
	mul	cx			; scale up previous result in ax
	mov	cl,es:[bx]		; get a digit
	inc	bx
	sub	cl,'0'			; remove ascii bias
	add	ax,cx			; add to current length
	adc	dx,0			; extend result to dx
	add	dx,di			; plus old high part
	pop	cx
	loop	getas2
	mov	diskio.sizelo,ax	; low order word
	mov	diskio.sizehi,dx	; high order word
	clc
	ret
getas3:	dec	bx			; backup
	stc				; fail the decode
	ret
					; Decode Kilobyte attribute
getak:	mov	ax,diskio.sizelo	; current filesize, low word
	add	ax,diskio.sizehi
	or	ax,ax			; zero if not used yet
	jz	getak1			; z = not used before
	dec	bx			; backup pointer
	stc				; set carry to ignore this field
	ret

getak1:	call	getas			; parse as if Byte field
	jnc	getak2			; nc = parsed ok
	ret				; c = failure
getak2:	mov	ax,diskio.sizelo	; get low word of size
	mov	dx,diskio.sizehi	; high word
	mov	dh,dl			; times 256
	mov	dl,ah
	mov	ah,al
	xor	al,al
	shl	dx,1			; times four to make times 1024
	shl	dx,1
	rol	ax,1			; two high bits of ah to al
	rol	ax,1
	and	al,3			; keep them
	or	dl,al			; insert into high word
	xor	al,al
	mov	diskio.sizehi,dx	; store high word
	mov	diskio.sizelo,ax	; store low word
	clc				; clear carry
	ret
					; File date and time
getatd:	mov	word ptr ftime,1	; two seconds past midnight
	mov	word ptr fdate,0
	mov	dl,es:[bx]		; field length
	xor	dh,dh
	sub	dl,' '			; remove ascii bias
	inc	bx			; next field
	add	dx,bx			; where next field begins
	mov	temp,dx			; save in temp
	cmp	byte ptr es:[bx+6],' '	; short form date (yymmdd)?
	je	getad2			; e = yes
	add	bx,2			; skip century digits (19)
getad2:	mov	ax,10
	mov	dx,es:[bx]		; get year tens and units digits
	add	bx,2			; dl has tens, dh has units
	sub	dx,'00'			; remove ascii bias
	mul	dl			; ax = high digit times ten
	add	al,dh			; units digit
	sub	ax,80			; remove rest of 1980 bias
	jns	getad2a			; ns = no sign = non-negative result
	xor	ax,ax			; don't store less than 1980
getad2a:shl	al,1			; adjust for DOS bit format
	mov	fdate+1,al
	mov	ax,es:[bx]		; get month digits
	add	bx,2
	sub	ax,'00'			; remove ascii bias
	or	al,al			; tens digit set?
	jz	getad2b			; z = no
	add	ah,10			; add to units digit
getad2b:cmp	ah,8			; high bit of month set?
	jb	getad3			; b = no
	or	fdate+1,1
	sub	ah,8			; and deduct it here
getad3:	mov	cl,5
	shl	ah,cl			; normalize months bits
	mov	fdate,ah
	mov	dx,es:[bx]		; do day of the month
	add	bx,2			; dh has units, dl has tens digit
	sub	dx,'00'			; remove ascii bias
	mov	ax,10
	mul	dl			; ax = ten times tens digit
	add	al,dh			; plus units digit
	or	fdate,al
	cmp	bx,temp			; are we at the end of this field?
	jae	getad5			; ae = yes, prematurely
	inc	bx			; skip space separator
	mov	ax,10			; prepare for hours
	mov	dx,es:[bx]		; hh digits
	add	bx,2
	sub	dx,'00'			; remove ascii bias
	mul	dl			; 10*high digit of hours
	add	al,dh			; plus low digit of hours
	mov	cl,3			; normalize bits
	shl	al,cl
	mov	ftime+1,al		; store hours
	inc	bx			; skip colon
	mov	ax,10			; prepare for minutes
	mov	dx,es:[bx]		; mm digits
	add	bx,2
	sub	dx,'00'			; remove ascii bias
	mul	dl			; 10*high digit of minutes
	add	al,dh			; plus low digit of minutes
	xor	ah,ah
	mov	cl,5			; normalize bits
	shl	ax,cl
	or	ftime+1,ah		; high part of minutes
	mov	ftime,al		; low part of minutes
	cmp	bx,temp			; are we at the end of this field
	jae	getad5			; ae = yes, quit here
	inc	bx			; skip colon
	mov	ax,10			; prepare for seconds
	mov	dx,es:[bx]		; ss digits
	add	bx,2
	sub	dx,'00'			; remove ascii bias
	mul	dl			; 10*high digit of seconds
	add	al,dh			; plus low digit of seconds
	shr	al,1			; store as double-seconds for DOS
	or	ftime,al		; store seconds
getad5:	ret
GETATT	ENDP
 
; Receive data (tolerates A D E M Z types)
 
RDATA	PROC	NEAR
	call	rcvpak			; get next packet
	jnc	rdata1			; nc = success
	ret				; else return to do new state

rdata1:	mov	ah,[si].pktype		; check packet type
	cmp	ah,'D'			; Data packet?
	je	rdata3			; e = yes
	cmp	ah,'A'			; Attributes packet?
	je	rdata4			; e = yes
	cmp	ah,'M'			; Message packet?
	jne	rdat2			; ne = no
	call	msgmsg			; display message
	jmp	ackpak0			; ack the packet, stay in this state

rdat2:	cmp	ah,'Z'			; EOF packet?
	jne	rdat2a			; ne = no
	mov	rstate,'Z'		; next state is EOF, do not ack yet
	ret

rdat2a:	call	bufrel			; Unknown packet type, release buffer
	ret				;  and ignore it
					; D data packets
rdata3:	cmp	filopn,2		; file opened yet?
	je	rdata3b			; e = yes
	call	goopen			; open it now
	jnc	rdata3a			; nc = success
	jmp	giveup			; failure, dx has message pointer
rdata3a:mov	filopn,2		; say file is open now
rdata3b:call	ptchr			; decode 'D' packet, output to file
	jc	rdat3c			; c = failure to write output
	jmp	ackpak0			; ack the packet, stay in this state

rdat3c:	mov	dx,offset erms11	; cannot store all the data
	jmp	giveup			; tell the other side

		     			; 'A' packet, analyze		
rdata4:	call	getatt			; get file attributes from packet
	mov	cx,0			; reply length, assume 0/nothing
	jnc	rdat4b			; nc = success, attributes accepted
	mov	cx,2			; 2 bytes, declining the file
	mov	encbuf,'N'		; decline the transfer
	mov	al,attrib		; get attribute causing rejection
	mov	encbuf+1,al		; report rejection reason to sender
	or	fsta.xstatus,ksrecv	; set status, failed
	mov	kstatus,ksrecv		; global status, failed
	mov	flags.cxzflg,'X'	; set this in case host ignores 'N'
	test	flags.remflg,dquiet	; quiet display?
	jnz	rdat4b			; nz = yes
	push	si
	push	cx
	push	ax
	mov	dx,offset erms29	; say rejecting the file
	call	ermsg			; show rejecting file, then reason
	pop	ax
	mov	dx,offset erms30
	cmp	al,'1'			; Byte count?
	je	rdat4a			; e = yes
	cmp	al,'!'			; Kilobyte count?
	je	rdat4a			; e = yes
	mov	dx,offset erms31
	cmp	al,'#'			; Date and Time?
	je	rdat4a			; e = yes
	mov	dx,offset erms32
	cmp	al,'+'			; Mail?
	je	rdat4a			; e = yes
	mov	dx,offset erms33
	cmp	al,'"'			; File Type?
	je	rdat4a
	mov	dx,offset erms34
	cmp	al,'*'			; Transfer Char-set?
	je	rdat4a
	mov	dx,offset erms36	; unknown reason
rdat4a:	call	prtasz			; display reason
	pop	cx
	pop	si
rdat4b:	push	si
	mov	si,offset rpacket	; encode to this packet
	call	doenc			; do encoding
	pop	si
	call	filekind		; report Text/Bin, char set
	jmp	ackpak			; ACK the attributes packet
rdata endp

; End of File processor (expects Z type to have been received elsewhere)
; Enter with packet pointer in SI to a 'Z' packet.
reof	proc	near			; 'Z' End of File packet
	cmp	flags.cxzflg,0		; interrupted?
	jne	reof3			; ne = yes, no 100% done indicator
	cmp	fmtdsp,0		; formatted screen?
	je	reof5			; e = no, no message
	cmp	wrpmsg,0		; written Percentage done yet?
	je	reof5			; e = no
	mov	ax,tfilsz		; obtained file size
	mov	word ptr diskio.sizelo,ax ; force to original size
	mov	ax,tfilsz+2
	mov	word ptr diskio.sizehi,ax
	call	perpr			; show percentage done, 100%
	jmp	short reof5		; file close common code

reof3:	call	intmsg			; show interrupt msg on local screen
	or	errlev,ksrecv		; set DOS error level
	or	fsta.xstatus,ksrecv+ksuser ; set status, failed + intervention
	mov	kstatus,ksrecv+ksuser	; global status
	cmp	flags.cxzflg,'X'	; kill one file?
	jne	reof5			; ne = no
	mov	flags.cxzflg,0		; clear ^X so next file survives
					; common code for file closing
reof5:	cmp	filopn,2		; file opened yet?
	je	reof5b			; e = yes
	call	goopen			; open it now
	jnc	reof5a			; nc = success
	jmp	giveup			; failure, dx has message pointer
reof5a:	mov	filopn,2		; say file is open now
reof5b:	call	fileclose		; close the file
	call	dodec			; decode incoming packet to decbuf
	cmp	decbuf,'D'		; is the data "D" for discard?
	jne	reof7			; ne = no, write out file
	call	filedel			; delete file incomplete file
	or	errlev,ksrecv		; set DOS error level
	or	fsta.xstatus,ksrecv+ksuser ; set status, failed + intervention
	mov	kstatus,ksrecv+ksuser	; global status

reof7:	mov	rstate,'F'
	call	ackpak0			; acknowledge the packet
	mov	diskio.string,0		; clear file name
	ret
reof	endp

; init variables for read
rrinit	proc	near
	mov	trans.windo,1		; one window slot before negotiations
	mov	cx,drpsiz		; default receive pkt length (94)
	call	makebuf			; construct & clear all buffer slots
	call	packlen			; compute packet length
	xor	ax,ax
	mov	numpkt,ax		; set the number of packets to zero
	mov	windlow,al		; starting sequence number of zero
	mov	fsta.pretry,ax		; set the number of retries to zero
	mov	filopn,al		; say no file opened yet
	mov	windflag,al		; windows in use init flag
	mov	fmtdsp,al		; no formatted display yet
	mov	diskio.string,al	; clear active filename buffer
	mov	fsta.xname,al		; clear statistics external name
	ret
rrinit	endp

; Deliver packets organized by sequence number.
; Delivers a packet pointer in SI whose sequence number matches windlow.
; If necessary a new packet is requested from the packet recognizer. Failures
; to receive are managed here and may generate NAKs. Updates formatted screen.
; Store packets which do not match windlow, process duplicates and strays.
; Error packet and ^C/^E interrupts are detected and managed here.
; Return success with carry clear and SI holding the packet structure address.
; Return failure with carry set, maybe with a new rstate.

rcvpak	proc	near
	mov	al,windlow		; sequence number we want
	call	pakptr			; find pkt pointer with this seqnum
	mov	si,bx			; the packet pointer
	jnc	rcvpa1a			; nc = got one, else read fresh pkt
	call	getbuf			; get a new buffer address into si
	jnc	rcvpa1			; nc = success
	mov	bx,offset erms15	; insufficient buffers
	jmp	giveup

rcvpa1:	call	winpr			; show window slots in use
	call	rpack			; receive a packet, si has buffer ptr
	jc	rcvpa2			; c = failure to receive, analyze
	inc	numpkt			; increment the number of packets
	cmp	flags.xflg,0		; receiving to screen?
	jne	rcvpa1a			; ne = yes, skip displaying
	cmp	flags.destflg,2		; destination is screen?
	je	rcvpa1a			; e = yes
	call	pktsize			; report packet qty and size
rcvpa1a:jmp	rcvpa6			; success, validate
; ------------------- failure to receive any packet -------------------------
					; Reception failed. What to do?
rcvpa2:	call	cntretry		; update retries, detect ^C, ^E
	jc	rcvpa2a			; c = exit now from ^C, ^E
	call	bufrel			; discard unused buffer
	inc	badrcv			; count receive retries
	mov	al,badrcv		; count # bad receptions in a row
	cmp	al,maxtry		; too many?
	jb	rcvpa4			; b = not yet, NAK intelligently
	mov	dx,offset erms14	; no response from host
	jmp	giveup			; tell the other side

rcvpa2a:call	bufrel			; discard unwanted buffer
	stc				; set carry for failure
	ret				; move to Error state

					; do NAKing
rcvpa4:	mov	al,windlow		; Timeout or Crunched packet
	add	al,trans.windo		; find next slot after last good
	dec	al
	and 	al,3fh			; start at window high
	mov	ah,-1			; set a not-found marker
	mov	cl,trans.windo		; cx = number of slots to examine
	xor	ch,ch
rcvpa4a:call	pakptr			; sequence number (in AL) in use?
	jnc	rcvpa4b			; nc = yes, stop here
	mov	ah,al			; remember seqnum of highest vacancy
	dec	al			; work backward in sequence numbers
	and	al,3fh
	loop	rcvpa4a

rcvpa4b:mov	al,ah			; last-found empty slot (-1 = none)
	cmp	ah,-1			; found a vacant slot?
	jne	rcvpa4c			; ne = no, else use first free seqnum
	call	firstfree		; set AL to first open slot
	jc	rcvpa4d			; c = no free slots, an error
rcvpa4c:mov	rpacket.seqnum,al	; NAK this unused sequence number
	call	nakpak			; NAK using rpacket
	jc	rcvpa4d			; c = failure on sending operation
	stc				; rcv failure, stay in current state
	ret

rcvpa4d:mov	dx,offset erms13	; failure, cannot send reply
	jmp	giveup			; show msg, change states
; ------------------------- received a packet ------------------------------
			; remove duplicates, validate sequence number
rcvpa6:	mov	badrcv,0		; clear retry counter
	cmp	[si].pktype,'E'		; Error packet? Accept w/any seqnum
	jne	rcvpa6a			; ne = no
	jmp	error			; display message, change states

rcvpa6a:mov	al,[si].seqnum		; this packet's sequence number
	mov	rpacket.seqnum,al	; save here for reply
	call	pakdup			; set ah to number of copies
	cmp	ah,1			; more than one copy?
	jbe	rcvpa7			; be = no, just one
	call	bufrel			; discard duplicate
	mov	al,rpacket.seqnum	; recover current sequence number
	call	pakptr			; get packet pointer for original
	mov	si,bx			; should not fail if pakdup works ok
	jnc	rcvpa7			; nc = ok, work on the original again
	ret				; say failure, stay in current state

rcvpa7:	call	chkwind			; validate sequence number (cx=status)
	jc	rcvpa7b			; c = outside current window
	mov	al,[si].seqnum		; get sequence number again
	cmp	al,windlow		; is it the desired sequence number?
	jne	rcvpa7a			; ne = no, do not change states yet
	clc
	ret				; return success, SI has packet ptr

rcvpa7a:stc				; not desired pkt, stay in this state
	ret				; do not increment retry counter here

rcvpa7b:or	cx,cx			; inside previous window?
	jg	rcvpa7c			; g = outside any window, ignore it
	mov	al,[si].pktype		; get packet Type
	cmp	al,'I'			; let 'I' and 'S' pkts be reported
	je	rcvpa7d			; even if in previous window, to
	cmp	al,'S'			; accomodate lost ack w/data
	je	rcvpa7d
	cmp	al,'Y'			; maybe our ACK echoed?
	je	rcvpa7c			; e = yes, discard
	cmp	al,'N'			; or our NAK echoed?
	je	rcvpa7c			; e = yes, discard
	call	ackpak0			; previous window, ack and ignore it
	stc				; rcv failure, stay in current state
	ret

rcvpa7c:call	bufrel			; ignore packet outside of any window
	stc				; rcv failure, stay in current state
	ret

rcvpa7d:mov	rstate,'R'		; redo initialization when 'I'/'S'
	stc				;  are observed, keep current pkt
	ret
rcvpak	endp

; Send ACK packet. Enter with rpacket data field set up.
; ACKPAK sends ack with data, ACKPAK0 sends ack without data.
ackpak	proc	near			; send an ACK packet
	cmp	rpacket.datlen,0	; really just no data?
	jne	ackpa2			; ne = no, send prepared ACK packet
ackpak0:mov	rpacket.datlen,0	; no data
	cmp	flags.cxzflg,0		; user interruption?
	je	ackpa2			; e = no
	push	cx			; yes, send the interrupt character
	push	si
	mov	si,offset rpacket
	mov	cl,flags.cxzflg		; send this so host knows about ^X/^Z
	mov	encbuf,cl		; put datum into the encode buffer
	mov	cx,1			; data size of 1 byte
	call	doenc			; encode, char count is in cx
	pop	si
	pop	cx
ackpa2:	mov	rpacket.pktype,'Y'	; ack packet
	mov	rpacket.numtry,0
ackpa3:	push	si
	mov	si,offset rpacket
	call	spack			; send the packet
	pop	si
	jnc	ackpa4			; nc = success
	cmp	flags.cxzflg,'C'	; Control-C abort?
	je	ackpa3a			; e = yes, quit now
	cmp	flags.cxzflg,'E'	; Control-E abort?
	je	ackpa3a			; e = yes, quit now
	push	ax			; send failure, retry
	mov	ax,100			; 0.1 sec
	call	pcwait			; small wait between retries
	inc	rpacket.numtry
	mov	al,rpacket.numtry
	cmp	al,maxtry		; exceeded retry limit?
	pop	ax
	jbe	ackpa3			; be = ok to try again
	mov	sstate,'A'		; set states to abort
	mov	rstate,'A'
	mov	rpacket.numtry,0
	mov	dx,offset erms13	; unable to send reply
	jmp	giveup
ackpa3a:stc				; set carry for failure
	ret

ackpa4:	mov	al,rpacket.seqnum	; success
	mov	rpacket.datlen,0	; clear old contents
	call	pakptr			; acking an active buffer?
	jc	ackpa5			; c = no such seqnum, stray ack
	push	si
	mov	si,bx			; packet pointer from pakptr
	call	bufrel			; release ack'ed packet
	pop	si
	mov	rpacket.numtry,0
	cmp	al,windlow		; acking window low?
	jne	ackpa5			; ne = no
	mov	al,windlow		; yes, rotate the window
	inc	al
	and	al,3fh
	mov	windlow,al
ackpa5:	clc
	ret
ackpak	endp

; Send a NAK. Uses rpacket structure.
NAKPAK	proc	near
	mov	rpacket.numtry,0
nakpa2:	push	si
	mov	si,offset rpacket
	mov	[si].datlen,0		; no data
	inc	fsta.nakscnt		; count NAKs sent
        mov	[si].pktype,'N'		; NAK that packet
	call	spack
	pop	si
	jc	nakpa3			; c = failure
	mov	rpacket.numtry,0
	clc
	ret				; return success

nakpa3:	cmp	flags.cxzflg,'C'	; Control-C abort?
	je	nakpa3a			; e = yes, quit now
	cmp	flags.cxzflg,'E'	; Control-E abort?
	je	nakpa3a			; e = yes, quit now
	push	ax			; send failure, retry
	mov	ax,100			; wait 0.1 second
	call	pcwait
	inc	rpacket.numtry		; count attempts to respond
	mov	al,rpacket.numtry
	cmp	al,maxtry		; tried enough times?
	pop	ax
	jbe	nakpa2			; be = ok to try again
	mov	sstate,'A'		; set states to abort
	mov	rstate,'A'
	mov	rpacket.numtry,0
	mov	dx,offset erms13	; unable to send reply
	jmp	giveup
nakpa3a:stc
	ret				; return failure
NAKPAK	ENDP

; Close, but do not delete, output file. Update file attributes,
; add Control-Z or Control-L, if needed.
fileclose proc	near
	cmp	filopn,0		; is a file open?
	jne	filec0			; ne = yes
	ret
filec0:	cmp	flags.xflg,0		; receiving to screen?
	jne	filec2			; ne = yes
	cmp	flags.destflg,1		; destination is disk?
	jne	filec1			; ne = no
	cmp	flags.eofcz,0		; should we write a ^Z?
	je	filec1			; e = no, keep going
	cmp	trans.xtype,0		; test mode tranfer?
	jne	filec2			; ne = no, binary, no ^Z
	push	si
	mov	rpacket.datlen,1	; one byte to decode and write
	push	es
	les	si,rpacket.datadr	; source buffer address
	mov	byte ptr es:[si],'Z'-40h ; put Control-Z in buffer
	pop	es
	mov	si,offset rpacket	; address for decoder
	call	ptchr			; decode and write to output
	pop	si
filec1:	cmp	flags.destflg,0		; file destination is printer?
	jne	filec2			; ne = no, skip next part
	push	si
	mov	rpacket.datlen,1	; one byte to decode and write
	push	es
	les	si,rpacket.datadr	; source buffer address
	mov	byte ptr es:[si],'L'-40h ; put Control-L (FF) in buffer
	pop	es
	mov	si,offset rpacket	; address for decoder
	call	ptchr			; decode and write to output
	pop	si
filec2:	mov	ah,write2		; write to file
	xor	cx,cx			; write 0 bytes to truncate length
	mov	bx,diskio.handle	; file handle
	or	bx,bx			; valid handle?
	jl	filec5			; l = no
	int	dos
	xor	al,al			; get device info
	mov	ah,ioctl
	int	dos
	test	dl,80h			; bit set if handle is for a device
	jnz	filec4			; nz = non-disk, no file attributes
					; do file attributes and close
	mov	cx,word ptr ftime	; new time
	mov	dx,word ptr fdate	; new date
	mov	word ptr fdate,0
	mov	word ptr ftime,0	; clear current time/date attributes
	mov	ax,cx
	or	ax,dx
	jz	filec4			; z = no attributes to set
	or	cx,cx			; time set as null?
	jnz	filec3			; nz = no
	inc	cl			; two seconds past midnight
filec3:	mov	ah,setattr		; set file date/time attributes
	mov	al,1			; set, not get
	mov	bx,diskio.handle	; file handle
	int	dos			; end of file attributes
filec4:	mov	bx,diskio.handle	; file handle
	push	dx			; save dx
	mov	ah,close2		; close file
	int	dos
	pop	dx
	mov	filopn,0		; say file is closed
filec5:	ret
fileclose endp

; Delete file whose asciiz name is in diskio.string
filedel	proc	near
	mov	dx,offset diskio.string	; file name, asciiz
	xor	ax,ax
	cmp	diskio.string,al	; filename present?
	je	filede2			; e = no
	cmp	flags.abfflg,al		; keep incomplete file?
	je	filede2			; e = yes
	test	flags.remflg,dquiet	; quiet display?
	jnz	filede1			; nz = yes
	cmp	flags.xflg,al		; receiving to screen?
	jne	filede1			; ne = yes, no message
	push	dx
	call	cxmsg			; clear Last message line
	mov	dx,offset infms7	; saying Discarding file
	mov	ah,prstr
	int	dos
	pop	dx
	call	prtasz			; show filename
filede1:mov	ah,del2			; delete the file
	int	dos
filede2:ret
filedel	endp

; Error exit. Enter with dx pointing to asciiz error message.
; Sends 'E' Error packet and shows message on screen. Changes state to 'A'.
; Always returns with carry set.
giveup	proc	near
	cmp	flags.destflg,2		; receiving to the screen?
	je	giveu1			; e = yes, no formatted display
	call	ermsg			; show msg on error line
giveu1:	mov	bx,dx			; set bx to error message
	call	errpack			; send error packet just in case
	mov	rstate,'A'		; change the state to abort
	stc				; set carry
	ret
giveup	endp

; ERROR sets abort state, positions the cursor and displays the Error message.
 
ERROR	PROC	NEAR
	mov	rstate,'A'		; set state to abort
	call	dodec			; decode to decbuf
	mov	dx,offset decbuf	; where msg got decoded, asciiz
	call	ermsg			; show string
	stc				; set carry for failure state
	ret
ERROR	ENDP

; Called by GETATT in receiver code to verify sufficient disk space.
; Gets file path from diskio.string setup in mssfil, remote size in diskio
; from getatt, and whether a disk file or not via ioctl on the file handle.
; Returns carry clear if enough space.
spchk	proc	near			; check for enough disk space
	push	ax
	push	bx
	push	cx
	push	dx
	mov	ah,ioctl		; ask DOS about this file handle
	xor	al,al			; get info
	mov	bx,diskio.handle
	int	dos
 	test	dl,80h			; handle is a disk file?
	jnz	spchk5b			; nz = no, always enough space
	mov	ah,gcurdsk		; get current disk
	int	dos
	add	al,'A'			; make 0 == A
	mov	cl,al			; assume this drive
	mov	dx,word ptr diskio.string ; filename used in open
	cmp	dh,':'			; drive letter given?
	jne	spchk1			; ne = no
	mov	cl,dl			; get the letter
	and	cl,not 20h		; convert to upper case
spchk1:	call	dskspace		; calculate space into dx:ax
	jc	spchk6			; c = error
	push	ax			; save low word of bytes
	push	dx			; save high word, dx:ax
	mov	dx,diskio.sizehi	; high word of file size dx:ax
	mov	ax,diskio.sizelo	; low word
	mov	cx,dx			; copy size long word to cx:bx
	mov	bx,ax
	shr	bx,1			; divide long word by two
	shr	cx,1
	jnc	spchk2			; nc = no carry down
	or	bx,8000h		; get carry down
spchk2:	shr	bx,1			; divide by two again
	shr	cx,1
	jnc	spchk3
	or	bx,8000h		; get carry down
spchk3:	shr	bx,1			; divide long word by two
	shr	cx,1
	jnc	spchk4			; nc = no carry down
	or	bx,8000h		; get carry down
spchk4:	shr	bx,1			; divide long word by two
	shr	cx,1
	jnc	spchk5			; nc = no carry down
	or	bx,8000h		; get carry down
spchk5:	add	ax,bx			; form dx:ax = (17/16) * dx:ax
	adc	dx,cx
	pop	cx			; high word of disk space
	pop	bx			; low word
	sub	bx,ax			; minus inflated file size, low word
	sbb	cx,dx			;  and high word
	js	spchk6			; s = not enough space for file
spchk5b:clc
	jmp	short spchk7		; enough space
spchk6:	stc
spchk7:	pop	dx
	pop	cx
	pop	bx
	pop	ax
	ret
spchk	endp

code	ends 
	end
