www.pudn.com > commutil.zip > YMODEM.CPP
// ******************************************************************** //
// //
// YMODEM.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 YMODEM protocol. This class is built on //
// the Protocol class and relies on an instance of the Comm class //
// to access the communications port. //
// //
// ******************************************************************** //
struct ym_stat // ymodem status information
{
unsigned
long filelen, // file length
totalbytes, // total bytes transferred
left; // bytes left to transfer
int error, // error count
nfiles; // number of files
char dir, // direction: 0=rcv, 1=send
*filename; // current filename
void *work; // user work pointer
};
class YModem : Protocol
{
public:
YModem(Comm *ci, // define an YMODEM instance
int (*sr)(struct ym_stat *,
int = 0) = 0,
void *w = 0);
int Receive(void), // receive a file
Send(char *SendTable[]); // send a file
~YModem(); // instance destructor
private:
Comm *c; // comm instance pointer
void BuildPacket(int); // read/build a packet
int pktsz, // packet size
cip, // characters in packet
fh, // file handle
canflag, // cancel flag
Wait1st(int timeout), // await first char routine
FillPacket(int len), // packet fill wait routine
ChkPacket(void), // check received packet
WaitChar(long secs), // wait for character
Error(int maxerr = 10), // tally an error
(*status)(struct ym_stat *ym, // status routine
int mtype = 0);
struct ym_stat *ym; // ..and status array
char seq, // sequence number
buf[1029], // packet buffer
fn[65], // space for file name
*p, // ..and packet pointer
lsr, msr; // line and modem status
};
// packet buffer layout
#define YM_SEQ buf[1] // packet sequence number
#define YM_CSEQ buf[2] // ..complementary seq nbr
#define YM_DATA buf[3] // ..start of data
#define YM_CRC buf[131] // ..crc field - 128 block
#define YM_CRC1 buf[1027] // ..crc field - 1024 block
#define NEWFLE creatnew(fn, FA_NORMAL) // create a new file
#define OPEN4READ open(ym->filename, S_IREAD) // open file for read
/* ******************************************************************** *
*
* YModem -- define a protocol transfer instance
*
* ******************************************************************** */
YModem::YModem(Comm *ci, // comm instance to use
int (*sr)(struct ym_stat *, // status routine address
int = 0),
void *w) // user work pointer
{
c = ci; // save addr of comm instance
status = sr; // ..and status routine
ym = new struct ym_stat; // allocate for status fields
memset(ym, 0, sizeof(struct ym_stat)); // ..initialize storage
ym->work = w; // ..and save work pointer
}
/* ******************************************************************** *
*
* Receive -- receive a file using YMODEM 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 YModem::Receive(void) // download path name
{
int rc = 0, // return code
loop = 1; // main loop control
UINT oldlcr; // old value of LCR
enum rcvstates // states when receiving
{
AskHdr, // ask for a header packet
WaitHdr, // await header 1st char
EOTHdr, // EOT processor
HdrTmo, // timeout waiting for hdr
GetHdr, // get rest of header
Cancel, // cancel rest of transfer
NakHdr, // NAK the header packet
ProcHdr, // process header packet
AckHdr, // ACK a header packet
Done, // transfer complete
AckPkt, // ACK a packet
Get1st, // await packet 1st char
Get1024, // get 1024-byte packet
Get128, // get 128-byte packet
GetPkt, // get rest of packet
NakPkt, // NAK a data packet
ProcPkt, // process a data packet
AckPrev, // ACK previous packet
EndFile // process end of file
} state;
oldlcr = c->Set8n(); // set 8 data bits, no parity
state = AskHdr; // set initial state
c->IClear(); // clear input buffer
ym->totalbytes = 0; // clear total bytes transferred
ym->nfiles = 0; // clear number of files
ym->dir = 0; // direction = receive
canflag = 0; // clear cancel flag
(status)(ym, 3); // .. display start message
/* *********************************************************************
*
* The following state table show the states of the YMODEM transfer
* protocol when receiving files.
*
* ********************************************************************
*
* State Action Event New state
* ------- ------- ----- ---------
* AskHdr Seqno = 0
* error = 0
* send C WaitHdr
*
*
* WaitHdr Timeout(5) HdrTmo
* SOH GetHdr
* EOT EOTHdr
* 2 CAN Cancel (Cancelled by sender)
*
*
* EOTHdr Send ACK
* error = 0 AskHdr
*
*
* HdrTmo User ESC Cancel (Cancelled by user)
* ++Errors>20 Cancel (Timeout error)
* AskHdr
*
*
* GetHdr Timeout(1) NakHdr
* 132 bytes ProcHdr
* Addbyte GetHdr
*
*
* Cancel Send 2 CAN
*
*
* NakHdr ++Error>10? Cancel (Too many errors)
* User ESC Cancel (Cancelled by user)
* Send NAK WaitHdr
*
*
* ProcHdr Seq# bad Cancel (Sequence error)
* Bad CRC NakHdr
* NoFileName Done
* Open file Open error Cancel (File error)
* Save len
* AckHdr
*
*
* AckHdr User ESC Cancel (Cancelled by user)
* Send Ack
* Send C
* error = 0
* Inc Seqno Get1st
*
*
* Done Send ACK
*
*
* AckPkt User ESC Cancel (Cancelled by user)
* Send Ack
* error = 0
* Inc Seqno Get1st
*
*
* Get1st Timeout(10) NakPkt
* 2 CAN Cancel (Cancelled by sender)
* EOT EndFile
* SOH Get128
* STX Get1024
*
*
* Get1024 LEN=1028 GetPkt
*
*
* Get128 LEN=132 GetPkt
*
*
* GetPkt Timeout(1) NakPkt
* LEN bytes ProcPkt
* AddByte GetPkt
*
*
* NakPkt Error++>10? Cancel (Too many errors)
* User ESC Cancel (Cancelled by user)
* Send Nak Get1st
*
*
* ProcPkt Seq# -1? AckPrev
* Seq# Bad Cancel (Sequence error)
* Bad CRC NakPkt
* Write Data Error? Cancel (File write error)
* AckPkt
*
*
* AckPrev Send ACK Get1st
*
*
* EndFile Close FIle
* Error = 0; EOTHdr
*
* *********************************************************************/
while (loop) // loop till end requested
{
switch (state) // handle each state
{
case AskHdr: // ask for header
seq = 0; // zero the sequence number
fh = 0; // show no file open
ym->error = 0; // no errors
ym->filelen = ym->left = 0; // no data transferred
c->Write('C'); // send the letter C
state = WaitHdr; // .. wait for the header
break;
case WaitHdr: // wait for first character
switch (Wait1st(5)) // ..and process return code
{
case 0: // end of transmission
state = EOTHdr; // process EOT
break; // ..and exit loop
case 1: // SOH received
state = GetHdr; // set up to receive packet
case 2: // STX received
break; // .. Ignore it, continue
case 3: // timeout
state = HdrTmo; // .. process timeout
break; // .. continue processing
case 4: // CAN received
if (++canflag == 2) // q. two in a row?
{ // a. yes .. cancel receive
rc = 4; // .. show the reason
state = Cancel; // .. go to cancel state
}
break; // .. continue processing
}
break; // ..then loop around
case EOTHdr: // EOT received for header
c->Write(ACK); // .. tell them we got it
ym->error = 0; // .. reset the error count
state = AskHdr; // .. wait for a header
continue; // .. loop around
case HdrTmo: // timeout awaiting header
if ((status)(ym)) // q. user request cancel?
{
rc = 2; // a. yes .. user requested
state = Cancel; // .. cancel the download
continue; // .. loop around
}
if (Error(20)) // q. 20 errors?
{
rc = 10; // a. yes .. timeout error
state = Cancel; // .. cancel the download
continue; // .. loop around
}
state = AskHdr; // try again for header
continue; // .. loop around
case GetHdr: // get remainder of header
switch (FillPacket(133)) // .. from the sender
{
case 0: // packet received
state = ProcHdr; // .. process the header
break;
case 1: // timeout
state = NakHdr; // .. NAK the packet
break;
}
continue;
case Cancel: // Cancel the transfer
Purge(c, 1); // .. purge receive buffer
c->Write(CAN); // .. write a cancel
c->Write(CAN); // .. twice
loop = 0; // .. done processing
break; // .. and get out
case NakHdr: // NAK the header packet
if (Error(10)) // q. more than 10 errors?
{
rc = 3; // a. yes .. too many errors
state = Cancel; // .. cancel the download
continue;
}
if ((status)(ym)) // q. user cancel?
{
rc = 2; // a. yes .. user requested
state = Cancel; // .. cancel the download
continue; // .. loop around
}
Purge(c, 1); // clear receive buffer
c->Write(NAK); // tell them header was bad
state = WaitHdr; // wait for a resend
continue; // .. loop around
case ProcHdr: // process header packet
switch(ChkPacket()) // packet process ok?
{
case 0: // packet ok
if (YM_DATA == 0) // q. file name?
{
state = Done; // a. no .. done with transfer
break;
}
strcpy(fn, &YM_DATA); // copy in the file name
touppers(fn); // .. make it uppercase
ym->filename = fn; // .. and save it for later
_fmode = O_BINARY; // set global for binary files
unlink(fn); // make sure no other exists
if ((fh = NEWFLE) == -1) // q. create new output ok?
{
rc = 5; // a. no .. unable to create
state=Cancel; // .. cancel the transfer
break;
}
sscanf(&YM_DATA+1+ // Scan for the file length
strlen(&YM_DATA),
"%lu",
&ym->filelen);
ym->left = ym->filelen; // bytes left to transfer
(status)(ym, 1); // display new file & length
state = AckHdr; // ACK the header packet
break;
case 1: // sequence matches previous
case 2: // bad sequence number
rc = 12; // .. unrecoverable error
state = Cancel; // .. cancel the transfer
case 4: // too many errors
rc = 3; // .. set the return code
state = Cancel; // .. cancel the transfer
break;
case 3: // error in packet
state = NakHdr; // NAK the header packet
break;
}
continue; // .. loop around
case AckHdr: // ACK header packet
if ((status)(ym)) // q. user escape?
{ // a. yes ..
rc = 4; // .. show user cancelled
state = Cancel; // .. cancel the transfer
break;
}
c->Write(ACK); // send an ACK
c->Write('C'); // .. then start the file
seq++; // go to next sequence number
state = Get1st; // get first char of packet
ym->error = 0; // reset error count
continue; // .. continue processing
case Done: // All files transferred
rc = 0; // .. show successful
loop = 0; // .. get out of loop
c->Write(ACK); // .. ack that last packet
(status)(ym, 2); // .. display final message
continue; // .. continue processing
case AckPkt: // ACK last packet
if ((status)(ym)) // q. user escape?
{ // a. yes ..
rc = 4; // .. show user cancelled
state = Cancel; // .. cancel the transfer
break;
}
c->Write(ACK); // send an ACK
seq++; // go to next sequence number
state = Get1st; // get first char of packet
ym->error = 0; // .. reset the error count
continue; // .. continue processing
case Get1st: // get first char of packet
switch (Wait1st(10)) // ..and process return code
{
case 0: // end of transmission
state = EndFile; // process EOT
break; // ..and exit loop
case 1: // SOH received
state = Get128; // get a 128-byte packet
break;
case 2: // STX received
state = Get1024; // get a 1024-byte packet
break;
case 3: // timeout
state = NakPkt; // .. NAK packet
break; // .. continue processing
case 4: // CAN received?
if (++canflag == 2) // q. two in a row?
{ // a. yes .. cancel the receive
rc = 4; // .. show the reason
state = Cancel; // .. go to cancel state
}
break; // continue processing
}
continue; // ..then loop around
case Get1024: // get a 1024-byte packet
pktsz = 1029; // .. set length for packet
state = GetPkt; // .. get the packet
continue;
case Get128: // get a 128-byte packet
pktsz = 133; // .. set length for packet
state = GetPkt; // .. get the packet
continue;
case GetPkt: // get the packet
switch (FillPacket(pktsz)) // .. from the sender
{
case 0: // packet received
state = ProcPkt; // .. process the header
break;
case 1: // timeout
state = NakPkt; // .. NAK the packet
break;
}
continue;
case NakPkt: // NAK a data packet
if (Error(10)) // q. too many errors?
{ // a. yes ..
rc = 3; // .. show too many errors
state = Cancel; // .. cancel the transfer
continue; // .. continue processing
}
if ((status)(ym)) // q. user cancel transfer?
{ // a. yes ..
rc = 4; // .. show user cancelled
state = Cancel; // .. cancel the transfer
continue; // .. continue processing
}
Purge(c, 1); // purge the receive buffer
c->Write(NAK); // NAK the packet
state = Get1st; // .. retry the receive
continue; // .. continue processing
case ProcPkt: // process a packet
switch(ChkPacket()) // q. did packet process ok?
{
case 0: // a. yes .. packet ok
pktsz -= 5; // get the data length
state = AckPkt; // .. ACK this packet
if (ym->left == 0) // q. anything left to write?
break; // a. no .. skip the write
pktsz = pktsz < ym->left // get the size to write
? pktsz
: (int) ym->left;
write(fh, &YM_DATA, // write the data
pktsz);
ym->left -= pktsz; // show amount left to write
break;
case 1: // sequence matches previous
state = AckPrev; // .. ACK the previous message
break;
case 2: // bad sequence number
rc = 12; // .. unrecoverable error
state = Cancel; // .. cancel the transfer
case 3: // error in packet
state = NakPkt; // NAK the header packet
break;
case 4: // too many errors
rc = 3; // .. set the return code
state = Cancel; // .. cancel the transfer
break;
}
continue; // .. loop around
case AckPrev: // ACK previous packet
c->Write(ACK); // .. send an ACK
ym->error = 0; // .. reset the error count
state = Get1st; // .. get first char of packet
continue;
case EndFile: // Complete this file
close(fh); // ..close the file
rc = 0; // ..no error
fh = 0; // ..show file closed
ym->totalbytes += ym->filelen; // ..add to bytes sent
ym->nfiles++; // ..increment file count
state = EOTHdr; // ..process the EOT
continue; // .. continue processing
}
}
if (fh) // q. file open?
close(fh); // a. yes .. close it
c->SetLine(oldlcr); // restore comm parameters
return(rc); // give user final state
}
/* ******************************************************************** *
*
* Send -- send a file using YMODEM 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 YModem::Send(char *SendTable[]) // files to send
{
int rc = 0, // return code
fileidx, // next file to send
loop = 1; // main loop control
UINT oldlcr; // old value of LCR
enum sendstates
{
StartSnd, // start the send
WaitC, // wait for C
Cancel, // cancel the transfer
NextFile, // get next file to send
BldHdr, // build the header packet
SendHdr, // send header packet
WaitHdrAck, // wait for ACK for header
HdrAckTmo, // timeout waiting for ACK
NakHdr, // NAK received for header
WaitC2, // wait for C to start file
BldPkt, // build file packet
Bld128, // build 128-byte packet
Bld1024, // build 1024-byte packet
ComplPkt, // complete the packet
SendPkt, // send the packet
WaitPktAck, // wait for ACK for packet
PktAckTmo, // timeout awaiting ACK
NakPkt, // NAK received for packet
ProcEof, // process end of file
SendEot, // send the EOT character
WaitEotAck, // wait for ACK for EOT
TmoEotAck, // timeout on EOT ACK
NakEot, // NAK received for EOT
LastPkt, // build final packet
SendLast, // send final packet
WaitLastAck, // wait for last ACK
LastAckTmo, // timeout awaiting ACK
NakLast // last packet NAK'd
} state; // current send state
oldlcr = c->Set8n(); // set 8 data bits, no parity
c->IClear(); // clear input buffer
state = StartSnd; // set initial state
ym->totalbytes = 0; // clear total bytes transferred
ym->nfiles = 0; // clear number of files
ym->dir = 1; // direction = send
canflag = 0; // clear cancel flag
(status)(ym, 3); // .. display start message
/* *********************************************************************
*
* The following state table show the states of the YMODEM transfer
* protocol when sending files.
*
* ********************************************************************
*
* State Action Event New state
* ------- ------- ----- ---------
* StartSnd fileidx = -1 WaitC
*
*
* WaitC 2 CAN Cancel (By Receiver)
* User ESC Cancel (By User)
* Timeout(60) Cancel (Timeout)
* "C" NextFile
*
*
* Cancel Send 2 CAN
*
*
* NextFile ++fileidx>4 LastPkt
* BldHdr
*
*
* BldHdr seq = 0
* open file Open error NextFile
* get length
* left = filelen
* build header
* Calc CRC SendHdr
*
*
* SendHdr SendHeaderRec WaitHdrAck
*
*
* WaitHdrAck Timeout(60) HdrAckTmo
* User ESC Cancel (By user)
* NAK NakHdr
* 2 CAN Cancel (By receiver)
* ACK WaitC2
*
*
* HdrAckTmo ++Error>5 Cancel (Timeout)
* SendHdr
*
* NakHdr ++Error>10 Cancel (Too many errors)
* SendHdr
*
* WaitC2 Timeout(60) HdrAckTmo
* User ESC Cancel (By user)
* CAN Cancel (By receiver)
* "C" BldPkt
*
*
* BldPkt seq++
* left = 0 ProcEof
* left<1024 Bld128
* Bld1024
*
* Bld128 1st = SOH
* read 128
* left -= 128 ComplPkt
*
*
* Bld1024 1st = STX
* read 1024
* left -= 1024 ComplPkt
*
*
* ComplPkt insert seqnos
* calc CRC SendPkt
*
*
* SendPkt Send packet WaitPktAck
*
*
* WaitPktAck Timeout(60) PktAckTmo
* User ESC Cancel (By user)
* NAK NakPkt
* 2 CAN Cancel (By receiver)
* ACK BldPkt
*
*
* PktAckTmo ++Error > 5 Cancel (Timeout)
* SendPkt
*
*
* NakPkt ++Error > 10 Cancel (Too many errors)
* SendPkt
*
*
* ProcEof reset variables SendEot
*
*
* SendEot Send EOT WaitEotAck
*
*
* WaitEotAck Timeout(10) TmoEotAck
* User ESC Cancel (By user)
* NAK NakEot
* 2 CAN Cancel (By Receiver)
* ACK WaitC
*
*
* TmoEotAck ++Error>10 Cancel (Timeout)
* SendEot
*
*
* NakEot ++Error>10 Cancel (Too many errors)
* SendEot
*
*
* LastPkt seq = 0
* zero packet
* build packet SendLast
*
*
*
* SendLast Send Last Packet WaitLastAck
*
*
* WaitLastAck Timeout(60) LastAckTmo
* User ESC Cancel (By user)
* NAK NakLast
* 2 CAN Cancel (By Receiver)
* ACK