/* * Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk * Distributed under terms of the MIT license. */ #include "XModem.h" #include "SerialApp.h" #include #include #include #include #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(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; }