MODULE ; XMODEM file transfer ; 2/18/86 ; check out disk I/O, text and binary ; what is convention for last byte when length is multiple of 256 ; figure out monitoring of keyboard etc. before/during xfer. ; sort out declarations ; decide on globals ; compile must be case sensitive if use LF here (ACSTERM has lf) ;SET $4CA=$FF; --will this provoke symbol overflow? PROC PBlock(BYTE ARRAY block, CARD size) CARD j BYTE DSPFLG=$2FE DSPFLG=1 ;write control char to screen (except EOL) FOR j=0 TO size-1 DO Put(block(j)) ; block(j)=0 OD DSPFLG=0 PutE() RETURN MODULE; BLKIO------------------------------ ; Copyright (c) 1983, 1984, 1985 by Action Computer Services (ACS) BYTE CIO_status CHAR FUNC CIO=*(BYTE dev, CARD addr, size, BYTE cmd, aux1, aux2) ~[$29$F$85$A0$86$A1$A$A$A$A$AA$A5$A5 $9D$342$A5$A3$9D$348$A5$A4$9D$349 $A5$A6$F0$8$9D$34A$A5$A7$9D$34B$98 $9D$345$A5$A1$9D$344$20$E456 $8C CIO_status$C0$88$D0$6$98$A4$A0 $99 EOF$A085$60] CARD FUNC ReadBlock=*(BYTE dev, CARD addr, size) ~[$48$A9$7$85$A5$A9$0$85$A6$A5$A3$5$A4 $D0$6$85$A0$85$A1$68$60$68$20 CIO $BD$348$85$A0$BD$349$85$A1$60] PROC WriteBlock=*(BYTE dev, CARD addr, size) ; Writes size bytes from addr to dev. ; Status is saved in CIO_status. ~[$48$A9$B$85$A5$A9$0$85$A6$A5$A3$5$A4 $D0$2$68$60$68$4C CIO] MODULE ; part of BLOCKIO ; These will be from ACSTERM: DEFINE modem = "5" DEFINE file = "3" DEFINE STRING = "CHAR ARRAY" DEFINE ASCII = "$0" DEFINE EOL = "$9B" CARD ARRAY end(0) BYTE ARRAY fbuf BYTE baud=~[14], fmode STRING Rdev(0)="R:" BYTE FUNC MStatus=*() BYTE QLi=$2EB; DVSTAT+1, input queue length XIO(modem,0,$D,0,0,Rdev) RETURN (QLi) PROC OpenModem(BYTE trans) Close(modem) Open(modem,Rdev,13,0) XIO(modem,0,36,baud,0,Rdev) XIO(modem,0,38,trans,0,Rdev) XIO(modem,0,40,0,0,Rdev) ; concurrent RETURN PROC MyClose(BYTE chan) Close(modem) Close(chan) ; more than this in ACSTERM? RETURN PROC OpenFile(STRING msg) BYTE ARRAY spec(30) Print(msg) InputMD(0,spec,30) MyClose(file) Open(file,spec,fmode,0) RETURN PROC GetKey() BYTE CH=$2FC, c IF CH<>255 THEN c=GetD(7) IF c=EOL THEN c=$D FI PutD(modem,c) FI RETURN MODULE ; ---------------------------- DEFINE FALSE = "0" DEFINE EOT = "4" DEFINE SOH = "1" DEFINE ACK = "6" DEFINE LF = "$A" DEFINE CR = "$D" DEFINE NAK = "$15" DEFINE SUB = "$1A" DEFINE TIMEOUT = "$FFFF" DEFINE RETRYMAX = "10" DEFINE ERRORMAX = "10" BYTE j, CheckSum, SectNum, TotErr, Errors BYTE transx=~[0], xeof CHAR ARRAY block(128) INT dbufp ; # of data bytes in fbuf, index of next CARD ibuf CARD BLen=~[2000]; length of buffer (must be >2*128?) CARD FUNC Receive(BYTE wait) CARD CDTMV3=$21C ; system timer counts down to 0 BYTE CONSOL=$D01F CDTMV3 = 60*wait DO GetKey() IF MStatus() THEN RETURN (GetD(modem)) FI IF CONSOL!7 THEN CDTMV3 = 60 FI ; force timer to 1 sec. UNTIL CDTMV3=0 OD RETURN (TIMEOUT) PROC Send(CHAR c) PutD(modem,c) RETURN PROC PurgeLine(BYTE wait) DO UNTIL Receive(wait)=TIMEOUT OD RETURN ; ----------------------------------- PROC WBuf() ; 128 bytes from block to disk buffer ; Must set dbufp=xeof=0 before 1st call. ; Caller must open and close file. ; Can't write out a block until next call, because don't know ; a block is last until EOT is received instead of next block. CARD j, len IF xeof THEN ; preceeding block was final. ;print("EOF") IF transx=ASCII THEN FOR j=dbufp-128 TO dbufp-1 DO IF fbuf(j)=SUB THEN EXIT FI OD dbufp = j ELSE dbufp ==- (128-fbuf(dbufp-1)) FI ELSE ;print("not EOF") FOR j=0 TO 127 DO fbuf(dbufp) = block(j) dbufp ==+ 1 OD FI ;PBlock(block,dbufp) ;printf("dbufp=%U%E",dbufp) IF dbufp>BLen-128 OR xeof<>0 THEN ; flush buffer to disk IF transx=ASCII THEN ;replace CR-LF by EOL ;Don't touch a trailing CR, as LF might be in next block. ibuf = 0 FOR j=0 TO dbufp-1 DO IF j<=dbufp-2 THEN IF (fbuf(j)&$7F)=CR AND fbuf(j+1)=LF THEN j ==+ 1 fbuf(j) = EOL FI FI fbuf(ibuf) = fbuf(j) ibuf ==+ 1 OD dbufp = ibuf FI Close(modem) IF xeof THEN len = dbufp ELSE len = dbufp-128 FI WriteBlock(file,fbuf,len) OpenModem(32) ;Move remaining dbufp-len bytes to front of fbuf FOR j=len TO dbufp-1 DO fbuf(j-len) = fbuf(j) OD dbufp ==- len FI RETURN ; WBuf PROC RecFile() CARD ch, FirstChar BYTE SectCurr, ErrorFlag BYTE CONSOL=$D01F fmode = 8 OpenFile("XMODEM download to file: ") dbufp = 0 : xeof = 0 OpenModem(32) SectNum = 0 Errors = 0 ; on current sector TotErr = 0 ; on file PurgeLine(0) Send(NAK) DO ErrorFlag = FALSE DO FirstChar = Receive(10) IF SectNum=0 OR (CONSOL&2)=0 THEN Put(FirstChar) ;** debug -FOX can type to screen FI UNTIL FirstChar=SOH OR FirstChar=EOT OR FirstChar=TIMEOUT OD IF FirstChar=TIMEOUT THEN ErrorFlag = 'T ; ELSEIF FirstChar=EOT THEN ; EXIT ELSEIF FirstChar=SOH THEN SectCurr = Receive(1) IF (SectCurr + Receive(1))=$FF THEN ; good sector number IF SectCurr=(SectNum+1) THEN CheckSum = 0 FOR j=0 TO 127 DO ch = Receive(1) IF ch=TIMEOUT THEN ErrorFlag = 'T EXIT FI block(j) = ch CheckSum = CheckSum+ch OD IF CheckSum=Receive(1) THEN SectNum = SectCurr PrintF("Rec'd %U after %U tries%E%C",SectNum,Errors,$1C) Errors = 0 WBuf() Send(ACK) ELSE ; bad checksum ***or timeout in block ErrorFlag = 'C FI ELSEIF SectCurr=SectNum THEN ; already received this PurgeLine(1) Send(ACK) ELSE ; lost a sector ErrorFlag = 'S FI ELSE ; bad header ErrorFlag = 'H FI FI IF ErrorFlag THEN Errors ==+ 1 IF SectNum THEN TotErr ==+ 1 FI PurgeLine(1) PrintF("Awaiting %U (try=%U, Errs=%U, type %C)%E", SectNum,Errors,TotErr,ErrorFlag) Send(NAK) FI UNTIL FirstChar=EOT OR Errors=ERRORMAX OD IF FirstChar=EOT AND Errors<ERRORMAX THEN Send(ACK) xeof=1 WBuf() ; write buffer, close file PrintF("%EDone") ELSE PrintF("%EAborting") FI MyClose(file) RETURN ; RecFile ; ----------------------------------- BYTE FUNC RBuf() ; read 128 bytes into block. BYTE i ; N.B.! set ibuf=dbufp=xeof=0 before 1st call IF xeof THEN RETURN(0) FI ; no more blocks i = 0 WHILE i<128 DO IF ibuf=dbufp THEN ; no more data IF EOF(file) THEN ; already got EOF xeof = 1 ; flag for NEXT call to RBuf EXIT ELSE Close(modem) dbufp = ReadBlock(file,fbuf,BLen-1) ; could be zero ibuf = 0 IF CIO_status=$88 THEN ; EOF CIO_status = 1 ; indicate OK IF transx=ASCII THEN fbuf(dbufp) = SUB ; CP/M & MSDOS EOF dbufp ==+ 1 FI FI OpenModem(32) ;PrintF("read %U bytes,xeof=%U,EOF=%U%E%E",dbufp,xeof,EOF(file)) IF dbufp=0 THEN EXIT FI FI FI IF fbuf(ibuf)=EOL AND transx=ASCII THEN block(i) = CR fbuf(ibuf) = LF ; to send next ELSE block(i) = fbuf(ibuf) ibuf ==+ 1 FI i ==+ 1 ; could make this a FOR loop?? OD j = i WHILE i < 128 DO ; fill out last block with number of data bytes block(i) = j i ==+ 1 OD RETURN (1) ;RBuf PROC SndFile() BYTE attempts=Errors, ch BYTE CONSOL=$D01F fmode = 4 OpenFile("XMODEM upload of file: ") ibuf = 0 : dbufp = 0 : xeof=0 OpenModem(32) PurgeLine(0) attempts = 0 TotErr = 0 PrintE("Await NAK or press start") WHILE Receive(10)<>NAK AND attempts<8 AND CONSOL=7 DO ; await initial NAK attempts ==+ 1 PrintF("%CTimeout %U%E",$1C,attempts) OD IF attempts=8 THEN PrintE("Timed out before initial NAK") MyClose(file) RETURN FI attempts = 0 SectNum = 1 WHILE RBuf()<>0 AND attempts<RETRYMAX DO ; blocks IF CIO_status<>1 THEN PrintF("DOS error %U%E",CIO_status) EXIT FI attempts = 0 DO ; send block ; PrintF("%Cblock %U%E",$1C,SectNum) Send(SOH) Send(SectNum) Send($FF-SectNum) CheckSum = 0 FOR j = 0 TO 127 DO ch=block(j) Send(ch) CheckSum = CheckSum+ch OD Send(CheckSum) PurgeLine(0) attempts ==+ 1 TotErr == +1 UNTIL Receive(10)=ACK OR attempts=RETRYMAX OD SectNum ==+ 1 TotErr ==- 1 OD ; loop on blocks IF attempts=RETRYMAX THEN PrintE("No ACK on sector") ELSE attempts=0 DO Send(EOT) PurgeLine(0) attempts ==+ 1 UNTIL Receive(10)=ACK OR attempts=RETRYMAX OD IF attempts=RETRYMAX THEN PrintE("No ACK on EOT") FI FI MyClose(file) PrintF("Done with %U retries%E",TotErr) RETURN ; SndFile ; ----------------------------------- PROC Main() BYTE ch fbuf = end transx = ASCII ;transx=1 ; BINARY ch = GetD(7)&$DF IF ch='R THEN RecFile() ELSEIF ch='T OR ch='S THEN SndFile() FI Close(modem) RETURN