NAME mssrcv ; File MSSRCV.ASM include mssdef.h ; Edit history ; Last edit 16 Jan 1990 ; Sliding Windows public read2, read, rrinit, ackpak, nakpak, rstate setattr equ 57h ; DOS get/set file's date and time data segment public 'data' 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 cemsg db 'User intervention',0 ermes6 db 'Unknown packet type',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 $' donemsg db '100%$' 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 temp dw 0 data ends code segment public 'code' extrn gofil:near, comnd:near, cntretry:near, chkwind:near extrn spack:near, rpack:near, 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, pakdup:near, packlen:near extrn dodec:near, doenc:near, errpack:near, intmsg:near extrn ihostr:near, getbuf:near, prtasz:near, begtim:near extrn endtim:near, pktsize:near,strlen:near,strcpy:near extrn bufclr:near, bufrel:near, pakptr:near, msgmsg:near extrn makebuf:near, clrbuf:near, pcwait:near, firstfree: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,2 ; set DOS error level or fsta.xstatus,2 ; set status, failed or kstatus,2 ; 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,0 ; global status, success mov windflag,0 ; init windows in use display flag mov numpkt,0 ; set the number of packets to zero mov fsta.pretry,0 ; clear total retry counter call cxerr ; clear Last Error line call cxmsg ; clear Last Message line call begtim ; start next statistics group mov flags.cxzflg,0 ; reset ^X/^Z flag 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 dispatch dispat2:cmp ah,'F' ; File header receive state? jne dispat3 call rfile ; receive file header jmp dispatch dispat3:cmp ah,'D' ; Data receive state? jne dispat4 call rdata ; get data packets jmp dispatch dispat4:cmp ah,'Z' ; EOF? jne dispat5 call reof ; do EOF wrapup jmp dispatch dispat5:cmp ah,'E' ; ^C or ^E abort? jne dispat6 ; ne = no call bufclr 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,2 ; set DOS error level or fsta.xstatus,2+80h ; set status, failed + intervention or kstatus,2+80h ; global status dispat7:xor ax,ax ; tell statistics this is a receive operation call endtim ; stop file statistics accumulator call bufclr ; release all buffers mov windlow,0 cmp rstate,'C' ; receive complete state? je dispat8 ; e = yes or errlev,2 ; Failed, set DOS error level or fsta.xstatus,2 ; set status, failed or kstatus,2 ; 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 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 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:cmp ah,'N' ; old NAK from a server? jne rinit4b ; ne = no call bufrel ; yes, release this packet buffer ret ; and ignore it rinit4b:mov dx,offset ermes6 ; say unknown packet type jmp giveup ; tell the other side 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 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? je rfile3 ; e = yes, 'X' pkt jmp rfile5 ; 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 cxerr ; clear Last Error line call cxmsg ; clear Last Message line 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 call gofil ; open the output file jnc rfile4 ; nc = success jmp giveup ; failure, dx has message pointer rfile4: mov filopn,2 ; say file is open for writing 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 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: mov dx,offset ermes6 ; unknown packet type jmp giveup ; tell both sides and quit 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 mov bx,[si].datadr ; pointer to data field getat0: push bx sub bx,[si].datadr ; bx = length to examine cmp bx,[si].datlen ; are we beyond end of data? pop bx jl getat1 ; l = not yet clc ret ; has carry clear for success getat1: cmp byte ptr [bx],'1' ; Byte length field? jne getat2 ; ne = no mov al,[bx] ; remember attribute mov attrib,al inc bx ; pointer test flags.attflg,attlen ; allowed to examine file length? jnz getat1a ; nz = yes jmp getatunk ; z = no, ignore getat1a:push si call getas ; get file size call spchk ; check available disk space pop si jnc getat0 ; nc = have enough space for file ret ; return failure getat2: cmp byte ptr [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,[bx] ; remember attribute mov attrib,al inc bx ; pointer call getak ; get file size jc getat2a ; carry means decode rejected call spchk ; check available disk space jnc short getat0 ret ; return failure getat3: cmp byte ptr [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,[bx] ; remember attribute mov attrib,al inc bx call getatd ; get file date jmp short getat0 getat4: cmp byte ptr [bx],'+' ; Disposition? jne getat5 ; ne = no mov al,[bx] ; remember attribute mov attrib,al cmp byte ptr [bx+2],'M' ; Mail indicator? je getat4a ; e = yes, fail jmp getatunk ; ne = no, ignore field getat4a:stc ; set carry for failure ret getat5: cmp byte ptr [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,[bx] ; get length inc bx sub cl,20h ; remove ascii bias jc getat5d ; c = error in length, fail cmp byte ptr [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 [bx],'B' ; "B" Binary? jne getat5d ; ne = no, fail cmp cl,2 ; full "B8"? jb getat5c ; b = no, just "B" cmp byte ptr [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 ret getat6: cmp byte ptr [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,[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 [bx],'A' ; Normal Transparent? jne getat6b ; be = not Transparent add bx,cx ; point at next field clc jmp getat0 getat6b:cmp byte ptr [bx],'C' ; character set? je getat7 ; e = yes getat6c:stc ; set carry for rejection ret getat7: push si ; examine transfer character set mov si,bx ; point at first data character add bx,cx ; point bx beyond the text dec cx ; deduct leading 'C' char from count 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,0 ; assume xfer char-set 0 (Transparent) getat7a:add bx,2 ; point to a member's address mov di,[bx] ; point at member [length, string] cmp cl,[di] ; string lengths the same? jne getat7b ; ne = no, try the next member inc si ; skip the 'C' inc di ; point at ident string cld push es push ds pop es ; set es:di to data segment push cx ; save incoming count push si ; save incoming string pointer repe cmpsb ; compare cx characters pop si pop cx pop es jne getat7b ; ne = idents do not match pop bx ; a match, use current trans.xchset pop si clc jmp getat0 ; success 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 mov trans.xchset,0 ; use Transparent for unknown char set cmp flags.unkchs,0 ; keep the file? je getat7c ; e = yes, regardless of unk char set stc ; set carry for rejection ret getat7c:jmp getat0 ; report success anyway ; workers for above getatunk:inc bx ; Unknown. Look at length field mov al,[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,[bx] ; length of file size field inc bx ; point at file size data sub cl,' ' ; remove ascii bias mov ch,0 mov ax,0 ; current length, bytes mov dx,0 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 mov dx,0 mov cx,10 ; also clears ch mul cx ; scale up previous result in ax mov cl,[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 ret ; Decode Kilobyte attribute getak: mov ax,diskio.sizelo ; current filesize, low word add ax,diskio.sizehi cmp ax,0 ; zero if not used yet je getak1 ; e = not used before dec bx ; backup pointer stc ; set carry to ignore this field ret getak1: call getas ; parse as if Byte field 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 mov al,0 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 mov al,0 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,[bx] ; field length mov dh,0 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[bx+6],' ' ; short form date (yymmdd)? je getad2 ; e = yes add bx,2 ; skip century digits (19) getad2: mov ax,10 mov dx,[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 mov ax,0 ; don't store less than 1980 getad2a:shl al,1 ; adjust for DOS bit format mov fdate+1,al mov ax,[bx] ; get month digits add bx,2 sub ax,'00' ; remove ascii bias cmp al,0 ; tens digit set? je getad2b ; e = 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,[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,[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,[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 mov ah,0 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,[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: mov dx,offset ermes6 ; Unknown packet type jmp giveup rdata3: call ptchr ; decode 'D' packet, output to file jc rdat3a ; c = failure to write output jmp ackpak0 ; ack the packet, stay in this state rdat3a: 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,2 ; set status, failed mov kstatus,2 ; global status, failed 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 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 call perpos ; position cursor to percent done mov dx,offset donemsg ; say 100% mov ah,prstr int dos jmp short reof5 ; file close common code reof3: call intmsg ; show interrupt msg on local screen or errlev,2 ; set DOS error level or fsta.xstatus,2+80h ; set status, failed + intervention mov kstatus,2+80h ; 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: 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,2 ; set DOS error level or fsta.xstatus,2+80h ; set status, failed + intervention mov kstatus,2+80h ; global status reof7: mov rstate,'F' call ackpak0 ; acknowledge the packet mov ax,0 ; tell statistics this was a receive call endtim mov diskio.string,0 ; clear file name ret reof endp ; init variables for read rrinit proc near call makebuf ; construct & clear all buffer slots call packlen ; compute packet length mov numpkt,0 ; set the number of packets to zero mov windlow,0 ; starting sequence number of zero mov fsta.pretry,0 ; set the number of retries to zero mov filopn,0 ; say no file opened yet mov windflag,0 ; windows in use init flag mov fmtdsp,0 ; no formatted display yet 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: 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 mov badrcv,0 ; clear retry counter 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:cmp cx,0 ; inside previous window? jg rcvpa7c ; g = outside any window, ignore it cmp [si].pktype,'I' ; let 'I' and 'S' pkts be reported je rcvpa7d ; even if in previous window, to cmp [si].pktype,'S' ; accomodate lost ack w/data je rcvpa7d call ackpak0 ; previous window, ack and ignore it stc ; rcv failure, stay in current state ret rcvpa7d:mov rstate,'R' ; redo initialization when 'I'/'S' stc ; are observed, keep current pkt ret rcvpa7c:call bufrel ; ignore packet outside of any window stc ; rcv failure, stay in current state 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 mov si,rpacket.datadr ; source buffer address mov byte ptr[si],'Z'-40h ; put Control-Z in buffer 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 mov si,rpacket.datadr ; source buffer address mov byte ptr [si],'L'-40h ; put Control-L (FF) in buffer 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 cmp bx,0 ; 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 cmp diskio.string,0 ; filename present? je filede2 ; e = no cmp flags.abfflg,0 ; keep incomplete file? je filede2 ; e = yes test flags.remflg,dquiet ; quiet display? jnz filede1 ; nz = yes cmp flags.xflg,0 ; 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 mov al,0 ; get info mov bx,diskio.handle int dos test dl,80h ; handle is a disk file? jnz spchk5b ; nz = no, always enough space and dl,01fh ; get current drive from bits 5-0 add dl,'A' ; convert to a letter mov cl,dl ; cl holds drive letter 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