Add Ymodem transmit translated to boosts
This commit is contained in:
585
xymodem.cpp
Normal file
585
xymodem.cpp
Normal file
@@ -0,0 +1,585 @@
|
||||
/*
|
||||
* xymodem file transferts
|
||||
*
|
||||
* (C) Copyright 2011 Angelo Dureghello <angelo70@gmail.com>
|
||||
*
|
||||
* 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 <cstring>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <boost/utility.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/thread/future.hpp>
|
||||
#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; }
|
||||
Reference in New Issue
Block a user