haiku/src/apps/serialconnect/XModem.cpp

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;
}