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
22static const char kSOH =  1;
23static const char kEOT =  4;
24static const char kACK =  6;
25static const char kNAK = 21;
26static const char kCAN = 24;
27static const char kSUB = 26;
28
29static const int kBlockSize = 128;
30
31
32XModemSender::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
52XModemSender::~XModemSender()
53{
54	delete fSource;
55}
56
57
58bool
59XModemSender::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
122void
123XModemSender::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
152status_t
153XModemSender::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
172uint16_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