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