1 /*
2 * Copyright (c) 2024 A Labs GmbH
3 * Copyright (c) 2024 tado GmbH
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 /*
9 * Implementation of the fragment encoding algorithm described in the LoRaWAN TS004-1.0.0.
10 * https://lora-alliance.org/wp-content/uploads/2020/11/fragmented_data_block_transport_v1.0.0.pdf
11 *
12 * Note: This algorithm is not compatible with TS004-2.0.0, which has some subtle differences
13 * in the parity matrix generation.
14 *
15 * Variable naming according to LoRaWAN specification:
16 *
17 * M: Number of uncoded fragments (original data)
18 * N: Number of coded fragments (including the original data at the beginning)
19 * CR: Coding ratio M/N
20 */
21
22 #include "frag_encoder.h"
23
24 #include <zephyr/sys/util.h>
25 #include <zephyr/logging/log.h>
26
27 LOG_MODULE_REGISTER(lorawan_frag_enc, CONFIG_LORAWAN_SERVICES_LOG_LEVEL);
28
29 /**
30 * Generate a 23bit Pseudorandom Binary Sequence (PRBS)
31 *
32 * @param seed Seed input value
33 *
34 * @returns Pseudorandom output value
35 */
prbs23(int32_t seed)36 static int32_t prbs23(int32_t seed)
37 {
38 int32_t b0 = seed & 1;
39 int32_t b1 = (seed & 32) / 32;
40
41 return (seed / 2) + ((b0 ^ b1) << 22);
42 }
43
44 /**
45 * Generate vector for coded fragment n of the MxN parity matrix
46 *
47 * @param m Total number of uncoded fragments (M)
48 * @param n Coded fragment number (starting at 1 and not 0)
49 * @param vec Output vector (buffer size must be greater than m)
50 */
lorawan_fec_parity_matrix_vector(int m,int n,uint8_t * vec)51 void lorawan_fec_parity_matrix_vector(int m, int n, uint8_t *vec)
52 {
53 int mm, x, r;
54
55 memset(vec, 0, m);
56
57 /*
58 * Powers of 2 must be treated differently to make sure matrix content is close
59 * to random. Powers of 2 tend to generate patterns.
60 */
61 if (is_power_of_two(m)) {
62 mm = m + 1;
63 } else {
64 mm = m;
65 }
66
67 x = 1 + (1001 * n);
68
69 for (int nb_coeff = 0; nb_coeff < (m / 2); nb_coeff++) {
70 r = (1 << 16);
71 while (r >= m) {
72 x = prbs23(x);
73 r = x % mm;
74 }
75 vec[r] = 1;
76 }
77 }
78
lorawan_frag_encoder(const uint8_t * uncoded,size_t uncoded_len,uint8_t * coded,size_t coded_size,size_t frag_size,unsigned int redundant_frags)79 int lorawan_frag_encoder(const uint8_t *uncoded, size_t uncoded_len, uint8_t *coded,
80 size_t coded_size, size_t frag_size, unsigned int redundant_frags)
81 {
82 int uncoded_frags = DIV_ROUND_UP(uncoded_len, frag_size);
83 int coded_frags = uncoded_frags + redundant_frags;
84 uint8_t parity_vec[frag_size];
85
86 memset(parity_vec, 0, sizeof(parity_vec));
87
88 if (coded_size < coded_frags * frag_size) {
89 LOG_ERR("output buffer not large enough");
90 return -EINVAL;
91 }
92
93 /* copy uncoded frags to the beginning of coded fragments and pad with zeros */
94 memcpy(coded, uncoded, uncoded_len);
95 memset(coded + uncoded_len, 0, uncoded_frags * frag_size - uncoded_len);
96
97 /* generate remaining coded (redundant) frags */
98 for (int i = 1; i <= redundant_frags; i++) {
99 lorawan_fec_parity_matrix_vector(uncoded_frags, i, parity_vec);
100
101 uint8_t *out = coded + (uncoded_frags + i - 1) * frag_size;
102
103 for (int j = 0; j < uncoded_frags; j++) {
104 if (parity_vec[j] == 1) {
105 for (int m = 0; m < frag_size; m++) {
106 out[m] ^= coded[j * frag_size + m];
107 }
108 }
109 }
110 }
111
112 return 0;
113 }
114