NAME mssrcv ; File MSSRCV.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 public read2, read, rrinit, ackpak, nakpak, rstate, fdate, ftime 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, filtst:byte, rdbuf:byte extrn ferbyte:byte, ferdate:byte, ferdisp:byte, fertype:byte extrn ferchar:byte, fername:byte, ferunk:byte, termserver:byte extrn k_rto:word, cwindow:byte, vfile:byte 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 ifndef nls_portuguese infms1 db cr,' Receiving: In progress',cr,lf,'$' else infms1 db cr,' Receiving: In progress',cr,lf,'$' endif ; nls_portuguese infms3 db 'Completed',cr,lf,'$' infms4 db 'Failed',cr,lf,'$' infms6 db 'Interrupted',cr,lf,'$' infms7 db 'Discarding $' 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 restart_flag db 0 ; non-zero if remote requests file restart rstate db 0 ; state of automata permchrset dw 0 ; permanent file character set holder permflwflg db 0 ; save file transfer set ten dw 10 temp dw 0 data ends data1 segment filhlp2 db ' Local path or filename or carriage return$' data1 ends code1 segment extrn bufclr:far, pakptr:far, bufrel:far, makebuf:far, chkwind:far extrn firstfree:far, getbuf:far, pakdup:far, rpar:far extrn rpack:far, spack:far, fcsrtype:far, prtasz:far, dskspace:far extrn logtransact:far, strlen:far, strcpy:far code1 ends code segment extrn gofil:near, comnd:near, cntretry:near, perpr:near extrn serini:near, spar:near, lnout:near extrn init:near, cxmsg:near, cxerr:near extrn ptchr:near, ermsg:near, winpr:near extrn stpos:near, rprpos:near, packlen:near, kbpr:near extrn dodec:near, doenc:near, errpack:near, intmsg:near extrn ihostr:near, begtim:near extrn endtim:near, pktsize:near extrn msgmsg:near, clrbuf:near, pcwait:far, 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 dx,offset filhlp2 ; help message mov bx,offset auxfile ; local file name string 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,dest_screen ; destination is screen? je read2 ; e = yes mov flags.xflg,0 ; not writing to screen, yet 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 mov al,flags.flwflg ; file warning flag mov permflwflg,al ; save around file transfer set mov si,offset auxfile mov di,offset vfile call strcpy ; copy name to \v(filename) buffer 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 cmp termserver,1 ; invoked from terminal emulator? jne dispat1 ; ne = no mov windlow,0 ; lowest acceptable packet number mov trans.chklen,1 ; Use 1 char for NAK packet mov rpacket.seqnum,0 call nakpak ; send an initial NAK mov fsta.nakscnt,0 ; do not count this NAK stimulus inc termserver ; say have responded to invokation dispat1: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 mov al,permflwflg ; saved around file transfer set mov flags.flwflg,al ; restore file warning state 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,dest_screen ; receiving to screen? je dispa11 ; e = yes, nothing to clean up test flags.remflg,dquiet ; quiet display mode? jnz dispa11 ; nz = yes, keep going test flags.remflg,dserial ; serial display mode? jnz dispa10a ; nz = yes, ring bell 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 dispa10a: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 mov termserver,0 clc ; return to ultimate caller, success ret dispatch endp ; Receive routines ; Receive initiate packet (tolerates I E F M S X Y types) RINIT PROC NEAR mov ax,18 ; 18.2 Bios ticks per second mul trans.stime ; byte, seconds mov k_rto,ax ; round trip timeout, Bios ticks 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 rinit3 ; ne = no call msgmsg ; display message mov trans.chklen,1 ; send Init checksum is always 1 char call ackpak0 ; ack and release packet ret rinit3: cmp ah,'I' ; unexpected 'I' packet? jne rinit4 ; e = yes, respond call spar ; unexpected I packet, parse info call packlen mov cx,trans.rlong ; max receiving pkt length to report call makebuf ; remake buffers for new windowing push si mov si,offset rpacket call rpar ; setup info about our reception pop si mov al,trans.chklen ; checksum length negotiated push ax ; save around reply mov trans.chklen,1 ; reply with 1 char checksum call ackpak ; send response pop ax ; restore checksum length mov dtrans.chklen,al ; to negotiation value ret ; stay in this state 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 rfile3 ; e = yes, 'F' pkt cmp [si].pktype,'X' ; visual display header? jne rfile5 ; ne = neither one mov flags.xflg,1 ; 'X', say receiving to the screen rfile3: 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 ax,permchrset ; permanent character set (Code Page) mov flags.chrset,ax ; active character set mov si,offset decbuf mov di,offset rdbuf+cmdblen-65 ; holding spot for original name call strcpy ; copy to holding spot 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 mov attrib,' ' ; clear failing attribute code push es les bx,[si].datadr ; pointer to data field getat0: push bx sub bx,word ptr [si].datadr ; bx => length examined cmp bx,[si].datlen ; more than supplied data? pop bx jl getat1 ; l = not yet pop es clc ret ; has carry clear for success getat1: mov al,es:[bx] ; get attribute kind mov attrib,al ; store for failure report cmp al,'1' ; Byte length field? jne getat2 ; ne = no test flags.attflg,attlen ; allowed to examine file length? jnz getat1b ; nz = yes getat1a:jmp getatunk ; z = no, ignore getat1b:inc bx ; step to length of field byte call getas ; get file size from packet jc getat1a ; c = failed to decode properly call spchk ; check available disk space jnc getat0 ; nc = have enough space for file pop es ret ; return failure getat2: cmp al,'!' ; 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:inc bx ; step to length of field byte call getak ; get file size from packet jc getat2a ; carry means decode rejected call spchk ; check available disk space jnc short getat0 ; nc = have enough space pop es ret ; return failure getat3: cmp al,'#' ; 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:inc bx ; point at length of field call getatd ; get file date jnc short getat0 pop es ret ; return failure getat4: cmp al,'+' ; Disposition? jne getat5 ; ne = no mov ax,es:[bx+1] ; count byte, disposition byte cmp ah,'M' ; Mail indicator? je getat4d ; e = yes, fail cmp ah,'P' ; REMOTE PRINT? jne getat4b ; ne = no test flags.remflg,dserver ; acting as a server now? jz getat4a ; z = no test denyflg,prtflg ; is this server command disabled? jnz getat4d ; nz = yes, disabled getat4a:mov word ptr diskio.string,'RP' ; output to PRN mov word ptr diskio.string+2,'N' ; ignore options inc bx ; step to data field sub al,20h ; count byte bias removal xor ah,ah add bx,ax ; step to next attribute jmp getat0 getat4b:cmp ah,'R' ; Restart? jne getat4c ; ne = no or restart_flag,1 ; say restarting getat4c:jmp getatunk ; ignore field getat4d:stc ; set carry for failure pop es ret getat5: cmp al,'"' ; File Type? jne getat6 ; ne = no test flags.attflg,atttype ; allowed to examine file type? jnz getat5a ; nz = yes jmp getatunk ; z = no, ignore field getat5a:inc bx ; step to length of field byte xor ch,ch mov cl,es:[bx] ; get length inc bx mov ax,es:[bx] ; data sub cl,20h ; remove ascii bias jc getat5d ; c = error in length, fail add bx,cx ; step to next field cmp al,'A' ; Type letter (A, B, I), Ascii? jne getat5b ; ne = no mov trans.xtype,0 ; say Ascii/Text file type jmp getat0 ; next item please getat5b:cmp al,'B' ; "B" Binary? jne getat5d ; ne = no, fail cmp cl,2 ; full "B8"? jb getat5c ; b = no, just "B" cmp ah,'8' ; proper length? jne getat5d ; ne = no getat5c:mov trans.xtype,1 ; say Binary or restart_flag,2 ; restart, remember binary mode jmp getat0 ; next item please getat5d:stc ; set carry for rejection pop es ret getat6: cmp al,'*' ; character set usage? jne getat8 ; ne = no test flags.attflg,attchr ; allowed to examine char-set? jnz getat6a ; nz = yes getat6d:jmp getatunk ; z = no, ignore getat6a:inc bx ; step to length field mov cl,es:[bx] ; get length sub cl,' ' ; remove ascii bias js getat6c ; c = length error, fail xor ch,ch inc bx ; first data byte mov al,es:[bx] mov trans.xchset,0 ; assume Transparent Transfer char-set cmp al,'A' ; Normal Transparent? jne getat6b ; be = not Transparent add bx,cx ; point to next attribute jmp getat0 getat6b:cmp al,'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 to next attribute push bx ; save bx dec cx ; deduct leading 'C' char from count inc di ; skip the 'C' 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:clc 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 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 jmp getat0 ; success getat7g:cmp trans.xchset,xfr_hebiso ; using Hebrew-ISO? jne getat7h ; ne = no mov flags.chrset,862 ; force CP862 getat7h:jmp getat0 getat8: cmp al,'@' ; attribute count of zero? jne getatunk ; ne = no inc bx ; step to length field mov al,es:[bx] ; length inc bx ; set to data field cmp al,' ' ; End of Attributes code? jne getatunk ; ne = no test restart_flag,1 ; has Restart been requested? jz getat8b ; z = no test restart_flag,2 ; and Binary transfer requested? jnz getat8b ; nz = yes, that's fine mov attrib,'+' ; failure reason is disposition getat8a:pop es stc ; fail ret getat8b:clc ; success, end attributes analysis pop es ret ; 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: sub bx,2 ; backup to attribute kind stc ; fail the decode ret ; Decode Kilobyte attribute worker 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 to attribute kind 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 worker 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 getad1 ; e = yes, get current century mov dx,es:[bx] ; get century digits sub dx,'00' ; subtract ascii bias mov al,10 mul dl add al,dh ; dh has units sub al,19 add bx,2 ; skip to year pair jmp getad2 getad1: mov ah,getdate ; DOS date (cx= yyyy, dh= mm, dl= dd) int dos mov ax,cx xor dx,dx div ten xor dx,dx div ten ; truncate to centuries sub al,19 ; remove 19 bias, current century getad2: mul ten ; al has centuries - 19 mul ten ; centuries equal 100 years mov cl,al ; keep results in cl 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 mov ax,10 mul dl ; ax = high digit times ten add al,dh ; units digit add al,cl ; add centuries since 1900 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:cmp ax,128 ; beyond 2108? (1980 + 128) jb getad2c ; b = no stc ; fail ret getad2c:shl al,1 ; adjust for DOS bit format mov fdate+1,al ; binary years after 1980 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: cmp flags.flwflg,filecol_update ; updating? jne getad6 ; ne = no cmp filopn,2 ; file opened yet? je getad6 ; e = yes call goopen ; open it now mov filopn,2 ; file is open (carry set for fail) mov bx,temp ; point to next attribute ret getad6: mov bx,temp ; point to next attribute clc ; success 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: mov fsta.xstatus,kssuc ; set status, success mov kstatus,kssuc ; global status, success test restart_flag,1 ; restart negotiations completed? jnz rdat3c ; nz = no, give up 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: cmp flags.flwflg,filecol_discard ; no-supersede existing file? jne rdata4e ; ne = no cmp flags.cxzflg,'X' ; file being refused from collision? jne rdata4e ; ne = no mov attrib,'?' ; say filename collision occured (?) mov flags.cxzflg,0 ; and clear this flag jmp short rdata4c ; and refuse file via attributes too rdata4e:call getatt ; get file attributes from packet mov cx,0 ; reply length, assume 0/nothing jnc rdat4b ; nc = success, attributes accepted rdata4c: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 mov fsta.xstatus2,al ; remember attributes reason cmp al,'#' ; date/time? jne rdata4g ; ne = no mov flags.cxzflg,0 ; don't say failure mov kstatus,kssuc mov fsta.xstatus,kssuc jmp short rdata4f rdata4g:or fsta.xstatus,ksrecv+ksattrib ; set status, failed, attributes mov kstatus,ksrecv+ksattrib ; global status, failed, attributes rdata4f: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 ferbyte cmp al,'1' ; Byte count? je rdat4a ; e = yes cmp al,'!' ; Kilobyte count? je rdat4a ; e = yes mov dx,offset ferdate cmp al,'#' ; Date and Time? je rdat4a ; e = yes mov dx,offset ferdisp cmp al,'+' ; Disposition? je rdat4a ; e = yes mov dx,offset fertype cmp al,'"' ; File Type? je rdat4a mov dx,offset ferchar cmp al,'*' ; Transfer Char-set? je rdat4a mov dx,offset fername cmp al,'?' ; filename collision? je rdat4a ; e = yes mov dx,offset ferunk ; unknown reason rdat4a: call prtasz ; display reason pop cx pop si ; Restart check, multiple A pkts ok rdat4b: test restart_flag,2 ; Binary mode requested? jz rdat6 ; z = no test restart_flag,1 ; Restart and Binary requested? jz rdat6 ; z = no rdat4d: mov cl,flags.flwflg ; Restart OK, save warning flag state push cx ; reg ax is needed for DX:AX in goopen mov flags.flwflg,filecol_append ; append to existing file mov di,offset decbuf mov byte ptr [di+64],0 ; force in null terminator mov si,offset rdbuf+cmdblen-65 ; holding spot for original name call strcpy ; copy from holding spot push diskio.sizelo push diskio.sizehi call gofil ; regularize name again pop diskio.sizehi pop diskio.sizelo call goopen ; open the file, DX:AX gets length pop cx ; recover file warning flag state mov flags.flwflg,cl ; restore state jnc rdat5 ; nc = success jmp giveup ; failure, dx has message pointer rdat5: mov filopn,2 ; say file is open now mov restart_flag,0 ; clear so first D pkt can proceed mov tfilsz,ax ; count file chars mov tfilsz+2,dx push ax push dx call kbpr ; show transfer percentages call perpr pop dx pop ax push di mov di,offset encbuf ; response buffer mov byte ptr [di],'1' ; file length (Bytes) specifier add di,2 ; skip specifier and count bytes call lnout ; convert file length, write to [di++] mov cx,di ; compute field length sub cx,offset encbuf ; total field for ACK mov al,cl add al,30 ; (32-2) string length to ascii mov encbuf+1,al ; length of file size string for doenc push si push es mov si,offset encbuf mov rpacket.datlen,cx ; size of data field les di,rpacket.datadr cld rep movsb ; copy to packet unencoded pop es pop si pop di jmp rcvpat2 rdat6: push si mov si,offset rpacket ; encode to this packet call doenc ; do encoding pop si rcvpat2: 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: call dodec ; decode incoming packet to decbuf cmp decbuf,'D' ; is the data "D" for discard? je reof6 ; e = yes, delete file cmp filopn,2 ; file opened yet? je reof5b ; e = yes call goopen ; open it now (zero length file) jnc reof5a ; nc = success push dx call logtransact pop dx jmp giveup ; failure, dx has message pointer reof5a: mov filopn,2 ; say file is open now reof5b: call fileclose ; close the file jmp short reof7 reof6: cmp filopn,2 ; is the file open? jne reof7 ; ne = no, declare success anyway call fileclose ; close the file call filedel ; delete file incomplete file reof6a: 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 call logtransact mov diskio.string,0 ; clear file name cmp flags.cxzflg,'Z' ; stop file group? je reof8 ; e = yes mov flags.cxzflg,0 ; else clear it reof8: 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 mov restart_flag,al ; restart file xfer to no 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 push ax mov al,trans.windo ; number of window slots negotiated mov cwindow,al ; assign as receive window pop ax call getbuf ; get a new buffer address into si jnc rcvpa1 ; nc = success mov dx,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,dest_screen ; 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,dest_disk ; 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 ; text 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,dest_printer ; 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 or dx,dx ; any date? 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,fileattr ; 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 diskio.handle,-1 mov filopn,0 ; say file is closed filec5: ret fileclose endp ; Delete file whose asciiz name is in diskio.string filedel proc near cmp flags.flwflg,filecol_update ; update an existing file? je filede2 ; e = yes 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,dest_screen ; 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 cmp filtst.fstat2,0 ; disk file? jne spchk5b ; ne = 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 cmp flags.flwflg,filecol_update ; updating? je spchk1a ; e = yes, file will be removed cmp flags.flwflg,filecol_overwrite ; overwrite existing file? jne spchk1b ; ne = no, file will be kept spchk1a:add ax,diskio.sizelo ; add size of file to be removed adc dx,diskio.sizehi ; to current disk space spchk1b: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 cmp trans.xtype,1 ; binary transfer? je spchk5a ; e = yes, do not inflate file size 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 spchk4a ; nc = no carry down or bx,8000h ; get carry down spchk4a:shr bx,1 ; divide long word by two shr cx,1 jnc spchk4b ; nc = no carry down or bx,8000h ; get carry down spchk4b: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 = (65/64) * dx:ax adc dx,cx spchk5a: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 ; indicate failure spchk7: pop dx pop cx pop bx pop ax ret spchk endp code ends end