Category : Pascal Source Code
Archive   : XFER10.ZIP
Filename : SXRX.INC

 
Output of file : SXRX.INC contained in archive : XFER10.ZIP

{ The code in this file handles transmit and receive of Xmodem Checksum, }
{ CRC, and 1K. This is the first include file of XFER.PAS. }

{ Copyright (C) 1990 By Andrew Bartels }
{ A Product of Digital Innovations }


{ This function transmits via Xmodem protocol the file contained in }
{ FileName. If CRCMode is True, a CRC check is used on all blocks, else }
{ a checksum is used. If OneK is True, each data block is sent 1K long, }
{ else each data block is 128 bytes long. All errors and transfer results }
{ are returned through the function. These codes are defined in the }
{ opening Const definition at the beginning of XFER.PAS. }

Function XmodemSend( CRCMode,
OneK : Boolean;
Var FileName : String) : Byte;

Var Ch : Char; { Character just received }
TimeOut : Boolean; { Becomes True if a Timeout condition occurs }

NumNAKS : Integer; { Hold number of NAKs receiver has sent on }
{ current block. If this value exceeds 10, }
{ then receiver cancels protocol by sending }
{ two CAN's }

BlockNum, { Contains the current block being xmitted. }
{ Starts at 1, and increments by one until }
{ it becomes 255, at which point, it rolls }
{ over to 0, and continues incrementing. }

TotalErrors : Byte; { Stores total number of NAK's and other }
{ error conditions that have occurred thus }
{ far in the protocol, pending or not. }

TotalBlocks, { Contains the total number of blocks sent }
{ thus far. Similar to BlockNum, except this}
{ one does not roll over at 255. }

BytesSent : LongInt; { Keeps count of how many bytes of file data }
{ have been successfully sent thus far. }

Hour, { Holds Hour from GetTime call }
Min, { Holds Minute from GetTime call }
Sec, { Holds Second from GetTime call }
Hund : Word; { Holds Hundredths from GetTime call }

StartTime, { Holds time in seconds of when protocol was }
{ initiated. Used in determining Chars/Sec }

EndTime : Real; { Holds the time in seconds as when last blk }
{ was transmitted. Used with StartTime in }
{ figuring Char/Sec. }

Data : BlockType;{ This holds the data block to be xmitted. }

DataLen : Word; { Is 128 for a 128 byte block, or 1024 for a }
{ 1K block. }

CancelCode : Byte; { Contains the number of CAN's that came from}
{ receiver in a row. If this is ever 2, then}
{ protocol aborts. }

File1 : File; { File handle used for disk I/O }



{ This sub-procedure is the main instrument of disk I/O for xmit. }
{ In 1K transfers, data blocks returned from this procedure are 1K }
{ long until there is less than 1K of the file left to be sent, at }
{ which point 128 byte blocks are returned. In regular xfers, 128 byte}
{ data blocks are always returned. }

Procedure GetNextBlock (Var Data : BlockType;
Var DataLen : Word );
Begin
FillChar(Data,1024,#26); { all of block is set to ASCII code 26 }
{ for CP/M systems that require #26 at }
{ end of file. These will be written }
{ over a little later if this is not }
{ the last block we're sending. }

If (FileSize(File1) - FilePos(File1) >= 1024) and OneK
then {If it's OK to send a 1K block, then read in 1K of data }
Begin
BlockRead(File1,Data,1024,DataLen);
End
else
Begin { Otherwise, read in 128 bytes of data }
BlockRead(File1,Data,128,DataLen);
If (DataLen < 128) and (DataLen <> 0) { If we couldn't get }
then { 128 bytes due to EOF, then we }
DataLen := 128; { say block is 128 bytes anyway, }
{ knowing there are ASCII 26's }
{ on the end of the block to fill}
{ up the rest of it. }

End;
End;


Begin { OK, here's where we start the xfer }

If not Com_Carrier { If there is no carrier, then exit because we }
then { cannot send data. Correct error code is returned}
Begin
XmodemSend := CarrierLostError;
Exit;
End;

XmodemSend := NoError; { Assume no error has occurred yet.}

If not OpenFile(File1,Rd,1,FileName) { Try opening the file to read }
then
Begin
XmodemSend := FileError; { If we can't open the file, error }
Exit;
End;

Writeln('Sending Xmodem.....',FileName);

Repeat { Now we wait for the receiver to tell us whether protocol is}
{ CRC or Checksum. We'll wait no more than 30 seconds for it}
MonitorReceive(Ch,30,TimeOut);
{ We want to keep on waiting until we get a timeout, or info }
{ from the receiver. All garbage is discarded. }
Until (TimeOut) or
(not (TimeOut) and (Ch = #21)) or { Receiver want's checksum }
(not (TimeOut) and (CrcMode) and (Ch='C')); {Recv'r wants CRC }

If not Com_Carrier { If there is no carrier, then exit with error }
then
Begin
XmodemSend := CarrierLostError;
Close(File1);
Exit;
End;

If TimeOut { If we timed out waiting for receiver, then abort protocol }
then
Begin
Com_TX(CAN);
Com_TX(CAN);
XmodemSend := MaxTimeOutError;
Close(File1);
Exit;
End;

CRCMode := (Ch = 'C'); { If receiver sent a 'C', then we're going to }
{ do a CRC error check, else we'll used Csum. }

NumNAKs := 0; { No NAK's on blocks yet. }
TotalErrors := 0; { No errors yet. }

BytesSent := 0; { Didn't send anything yet }
TotalBlocks := 0; { ditto. }

GetTime(Hour,Min,Sec,Hund); { Figure out time to the hundredths of sec.}
StartTime := (3600 * Hour) + (60 * Min) + Sec + (Hund/100);

GotoXY(1,13); { Tell the user what's going on now. }
Writeln('FileName : ',FileName);
Writeln('File Size : ',FileSize(File1));
Write('Error Correction : ');
If CRCMode
then
Writeln('CRC-CCITT')
else
Writeln('Checksum');
Writeln('Block Number : ',0);
Writeln('Bytes Sent : ',0);
Writeln('Characters/Second : ',0);
Writeln('Elapsed Time : ',0,' min. ',0,' sec. ');
Writeln('Block Errors : ',0);
Writeln('Total Errors : ',0);

BlockNum := 1; { First block number is 1 }
TotalBlocks := 1; { We're sending first block now. }

Repeat
GetNextBlock(Data,DataLen); { Get block of data from file }
If DataLen > 0 { If not EOF in file, then..... }
then
Repeat

SendBlock(Data,DataLen,BlockNum,CRCMode); { ...send the block }

CancelCode := 0; { No CAN's received yet on this block }

Repeat
If KeyPressed { If user presses a key, error out }
then
Begin
XmodemSend := OperatorAbortError;
Close(File1);
Exit;
End;
If not Com_Carrier { If carrier drops, error out }
then
Begin
XmodemSend := CarrierLostError;
Close(File1);
Exit;
End;
MonitorReceive(Ch,10,TimeOut); {Wait for response from recv'r }

If (not TimeOut) and (Ch = CAN) { If receiver sends CAN, then }
then
Inc(CancelCode) { count it to see if we got 2 }
{ in a row yet. }
else
CancelCode := 0; { Otherwise we couldn't have }
{ gotten 2 in a row. }

{ Keep on looping until we either TimeOut, get an ACK, NAK, or }
{ 2 CAN's in a row. Discard all garbage. }
Until TimeOut or (Ch=ACK) or (Ch=NAK) or (CancelCode = 2);

Case Ch of { If we got a NAK, then count up the errors }
NAK : Begin
Inc(NumNAKs);
Inc(TotalErrors);
End;
ACK : Begin { If we got an ACK, then zero error count & go to }
{ next block. }
NumNAKs := 0;
BytesSent := BytesSent + DataLen;
End;
End;{Case}

{ Now, tell user how the xfer is going. }

GetTime(Hour,Min,Sec,Hund);
EndTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100);
If EndTime < StartTime
then
EndTime := EndTime + (3600 * 24);
GotoXY(1,16);
Writeln('Block Number : ',TotalBlocks);
Writeln('Bytes Sent : ',BytesSent);
Writeln('Characters/Second : ',Trunc((BytesSent/(EndTime-StartTime)*100))/100:0:2);
Writeln('Elapsed Time : ',Trunc(EndTime-StartTime) div 60,' min. ',
Trunc(EndTime-StartTime) mod 60,' sec. ');
Writeln('Block Errors : ',NumNAKs);
Writeln('Total Errors : ',TotalErrors);

{ This loop goes until we either get an ACK, or 2 CAN's. This }
{ way, we will re-xmit the block if we got a NAK, or a timeout }
{ as the protocol definition describes. }

Until (Ch =ACK) or (CancelCode = 2);

Inc(BlockNum); { OK, set up for next block. }
Inc(TotalBlocks); { ditto }

{ This loop repeats for each data block, until DataLen is zero, which }
{ means the last block xmitted was the last in the file. The other }
{ way this loop exits is if we got 2 CAN's in a row from receiver. }

Until (DataLen=0) or (CancelCode = 2);

If CancelCode = 2 { If we got 2 CAN's, then display the error & exit }
then
Begin
XmodemSend := RemoteCancelError;
Close(File1);
Exit;
End;

Repeat { Now that EOF has been reached, we need to send EOT until the }
{ receiver ACKnowledges it. }
Com_TX(EOT);
MonitorReceive(Ch,10,TimeOut);
Until (TimeOut) or (Ch = ACK);

If TimeOut { If we timed out while sending the EOT, then issue an }
{ error. }
then
XmodemSend := MaxTimeOutError;

Close(File1); { Close the file, and exit }

End;


{ This function will receive a file by Xmodem protocol under the name }
{ specified in FileName. The receiver only needs to know if the user }
{ wants CRC transfers or not. This routine will handle either 1K or }
{ regular transfers automatically. It is recommended that if 1K blocks }
{ are used, the CRCMode be turned on. }

Function XmodemReceive( CRCMode : Boolean;
Var FileName : String ) : Byte;

Var Ch :Char; { Character just received }

TimeOut, { Becomes true when no character has been }
{ received for 10 seconds (a time-out }
{ condition). }

CRCOn, { True if CRC transfer, False if checksum }

Done, { True if protocol is to exit, either on }
{ cancel or EOT condition. }

StartUp, { Used to enter the main loop and not wait }
{ for a character from modem once CRC has }
{ been initiated. Is False if Checksum. }

ForceACK : Boolean; { Becomes true if block received = Last }
{ block sent. If True, block is ACK'd, }
{ but not saved to disk. }

CRC, { Holds current CRC value as data comes in }

Code, { Used to return an error code from IBMCom }
{ when we initialize the COM Port. }

BlockCRC, { Holds the CRC of the block, as sent by }
{ transmitter. This is compared with the }
{ CRC variable to determine if block is }
{ error-free. }

Status, { Status code for writing to disk. }

Hour, { Holds hour number (0-23) of timing code }
Min, { Holds minute number (0-59) in timing }
Sec, { Holds second number (0-59) in timing }
Hund : Word; { Holds 100th of a second in timing code }

Count, { Used in counting C's sent in attempt to }
{ initiate CRC mode. }

Operation, { The process of receiving an Xmodem block }
{ has been broken down into six different }
{ operations. These are shown in the large}
{ Case statement in the main loop. This }
{ variable is used to keep the steps in }
{ sequence. }

NumNAKS, { Counts number of NAK's that have been }
{ sent on this block. If it ever exceeds }
{ 10, then two CAN's are sent & protocol }
{ is aborted. }

DataNum, { Holds how many data bytes in the block }
{ have been received. This increments by }
{ one as each byte comes in, and stops }
{ when it equals MaxDataLength. }

MaxDataLength : Integer; { If first character of block is SOH, then }
{ this is 128. If first char is STX, then }
{ this is 1024, for 1K Xmodem support. }

CANNum, { Used to count how many CAN chars have }
{ been received in a row. Once two have }
{ been received in a row, protocol exits }
{ under a cancel condition. }

EOTNum, { Used to count how many EOT chars have }
{ been received in a row. Once two have }
{ been received in a row, protocol exits }
{ because transmission is complete. }

Checksum, { Used to hold the checksum of the data in }
{ checksum transfers. }

CRCBytes, { The CRC is two bytes long. In CRC mode, }
{ this is a counter to make sure both bytes}
{ of the CRC have been received from the }
{ transmitter. }


BlockNum, { Holds the current block number as sent }
{ by transmitter. Starts at 1, and }
{ increments to 255, then to 0, and starts }
{ counting up over again. }

TotalErrors, { Counter used to hold the total number of }
{ errors encountered thus far during the }
{ transfer. Used for screen information }
{ only. }

LastBlock : Byte; { Holds the last block's number. If this }
{ ever equals the value of BlockNum, then }
{ ForceACK is set to true. }

TotalBlocks, { Similar to BlockNum, except to does not }
{ wrap around to zero when it reaches 255 }
{ Used to display info for the user only. }

BytesReceived : LongInt; { Counts the total number of bytes received}
{ thus far in the transfer. Used to calc. }
{ the CPS field on the screen. }

StartTime, { Holds the time, in seconds, when the }
{ protocol was started. }

EndTime : Real; { Holds the current time, in seconds. Used}
{ with StartTime to determine how much time}
{ has passed since start of transfer, and }
{ to figure CPS rate. }

Data : BlockType; { Buffer used to hold actual }
{ file data as it is received. }
{ Later, it is written to disk }

File1 : File; { File handle for disk I/O }


{ This procedure is used only for displaying information to the user }
{ about how the xfer is going. There are a couple of places where }
{ this is needed, so it has been put into a procedure. }

Procedure UpdateVideo;
Begin
GetTime(Hour,Min,Sec,Hund); { Get the current time in seconds here }
EndTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100);

If EndTime < StartTime { Allow for xfers spanning over midnight }
then
EndTime := EndTime + (3600 * 24);

GotoXY(1,15);
Writeln('Block Number : ',TotalBlocks);
Writeln('Bytes Received : ',BytesReceived);
Writeln('Characters/Second : ',Trunc((BytesReceived/(EndTime-StartTime)*100))/100:0:2);
Writeln('Elapsed Time : ',Trunc(EndTime-StartTime) div 60,' min. ',
Trunc(EndTime-StartTime) mod 60,' sec. ');
Writeln('Block Errors : ',NumNAKs);
Writeln('Total Errors : ',TotalErrors);
End;


{ There are many places in the receive routine where it is necessary to}
{ send a NAK character. This procedure sends the NAK, and updates the }
{ NAK counter so we know how many NAK's have been sent on this block. }
{ If the NAK counter exceeds 10, we immediately send two CAN's and exit}
{ the protocol due to exceeding the timeout maximum (when Operation is }
{ 200). }

Procedure SendNak;
Begin
Inc(NumNAKs); { Increment the counters by 1 }
Inc(TotalErrors);
If NumNAKs < 11 { If we didn't exceed the limit, send the NAK }
then
Com_TX(NAK);
Operation := 1; { Next operation is wait for next SOH char }
If NumNAKs = 11 { unless we met NAK limit, in which case we }
then { must exit the protocol after sending 2 CAN's}
Begin
Operation := 200;
XmodemReceive := MaxTimeOutError;
End;
GotoXY(1,19); { Tell user there was an error }
Writeln('Block Errors : ',NumNAKs);
Writeln('Total Errors : ',TotalErrors);
End;




Begin

If not Com_Carrier { If there is no carrier, then exit because we }
then { can't send anything like that. }
Begin
XmodemReceive := CarrierLostError;
Exit;
End;

XmodemReceive := NoError; { OK, assume there are no errors }

If not OpenFile(File1,Wt,1,FileName) { Try to open the file here }
then
Begin
XmodemReceive := FileError; { If not successful, then exit }
Exit; { under error condition. }
End;

If CRCMode { If we were told to support CRC }
then { then tell user we're trying to.}
Writeln('Testing for support of CRC...');

CRCOn := True; { Assume we are really doing CRC }

Count := 0; { This counts 3 second timeouts for us. Start }
{ with none while attempting to initiate CRC }

CANNum := 0; { No CAN's received yet. }

EOTNum := 0; { No EOT's received yet. }

ForceACK := False; { Don't for an ACK on any blocks yet. }

LastBlock := 0; { Didn't have a last block, so set it to zero }

BytesReceived := 0; { No bytes received yet. }

BlockNum := 0; { No blocks received yet, so set to zero. }

TotalBlocks := 0; { Ditto. }

TotalErrors := 0; { No errors yet either. }

TimeOut := True; { Prime the Repeat/Until loop }

If CRCMode { If we're supposed to do CRC, then try initiation. }
then
Repeat { Start of loop }

If TimeOut { If we had a timeout or this is start of loop, then}
then
Com_TX('C'); { send a 'C' to try to initiate CRC xfer. }

If not Com_Carrier { If we don't have a carrier for some reason, }
then
Begin { Return an error to user. }
XmodemReceive := CarrierLostError;
Close(File1);
Exit;
End;

MonitorReceive(Ch,3,TimeOut); { Monitor receive for 3 secs }

If TimeOut { If no response in 3 secs, then increment timeout ctr}
then
Inc(Count);

{ This loop continues until we've either got an SOH (start of a }
{ 128 byte block), a STX (start of 1K block), or the timeout ctr }
{ is 3 (meaning that the transmitter does not support CRC, and the}
{ xfer is to be checksum). }

Until (Count = 3) or (Ch = SOH) or (Ch = STX);


If TimeOut { If we're still sitting on a timeout, then assume a }
then { checksum. If TimeOut = False, then Count <> 3, and }
{ CRC mode was initiated from above loop. }
Begin
Writeln('Resorting to checksum now...');
CRCOn := False; { Not doing CRC here }
StartUp := False; { set the start up flag for checksum }
Com_TX(NAK); { send te first NAK to initate checksum }
End
else
Begin
StartUp := True; { If CRC is initiated, then set startup flag }
End;

NumNAKs := 0; { No NAK's for errors yet. }
Done := False; { Prime the loop, we're not done - we're beginning }
Operation := 1; { First operation we're looking at is 1 }

GetTime(Hour,Min,Sec,Hund); { Grab the starting time before we get }
StartTime := (Hour*3600) + (Min * 60) + Sec + (Hund/100); { started }

GotoXY(1,13); { Tell the user what's going on. }
Writeln('FileName : ',FileName);
Write('Error Correction : ');
If CRCOn
then
Writeln('CRC-CCITT')
else
Writeln('Checksum');
Writeln('Block Number : ',0);
Writeln('Bytes Received : ',0);
Writeln('Characters/Second : ',0);
Writeln('Elapsed Time : ',0,' min. ',0,' sec. ');
Writeln('Block Errors : ',0);
Writeln('Total Errors : ',0);


{ This loop has a very large CASE statement in it used to determine how }
{ data received in interpreted. I have divided the receiving of the }
{ Xmodem block into 5 steps (the variable Operation iterates from 1 to 5 }
{ on every block, and it's value determines which step is next taken in }
{ the loop.) The following is a list of the steps I have defined: }

{ Step 1: Receive an SOH, STX, EOT, or CAN character. SOH means that }
{ a 128 byte block is comming. STX means that a 1024 byte }
{ block is comming. EOT means that the file xfer is done. The }
{ code requires two of these to recognize a true EOT condition. }
{ A CAN character means the remote end has aborted the xfer. }
{ The code must receive two of these to recognize a true cancel }
{ condition. }

{ Step 2: Receive the block number. This is just 1 byte long. }

{ Step 3: Receive the one's complement of the block number. If the char }
{ received here is NOT the one's complement of the block number }
{ then the code simply returns to step 1, assuming the chars }
{ received thus far were garbage caused by line noise. If the }
{ byte here is the correct one's complement of the block, then }
{ it is assumed that the block has begun. If the block # does }
{ equal the next block expected in sequence, protocol aborts }
{ quickly with 2 CAN's. If the block number is the same as the }
{ last one, then a flag is set to force an ACK to be sent for }
{ this block down in step 5. }

{ Step 4: This step involves receiving the actual data of the block. }
{ If an SOH was received in step 1, then 128 bytes are expected }
{ from the transmitter. If an STX was received in step 1, then }
{ 1024 bytes are expected from transmitter. As each byte of }
{ data comes in, the CRC or Checksum (whichever is aplicable }
{ for this xfer) is updated. The data is stored in an array. }
{ When the number of bytes expected in the data has been rec'd }
{ we proceed to step 5. }

{ Step 5: Receive the block check. For CRC xfers, a 2 byte CRC is sent }
{ on the end of the block. For Checksum xfers, sum of the }
{ data bytes in the block mod 256 is sent. This step invloves }
{ receiving the CRC or checksum, and then comparing this value }
{ with the one we figured in step 4 as the data came in. If }
{ the check values are the same, or the ForceACK flag (see step }
{ 3) is True, then we send an ACK to the transmitter. If the }
{ ForceACK flag is false, we save the data to the disk file, }
{ otherwise, the data in this block is already in the file, and }
{ does not need to be saved twice in a row. If the check val }
{ does NOT equal the one we figured, then a NAK is sent, and }
{ the data is not saved to disk. In either case, we go to step }
{ 1 & continue processing. If the block is ACK'd, then we zero }
{ our NAK counter because 10 errors must happen on the same }
{ block in order to cause an error. If the block comes thru OK }
{ then we needn;t keep track of the NAKs for it anymore. }

{ Keep in mind that if at any time a span of 10 seconds occurs without }
{ getting a character from the xmitter, a timeout condition occurrs. }
{ When we have a timeout condition, we send a NAK, and increment our }
{ NAK counter. If the NAK counter ever exceeds 10, the protocol is }
{ aborted by sending two CAN's to the transmitter, and exiting the prog. }
{ Also, if ever we lose carrier, or the user presses a key, then the }
{ protocol is aborted immediately with 2 CAN's. -Andrew }


Repeat {Start a loop that only ends when Done = True }

If not Com_Carrier { If we ever lose carrier, generate error & exit }
then
Begin
XmodemReceive := CarrierLostError;
Close(File1);
Exit;
End;
{ StartUp is True if we've initiated CRC correction and the first }
{ SOH or STX character is held in variabel Ch. }
If StartUp { So, if first SOH/STX is in Ch......}
then
Begin
StartUp := False; {...then reset flag, and reset NAK Count... }
NumNAKs := 0;
End
else { ....otherwise try waiting for a character }
Begin

{ When Operation = 200, the protocol has an abort condition, and }
{ We should not wait around for any characters. }

If Operation <> 200 { If we've not got an abort condition.... }
then
Begin
Repeat {...then start a loop that continues until we've }
{ received a character or 10 timeouts have }
{ occurred on this block. }

If Com_RX_Empty { If the receive buffer is empty (i.e. }
{ there is no character waiting to be }
{ used), then start waiting...... }
then
Begin
MonitorReceive(Ch,10,TimeOut); { For for 10 secs }

If TimeOut and (EOTNum = 1) { If 10 secs pass w/o }
{ character & we've been waiting for the second }
{ EOT, then we'll issue a wairning to user and }
{ exit, assuming the 2ns EOT is not comming. }
then
Begin
XmodemReceive := EOTTimeOut;
Close(File1);
Exit;
End;
If TimeOut { If we had a timeout, but were not }
{ necessarily looking for a 2nd EOT, then we'll }
{ increment out timeout counter & send a NAK. }
then
SendNAK;
End
else { If there was a character in the buffer, by all }
{ means, let's go get it now and skip all that }
{ junk in the above Begin/End pair. }
Begin
Ch := Com_RX;
TimeOut := False;
End;

If KeyPressed { If the user presses a key, let's cancel }
then { the protocol now. }
Begin
Operation := 200;
XmodemReceive := OperatorAbortError;
TimeOut := False;
End;

Until (Not TimeOut) or (NumNAKs = 11); { This loop keeps }
{ on going until we get a character or 10 timeouts have }
{ happened. }

End;
End;

{ OK, here is where the code decides what actually gets done with each }
{ character it receives. Operation holds the step number it will do }
{ next. As each step is completed, Operation is set to the next }
{ consecutive step, so it will do the right thing when it loops thru }
{ here next time. }


Case Operation of
1 : Begin { Step 1, waiting for SOH, STX, EOT, or CAN }
Case Ch of
SOH, { If it was SOH or STX, then...}
STX : Begin
Operation := 2; { we think block has started. }
CANNum := 0; { reset a few counters & set up }
EOTNum := 0; { defaults. }
ForceACK := False;
NumNAKs := 0;
Case Ch of
SOH : MaxDataLength := 128; { SOH = 128 byte block }
STX : MaxDataLength := 1024; { STX = 1024 byte blk. }
End;{Case}
End;

EOT : Begin { If it was an EOT, then... }
CANNum := 0; { we certainly didn't get 2 of these.}
Inc(EOTNum); { but increment EOT counter. }
If EOTNum = 2 { If we got 2 EOT's together, then }
then
Begin
Com_TX(ACK); { ...ACK the 2nd EOT and exit }
{ under a normal condition. }
Writeln('End of Transmission.');
XmodemReceive := NoError;
Done := True;
End
else { But if this was first EOT, we'll NAK it }
Begin
Com_TX(NAK);
End;
End;
CAN : Begin { If char was a CAN, then }
Inc(CANNum); { Increment CAN counter }
If CANNum = 2 { If we've gotten 2, cancel w/ error }
then
Begin
Done := True;
Writeln('Xmodem Receive Cancelled.');
XmodemReceive := RemoteCancelError;
End;
{ If we've only gotten 1 CAN, ignore it right yet. }
End;

else Begin { If char wasn't SOH, STX, EOT, or CAN, then }
EOTNum := 0; { Ignore it, as it may be garbage, but }
CANNum := 0; { we'll reset our counters anyway. }
ForceACK := False;
End;
End;{Case}
End;
2 : Begin { Step 2 - Get the block number }
BlockNum := Ord(Ch); { We're assuming from the fact that an }
Operation := 3; { SOH or STX was received that the block # is }
{ next to be sent. But if garbage caused the SOH/STX, we'll }
{ catch the error in the next step. }
End;
3 : Begin { Step 3 - Get ones complement of block number }

If BlockNum = (not Ord(Ch)) { If one's compl is correct for }
{ the block # received in step 2, we're officially }
{ assuming the block has started. }
then
Begin
If (( (LastBlock + 1) and $FF ) = BlockNum) or
(LastBlock = BlockNum)
{ If the block number is the next in sequence, or it is the }
{ same one we got before, we'll accept it. }
then
Begin
ForceACK := LastBlock = BlockNum; { If last block }
{ equals this block, then ForceACK is True. }
Operation := 4; { Go to next step on next loop }
DataNum := 0; { No data received in block yet. }
CRC := 0; { Clear out the check, regardless }
Checksum := 0; { if it's CRC or checksum }
End
else
Begin { If block # is our of seq, STOP NOW! }
Operation := 200;
XmodemReceive := BlockOutOfSequenceError;
End;
End
else
Begin { If line noise caused a false SOH/STX, then the }
{ block # and the block #'s one's compl won't be }
{ correct, so if that happens, here is where we're }
{ going back to step 1 to wait for a better SOH or }
{ STX. }
Operation := 1;
End;
End;
4 : Begin { Step 4 - Receive the data block. }

Inc(DataNum); { Increment our data received counter }
Data[DataNum] := Ord(Ch); { store character in the array. }

If CRCOn
then
CRC := UpdCRC(Ord(Ch),CRC) { If CRC mode, then update CRC }
else
Checksum := Checksum + Ord(Ch); { else update checksum. }

If (DataNum = MaxDataLength) { If we've received the total amt }
{ of the data (determined in step 1 as either 128 or }
{ 1024), then it's time to go to step 5. }
then
Begin
Operation := 5;
CRCBytes := 0; { have gotten 0 bytes of CRC yet (see }
{ step 5 about this). }

ReverseWord(CRC); { We're reversing our CRC to be right }
{ for transmission. The xmitter will be sending it bit reversed. }
{ NOTE: From this point on, we don't actually have the CRC of the data }
{ in the CRC variable. We only have what we expect the xmitter to send. }

End;
End;
5 : Begin { Step 5 - get error check & verify block's correctness }

If CRCOn { If we're doing CRC, then handle 2 CRC bytes}
then
Begin
If CRCBytes = 0 { If we've not gotten anything for CRC }
{ yet, then treat this char as the high }
{ byte of the 16 bit word. }
then
Begin
BlockCRC := Ord(Ch) * 256; { Make it high byte }
Inc(CRCBytes); { increment byte counter for CRC }
End
else
Begin { If we've gotten a byte before on the CRC, }
{ this one is the lower byte. }

BlockCRC := BlockCRC + Ord(Ch); { add in low byte }

If (CRC = BlockCRC) or ForceACK { If CRC is right }
{ or we're forced to ACK this block, then we'll }
{ send an ACK....... }

then
Begin
Com_TX(ACK); { Send ACK to xmitter. }
LastBlock := BlockNum; { Keep track of the }
{ last block sent in LastBlock }

Operation := 1; { Back to waiting for SOH or}
{ STX (next block starting) }

If not ForceACK { If we were not forced to }
{ ACK this block, then we can save it to }
{ disk. }

then
Begin
{ write MaxDataLength (step 4) bytes }
{ to disk }
BlockWrite(File1,Data,MaxDataLength,Status);
{ Increment counter of bytes }
BytesReceived := BytesReceived + MaxDataLength;
{ Increment counter of blocks }
Inc(TotalBlocks);
End;
NumNAKs := 0; { Zero NAK/error counter }
UpdateVideo; { Tell user what's going on. }
End
else
{ If the CRC ** DOES NOT ** match, and we're }
{ not forcing an ACK on this block, then we'll }
{ have to NAK it because it's not good data. }
Begin

Repeat { Wait until there's not anything in }
{ the buffer. This is important to do if }
{ there was an error. Line noise might have }
{ caused a bunch of extra characters to come }
{ across, and we'll want to get rid of them. }
Ch := Com_Rx;
Until Com_RX_Empty;

SendNAK; { NAK the block, Operation is }
{ automatically set to 1, unless we've gone }
{ more than 10 errors on this block. }
End;
End;
End

{ Whew! That was a lot of stuff to do when we get to the end of the }
{ block. Fortunately, most of it (updating video, saving to disk) all }
{ happens after we've put the ACK in the buffer to be sent. Thus, the }
{ ACK is being xmitted, and the next block may already be being sent to }
{ the COM port buffer before we actually get out of here & ready to }
{ take the next block. The other steps are very quick by comparison, }
{ so we easily catch up with the buffer. On faster machines, we may }
{ find the code actually waiting for the next character to come in only }
{ a few loops later. Of course, baud rate has a lot to do with how }
{ much waiting we're going to do. }

{ Enough of the excursions...now let's see what happens when we check }
{ a block & save it under Checksum correction: }

else
Begin

If (Checksum = Ord(Ch)) or ForceACK { If the csum is }
{ correct or we're forced to ACK this block, then }
then
Begin
Com_TX(ACK); { Send the ACK }
LastBlock := BlockNum; { Keep track of blk. seq. }
Operation := 1; { we're going to step 1 again }

If not ForceACK { If we're not forced to ACK this }
{ block, then we'll save it to disk. }
then
Begin
{ Write the data block }
BlockWrite(File1,Data,MaxDataLength,Status);
{ increment bytes recevied counter }
BytesReceived := BytesReceived + MaxDataLength;
{ increment blocks counter }
Inc(TotalBlocks);
End;
NumNAKs := 0; { Zero out error counter }
UpdateVideo; { Tell user what has been happening }
End
else
Begin { If we're NAKing this block, then....}

Repeat { wait for the buffer to get empty. }
Ch := Com_Rx;
Until Com_RX_Empty;
SendNAK; { Send a NAK on the block }
End;
End;
End;

200 : Begin { Here is where it winds up if Operation = 200 }
{ This isn't actually a step. But the computer winds up here if }
{ ever we need to abort the protocol, and need to tell the xmitter }
{ this by sending two CAN's. }
Com_TX(CAN);
Com_TX(CAN);
Done := True; { time to drop out of loop }
End;
End;{Case}

Until Done; { Loop only lasts until Done }

Close(File1); { Close the file now }

End;