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