/* * xymodem file transferts * * (C) Copyright 2011 Angelo Dureghello * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include "xymodem.h" #include "Thread.h" using namespace std; using namespace boost; static const unsigned char ZPAD = 0x2a; static const unsigned char ZDLE = 0x18; static const unsigned char ZBIN = 0x41; static const unsigned char ZHEX = 0x42; static const unsigned char XON = 0x11; static const unsigned char ZMABORT[] = {ZDLE,ZDLE,ZDLE,ZDLE,ZDLE}; static const unsigned char ESC_HEXHDR[] = {ZPAD,ZPAD,ZDLE,ZHEX}; static const unsigned char ZRQINIT[] = "0000000000195E"; static const unsigned char ZTERM[]= {0x0d,0x0a,XON}; class __tools { public: __tools () {} public: unsigned short crc16_ccitt( char *buf, int len ) { unsigned short crc = 0; while( len-- ) { int i; crc ^= *(char *)buf++ << 8; for( i = 0; i < 8; ++i ) { if( crc & 0x8000 ) crc = (crc << 1) ^ 0x1021; else crc = crc << 1; } } return crc; } unsigned char calc_xmodem_checksum (unsigned char *buf, int sz) { int i; int cks = 0; for (i = 0; i < sz; ++i) { cks += (int)buf[i]; } return cks%256; } }; /* XMODEM */ static const unsigned char SOH = 0x01; static const unsigned char STX = 0x02; static const unsigned char EOT = 0x04; static const unsigned char ACK = 0x06; static const unsigned char NAK = 0x15; static const unsigned char ETB = 0x17; static const unsigned char CAN = 0x18; static const unsigned char SYN = 0x43; static const unsigned char CPMEOF = 0x1a; Operator::Operator (TimeoutSerial &c) : com(&c) { obuff.resize (512 , 0); obuff.resize (2048, 0); } void Operator::SessionStartup (const string &file) { fname = file; f.open(file.c_str(), fstream::binary | fstream::in); /* adding a cr, C was probably on the screen */ if (!f.is_open()) { BOOST_LOG_TRIVIAL(debug) << "Cannot open file " << file << " please check the path"; } BOOST_LOG_TRIVIAL(debug) << "File: " << file; /* getting file size */ f.seekg (0, ios::end); fsize = f.tellg(); f.seekg (0, ios::beg); stringstream ss; string size; ss << fsize; ss >> size; BOOST_LOG_TRIVIAL(debug) << "Size: " << size << " bytes"; } void Operator::StartTransfert () { BOOST_LOG_TRIVIAL(debug) << "Starting file transfer ..."; Entry(); } void Operator::SendBlock(char *block, int size) { int hsz, csz; /* prepare block header */ hsz = SetupBlockHdr(); /* prepare block binary data */ memcpy (&obuff[hsz], block, size); csz = SetupCheckSum (&obuff[hsz], &obuff[hsz+size]); q->write (&obuff[0], hsz+size+csz); } /* * A thread process the entire transfert and allows UI to be free of operating */ enum states { SESSION_START, SESSION_BLOCKS, SESSION_CLOSE, SESSION_CLOSE_WAIT_ACK, SESSION_END, }; bool Operator::GetChar() { try { q->read(&ibuff[0], 1); return true; } catch(boost::system::system_error& e) { return false; } } bool Operator::IsChar () { return (ibuff[0]!=0); } bool Operator::IsAck () { return (ibuff[0] == GetAck()); } bool Operator::IsSync () { return (ibuff[0] == GetSync()); } bool Operator::IsNack () { return (ibuff[0] == GetNack()); } bool Operator::IsCan () { return (ibuff[0] == GetCan()); } void Operator::SendEot () { char c[2]; c[0] = GetEot(); q->write (c, 1); } void Operator::SetupInfoBlock (string &block) { stringstream ss; unsigned int i = fname.rfind('/'); if (i==string::npos) i = fname.rfind('\\'); if (i!=string::npos) { fname = fname.substr(i+1); } i=fname.size(); ss << fsize; pkt = 0; /* prepare first block */ memcpy(&block[0],&fname[0], i); block[i++]=0; memcpy(&block[i],ss.str().c_str(),ss.str().size()); /* * space after length should be needed only if mod date is sent * but some receiver (u-boot) need it. */ block[i+ss.str().size()] = ' '; } void Operator::UpdateProgress (int size) { stringstream ss; if (!size) { psize = GetBlockSize() * count++; } else psize += size; ss << psize << "/" << fsize; // EvtDel (); // EvtMsg (ss.str()); } int Operator::Entry() { string block(1024,0); struct y_modem_data ymd; int state = SESSION_START; int bsize = GetBlockSize(); int blnum = fsize / bsize; int reminder=0; count=1; /* progress size */ psize=0; stringstream ss; string ascii_bsize; ss << bsize; ss >> ascii_bsize; /* clone */ q = com; pkt = 1; if (GetProto()=='Y') { /* * always sending file size allows a faster close of * the session from the receiver. * * = SPEC = * The pathname (conventionally, the file name) is sent as a null * terminated ASCII string. * No spaces are included in the pathname. Normally only the file name * stem (no directory prefix) is transmitted unless the sender has * selected YAM's f option to send the full pathname. The source drive * (A:, B:, etc.) is not sent. * */ SetupInfoBlock(block); blnum++; /* signal to skip update progress for first info block */ ymd.first_pkt = true; } else /* read first block from file*/ f.read(&block[0], bsize); for (;;) { /* always, wait for tranfert occour and proper replies */ boost::this_thread::sleep(posix_time::milliseconds(10)); if (GetChar()==false) continue; switch (state) { case SESSION_START: if (IsSync()) { if (ymd.first_ack) { BOOST_LOG_TRIVIAL(debug) << "Entry() : YMODEM FIRST C after ACK"; /* * YMODEM, as per spec we expect receiver * sends this furhter 'C' */ state++; /* * clear rx data for walkthrough and * send 1st block */ ibuff[0]=0; } else { if (GetProto()=='X') { /* * C here mean receiver wants * 16bit CRC mode and * assumes we know it if we send * a block */ SetMode(XMODEMCRC); /* send and promote */ state++; } /* received SYN */ BOOST_LOG_TRIVIAL(debug) << "Sync received, transfert start"; /* * resend same initial packet * note: non sense for Y-MODEM use 1024 */ SetSize(128); SendBlock (&block[0], 128); if (GetProto()=='Y') SetSize(1024); boost::this_thread::sleep(posix_time::milliseconds(10));; continue; } } else if (IsAck()) { if (GetProto()=='Y') { BOOST_LOG_TRIVIAL(debug) << "Entry() : YMODEM FIRST ACK"; ymd.first_ack=true; /* back up to get C after ack */ continue; } else state++; } else if (IsNack()) { /* * note ! programs as rx or lrz need filename as * additional parameter, otherwise they sends * C.. C and a CAN just after. */ if (GetProto()=='X') { BOOST_LOG_TRIVIAL(debug) << "Nack received, transfert start\r\n"; /* resend same initial packet*/ SendBlock (&block[0], bsize); boost::this_thread::sleep(posix_time::milliseconds(10)); /* promoted */ state++; } else { BOOST_LOG_TRIVIAL(debug) << "Entry() : NACK FIRST"; } } // NO BREAK, // FALL THROUGH IS INTENTIONAL case SESSION_BLOCKS: if (IsChar() && !IsAck()) { if (IsSync()) { if (GetProto()=='Y') { /* * first synch after first * block sent, do nothing here */ } } else if (IsNack()) { //EvtMsg("Nack/Sync\r\n"); /* resend */ if (GetProto()=='Y' || (GetProto()=='X' && pkt!=1)) { BOOST_LOG_TRIVIAL(debug) << "entry() : NACK, RESEND"; SendBlock (&block[0], bsize); boost::this_thread::sleep(posix_time::milliseconds(10)); } } else if (IsCan()) { BOOST_LOG_TRIVIAL(debug) << "Transfert canceled from receiver"; boost::this_thread::sleep(posix_time::milliseconds(100)); /* destroy object and the thread */ return (void*)1; } else { /* to do, if some other chances */ } continue; } // ACK received, so progress if (blnum) { if (GetProto()=='Y') { if (ymd.first_pkt) ymd.first_pkt=false; else UpdateProgress(); } else // XMODEM, always UpdateProgress(); /* if multiple of 256/1024 close */ if (blnum==1 && psize==fsize) { blnum--; goto close_session; } } else { close_session: /* completed */ UpdateProgress (reminder); BOOST_LOG_TRIVIAL(debug) << "Closing session ..."; SendEot(); if (GetProto()=='X') state=SESSION_END; else state++; continue; } blnum--; pkt++; /* end of blocks ? */ if (blnum>0) { f.read(&block[0], bsize); SendBlock (&block[0], bsize); } else { reminder=fsize%bsize; if (reminder) { f.read(&block[0], reminder); memset (&block[reminder],CPMEOF,bsize-reminder); SendBlock (&block[0], bsize); } } break; case SESSION_CLOSE: /* YMODEM ONLY */ if (IsAck()) state++; else if (IsNack()) { /* * in YMODEM termination is EOT-> NACK<-, * EOT-> ACK<- */ SendEot(); } break; case SESSION_CLOSE_WAIT_ACK: /* YMODEM ONLY */ if (IsSync()) { /* Synch, terminating.. */ memset (&block[0], 0, 128); SetSize(128); pkt=0; SendBlock (&block[0], 128); state++; } break; case SESSION_END: if (!IsAck()) continue; BOOST_LOG_TRIVIAL(debug) << "Transfert complete"; boost::this_thread::sleep(posix_time::milliseconds(100)); /* destroy object and the thread */ //delete q; //EvtEnd (); return (void*)1; break; } } return 0; } XModem::XModem (TimeoutSerial &c) : Operator (c), t(*new __tools) { xmodem_mode = 0; psize = LEN_B_XMODEM; } int XModem::SetupBlockHdr() { obuff[0]=(xmodem_mode&XMODEM1K)?STX:SOH; obuff[1]=pkt; obuff[2]=0xff-pkt; return 3; } int XModem::SetupCheckSum (char *data, char *dest) { if (xmodem_mode&XMODEMCRC) { unsigned short crc = t.crc16_ccitt(data, psize); *(unsigned short*)dest = (unsigned short) ((crc<<8)&0xFF00) | ((crc>>8)&0xff); return 2; } else { unsigned char crc = t.calc_xmodem_checksum((unsigned char*)data, psize); *dest = crc; return 1; } } inline char XModem::GetSync () { return SYN; } inline char XModem::GetAck () { return ACK; } inline char XModem::GetNack () { return NAK; } inline char XModem::GetCan () { return CAN; } inline char XModem::GetEot () { return EOT; } inline char XModem::GetProto() { return 'X'; } inline void XModem::SetSize (int size) { psize = size; } YModem::YModem (TimeoutSerial &c) : Operator (c), t(*new __tools) { psize = LEN_B_YMODEM; } /* YModem allows 128 (std) blocks or 1024 */ int YModem::SetupBlockHdr() { obuff[0]=(psize==LEN_B_YMODEM)?STX:SOH; obuff[1]=pkt; obuff[2]=0xff-pkt; return 3; } int YModem::SetupCheckSum (char *data, char *dest) { unsigned short crc = t.crc16_ccitt(data, psize); *(unsigned short*)dest = (unsigned short)((crc<<8)&0xFF00) | ((crc>>8)&0xff); return 2; } inline char YModem::GetSync () { return SYN; } inline char YModem::GetAck () { return ACK; } inline char YModem::GetNack () { return NAK; } inline char YModem::GetCan () { return CAN; } inline char YModem::GetEot () { return EOT; } inline char YModem::GetProto() { return 'Y'; } inline void YModem::SetSize (int size) { psize = size; }