187 lines
3.4 KiB
C++
187 lines
3.4 KiB
C++
/*
|
|
* Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk
|
|
* Distributed under terms of the MIT license.
|
|
*/
|
|
|
|
|
|
#include "XModem.h"
|
|
|
|
#include "SerialApp.h"
|
|
|
|
#include <Catalog.h>
|
|
#include <String.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
|
|
#define B_TRANSLATION_CONTEXT "XModemStatus"
|
|
|
|
|
|
// ASCII control characters used in XMODEM protocol
|
|
static const char kSOH = 1;
|
|
static const char kEOT = 4;
|
|
static const char kACK = 6;
|
|
static const char kNAK = 21;
|
|
static const char kCAN = 24;
|
|
static const char kSUB = 26;
|
|
|
|
static const int kBlockSize = 128;
|
|
|
|
|
|
XModemSender::XModemSender(BDataIO* source, BSerialPort* sink, BHandler* listener)
|
|
: fSource(source),
|
|
fSink(sink),
|
|
fListener(listener),
|
|
fBlockNumber(0),
|
|
fEotSent(false),
|
|
fUseCRC(false)
|
|
{
|
|
fStatus = B_TRANSLATE("Waiting for receiver" B_UTF8_ELLIPSIS);
|
|
|
|
BPositionIO* pos = dynamic_cast<BPositionIO*>(source);
|
|
if (pos)
|
|
pos->GetSize(&fSourceSize);
|
|
else
|
|
fSourceSize = 0;
|
|
|
|
NextBlock();
|
|
}
|
|
|
|
|
|
XModemSender::~XModemSender()
|
|
{
|
|
delete fSource;
|
|
}
|
|
|
|
|
|
bool
|
|
XModemSender::BytesReceived(const uint8_t* data, size_t length)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < length; i++)
|
|
{
|
|
switch (data[i])
|
|
{
|
|
case 'C':
|
|
// A 'C' to request the first block is a request to use a CRC
|
|
// in place of an 8-bit checksum.
|
|
// In any other place, it is ignored.
|
|
if (fBlockNumber <= 1) {
|
|
fStatus = B_TRANSLATE("CRC requested");
|
|
fUseCRC = true;
|
|
SendBlock();
|
|
} else
|
|
break;
|
|
case kNAK:
|
|
if (fEotSent) {
|
|
fSink->Write(&kEOT, 1);
|
|
} else {
|
|
fStatus = B_TRANSLATE("Checksum error, re-send block");
|
|
SendBlock();
|
|
}
|
|
break;
|
|
|
|
case kACK:
|
|
if (fEotSent) {
|
|
return true;
|
|
}
|
|
|
|
if (NextBlock() == B_OK) {
|
|
fStatus = B_TRANSLATE("Sending" B_UTF8_ELLIPSIS);
|
|
SendBlock();
|
|
} else {
|
|
fStatus = B_TRANSLATE("Everything sent, "
|
|
"waiting for acknowledge");
|
|
fSink->Write(&kEOT, 1);
|
|
fEotSent = true;
|
|
}
|
|
break;
|
|
|
|
case kCAN:
|
|
{
|
|
BMessage msg(kMsgProgress);
|
|
msg.AddInt32("pos", 0);
|
|
msg.AddInt32("size", 0);
|
|
msg.AddString("info",
|
|
B_TRANSLATE("Remote cancelled transfer"));
|
|
fListener.SendMessage(&msg);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
XModemSender::SendBlock()
|
|
{
|
|
uint8_t header[3];
|
|
uint8_t checksum = 0;
|
|
int i;
|
|
|
|
header[0] = kSOH;
|
|
header[1] = fBlockNumber;
|
|
header[2] = 255 - fBlockNumber;
|
|
|
|
fSink->Write(header, 3);
|
|
fSink->Write(fBuffer, kBlockSize);
|
|
|
|
if (fUseCRC) {
|
|
uint16_t crc = CRC(fBuffer, kBlockSize);
|
|
uint8_t crcBuf[2];
|
|
crcBuf[0] = crc >> 8;
|
|
crcBuf[1] = crc & 0xFF;
|
|
fSink->Write(crcBuf, 2);
|
|
} else {
|
|
// Use a traditional (and fragile) checksum
|
|
for (i = 0; i < kBlockSize; i++)
|
|
checksum += fBuffer[i];
|
|
|
|
fSink->Write(&checksum, 1);
|
|
}
|
|
}
|
|
|
|
|
|
status_t
|
|
XModemSender::NextBlock()
|
|
{
|
|
memset(fBuffer, kSUB, kBlockSize);
|
|
|
|
if (fSource->Read(fBuffer, kBlockSize) > 0) {
|
|
// Notify for progress bar update
|
|
BMessage msg(kMsgProgress);
|
|
msg.AddInt32("pos", fBlockNumber);
|
|
msg.AddInt32("size", fSourceSize / kBlockSize);
|
|
msg.AddString("info", fStatus);
|
|
fListener.SendMessage(&msg);
|
|
|
|
// Remember that we moved to next block
|
|
fBlockNumber++;
|
|
return B_OK;
|
|
}
|
|
return B_ERROR;
|
|
}
|
|
|
|
uint16_t XModemSender::CRC(const uint8_t *buf, size_t len)
|
|
{
|
|
uint16_t crc = 0;
|
|
while( len-- ) {
|
|
int i;
|
|
crc ^= ((uint16_t)(*buf++)) << 8;
|
|
for( i = 0; i < 8; ++i ) {
|
|
if( crc & 0x8000 )
|
|
crc = (crc << 1) ^ 0x1021;
|
|
else
|
|
crc = crc << 1;
|
|
}
|
|
}
|
|
return crc;
|
|
}
|