www.pudn.com > commutil.zip > XMODEM.CPP
// ******************************************************************** //
// //
// XMODEM.CPP //
// Copyright (c) 1993, Michael Holmes and Bob Flanders //
// C++ Communication Utilities //
// //
// Chapter 7: Receiving a FAX //
// Last changed in chapter 6 //
// //
// This file contains the functions to implement a //
// communications class to support the XMODEM and XMODEM/CRC //
// protocol. This class is built on the Protocol class and //
// relies on an instance of the Comm class to access the //
// communications port. //
// //
// ******************************************************************** //
struct xm_stat // xmodem status information
{
long pktcnt, // packet count
user; // ..user data count
int error; // error count
char crc, // xmodem/crc mode
done, // done flag
dir, // direction
*filename; // current filename
void *work; // user work pointer
};
class XModem : Protocol
{
public:
XModem(Comm *ci, // define an XMODEM instance
int (*sr)(struct xm_stat *,
int msgtype=0) = 0,
void *w = 0);
int Receive(char *f), // receive a file
Send(char *f); // send a file
~XModem(); // instance destructor
private:
Comm *c; // comm instance pointer
int pktsz, // packet size
cip, // characters in packet
fh, // file handle
state, // process state
WaitSOH(void), // SOH wait routine
FillPacket(void), // packet fill wait routine
WaitNAK(void), // NAK wait routine
BuildPacket(void), // read/build a packet
WaitACK(void), // ACK wait routine
Error(void), // tally an error
(*status)(struct xm_stat *, // status routine
int msgtype = 0);
struct xm_stat *xm; // ..and status array
char nak, // NAK character
soh_flag, // SOH flag
seq, // sequence number
buf[133], // packet buffer
*p, // ..and packet pointer
lsr, msr; // line and modem status
};
// packet buffer layout
#define XM_SEQ buf[1] // packet sequence number
#define XM_CSEQ buf[2] // ..complementary seq nbr
#define XM_DATA buf[3] // ..start of data
#define XM_CK buf[131] // ..checksum or crc field
/* ******************************************************************** *
*
* XModem -- define a protocol transfer instance
*
* ******************************************************************** */
XModem::XModem(Comm *ci, // comm instance to use
int (*sr)(struct xm_stat *, // status routine address
int msgtype = 0),
void *w) // user work pointer
{
c = ci; // save addr of comm instance
status = sr; // ..and status routine
xm = new struct xm_stat; // allocate for status fields
memset(xm, 0, sizeof(struct xm_stat)); // ..initialize storage
xm->work = w; // ..and save work pointer
}
/* ******************************************************************** *
*
* Receive -- receive a file using XMODEM or XMODEM/CRC protocol
*
* returns: 0 = successful
* 1 = output file already exists
* 2 = user cancelled transfer
* 3 = fatal protocol error (too many errors)
* 4 = sender cancelled transfer
* 5 = write error
*
* ******************************************************************** */
int XModem::Receive(char *f) // filename to use
{
int rc = 0, // return code
loop = 1; // main loop control
UINT oldlcr; // old value of LCR
oldlcr = c->Set8n(); // set 8 data bits, no parity
xm->dir = 0; // show receiving
xm->filename = f; // save filename for later
_fmode = O_BINARY; // set global for binary files
if ((fh = creatnew(f, FA_NORMAL)) == -1) // q. create new output ok?
return(1); // a. no .. just return
state = 0; // start with SOH state
nak = 'C'; // save NAK for xmodem/crc
xm->crc = 1; // ..set crc flag
pktsz = 133; // ..packet size
seq = 1; // ..first sequence number
soh_flag = 0; // ..SOH character seen
c->IClear(); // clear input buffer
c->Write(nak); // ..and send the first NAK
while (loop) // loop till end requested
{
switch (state) // handle each state
{
case 0: // wait for a SOH
switch (rc = WaitSOH()) // ..and process return code
{
case 1: // SOH received
state = 1; // ..receive packet
break; // ..loop around and get data
case 0: // end of transmission
case 2: // user requested termination
case 3: // too many errors
case 4: // sender cancelled
state = 2; // go to cancel state
break; // ..and exit loop
}
continue; // ..then loop around
case 1: // wait for end of packet
switch (rc = FillPacket()) // ..from the sender
{
case 0: // duplicate packet
c->Write(ACK); // send the acknowledgment
state = 0; // back to waiting for SOH
break; // ..loop around and get data
case 1: // buffer filled
if (write(fh, &XM_DATA, // q. write ok?
128) != 128) // a. successfully?
{
state = 2; // a. no .. cancel receive
rc = 5; // ..due to file error
break; // ..and exit here
}
c->Write(ACK); // else .. ACK the sender
xm->error = 0; // ..reset error count
state = 0; // ..and back to SOH state
break; // ..to get next packet
case 2: // user requested termination
case 3: // fatal error
state = 2; // ..go to cancel state
break; // ..and continue
case 4: // bad packet, restart again
Purge(c, 1); // ..purge the line
c->Write(NAK); // ..send non-acknowledgment
state = 0; // ..back to waiting for SOH
break; // ..loop around once more
}
continue;
case 2: // cancel the transfer
Purge(c, 1); // ..purge the line
c->Write(CAN); // ..write one CAN character
c->Write(CAN); // ..and another
loop = 0; // ..and exit loop
continue;
}
}
close(fh); // close output file
if (rc) // q. need to kill file?
unlink(f); // a. yes .. delete it
c->SetLine(oldlcr); // restore comm parameters
return(rc); // give user final state
}
/* ******************************************************************** *
*
* Send -- send a file using XMODEM or XMODEM/CRC protocol
*
* returns: 0 = successful
* 1 = input file not found
* 2 = user cancelled transfer
* 3 = fatal protocol error (too many errors)
* 4 = receiver cancelled transfer
* 5 = file error
*
* ******************************************************************** */
int XModem::Send(char *f) // filename to use
{
int rc = 0, // return code
loop = 1; // main loop control
UINT oldlcr; // old value of LCR
oldlcr = c->Set8n(); // set 8 data bits, no parity
xm->dir = 1; // show sending
xm->filename = f; // save filename for later
_fmode = O_BINARY; // set global for binary files
if ((fh = open(f, S_IREAD)) == -1) // q. open input file ok?
return(1); // a. no .. just return
state = 0; // start with SOH wait state
buf[0] = SOH; // ..and an SOH character
pktsz = 132; // ..and XMODEM packet size
seq = 1; // ..first sequence number
xm->crc = 0; // clear XMODEM/CRC flag
soh_flag = 0; // ..and first packet flag
c->IClear(); // clear input buffer
while (loop) // loop till end requested
{
switch (state) // handle each state
{
case 0: // wait for a NAK
switch (rc = WaitNAK()) // ..and process return code
{
case 1: // NAK received
state = 1; // set up to read/send packet
(status)(xm, 1); // ..display startup message
break; // ..loop around and get data
case 2: // user requested termination
rc = 14; // ..user requested
case 3: // too many errors
case 4: // receiver cancelled
state = 4; // ..set the cancel state
break; // ..and exit loop
}
continue; // rerun the state machine
case 1: // read/build the next packet
switch (rc = BuildPacket()) // ..and process return code
{
case 0: // end of transmission
loop = 0; // clear loop flag
break; // ..and exit loop
case 1: // Packet sent
state = 2; // ..wait for ACK
break; // ..loop around and get data
case 2: // user requested termination
rc = 14; // ..set rc
case 3: // too many errors
case 4: // receiver cancelled
case 5: // file error
state = 4; // ..cancel the send
break; // ..and exit loop
}
continue; // rerun the state machine
case 2: // send a packet
c->Write(buf, pktsz); // ..to the comm port
state = 3; // set up to wait for the ACK
continue; // rerun the state machine
case 3: // wait for an ack
switch (rc = WaitACK()) // ..and process return code
{
case 0: // ACK rec'd send nxt packet
state = 1; // set up state machine
break; // ..and loop around
case 1: // NAK rec'd
state = 2; // set up to wait for ack
Purge(c, 1); // ..purge the receive buffer
break; // ..loop around and get data
case 2: // user requested termination
rc = 14; // .. set rc
case 3: // too many errors
case 4: // sender cancelled
state = 4; // ..set cancel state
break; // ..and exit loop
}
continue; // rerun the state machine
case 4: // cancel the transfer
Purge(c, 1); // .. purge receive buffer
c->Write(CAN); // .. write one CAN char
c->Write(CAN); // .. and another
loop = 0; // .. and exit loop
continue;
}
}
close(fh); // close output file
c->SetLine(oldlcr); // restore comm parameters
return(rc); // give user final state
}
/* ******************************************************************** *
*
* WaitNAK -- wait for receiver to send a NAK
*
* returns: 1 = read then send packet
* 2 = user cancelled transfer
* 3 = fatal error, terminate transfer
* 4 = receiver cancelled transfer
*
* ******************************************************************** */
int XModem::WaitNAK(void)
{
int ec = 0; // error count
char wc; // comm port character
long timer; // timeout clock
timer = SECS(10); // set timer for 10 seconds
for (;;) // set up a loop
{
if (TimeOut(&timer)) // q. timeout waiting around?
{ // a. yes .. process an error
if (NOT (++ec % 6) && Error()) // q. too many errors?
return(3); // a. yes .. return to sender
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
timer = SECS(10); // ..and restart timer
}
if (c->Read(&wc, &msr, &lsr) != 0) // q. anything available?
continue; // a. no .. then wait more
switch (wc) // check received character
{
case NAK: // NAK character
soh_flag = 1; // show SOH received
return(1); // ..and start next state
case 'C': // XMODEM/CRC start character
if (soh_flag) // q. first packet sent?
continue; // a. yes .. can't change type
xm->crc = 1; // set XMODEM/CRC flag
pktsz = 133; // ..and packet size
return(1); // ..then return to caller
case CAN: // CAN character
return(4); // ..and return with error
}
}
}
/* ******************************************************************** *
*
* BuildPacket -- read and build a block to send to the receiver
*
* returns: 0 = sucessful, transfer complete
* 1 = buffer filled, send to receiver
* 2 = user cancelled transfer
* 3 = fatal protocol error (too many errors)
* 4 = receiver cancelled transfer
* 5 = file error
*
* ******************************************************************** */
int XModem::BuildPacket(void)
{
int rc; // read function return code
char wc; // comm port character
long timer; // timeout clock
XM_SEQ = seq; // save new sequence number
XM_CSEQ = ~seq++; // ..and complement
memset(&XM_DATA, 0, 128); // ..clear buffer to nulls
if ((rc = read(fh, &XM_DATA, 128)) == -1) // q. error reading file?
return(5); // ..and return to caller
else if (rc == 0) // q. end of file?
{
c->Write(EOT); // a. yes .. send an EOT
timer = SECS(5); // ..set timer for 5 seconds
for (;;) // set up a loop
{
if (TimeOut(&timer)) // q. timeout waiting around?
{ // a. yes .. process an error
if (Error()) // q. too many errors?
return(3); // a. yes .. return to sender
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
timer = SECS(5); // ..and restart timer
}
if (c->Read(&wc, &msr, &lsr) != 0) // q. anything available?
continue; // a. no .. then wait more
switch (wc) // check received character
{
case NAK: // NAK character
c->Write(EOT); // send the EOT character
if (Error()) // q. too many errors?
return(3); // a. yes .. return to sender
timer = SECS(5); // restart timer
break; // ..and wait some more
case ACK: // ACK character
xm->done = 1; // set all done flag
return(0); // ..return everything is done
case CAN: // CAN character
return(4); // ..and return with error
}
}
}
else // else .. build packet
{
if (xm->crc) // q. doing a XMODEM/CRC run?
*(UINT *) &XM_CK = // a. yes .. calculate the crc
CRC(&XM_DATA, 128); // ..and store both bytes
else
XM_CK = CheckSum(&XM_DATA, 128); // else .. compute a checksum
return(1); // ..and proceed to next state
}
}
/* ******************************************************************** *
*
* WaitACK -- wait for an ACK from the receiver
*
* returns: 0 = ACK received, send next packet
* 1 = NAK received, resend current packet
* 2 = user cancelled transfer
* 3 = fatal protocol error (too many errors)
* 4 = receiver cancelled transfer
*
* ******************************************************************** */
int XModem::WaitACK(void)
{
char wc; // comm port character
long timer; // timeout clock
timer = SECS(5); // init timer for 1 second
for (;;) // set up a loop
{
if (TimeOut(&timer)) // q. timeout waiting around?
{ // a. yes .. process an error
if (Error()) // q. too many errors?
return(3); // a. yes .. return to sender
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
timer = SECS(5); // ..and restart timer
}
if (c->Read(&wc, &msr, &lsr) != 0) // q. anything available?
continue; // a. no .. then wait more
switch (wc) // check received character
{
case ACK: // ACK character
xm->error = 0; // reset error count
xm->pktcnt++; // increment packet count
return((status)(xm) ? 2 : 0); // return cancel or continue
case NAK: // NAK character
return(Error() ? 3 : 1); // resend packet or cancel
case CAN: // CAN character
return(4); // ..and return with error
}
}
}
/* ******************************************************************** *
*
* WaitSOH -- wait for sender to send first byte of the packet
*
* returns: 0 = EOT received, close file
* 1 = SOH received, get packet data
* 2 = user cancelled download
* 3 = fatal error, terminate download
* 4 = CAN received, cancel download
*
* ******************************************************************** */
int XModem::WaitSOH(void)
{
long timer; // timeout clock
timer = SECS(10); // set timer for 10 seconds
for (;;) // set up a loop
{
if (TimeOut(&timer)) // q. timeout waiting around?
{ // a. yes .. process an error
if (Error()) // q. too many errors?
return(3); // a. yes .. return to sender
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
if (xm->error > 2 && NOT soh_flag) // q. time for regular xmodem?
{
nak = NAK; // a. yes .. use real NAK char
pktsz = 132; // ..set up packet size
xm->crc = 0; // ..and clear crc flag
}
Purge(c, 1); // purge the receive buffer
c->Write(nak); // send a NAK char
timer = SECS(10); // ..and restart timer
}
if (c->Read(buf, &msr, &lsr) != 0) // q. anything available?
continue; // a. no .. then wait more
switch (buf[0]) // check received character
{
case SOH: // SOH character
if (NOT soh_flag) // q. first packet?
(status)(xm, 1); // a. yes .. display message
soh_flag = 1; // show SOH received
p = &buf[1]; // set up buffer pointer
nak = NAK; // ..correct NAK character
cip = 1; // ..characters in packet
return(1); // ..and start next state
case EOT: // EOT character
c->Write(ACK); // send an ACK
xm->done = 1; // ..set all done flag
return(0); // ..and return to caller
case CAN: // CAN character
return(4); // ..and return with error
}
}
}
/* ******************************************************************** *
*
* FillPacket() -- process filling the packet
*
* returns: 0 = duplicate packet, start next packet
* 1 = buffer filled, start next packet
* 2 = user cancelled download
* 3 = fatal error, terminate download
* 4 = packet error, start same packet again
*
* ******************************************************************** */
int XModem::FillPacket(void)
{
long timer; // timeout clock
timer = SECS(1); // init timer for 1 second
for (;;) // set up a loop
{
if (TimeOut(&timer)) // q. timeout waiting around?
{ // a. yes .. process an error
if (Error()) // q. too many errors?
return(3); // a. yes .. return to sender
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
timer = SECS(1); // else .. restart timer
}
if (c->Read(p, &msr, &lsr) != 0) // q. anything available?
continue; // a. no .. then wait more
p++; // next buffer position
timer = SECS(1); // ..and restart timer
if (++cip != pktsz) // q. buffer full?
continue; // a. no .. wait some more
if (xm->crc) // q. using xmodem/crc?
{ // a. yes .. check w/crc
if (CRC(&XM_DATA, 128) != // q. does the crc match
*(UINT *) &XM_CK) // ..what is in the buffer?
return(Error() ? 3 : 4); // a. no .. restart packet
}
else // else .. regular xmodem
{
if (CheckSum(&XM_DATA, 128) != // q. does checksum match
(unsigned char) XM_CK) // ..that is in the buffer
return(Error() ? 3 : 4); // a. no .. restart packet
}
if (XM_SEQ != ~XM_CSEQ) // q. seq nbrs consistent?
return(Error() ? 3 : 4); // a. no .. restart packet
if (XM_SEQ != seq) // q. expected sequence nbr?
{ // a. no .. check things out
if (XM_SEQ == (seq - 1)) // q. one packet back?
return(Error() ? 3 : 0); // a. yes .. bypass duplicate
else
return(3); // else .. return fatal error
}
seq++; // next sequence number
xm->user += 128; // count user data bytes
xm->pktcnt++; // ..and packets received
xm->error = 0; // restart error count
if ((status)(xm)) // q. user want to cancel?
return(2); // a. yes .. cancel download
return(1); // ..then rtn w/buffer filled
}
}
/* ******************************************************************** *
*
* Error -- tally an error and possibly cancel the transfer
*
* ******************************************************************** */
int XModem::Error(void)
{
return (++(xm->error) > 10); // true if too many errors
}
/* ******************************************************************** *
*
* ~XModem -- class destructor
*
* ******************************************************************** */
XModem::~XModem(void)
{
delete xm; // release status control blk
}