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