www.pudn.com > commutil.zip > XMODEM.CPP


// ******************************************************************** // 
//                                                                      // 
//      XMODEM.CPP                                                      // 
//      Copyright (c) 1993, Michael Holmes and Bob Flanders             // 
//      C++ Communication Utilities                                     // 
//                                                                      // 
//      Chapter 6: Transferring Files                                   // 
//      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 
 
}