MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
mrtk_has_rs.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * mrtk_has_rs.c : Galileo HAS RS(255,32,224) erasure decoder
3 *
4 * Implements the High-Parity Vertical Reed-Solomon (HPVRS) outer-layer code of
5 * the Galileo HAS SIS ICD Issue 1.0 (May 2022) section 6:
6 *
7 * - GF(256) arithmetic with primitive polynomial p(a)=a^8+a^4+a^3+a^2+1
8 * (0x11D), via log/antilog tables (ICD section 6.1, Table 41).
9 * - Generator polynomial g(x)=prod_{i=1..223}(x-a^i) (ICD Eq. 8, Table 42).
10 * - Systematic 255x32 generator matrix G=[I32; P], built by polynomial-
11 * remainder encoding of the 32 unit information vectors (ICD section 6.2.2).
12 * - Erasure decoder: from k distinct received pages, build the k x k matrix D
13 * out of the rows pids[i]-1 and the first k columns of G, invert it in
14 * GF(256) by Gauss-Jordan, and recover the message column by column
15 * (ICD section 6.4, Eq. 16/17).
16 *
17 * Indexing (ICD section 6.2/6.3):
18 * - info vector c^T = [c31,...,c0], with c31 = message page 1.
19 * - code vector G^T = [G254,...,G0] = [c31,...,c0, g222,...,g0].
20 * - encoded page PID p <-> row p-1 of G (G listed as [G254,...,G0], so
21 * row p-1 holds code symbol G_{255-p}).
22 * - column j (0-based) <-> message page j+1, i.e. info symbol c_{31-j}.
23 * PIDs 1..32 are systematic: G[p-1][j] for p in 1..32 is the identity I32,
24 * so taking PIDs 1..k of an encoded message reproduces the message pages.
25 *-----------------------------------------------------------------------------*/
26#include "mrtk_has_rs.h"
27
28#include <string.h>
29
30#define GF_PRIM 0x11D /* primitive polynomial a^8+a^4+a^3+a^2+1 */
31#define RS_N 255 /* code vector length (2^8 - 1) */
32#define RS_K 32 /* information vector length */
33#define RS_NPAR (RS_N - RS_K) /* number of parity symbols = 223 */
34#define PAGE_OCT 53 /* octets per page */
35
36/* GF(256) log/antilog tables. gf_exp is doubled (510 entries) so that
37 * gf_exp[gf_log[a] + gf_log[b]] needs no modular reduction. */
38static uint8_t gf_exp[2 * RS_N];
39static uint8_t gf_log[256];
40
41/* systematic generator matrix G = [I32; P], 255 rows x 32 columns. */
42static uint8_t G[RS_N][RS_K];
43
44static int rs_ready = 0;
45
46/* multiply two GF(256) elements via log/antilog tables. */
47static uint8_t gf_mul(uint8_t a, uint8_t b) {
48 if (a == 0 || b == 0) return 0;
49 return gf_exp[gf_log[a] + gf_log[b]];
50}
51
52/* multiplicative inverse in GF(256); inv(0) is undefined and returns 0. */
53static uint8_t gf_inv(uint8_t a) {
54 if (a == 0) return 0;
55 return gf_exp[RS_N - gf_log[a]];
56}
57
58/* build the GF(256) log/antilog tables from the primitive polynomial. */
59static void gf_build_tables(void) {
60 int x = 1;
61 for (int i = 0; i < RS_N; i++) {
62 gf_exp[i] = (uint8_t)x;
63 gf_log[x] = (uint8_t)i;
64 x <<= 1;
65 if (x & 0x100) x ^= GF_PRIM;
66 }
67 /* mirror the first half so additive indices up to 2*254 are valid. */
68 for (int i = RS_N; i < 2 * RS_N; i++) gf_exp[i] = gf_exp[i - RS_N];
69 gf_log[0] = 0; /* unused; inv(0)/mul(0) are guarded above. */
70}
71
72/* build g(x)=prod_{i=1..223}(x-a^i). Coefficients gpoly[0..223] are stored low
73 * order first, with gpoly[223]=1 (the monic x^223 term). Subtraction equals
74 * addition (XOR) in GF(256), so (x - a^i) == (x + a^i). */
75static void gf_build_genpoly(uint8_t gpoly[RS_NPAR + 1]) {
76 memset(gpoly, 0, RS_NPAR + 1);
77 gpoly[0] = 1; /* start with g(x) = 1 */
78 int deg = 0;
79 for (int i = 1; i <= RS_NPAR; i++) {
80 uint8_t root = gf_exp[i]; /* a^i */
81 /* multiply current g(x) by (x + root). */
82 for (int j = deg + 1; j > 0; j--) {
83 gpoly[j] = gf_mul(gpoly[j], root) ^ gpoly[j - 1];
84 }
85 gpoly[0] = gf_mul(gpoly[0], root);
86 deg++;
87 }
88}
89
90/* systematic encoding of a unit information vector to fill one column of G.
91 *
92 * The information symbol that is 1 corresponds to message page (col+1), i.e.
93 * info coefficient c_{31-col} of c(x)=sum_j c_j x^j (ICD Eq. 9). The systematic
94 * codeword is G(x)=c(x)*x^{n-k} - R_{g(x)}[c(x)*x^{n-k}] (ICD Eq. 11), whose
95 * coefficients in transposed order [G254,...,G0] map to G rows 0..254.
96 *
97 * For column `col`: c_{31-col}=1, all other c_j=0, so c(x)*x^{n-k} is the single
98 * term x^{(31-col)+RS_NPAR}. We compute its remainder modulo g(x) to get the
99 * parity, then place the information and parity symbols at their code positions.
100 */
101static void rs_encode_column(int col, const uint8_t gpoly[RS_NPAR + 1]) {
102 /* info exponent in c(x): c_{31-col} -> x^{31-col}. */
103 int info_exp = (RS_K - 1) - col; /* 31 - col */
104 /* dividend = x^{info_exp + RS_NPAR}: a single nonzero coefficient. */
105 int div_deg = info_exp + RS_NPAR;
106
107 /* polynomial long division of x^div_deg by g(x) (degree RS_NPAR, monic).
108 * rem holds the running remainder, indexed by coefficient degree. */
109 uint8_t rem[RS_N];
110 memset(rem, 0, sizeof(rem));
111 rem[div_deg] = 1;
112 for (int d = div_deg; d >= RS_NPAR; d--) {
113 uint8_t lead = rem[d];
114 if (lead == 0) continue;
115 /* subtract lead * g(x) shifted so its top term cancels rem[d].
116 * g is monic (gpoly[RS_NPAR]=1), so quotient coeff = lead. */
117 for (int j = 0; j <= RS_NPAR; j++) {
118 rem[d - RS_NPAR + j] ^= gf_mul(lead, gpoly[j]);
119 }
120 }
121 /* now rem[0..RS_NPAR-1] are the parity coefficients g_0..g_222. */
122
123 /* G(x) = info_term*x^{NPAR}=x^{div_deg} XOR parity (the subtracted rem).
124 * Code symbol G_r for r=0..254:
125 * r in [RS_NPAR..254] : information part, G_r = c_{r-RS_NPAR}.
126 * r in [0..RS_NPAR-1] : parity part, G_r = g_r (= rem[r]).
127 * Row of G (listing [G254,...,G0]) is row (254 - r). */
128 for (int r = 0; r < RS_N; r++) {
129 uint8_t sym;
130 if (r >= RS_NPAR) {
131 sym = (r - RS_NPAR == info_exp) ? 1 : 0; /* c_{r-NPAR} */
132 } else {
133 sym = rem[r]; /* g_r parity */
134 }
135 G[RS_N - 1 - r][col] = sym;
136 }
137}
138
139/* invert a k x k GF(256) matrix in place via Gauss-Jordan elimination.
140 * `a` is overwritten; `inv` receives a^{-1}. Returns 0 on success, -1 if
141 * singular. */
142static int gf_invert(int k, uint8_t a[RS_K][RS_K], uint8_t inv[RS_K][RS_K]) {
143 for (int i = 0; i < k; i++) {
144 for (int j = 0; j < k; j++) inv[i][j] = (i == j) ? 1 : 0;
145 }
146 for (int col = 0; col < k; col++) {
147 /* find a pivot row with a nonzero entry in this column. */
148 int piv = -1;
149 for (int r = col; r < k; r++) {
150 if (a[r][col] != 0) {
151 piv = r;
152 break;
153 }
154 }
155 if (piv < 0) return -1; /* singular */
156 if (piv != col) {
157 for (int j = 0; j < k; j++) {
158 uint8_t t = a[col][j];
159 a[col][j] = a[piv][j];
160 a[piv][j] = t;
161 t = inv[col][j];
162 inv[col][j] = inv[piv][j];
163 inv[piv][j] = t;
164 }
165 }
166 /* scale pivot row so a[col][col] == 1. */
167 uint8_t pinv = gf_inv(a[col][col]);
168 for (int j = 0; j < k; j++) {
169 a[col][j] = gf_mul(a[col][j], pinv);
170 inv[col][j] = gf_mul(inv[col][j], pinv);
171 }
172 /* eliminate this column from every other row. */
173 for (int r = 0; r < k; r++) {
174 if (r == col) continue;
175 uint8_t f = a[r][col];
176 if (f == 0) continue;
177 for (int j = 0; j < k; j++) {
178 a[r][j] ^= gf_mul(f, a[col][j]);
179 inv[r][j] ^= gf_mul(f, inv[col][j]);
180 }
181 }
182 }
183 return 0;
184}
185
186int has_rs_init(void) {
187 if (rs_ready) return 0;
189 uint8_t gpoly[RS_NPAR + 1];
190 gf_build_genpoly(gpoly);
191 for (int col = 0; col < RS_K; col++) rs_encode_column(col, gpoly);
192 rs_ready = 1;
193 return 0;
194}
195
196int has_rs_decode(const uint8_t pages[][PAGE_OCT], const uint8_t* pids, int k, uint8_t* out) {
197 if (!pages || !pids || !out) return -1;
198 if (k < 1 || k > RS_K) return -1;
199
200 has_rs_init();
201
202 /* validate PIDs: range 1..255 and pairwise distinct. */
203 uint8_t seen[256];
204 memset(seen, 0, sizeof(seen));
205 for (int i = 0; i < k; i++) {
206 int p = pids[i];
207 if (p < 1 || p > RS_N) return -1;
208 if (seen[p]) return -1; /* duplicate */
209 seen[p] = 1;
210 }
211
212 /* build D (k x k): row i from G row pids[i]-1, first k columns. */
213 uint8_t D[RS_K][RS_K], Dinv[RS_K][RS_K];
214 for (int i = 0; i < k; i++) {
215 int row = pids[i] - 1;
216 for (int j = 0; j < k; j++) D[i][j] = G[row][j];
217 }
218 if (gf_invert(k, D, Dinv) != 0) return -1; /* singular */
219
220 /* for each octet position j, m_j = Dinv * w'_j (ICD Eq. 17). The encoded
221 * column w'_j gathers octet j of every received page; the recovered column
222 * m_j gives octet j of each message page. */
223 for (int oct = 0; oct < PAGE_OCT; oct++) {
224 for (int i = 0; i < k; i++) {
225 uint8_t acc = 0;
226 for (int r = 0; r < k; r++) {
227 acc ^= gf_mul(Dinv[i][r], pages[r][oct]);
228 }
229 out[i * PAGE_OCT + oct] = acc;
230 }
231 }
232 return 0;
233}
static uint8_t gf_mul(uint8_t a, uint8_t b)
Definition mrtk_has_rs.c:47
static uint8_t gf_inv(uint8_t a)
Definition mrtk_has_rs.c:53
static void gf_build_genpoly(uint8_t gpoly[(255 - 32)+1])
Definition mrtk_has_rs.c:75
static int rs_ready
Definition mrtk_has_rs.c:44
#define GF_PRIM
Definition mrtk_has_rs.c:30
static void gf_build_tables(void)
Definition mrtk_has_rs.c:59
static uint8_t gf_log[256]
Definition mrtk_has_rs.c:39
int has_rs_init(void)
Build the GF(256) log/antilog tables and the systematic generator matrix G. Idempotent: safe to call ...
static void rs_encode_column(int col, const uint8_t gpoly[(255 - 32)+1])
static uint8_t gf_exp[2 *255]
Definition mrtk_has_rs.c:38
#define PAGE_OCT
Definition mrtk_has_rs.c:34
#define RS_NPAR
Definition mrtk_has_rs.c:33
static uint8_t G[255][32]
Definition mrtk_has_rs.c:42
static int gf_invert(int k, uint8_t a[32][32], uint8_t inv[32][32])
#define RS_K
Definition mrtk_has_rs.c:32
int has_rs_decode(const uint8_t pages[][53], const uint8_t *pids, int k, uint8_t *out)
Decode a k-page HAS message from k distinct received encoded pages using the RS(255,...
#define RS_N
Definition mrtk_has_rs.c:31