-
Notifications
You must be signed in to change notification settings - Fork 18
/
fymodem.c
597 lines (545 loc) · 17.2 KB
/
fymodem.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
/**
* Free YModem implementation.
*
* Fredrik Hederstierna 2014
*
* This file is in the public domain.
* You can do whatever you want with it.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
/**
* Some future improvements and ideas
*
* Add rx/tx callbacks if eg storing file to external storage,
* and full buffer cannot be in memory at the same time.
*
* Add support for Ymodem-G standard as described in docs for streaming.
*
* Add suppport for async operation mode if calling system cannot handle
* that calls are synchroneus and blocking.
*
* Add unittest
*
* Add support for ZModem (which may imply total rewrite).
*/
#include <fymodem.h>
/* filesize 999999999999999 should be enough... */
#define YM_FILE_SIZE_LENGTH (16)
/* packet constants */
#define YM_PACKET_SEQNO_INDEX (1)
#define YM_PACKET_SEQNO_COMP_INDEX (2)
#define YM_PACKET_HEADER (3) /* start, block, block-complement */
#define YM_PACKET_TRAILER (2) /* CRC bytes */
#define YM_PACKET_OVERHEAD (YM_PACKET_HEADER + YM_PACKET_TRAILER)
#define YM_PACKET_SIZE (128)
#define YM_PACKET_1K_SIZE (1024)
#define YM_PACKET_RX_TIMEOUT_MS (1000)
#define YM_PACKET_ERROR_MAX_NBR (5)
/* contants defined by YModem protocol */
#define YM_SOH (0x01) /* start of 128-byte data packet */
#define YM_STX (0x02) /* start of 1024-byte data packet */
#define YM_EOT (0x04) /* End Of Transmission */
#define YM_ACK (0x06) /* ACKnowledge, receive OK */
#define YM_NAK (0x15) /* Negative ACKnowledge, receiver ERROR, retry */
#define YM_CAN (0x18) /* two CAN in succession will abort transfer */
#define YM_CRC (0x43) /* 'C' == 0x43, request 16-bit CRC, use in place of first NAK for CRC mode */
#define YM_ABT1 (0x41) /* 'A' == 0x41, assume try abort by user typing */
#define YM_ABT2 (0x61) /* 'a' == 0x61, assume try abort by user typing */
/* ------------------------------------------------ */
/* user callbacks, implement these for your target */
/* user function __ym_getchar() should return -1 in case of timeout */
#define __ym_getchar(timeout_ms) printf("%d", (int)timeout_ms)
#define __ym_putchar(c) do { (void)(c); /* printf("%d",(int)c); */ } while(0)
#define __ym_sleep_ms(delay_ms) do { (void)(delay_ms); } while(0)
#define __ym_flush() do { ; } while(0)
/* example functions for POSIX/Unix */
#define __ym_getchar_posix(timeout_ms) read(timeout_ms/1000)
#define __ym_putchar_posix(c) (void)write(c)
#define __ym_sleep_ms_posix(delay_ms) sleep(delay_ms/1000)
#define __ym_flush_posix() (void)flush()
/* error logging function */
#define YM_ERR(fmt, ...) do { printf(fmt, __VA_ARGS__); } while(0)
/* ------------------------------------------------ */
/* calculate crc16-ccitt very fast
Idea from: http://www.ccsinfo.com/forum/viewtopic.php?t=24977
*/
static uint16_t ym_crc16(const uint8_t *buf, uint16_t len)
{
uint16_t x;
uint16_t crc = 0;
while (len--) {
x = (crc >> 8) ^ *buf++;
x ^= x >> 4;
crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x;
}
return crc;
}
/* ------------------------------------------------- */
/* write 32bit value as asc to buffer, return chars written. */
static uint32_t ym_writeU32(uint32_t val, uint8_t *buf)
{
uint32_t ci = 0;
if (val == 0) {
/* If already zero then just return zero */
buf[ci++] = '0';
}
else {
/* Maximum number of decimal digits in uint32_t is 10, add one for z-term */
uint8_t s[11];
int32_t i = sizeof(s) - 1;
/* z-terminate string */
s[i] = 0;
while ((val > 0) && (i > 0)) {
/* write decimal char */
s[--i] = (val % 10) + '0';
val /= 10;
}
uint8_t *sp = &s[i];
/* copy results to out buffer */
while (*sp) {
buf[ci++] = *sp++;
}
}
/* z-term */
buf[ci] = 0;
/* return chars written */
return ci;
}
/* ------------------------------------------------- */
/* read 32bit asc value from buffer */
static void ym_readU32(const uint8_t* buf, uint32_t *val)
{
const uint8_t *s = buf;
uint32_t res = 0;
uint8_t c;
/* trim and strip leading spaces if any */
do {
c = *s++;
} while (c == ' ');
while ((c >= '0') && (c <= '9')) {
c -= '0';
res *= 10;
res += c;
/* next char */
c = *s++;
}
*val = res;
}
/* -------------------------------------------------- */
/**
* Receive a packet from sender
* @param rxlen
* 0: end of transmission
* -1: abort by sender
* >0: packet length
* @return 0: normally return, success
* -1: timeout or packet error
* 1: abort by user / corrupt packet
*/
static int32_t ym_rx_packet(uint8_t *rxdata,
int32_t *rxlen,
uint32_t packets_rxed,
uint32_t timeout_ms)
{
*rxlen = 0;
int32_t c = __ym_getchar(timeout_ms);
if (c < 0) {
/* end of stream */
return -1;
}
uint32_t rx_packet_size;
switch (c) {
case YM_SOH:
rx_packet_size = YM_PACKET_SIZE;
break;
case YM_STX:
rx_packet_size = YM_PACKET_1K_SIZE;
break;
case YM_EOT:
/* ok */
return 0;
case YM_CAN:
c = __ym_getchar(timeout_ms);
if (c == YM_CAN) {
*rxlen = -1;
/* ok */
return 0;
}
/* fall-through */
case YM_CRC:
if (packets_rxed == 0) {
/* could be start condition, first byte */
return 1;
}
/* fall-through */
case YM_ABT1:
case YM_ABT2:
/* User try abort, 'A' or 'a' received */
return 1;
default:
/* This case could be the result of corruption on the first octet
of the packet, but it's more likely that it's the user banging
on the terminal trying to abort a transfer. Technically, the
former case deserves a NAK, but for now we'll just treat this
as an abort case. */
*rxlen = -1;
return 0;
}
/* store data RXed */
*rxdata = (uint8_t)c;
uint32_t i;
for (i = 1; i < (rx_packet_size + YM_PACKET_OVERHEAD); i++) {
c = __ym_getchar(timeout_ms);
if (c < 0) {
/* end of stream */
return -1;
}
/* store data RXed */
rxdata[i] = (uint8_t)c;
}
/* just a sanity check on the sequence number/complement value.
caller should check for in-order arrival. */
uint8_t seq_nbr = (rxdata[YM_PACKET_SEQNO_INDEX] & 0xff);
uint8_t seq_cmp = ((rxdata[YM_PACKET_SEQNO_COMP_INDEX] ^ 0xff) & 0xff);
if (seq_nbr != seq_cmp) {
/* seq nbr error */
return 1;
}
/* check CRC16 match */
uint16_t crc16_val = ym_crc16((const unsigned char *)(rxdata + YM_PACKET_HEADER),
rx_packet_size + YM_PACKET_TRAILER);
if (crc16_val) {
/* CRC error, non zero */
return 1;
}
*rxlen = rx_packet_size;
/* success */
return 0;
}
/* ------------------------------------------------- */
/**
* Receive a file using the ymodem protocol
* @param rxdata Pointer to the first byte
* @param rxlen Max in length
* @return The length of the file received, or 0 on error
*/
int32_t fymodem_receive(uint8_t *rxdata,
size_t rxlen,
char filename[FYMODEM_FILE_NAME_MAX_LENGTH])
{
/* alloc 1k on stack, ok? */
uint8_t rx_packet_data[YM_PACKET_1K_SIZE + YM_PACKET_OVERHEAD];
int32_t rx_packet_len;
uint8_t filesize_asc[YM_FILE_SIZE_LENGTH];
uint32_t filesize = 0;
bool first_try = true;
bool session_done = false;
uint32_t nbr_errors = 0;
/* z-term string */
filename[0] = 0;
/* receive files */
do { /* ! session done */
if (first_try) {
/* initiate transfer */
__ym_putchar(YM_CRC);
}
first_try = false;
bool crc_nak = true;
bool file_done = false;
uint32_t packets_rxed = 0;
/* set start position of rxing data */
uint8_t *rxptr = rxdata;
do { /* ! file_done */
/* receive packets */
int32_t res = ym_rx_packet(rx_packet_data,
&rx_packet_len,
packets_rxed,
YM_PACKET_RX_TIMEOUT_MS);
switch (res) {
case 0: {
/* packet received, clear packet error counter */
nbr_errors = 0;
switch (rx_packet_len) {
case -1: {
/* aborted by sender */
__ym_putchar(YM_ACK);
return 0;
}
case 0: {
/* EOT - End Of Transmission */
__ym_putchar(YM_ACK);
/* TODO: Add some sort of sanity check on the number of
packets received and the advertised file length. */
file_done = true;
/* resend CRC to re-initiate transfer */
__ym_putchar(YM_CRC);
break;
}
default: {
/* normal packet, check seq nbr */
uint8_t seq_nbr = rx_packet_data[YM_PACKET_SEQNO_INDEX];
if (seq_nbr != (packets_rxed & 0xff)) {
/* wrong seq number */
__ym_putchar(YM_NAK);
} else {
if (packets_rxed == 0) {
/* The spec suggests that the whole data section should
be zeroed, but some senders might not do this.
If we have a NULL filename and the first few digits of
the file length are zero, then call it empty. */
int32_t i;
for (i = YM_PACKET_HEADER; i < YM_PACKET_HEADER + 4; i++) {
if (rx_packet_data[i] != 0) {
break;
}
}
/* non-zero bytes found in header, filename packet has data */
if (i < YM_PACKET_HEADER + 4) {
/* read file name */
uint8_t *file_ptr = (uint8_t*)(rx_packet_data + YM_PACKET_HEADER);
i = 0;
while ((*file_ptr != '\0') &&
(i < FYMODEM_FILE_NAME_MAX_LENGTH)) {
filename[i++] = *file_ptr++;
}
filename[i++] = '\0';
/* skip null term char */
file_ptr++;
/* read file size */
i = 0;
while ((*file_ptr != '\0') &&
(*file_ptr != ' ') &&
(i < YM_FILE_SIZE_LENGTH)) {
filesize_asc[i++] = *file_ptr++;
}
filesize_asc[i++] = '\0';
/* convert file size */
ym_readU32(filesize_asc, &filesize);
/* check file size */
if (filesize > rxlen) {
YM_ERR("YM: RX buffer too small (0x%08x vs 0x%08x)\n", (unsigned int)rxlen, (unsigned int)filesize);
goto rx_err_handler;
}
__ym_putchar(YM_ACK);
__ym_putchar(crc_nak ? YM_CRC : YM_NAK);
crc_nak = false;
}
else {
/* filename packet is empty, end session */
__ym_putchar(YM_ACK);
file_done = true;
session_done = true;
break;
}
}
else {
/* This shouldn't happen, but we check anyway in case the
sender sent wrong info in its filename packet */
if (((rxptr + rx_packet_len) - rxdata) > (int32_t)rxlen) {
YM_ERR("YM: RX buffer overflow (exceeded 0x%08x)\n", (unsigned int)rxlen);
goto rx_err_handler;
}
int32_t i;
for (i = 0; i < rx_packet_len; i++) {
rxptr[i] = rx_packet_data[YM_PACKET_HEADER + i];
}
rxptr += rx_packet_len;
__ym_putchar(YM_ACK);
}
packets_rxed++;
} /* sequence number check ok */
} /* default */
} /* inner switch */
break;
} /* case 0 */
default: {
/* ym_rx_packet() returned error */
if (packets_rxed > 0) {
nbr_errors++;
if (nbr_errors >= YM_PACKET_ERROR_MAX_NBR) {
YM_ERR("YM: RX errors too many: %d - ABORT.\n", (unsigned int)nbr_errors);
goto rx_err_handler;
}
}
__ym_putchar(YM_CRC);
break;
} /* default */
} /* switch */
/* check end of receive packets */
} while (! file_done);
/* check end of receive files */
} while (! session_done);
/* return bytes received */
return filesize;
rx_err_handler:
__ym_putchar(YM_CAN);
__ym_putchar(YM_CAN);
__ym_sleep_ms(1000);
return 0;
}
/* ------------------------------------ */
static void ym_send_packet(uint8_t *txdata,
int32_t block_nbr)
{
int32_t tx_packet_size;
/* We use a short packet for block 0, all others are 1K */
if (block_nbr == 0) {
tx_packet_size = YM_PACKET_SIZE;
}
else {
tx_packet_size = YM_PACKET_1K_SIZE;
}
uint16_t crc16_val = ym_crc16(txdata, tx_packet_size);
/* For 128 byte packets use SOH, for 1K use STX */
__ym_putchar( (block_nbr == 0) ? YM_SOH : YM_STX );
/* write seq numbers */
__ym_putchar(block_nbr & 0xFF);
__ym_putchar(~block_nbr & 0xFF);
/* write txdata */
int32_t i;
for (i = 0; i < tx_packet_size; i++) {
__ym_putchar(txdata[i]);
}
/* write crc16 */
__ym_putchar((crc16_val >> 8) & 0xFF);
__ym_putchar(crc16_val & 0xFF);
}
/* ----------------------------------------------- */
/* Send block 0 (the filename block), filename might be truncated to fit. */
static void ym_send_packet0(const char* filename,
int32_t filesize)
{
int32_t pos = 0;
/* put 256byte on stack, ok? reuse other stack mem? */
uint8_t block[YM_PACKET_SIZE];
if (filename) {
/* write filename */
while (*filename && (pos < YM_PACKET_SIZE - YM_FILE_SIZE_LENGTH - 2)) {
block[pos++] = *filename++;
}
/* z-term filename */
block[pos++] = 0;
/* write size, TODO: check if buffer can overwritten here. */
pos += ym_writeU32(filesize, &block[pos]);
}
/* z-terminate string, pad with zeros */
while (pos < YM_PACKET_SIZE) {
block[pos++] = 0;
}
/* send header block */
ym_send_packet(block, 0);
}
/* ------------------------------------------------- */
static void ym_send_data_packets(uint8_t* txdata,
uint32_t txlen,
uint32_t timeout_ms)
{
int32_t block_nbr = 1;
while (txlen > 0) {
/* check if send full 1k packet */
uint32_t send_size;
if (txlen > YM_PACKET_1K_SIZE) {
send_size = YM_PACKET_1K_SIZE;
} else {
send_size = txlen;
}
/* send packet */
ym_send_packet(txdata, block_nbr);
int32_t c = __ym_getchar(timeout_ms);
switch (c) {
case YM_ACK: {
txdata += send_size;
txlen -= send_size;
block_nbr++;
break;
}
case -1:
case YM_CAN: {
return;
}
default:
break;
}
}
int32_t ch;
do {
__ym_putchar(YM_EOT);
ch = __ym_getchar(timeout_ms);
} while ((ch != YM_ACK) && (ch != -1));
/* send last data packet */
if (ch == YM_ACK) {
ch = __ym_getchar(timeout_ms);
if (ch == YM_CRC) {
do {
ym_send_packet0(0, 0);
ch = __ym_getchar(timeout_ms);
} while ((ch != YM_ACK) && (ch != -1));
}
}
}
/* ------------------------------------------------------- */
int32_t fymodem_send(uint8_t* txdata, size_t txsize, const char* filename)
{
/* flush the RX FIFO, after a cool off delay */
__ym_sleep_ms(1000);
__ym_flush();
(void)__ym_getchar(1000);
/* not in the specs, send CRC here just for balance */
int32_t ch;
do {
__ym_putchar(YM_CRC);
ch = __ym_getchar(1000);
} while (ch < 0);
/* we do require transfer with CRC */
if (ch != YM_CRC) {
goto tx_err_handler;
}
bool crc_nak = true;
bool file_done = false;
do {
ym_send_packet0(filename, txsize);
/* When the receiving program receives this block and successfully
opened the output file, it shall acknowledge this block with an ACK
character and then proceed with a normal XMODEM file transfer
beginning with a "C" or NAK tranmsitted by the receiver. */
ch = __ym_getchar(YM_PACKET_RX_TIMEOUT_MS);
if (ch == YM_ACK) {
ch = __ym_getchar(YM_PACKET_RX_TIMEOUT_MS);
if (ch == YM_CRC) {
ym_send_data_packets(txdata, txsize, YM_PACKET_RX_TIMEOUT_MS);
/* success */
file_done = true;
}
}
else if ((ch == YM_CRC) && (crc_nak)) {
crc_nak = false;
continue;
}
else if ((ch != YM_NAK) || (crc_nak)) {
goto tx_err_handler;
}
} while (! file_done);
return txsize;
tx_err_handler:
__ym_putchar(YM_CAN);
__ym_putchar(YM_CAN);
__ym_sleep_ms(1000);
return 0;
}
/* ------------------------------------ */
/* Simple example function how to use */
int main(void)
{
char buf[100];
char fname[FYMODEM_FILE_NAME_MAX_LENGTH];
size_t length = sizeof(buf);
uint32_t i = 0;
while (i < sizeof(fname)) { fname[i++] = '\0'; }
sprintf(fname, "%s", "test.txt");
int32_t r = fymodem_receive((uint8_t *)buf, length, fname);
int32_t s = fymodem_send((uint8_t *)buf, length, "test.txt");
return (int)r+(int)s;
}