1 /* 2 * Copyright 2017, Adrien Destugues, pulkomandy@pulkomandy.tk 3 * Distributed under terms of the MIT license. 4 */ 5 6 7 #include "XModem.h" 8 9 #include "SerialApp.h" 10 11 #include <Catalog.h> 12 #include <String.h> 13 14 #include <stdio.h> 15 #include <string.h> 16 17 18 #define B_TRANSLATION_CONTEXT "XModemStatus" 19 20 21 // ASCII control characters used in XMODEM protocol 22 static const char kSOH = 1; 23 static const char kEOT = 4; 24 static const char kACK = 6; 25 static const char kNAK = 21; 26 static const char kCAN = 24; 27 static const char kSUB = 26; 28 29 static const int kBlockSize = 128; 30 31 32 XModemSender::XModemSender(BDataIO* source, BSerialPort* sink, BHandler* listener) 33 : fSource(source), 34 fSink(sink), 35 fListener(listener), 36 fBlockNumber(0), 37 fEotSent(false), 38 fUseCRC(false) 39 { 40 fStatus = B_TRANSLATE("Waiting for receiver" B_UTF8_ELLIPSIS); 41 42 BPositionIO* pos = dynamic_cast<BPositionIO*>(source); 43 if (pos) 44 pos->GetSize(&fSourceSize); 45 else 46 fSourceSize = 0; 47 48 NextBlock(); 49 } 50 51 52 XModemSender::~XModemSender() 53 { 54 delete fSource; 55 } 56 57 58 bool 59 XModemSender::BytesReceived(const uint8_t* data, size_t length) 60 { 61 size_t i; 62 63 for (i = 0; i < length; i++) 64 { 65 switch (data[i]) 66 { 67 case 'C': 68 // A 'C' to request the first block is a request to use a CRC 69 // in place of an 8-bit checksum. 70 // In any other place, it is ignored. 71 if (fBlockNumber <= 1) { 72 fStatus = B_TRANSLATE("CRC requested"); 73 fUseCRC = true; 74 SendBlock(); 75 } else 76 break; 77 case kNAK: 78 if (fEotSent) { 79 fSink->Write(&kEOT, 1); 80 } else { 81 fStatus = B_TRANSLATE("Checksum error, re-send block"); 82 SendBlock(); 83 } 84 break; 85 86 case kACK: 87 if (fEotSent) { 88 return true; 89 } 90 91 if (NextBlock() == B_OK) { 92 fStatus = B_TRANSLATE("Sending" B_UTF8_ELLIPSIS); 93 SendBlock(); 94 } else { 95 fStatus = B_TRANSLATE("Everything sent, " 96 "waiting for acknowledge"); 97 fSink->Write(&kEOT, 1); 98 fEotSent = true; 99 } 100 break; 101 102 case kCAN: 103 { 104 BMessage msg(kMsgProgress); 105 msg.AddInt32("pos", 0); 106 msg.AddInt32("size", 0); 107 msg.AddString("info", 108 B_TRANSLATE("Remote cancelled transfer")); 109 fListener.SendMessage(&msg); 110 return true; 111 } 112 113 default: 114 break; 115 } 116 } 117 118 return false; 119 } 120 121 122 void 123 XModemSender::SendBlock() 124 { 125 uint8_t header[3]; 126 uint8_t checksum = 0; 127 int i; 128 129 header[0] = kSOH; 130 header[1] = fBlockNumber; 131 header[2] = 255 - fBlockNumber; 132 133 fSink->Write(header, 3); 134 fSink->Write(fBuffer, kBlockSize); 135 136 if (fUseCRC) { 137 uint16_t crc = CRC(fBuffer, kBlockSize); 138 uint8_t crcBuf[2]; 139 crcBuf[0] = crc >> 8; 140 crcBuf[1] = crc & 0xFF; 141 fSink->Write(crcBuf, 2); 142 } else { 143 // Use a traditional (and fragile) checksum 144 for (i = 0; i < kBlockSize; i++) 145 checksum += fBuffer[i]; 146 147 fSink->Write(&checksum, 1); 148 } 149 } 150 151 152 status_t 153 XModemSender::NextBlock() 154 { 155 memset(fBuffer, kSUB, kBlockSize); 156 157 if (fSource->Read(fBuffer, kBlockSize) > 0) { 158 // Notify for progress bar update 159 BMessage msg(kMsgProgress); 160 msg.AddInt32("pos", fBlockNumber); 161 msg.AddInt32("size", fSourceSize / kBlockSize); 162 msg.AddString("info", fStatus); 163 fListener.SendMessage(&msg); 164 165 // Remember that we moved to next block 166 fBlockNumber++; 167 return B_OK; 168 } 169 return B_ERROR; 170 } 171 172 uint16_t XModemSender::CRC(const uint8_t *buf, size_t len) 173 { 174 uint16_t crc = 0; 175 while( len-- ) { 176 int i; 177 crc ^= ((uint16_t)(*buf++)) << 8; 178 for( i = 0; i < 8; ++i ) { 179 if( crc & 0x8000 ) 180 crc = (crc << 1) ^ 0x1021; 181 else 182 crc = crc << 1; 183 } 184 } 185 return crc; 186 } 187