TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
AX25FEC.h
Go to the documentation of this file.
1#pragma once
2
3#include <ctype.h>
4#include <stdint.h>
5#include <string.h>
6
7#include "AX25.h"
8#include "ReedSolomon.h"
9
10namespace tinymodem {
11
20class FX25 {
21 public:
23 struct Tag {
24 uint64_t value;
26 int data;
28 int check;
29 };
30
31 static constexpr int BLOCK_SIZE = 255;
32 static constexpr int MAX_DATA = 239;
33 static constexpr int FIRST_TAG = 1;
34 static constexpr int LAST_TAG = 11;
36 static constexpr int TAG_TOLERANCE = 8;
37
39 static const Tag& tag(int index) {
40 static const Tag tags[12] = {
41 {0x566ED2717946107Eull, 0, 0}, // reserved
42 {0xB74DB7DF8A532F3Eull, 239, 16}, // RS(255,239)
43 {0x26FF60A600CC8FDEull, 128, 16}, // RS(144,128)
44 {0xC7DC0508F3D9B09Eull, 64, 16}, // RS(80,64)
45 {0x8F056EB4369660EEull, 32, 16}, // RS(48,32)
46 {0x6E260B1AC5835FAEull, 223, 32}, // RS(255,223)
47 {0xFF94DC634F1CFF4Eull, 128, 32}, // RS(160,128)
48 {0x1EB7B9CDBC09C00Eull, 64, 32}, // RS(96,64)
49 {0xDBF869BD2DBB1776ull, 32, 32}, // RS(64,32)
50 {0x3ADB0C13DEAE2836ull, 191, 64}, // RS(255,191)
51 {0xAB69DB6A543188D6ull, 128, 64}, // RS(192,128)
52 {0x4A4ABEC4A724B796ull, 64, 64}, // RS(128,64)
53 };
54 return tags[index];
55 }
56
58 static int findTag(uint64_t bits) {
59 for (int j = FIRST_TAG; j <= LAST_TAG; j++)
60 if (popcount(bits ^ tag(j).value) <= TAG_TOLERANCE) return j;
61 return 0;
62 }
63
66 static int pickTag(int check_bytes, int data_len) {
67 int result = 0;
68 for (int j = FIRST_TAG; j <= LAST_TAG; j++) {
69 const Tag& t = tag(j);
70 if (t.check == check_bytes && t.data >= data_len &&
71 (result == 0 || t.data < tag(result).data))
72 result = j;
73 }
74 return result;
75 }
76
80 static int encode(const uint8_t* frame, int len, int check_bytes,
81 uint8_t* block, int& block_len) {
82 uint8_t data[BLOCK_SIZE];
83 int data_len = stuff(frame, len, data, MAX_DATA);
84 if (data_len <= 0) return 0;
85 int t = pickTag(check_bytes, data_len);
86 if (t == 0) return 0;
87 const Tag& tg = tag(t);
88 // the data part is padded with flags, the shortened part is 0
89 memset(data + tg.data, 0, BLOCK_SIZE - tg.data);
90 uint8_t codeword[BLOCK_SIZE];
91 RSCodec::encode(data, BLOCK_SIZE - tg.check, tg.check, codeword, 1);
92 memcpy(block, codeword, tg.data);
93 memcpy(block + tg.data, codeword + BLOCK_SIZE - tg.check, tg.check);
94 block_len = tg.data + tg.check;
95 return t;
96 }
97
101 static int correct(int t, uint8_t* received) {
102 const Tag& tg = tag(t);
103 uint8_t codeword[BLOCK_SIZE] = {0};
104 memcpy(codeword, received, tg.data);
105 memcpy(codeword + BLOCK_SIZE - tg.check, received + tg.data, tg.check);
106 int result = RSCodec::correct(codeword, BLOCK_SIZE - tg.check, tg.check, 1);
107 if (result < 0) return -1;
108 // the shortened part must still be 0
109 for (int j = tg.data; j < BLOCK_SIZE - tg.check; j++)
110 if (codeword[j] != 0) return -1;
111 memcpy(received, codeword, tg.data);
112 return result;
113 }
114
117 static int extract(const uint8_t* data, int len, uint8_t* frame,
118 int frame_size) {
119 uint8_t history = 0;
120 int ones = 0, bits = 0, n = 0;
121 bool in_frame = false;
122 uint8_t current = 0;
123 for (int j = 0; j < len; j++) {
124 for (int b = 0; b < 8; b++) {
125 int bit = (data[j] >> b) & 1;
126 history = (history >> 1) | (bit << 7);
127 if (history == 0x7E) {
128 if (in_frame && n >= AX25Frame::MIN_FRAME + 2) {
129 uint16_t fcs = frame[n - 2] | (frame[n - 1] << 8);
130 return AX25Frame::fcs(frame, n - 2) == fcs ? n - 2 : 0;
131 }
132 in_frame = true;
133 n = bits = ones = 0;
134 current = 0;
135 continue;
136 }
137 if (!in_frame) continue;
138 if (bit) {
139 if (++ones > 6) return 0;
140 } else {
141 if (ones == 5) { // stuffed bit
142 ones = 0;
143 continue;
144 }
145 ones = 0;
146 }
147 current |= bit << bits;
148 if (++bits == 8) {
149 if (n >= frame_size) return 0;
150 frame[n++] = current;
151 current = 0;
152 bits = 0;
153 }
154 }
155 }
156 return 0;
157 }
158
159 protected:
160 static int popcount(uint64_t v) {
161 int n = 0;
162 for (; v; v &= v - 1) n++;
163 return n;
164 }
165
168 static int stuff(const uint8_t* frame, int len, uint8_t* out, int size) {
169 memset(out, 0, size);
170 int nbits = 0;
171 int max_bits = size * 8;
172 auto put = [&](int bit) {
173 if (nbits >= max_bits) return false;
174 if (bit) out[nbits >> 3] |= 1 << (nbits & 7);
175 nbits++;
176 return true;
177 };
178 for (int b = 0; b < 8; b++) put((0x7E >> b) & 1);
179 uint16_t fcs = AX25Frame::fcs(frame, len);
180 int ones = 0;
181 for (int j = 0; j < len + 2; j++) {
182 uint8_t value = j < len ? frame[j] : (j == len ? fcs & 0xFF : fcs >> 8);
183 for (int b = 0; b < 8; b++) {
184 int bit = (value >> b) & 1;
185 if (!put(bit)) return -1;
186 if (bit) {
187 if (++ones == 5) {
188 if (!put(0)) return -1;
189 ones = 0;
190 }
191 } else {
192 ones = 0;
193 }
194 }
195 }
196 for (int b = 0; b < 8; b++)
197 if (!put((0x7E >> b) & 1)) return -1;
198 int used = (nbits + 7) / 8;
199 // fill with the (rotated) flag pattern
200 for (int b = 0; nbits < max_bits; b++) put((0x7E >> (b & 7)) & 1);
201 return used;
202 }
203};
204
214class IL2P {
215 public:
216 static constexpr uint8_t PREAMBLE = 0x55;
217 static constexpr uint32_t SYNC_WORD = 0xF15E48;
218 static constexpr int HEADER_SIZE = 13;
219 static constexpr int HEADER_PARITY = 2;
220 static constexpr int MAX_PAYLOAD = 1023;
221 static constexpr int MAX_ENCODED =
223
226 static int encode(const uint8_t* frame, int len, bool max_fec,
227 uint8_t* out) {
228 uint8_t hdr[HEADER_SIZE];
229 int info_pos = type1Header(frame, len, max_fec, hdr);
230 const uint8_t* payload = frame + info_pos;
231 int payload_len = len - info_pos;
232 if (info_pos < 0) {
233 // type 0: the complete frame is the payload
234 if (len < 14 || len > MAX_PAYLOAD) return 0;
235 memset(hdr, 0, sizeof(hdr));
236 setField(hdr, 7, 0, 1, max_fec);
237 setField(hdr, 7, 11, 10, len);
238 payload = frame;
239 payload_len = len;
240 }
241 uint8_t scrambled[HEADER_SIZE];
242 scramble(hdr, scrambled, HEADER_SIZE);
243 RSCodec::encode(scrambled, HEADER_SIZE, HEADER_PARITY, out);
244 int n = HEADER_SIZE + HEADER_PARITY;
245 int k = encodePayload(payload, payload_len, max_fec, out + n);
246 if (k < 0) return 0;
247 return n + k;
248 }
249
252 static int decodeHeader(const uint8_t* received, uint8_t* hdr,
253 int* corrected = nullptr) {
254 uint8_t block[HEADER_SIZE + HEADER_PARITY];
255 memcpy(block, received, sizeof(block));
257 if (e < 0) return -1;
258 if (corrected) *corrected = e;
259 descramble(block, hdr, HEADER_SIZE);
260 Blocks p;
261 if (!blocks(payloadLength(hdr), maxFec(hdr), p)) return -1;
262 return p.encoded;
263 }
264
267 static int decodeFrame(const uint8_t* hdr, const uint8_t* encoded,
268 uint8_t* frame, int frame_size,
269 int* corrected = nullptr) {
270 int payload_len = payloadLength(hdr);
271 bool type0 = getField(hdr, 7, 1, 1) == 0;
272 if (type0 && payload_len < AX25Frame::MIN_FRAME) return 0;
273 if (frame_size < 16) return 0;
274 // type 1: addresses, control and pid come from the header
275 int n = type0 ? 0 : type1Frame(hdr, frame);
276 if (n < 0 || (!type0 && n == 0) || n + payload_len > frame_size) return 0;
277 // the payload is decoded directly into the frame
278 if (payload_len > 0 && !decodePayload(encoded, payload_len, maxFec(hdr),
279 frame + n, corrected))
280 return 0;
281 return n + payload_len;
282 }
283
284 static int payloadLength(const uint8_t* hdr) {
285 return getField(hdr, 7, 11, 10);
286 }
287
288 static bool maxFec(const uint8_t* hdr) { return getField(hdr, 7, 0, 1); }
289
291 static void scramble(const uint8_t* in, uint8_t* out, int len) {
292 int state = 0x00f;
293 memset(out, 0, len);
294 bool skipping = true;
295 int ob = 0, om = 0x80;
296 auto put = [&](int s) {
297 if (s) out[ob] |= om;
298 om >>= 1;
299 if (om == 0) {
300 om = 0x80;
301 ob++;
302 }
303 };
304 for (int ib = 0; ib < len; ib++) {
305 for (int im = 0x80; im != 0; im >>= 1) {
306 int s = scrambleBit((in[ib] & im) != 0, state);
307 if (ib == 0 && im == 0x04) skipping = false;
308 if (!skipping) put(s);
309 }
310 }
311 for (int n = 0; n < 5; n++) put(scrambleBit(0, state));
312 }
313
314 static void descramble(const uint8_t* in, uint8_t* out, int len) {
315 int state = 0x1f0;
316 memset(out, 0, len);
317 for (int b = 0; b < len; b++) {
318 for (int m = 0x80; m != 0; m >>= 1) {
319 int bit = (in[b] & m) != 0;
320 int d = (bit ^ state) & 1;
321 state = ((state >> 1) | (bit << 8)) ^ (bit << 3);
322 if (d) out[b] |= m;
323 }
324 }
325 }
326
327 protected:
329 struct Blocks {
330 int count = 0, small_size = 0, large_count = 0, parity = 0, encoded = 0;
331 };
332
333 static bool blocks(int len, bool max_fec, Blocks& p) {
334 p = Blocks();
335 if (len < 0 || len > MAX_PAYLOAD) return false;
336 if (len == 0) return true;
337 p.count = max_fec ? (len + 238) / 239 : (len + 246) / 247;
338 p.small_size = len / p.count;
339 p.large_count = len - p.count * p.small_size;
340 if (max_fec) {
341 p.parity = 16;
342 } else if (p.small_size <= 61) {
343 p.parity = 2;
344 } else if (p.small_size <= 123) {
345 p.parity = 4;
346 } else if (p.small_size <= 185) {
347 p.parity = 6;
348 } else {
349 p.parity = 8;
350 }
351 p.encoded = len + p.count * p.parity;
352 return true;
353 }
354
355 static int encodePayload(const uint8_t* payload, int len, bool max_fec,
356 uint8_t* out) {
357 Blocks p;
358 if (!blocks(len, max_fec, p)) return -1;
359 int n = 0;
360 for (int b = 0; b < p.count; b++) {
361 int size = p.small_size + (b < p.large_count ? 1 : 0);
362 uint8_t scrambled[255];
363 scramble(payload, scrambled, size);
364 RSCodec::encode(scrambled, size, p.parity, out + n);
365 payload += size;
366 n += size + p.parity;
367 }
368 return n;
369 }
370
371 static bool decodePayload(const uint8_t* in, int len, bool max_fec,
372 uint8_t* payload, int* corrected) {
373 Blocks p;
374 if (!blocks(len, max_fec, p)) return false;
375 for (int b = 0; b < p.count; b++) {
376 int size = p.small_size + (b < p.large_count ? 1 : 0);
377 uint8_t block[255];
378 memcpy(block, in, size + p.parity);
379 int e = RSCodec::correct(block, size, p.parity);
380 if (e < 0) return false;
381 if (corrected) *corrected += e;
382 descramble(block, payload, size);
383 in += size + p.parity;
384 payload += size;
385 }
386 return true;
387 }
388
389 static int scrambleBit(int in, int& state) {
390 int out = ((state >> 4) ^ state) & 1;
391 state = ((((in ^ state) & 1) << 9) | (state ^ ((state & 1) << 4))) >> 1;
392 return out;
393 }
394
395 static void setField(uint8_t* hdr, int bit, int lsb_index, int width,
396 int value) {
397 while (width > 0 && value != 0) {
398 if (value & 1) hdr[lsb_index] |= 1 << bit;
399 value >>= 1;
400 lsb_index--;
401 width--;
402 }
403 }
404
405 static int getField(const uint8_t* hdr, int bit, int lsb_index, int width) {
406 int result = 0;
407 for (int idx = lsb_index - width + 1; idx <= lsb_index; idx++)
408 result = (result << 1) | ((hdr[idx] >> bit) & 1);
409 return result;
410 }
411
412 static int encodePid(uint8_t pid) {
413 if ((pid & 0x30) == 0x20 || (pid & 0x30) == 0x10) return 0x2;
414 switch (pid) {
415 case 0x01: return 0x3;
416 case 0x06: return 0x4;
417 case 0x07: return 0x5;
418 case 0x08: return 0x6;
419 case 0xcc: return 0xb;
420 case 0xcd: return 0xc;
421 case 0xce: return 0xd;
422 case 0xcf: return 0xe;
423 case 0xf0: return 0xf;
424 default: return -1;
425 }
426 }
427
428 static uint8_t decodePid(int pid) {
429 static const uint8_t pids[16] = {0xf0, 0xf0, 0x20, 0x01, 0x06, 0x07,
430 0x08, 0xf0, 0xf0, 0xf0, 0xf0, 0xcc,
431 0xcd, 0xce, 0xcf, 0xf0};
432 return pids[pid & 15];
433 }
434
436 static const uint8_t* uFrames() {
437 static const uint8_t codes[8] = {0x2F, 0x43, 0x0F, 0x63,
438 0x87, 0x03, 0xAF, 0xE3};
439 return codes;
440 }
441
444 static int type1Header(const uint8_t* frame, int len, bool max_fec,
445 uint8_t* hdr) {
446 memset(hdr, 0, HEADER_SIZE);
447 if (len < 15 || !(frame[13] & 0x01) || (frame[6] & 0x01)) return -1;
448 for (int j = 0; j < 12; j++) {
449 int ch = frame[j < 6 ? j : j + 1] >> 1;
450 if (ch < ' ' || ch > '_') return -1;
451 hdr[j] = ch - ' ';
452 }
453 hdr[12] = (((frame[6] >> 1) & 0x0F) << 4) | ((frame[13] >> 1) & 0x0F);
454 int cbit = (frame[6] & 0x80) ? 1 : 0; // command (or 11)
455 uint8_t c = frame[14];
456 int pf = (c >> 4) & 1;
457 int pos = 15;
458 if ((c & 1) == 0) {
459 // I frame
460 if (len < 16) return -1;
461 int pid = encodePid(frame[15]);
462 if (pid < 0) return -1;
463 setField(hdr, 6, 4, 4, pid);
464 setField(hdr, 6, 11, 7, (pf << 6) | (((c >> 5) & 7) << 3) | ((c >> 1) & 7));
465 pos = 16;
466 } else if ((c & 3) == 1) {
467 // S frame
468 if (len != 15) return -1;
469 setField(hdr, 6, 11, 7,
470 (pf << 6) | (((c >> 5) & 7) << 3) | (cbit << 2) | ((c >> 2) & 3));
471 } else {
472 // U frame
473 int type = -1;
474 for (int j = 0; j < 8; j++)
475 if ((c & ~0x10) == uFrames()[j]) type = j;
476 if (type < 0) return -1;
477 if (type == 5) {
478 // UI
479 if (len < 16) return -1;
480 int pid = encodePid(frame[15]);
481 if (pid < 0) return -1;
482 setField(hdr, 6, 0, 1, 1);
483 setField(hdr, 6, 4, 4, pid);
484 pos = 16;
485 } else {
486 setField(hdr, 6, 4, 4, 1);
487 }
488 setField(hdr, 6, 11, 7, (pf << 6) | (type << 3) | (cbit << 2));
489 }
490 if (len - pos > MAX_PAYLOAD) return -1;
491 setField(hdr, 7, 0, 1, max_fec);
492 setField(hdr, 7, 1, 1, 1);
493 setField(hdr, 7, 11, 10, len - pos);
494 return pos;
495 }
496
499 static int type1Frame(const uint8_t* hdr, uint8_t* frame) {
500 // callsigns: upper case letters and digits, trailing spaces
501 for (int a = 0; a < 2; a++) {
502 bool end = false;
503 for (int j = 0; j < 6; j++) {
504 char ch = (hdr[a * 6 + j] & 0x3f) + ' ';
505 if (ch == ' ') {
506 end = true;
507 } else if (end || !(isupper((unsigned char)ch) || isdigit((unsigned char)ch))) {
508 return 0;
509 }
510 frame[a * 7 + j] = ch << 1;
511 }
512 }
513 if (frame[0] == (' ' << 1) || frame[7] == (' ' << 1)) return 0;
514 int pid = getField(hdr, 6, 4, 4);
515 int ui = getField(hdr, 6, 0, 1);
516 int control = getField(hdr, 6, 11, 7);
517 int pf = (control >> 6) & 1;
518 bool command = (control & 0x04) != 0;
519 int n = 14;
520 if (pid == 0) {
521 // S frame
522 frame[n++] = (((control >> 3) & 7) << 5) | (pf << 4) | ((control & 3) << 2) | 1;
523 } else if (pid == 1) {
524 // U frame
525 uint8_t c = uFrames()[(control >> 3) & 7] | (pf << 4);
526 frame[n++] = c;
527 if ((c & ~0x10) == 0x03) frame[n++] = 0xf0;
528 } else if (ui) {
529 frame[n++] = 0x03 | (pf << 4);
530 frame[n++] = decodePid(pid);
531 } else {
532 // I frame: always a command
533 command = true;
534 frame[n++] = (((control >> 3) & 7) << 5) | (pf << 4) | ((control & 7) << 1);
535 frame[n++] = decodePid(pid);
536 }
537 frame[6] = 0x60 | (((hdr[12] >> 4) & 0x0F) << 1) | (command ? 0x80 : 0);
538 frame[13] = 0x60 | ((hdr[12] & 0x0F) << 1) | (command ? 0 : 0x80) | 0x01;
539 return n;
540 }
541};
542
543} // namespace tinymodem
static uint16_t fcs(const uint8_t *data, size_t len)
AX.25 frame check sequence: CRC-16-CCITT with reflected bits.
Definition AX25.h:151
static constexpr int MIN_FRAME
Definition AX25.h:144
FX.25: AX.25 frames with Reed-Solomon forward error correction (as implemented by direwolf)....
Definition AX25FEC.h:20
static int pickTag(int check_bytes, int data_len)
Definition AX25FEC.h:66
static int correct(int t, uint8_t *received)
Definition AX25FEC.h:101
static int extract(const uint8_t *data, int len, uint8_t *frame, int frame_size)
Definition AX25FEC.h:117
static constexpr int FIRST_TAG
Definition AX25FEC.h:33
static const Tag & tag(int index)
The tag 1 - 11.
Definition AX25FEC.h:39
static constexpr int BLOCK_SIZE
Definition AX25FEC.h:31
static constexpr int TAG_TOLERANCE
max number of wrong bits in a received tag
Definition AX25FEC.h:36
static int encode(const uint8_t *frame, int len, int check_bytes, uint8_t *block, int &block_len)
Definition AX25FEC.h:80
static int findTag(uint64_t bits)
Finds the tag which matches the received 64 bits: returns 0 if none.
Definition AX25FEC.h:58
static constexpr int MAX_DATA
Definition AX25FEC.h:32
static int popcount(uint64_t v)
Definition AX25FEC.h:160
static int stuff(const uint8_t *frame, int len, uint8_t *out, int size)
Definition AX25FEC.h:168
static constexpr int LAST_TAG
Definition AX25FEC.h:34
IL2P (Improved Layer 2 Protocol, as implemented by direwolf): an AX.25 frame is sent as a scrambled h...
Definition AX25FEC.h:214
static int payloadLength(const uint8_t *hdr)
Definition AX25FEC.h:284
static constexpr uint8_t PREAMBLE
Definition AX25FEC.h:216
static int decodeHeader(const uint8_t *received, uint8_t *hdr, int *corrected=nullptr)
Definition AX25FEC.h:252
static int type1Header(const uint8_t *frame, int len, bool max_fec, uint8_t *hdr)
Definition AX25FEC.h:444
static int decodeFrame(const uint8_t *hdr, const uint8_t *encoded, uint8_t *frame, int frame_size, int *corrected=nullptr)
Definition AX25FEC.h:267
static bool maxFec(const uint8_t *hdr)
Definition AX25FEC.h:288
static int encodePayload(const uint8_t *payload, int len, bool max_fec, uint8_t *out)
Definition AX25FEC.h:355
static constexpr int HEADER_PARITY
Definition AX25FEC.h:219
static int encode(const uint8_t *frame, int len, bool max_fec, uint8_t *out)
Definition AX25FEC.h:226
static int scrambleBit(int in, int &state)
Definition AX25FEC.h:389
static void setField(uint8_t *hdr, int bit, int lsb_index, int width, int value)
Definition AX25FEC.h:395
static void descramble(const uint8_t *in, uint8_t *out, int len)
Definition AX25FEC.h:314
static int encodePid(uint8_t pid)
Definition AX25FEC.h:412
static uint8_t decodePid(int pid)
Definition AX25FEC.h:428
static bool decodePayload(const uint8_t *in, int len, bool max_fec, uint8_t *payload, int *corrected)
Definition AX25FEC.h:371
static constexpr int MAX_PAYLOAD
Definition AX25FEC.h:220
static int type1Frame(const uint8_t *hdr, uint8_t *frame)
Definition AX25FEC.h:499
static const uint8_t * uFrames()
U frame control fields (without poll/final) in the order of IL2P.
Definition AX25FEC.h:436
static int getField(const uint8_t *hdr, int bit, int lsb_index, int width)
Definition AX25FEC.h:405
static constexpr uint32_t SYNC_WORD
Definition AX25FEC.h:217
static void scramble(const uint8_t *in, uint8_t *out, int len)
Scrambles a block (the scrambler output is delayed by 5 bits).
Definition AX25FEC.h:291
static constexpr int HEADER_SIZE
Definition AX25FEC.h:218
static constexpr int MAX_ENCODED
Definition AX25FEC.h:221
static bool blocks(int len, bool max_fec, Blocks &p)
Definition AX25FEC.h:333
static void encode(const uint8_t *msg, int len, int ecc, uint8_t *out, int fcr=0)
Definition ReedSolomon.h:28
static int correct(uint8_t *data, int len, int ecc, int fcr=0)
Definition ReedSolomon.h:58
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
a code (correlation tag)
Definition AX25FEC.h:23
uint64_t value
Definition AX25FEC.h:24
int check
check bytes
Definition AX25FEC.h:28
int data
transmitted data bytes
Definition AX25FEC.h:26
Partitioning of the payload into Reed-Solomon blocks.
Definition AX25FEC.h:329
int count
Definition AX25FEC.h:330
int parity
Definition AX25FEC.h:330
int encoded
Definition AX25FEC.h:330
int small_size
Definition AX25FEC.h:330
int large_count
Definition AX25FEC.h:330