xref: /haiku/src/apps/serialconnect/XModem.cpp (revision 21258e2674226d6aa732321b6f8494841895af5f)
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