MRTKLIB 0.7.14
Modernized GNSS Positioning Library
Loading...
Searching...
No Matches
mrtk_has.c
Go to the documentation of this file.
1/*------------------------------------------------------------------------------
2 * mrtk_has.c : Galileo HAS (High Accuracy Service) processing functions
3 *
4 * Copyright (C) 2026 H.SHIONO (MRTKLIB Project)
5 *
6 * SPDX-License-Identifier: BSD-2-Clause
7 *
8 * Reference:
9 * [1] Galileo High Accuracy Service Signal-in-Space Interface Control
10 * Document (HAS SIS ICD), Issue 1.0, May 2022, European Union.
11 *----------------------------------------------------------------------------*/
12#include "mrtklib/mrtk_has.h"
13
14#include <math.h>
15#include <stdlib.h>
16#include <string.h>
17
18#include "mrtk_has_rs.h"
19#include "mrtklib/mrtk_bits.h"
20#include "mrtklib/mrtk_const.h"
21#include "mrtklib/mrtk_time.h"
22#include "mrtklib/mrtk_trace.h"
23
24/* forward declarations (resolved at link time) ------------------------------*/
25extern int satno(int sys, int prn);
26extern void satno2id(int sat, char* id);
27extern double time2gpst(gtime_t t, int* week);
28extern char* time_str(gtime_t t, int n);
29
30/* constants -----------------------------------------------------------------*/
31
32#define HAS_DUMMY_HEADER 0xAF3BC3 /* 24-bit HAS header of a dummy page */
33#define HAS_TIMEOUT 150.0 /* message collection timeout (s) [1] 6.4.1 */
34
35#define HAS_MT1 1 /* HAS Message Type 1 (satellite corrections) */
36
37#define HAS_GNSS_GPS 0 /* HAS GNSS ID: GPS ([1] Table 18) */
38#define HAS_GNSS_GAL 2 /* HAS GNSS ID: Galileo ([1] Table 18) */
39
40#define HAS_HASS_DONTUSE 3 /* HAS Status: don't use ([1] Table 9) */
41
42#define HAS_SATM_BITS 40 /* satellite mask length ([1] 5.2.1.2) */
43#define HAS_SIGM_BITS 16 /* signal mask length ([1] 5.2.1.3) */
44
45#define HAS_DR_NA (-4096) /* DR "data not available" (1000...0, 13-bit) */
46#define HAS_DIT_NA (-2048) /* DIT "data not available" (1000...0, 12-bit) */
47#define HAS_DCT_NA (-2048) /* DCT "data not available" (1000...0, 12-bit) */
48#define HAS_DCC_NA (-4096) /* DCC "data not available" (1000...0, 13-bit) */
49#define HAS_DCC_NU (4095) /* DCC "shall not be used" (0111...1, 13-bit) */
50#define HAS_CB_NA (-1024) /* CB "data not available" (1000...0, 11-bit) */
51#define HAS_PB_NA (-1024) /* PB "data not available" (1000...0, 11-bit) */
52
53/* Galileo signal index -> obs code ([1] Table 20) ---------------------------*/
57
58/* GPS signal index -> obs code ([1] Table 20) -------------------------------*/
62
63/* map HAS GNSS ID to satellite system ---------------------------------------*/
64static int has_gnssid2sys(int gnssid) {
65 switch (gnssid) {
66 case HAS_GNSS_GPS:
67 return SYS_GPS;
68 case HAS_GNSS_GAL:
69 return SYS_GAL;
70 }
71 return SYS_NONE;
72}
73
74/* map HAS GNSS ID + signal index to obs code --------------------------------*/
75static uint8_t has_sigcode(int gnssid, int sigidx) {
76 if (sigidx < 0 || sigidx >= HAS_MAX_SIG) {
77 return CODE_NONE;
78 }
79 switch (gnssid) {
80 case HAS_GNSS_GPS:
81 return has_sig_gps[sigidx];
82 case HAS_GNSS_GAL:
83 return has_sig_gal[sigidx];
84 }
85 return CODE_NONE;
86}
87
88/* IODref field width (bits) for a HAS GNSS ID ([1] Table 26) -----------------*/
89static int has_iodref_bits(int gnssid) {
90 switch (gnssid) {
91 case HAS_GNSS_GPS:
92 return 8;
93 case HAS_GNSS_GAL:
94 return 10;
95 }
96 return 0; /* unknown */
97}
98
99/* carrier wavelength for a Galileo/GPS obs code (m) --------------------------*/
100static double has_code2lam(int sys, uint8_t code) {
101 double freq = 0.0;
102
103 if (sys == SYS_GAL) {
104 switch (code) {
105 case CODE_L1B:
106 case CODE_L1C:
107 case CODE_L1X:
108 freq = FREQ1; /* E1 */
109 break;
110 case CODE_L5I:
111 case CODE_L5Q:
112 case CODE_L5X:
113 freq = FREQ5; /* E5a */
114 break;
115 case CODE_L7I:
116 case CODE_L7Q:
117 case CODE_L7X:
118 freq = FREQ7; /* E5b */
119 break;
120 case CODE_L8I:
121 case CODE_L8Q:
122 case CODE_L8X:
123 freq = FREQ8; /* E5 (E5a+E5b) */
124 break;
125 case CODE_L6B:
126 case CODE_L6C:
127 case CODE_L6X:
128 freq = FREQ6; /* E6 */
129 break;
130 }
131 } else if (sys == SYS_GPS) {
132 switch (code) {
133 case CODE_L1C:
134 case CODE_L1S:
135 case CODE_L1L:
136 case CODE_L1X:
137 freq = FREQ1; /* L1 */
138 break;
139 case CODE_L2S:
140 case CODE_L2L:
141 case CODE_L2X:
142 case CODE_L2W:
143 freq = FREQ2; /* L2 */
144 break;
145 case CODE_L5I:
146 case CODE_L5Q:
147 case CODE_L5X:
148 freq = FREQ5; /* L5 */
149 break;
150 }
151 }
152 return (freq > 0.0) ? CLIGHT / freq : 0.0;
153}
154
155/* bounds check: are bits [i, i+n) within the recovered message [0, nbit)? ----
156 * Every block parser verifies this BEFORE each getbitu/getbits so a structured-
157 * garbage message (e.g. RS decode over pages mixed from two content generations
158 * of a reused MID within the 150 s window) cannot drive the cursor past the
159 * calloc(ms*53) buffer and over-read the heap. */
160static int has_bits_ok(int i, int n, int nbit) { return i >= 0 && n >= 0 && i + n <= nbit; }
161
162/* maximum tolerated MT1 latency (rec_sow - toh, mod 3600); genuine messages
163 * show 1-21 s on real data, so 600 s rejects MID-reuse structured garbage while
164 * leaving a wide margin for the real signal ([1] 6.4.1 collection window). */
165#define HAS_MAX_LATENCY 600.0
166
167/* count set bits in a HAS satellite mask span -------------------------------*/
168static int popcount_field(const uint8_t* buff, int pos, int len) {
169 int i, cnt = 0;
170
171 for (i = 0; i < len; i++) {
172 if (getbitu(buff, pos + i, 1)) {
173 cnt++;
174 }
175 }
176 return cnt;
177}
178
179/* time of application of the HAS corrections ([1] 7.7, Eq. 28/29) -----------*/
180static gtime_t has_t_mt1(gtime_t time, int toh) {
181 double sow_r, hr3600;
182 int week;
183
184 sow_r = time2gpst(time, &week); /* GPST ~= GST; week-relative SoW */
185 hr3600 = floor(sow_r / 3600.0) * 3600.0;
186 if (hr3600 + (double)toh > sow_r) {
187 hr3600 -= 3600.0; /* hour transition between generation and reception */
188 }
189 return gpst2time(week, hr3600 + (double)toh);
190}
191
192/* decode MT1 mask block ([1] 5.2.1) -----------------------------------------
193 * fills has->mask[mid]; returns updated bit position, -1 on unknown GNSS,
194 * slot overflow, duplicate GNSS, or a read past the recovered message */
195static int decode_has_mask(has_t* has, const uint8_t* msg, int i, int nbit, int mid) {
196 has_mask_t* m = &has->mask[mid];
197 int j, k, s, nsys, gnssid, cmaf, nm, nsat, nsig, sys, prn;
198 int seen_gnss[16] = {0}; /* GNSS ID is 4 bits; one mask per GNSS ([1] 5.2.1) */
199 char satid[8];
200
201 memset(m, 0, sizeof(has_mask_t));
202
203 if (!has_bits_ok(i, 4, nbit)) {
204 return -1;
205 }
206 nsys = (int)getbitu(msg, i, 4);
207 i += 4;
208 m->nsys = nsys;
209 s = 0; /* running mask-slot index across all GNSS */
210
211 for (j = 0; j < nsys; j++) {
212 if (!has_bits_ok(i, 4, nbit)) {
213 return -1;
214 }
215 gnssid = (int)getbitu(msg, i, 4);
216 i += 4;
217 sys = has_gnssid2sys(gnssid);
218 if (sys == SYS_NONE) {
219 /* IODref width for an unknown GNSS is undefined ([1] Table 26):
220 * orbit/clock/bias block lengths cannot be computed -> abort. */
221 trace(NULL, 2, "decode_has_mask: unknown gnssid=%d, abort\n", gnssid);
222 return -1;
223 }
224 if (seen_gnss[gnssid]) {
225 /* the ICD defines exactly one mask per GNSS ([1] 5.2.1); a repeat is
226 * structured garbage (e.g. MID-reuse RS mixing) -> abort. */
227 trace(NULL, 2, "decode_has_mask: duplicate gnssid=%d, abort\n", gnssid);
228 return -1;
229 }
230 seen_gnss[gnssid] = 1;
231
232 if (!has_bits_ok(i, HAS_SATM_BITS, nbit)) {
233 return -1;
234 }
235 nsat = popcount_field(msg, i, HAS_SATM_BITS);
236 if (s + nsat > MAXSAT) {
237 /* accumulated mask slots exceed the m->sat[]/m->gnssid[]/... arrays
238 * (sized MAXSAT); abort rather than write out of bounds. */
239 trace(NULL, 2, "decode_has_mask: slot overflow s=%d+nsat=%d > %d, abort\n", s, nsat, MAXSAT);
240 return -1;
241 }
242 for (k = 0; k < HAS_SATM_BITS; k++) {
243 if (getbitu(msg, i + k, 1)) {
244 prn = k + 1; /* satellite index 0 <-> PRN 1 ([1] Table 19) */
245 m->sat[s] = satno(sys, prn);
246 m->gnssid[s] = gnssid;
247 s++;
248 }
249 }
250 i += HAS_SATM_BITS;
251
252 if (!has_bits_ok(i, HAS_SIGM_BITS + 1, nbit)) {
253 return -1;
254 }
255 nsig = popcount_field(msg, i, HAS_SIGM_BITS);
256 cmaf = (int)getbitu(msg, i + HAS_SIGM_BITS, 1);
257
258 /* signal codes for this GNSS, in mask order */
259 uint8_t codes[HAS_MAX_SIG];
260 int nc = 0;
261 for (k = 0; k < HAS_SIGM_BITS; k++) {
262 if (getbitu(msg, i + k, 1)) {
263 codes[nc++] = has_sigcode(gnssid, k);
264 }
265 }
266 i += HAS_SIGM_BITS;
267 i += 1; /* CMAF */
268
269 for (k = s - nsat; k < s; k++) {
270 int c;
271 uint16_t cell;
272 m->nsig[k] = nsig;
273 for (c = 0; c < nsig; c++) {
274 m->code[k][c] = codes[c];
275 }
276 if (cmaf) {
277 if (!has_bits_ok(i, nsig, nbit)) {
278 return -1;
279 }
280 cell = (uint16_t)getbitu(msg, i, nsig);
281 i += nsig;
282 } else {
283 cell = (uint16_t)((nsig >= 16) ? 0xFFFF : ((1u << nsig) - 1u));
284 }
285 m->cellmask[k] = cell;
286 }
287
288 if (!has_bits_ok(i, 3, nbit)) {
289 return -1;
290 }
291 nm = (int)getbitu(msg, i, 3);
292 i += 3;
293 for (k = s - nsat; k < s; k++) {
294 m->navmsg[k] = nm;
295 }
296
297 trace(NULL, 4, "decode_has_mask: gnssid=%d, nsat=%d, nsig=%d, cmaf=%d, nm=%d\n", gnssid, nsat, nsig, cmaf, nm);
298 }
299 if (!has_bits_ok(i, 6, nbit)) {
300 return -1;
301 }
302 i += 6; /* Reserved */
303
304 m->nsat = s;
305 m->valid = 1;
306
307 for (k = 0; k < s; k++) {
308 if (m->sat[k]) {
309 satno2id(m->sat[k], satid);
310 trace(NULL, 4, "decode_has_mask: slot=%d, sat=%s, nsig=%d, cell=0x%04X\n", k, satid, m->nsig[k],
311 m->cellmask[k]);
312 }
313 }
314 return i;
315}
316
317/* decode MT1 orbit block ([1] 5.2.2); fills deph + iode + iodset cache ------*/
318static int decode_has_orbit(has_t* has, const uint8_t* msg, int i, int nbit, int mid, int iodset, gtime_t t0) {
319 const has_mask_t* m = &has->mask[mid];
320 has_iodset_t* io = &has->iodset[mid][iodset];
321 int j, sat, nb, iodref, dr, dit, dct;
322
323 if (!has_bits_ok(i, 4, nbit)) {
324 return -1;
325 }
326 i += 4; /* VI (validity interval index; unused: t0 carries applicability) */
327
328 memset(io, 0, sizeof(has_iodset_t));
329
330 for (j = 0; j < m->nsat; j++) {
331 nb = has_iodref_bits(m->gnssid[j]);
332 if (!has_bits_ok(i, nb + 13 + 12 + 12, nbit)) {
333 return -1;
334 }
335 iodref = (int)getbitu(msg, i, nb);
336 i += nb;
337 dr = getbits(msg, i, 13);
338 i += 13;
339 dit = getbits(msg, i, 12);
340 i += 12;
341 dct = getbits(msg, i, 12);
342 i += 12;
343
344 io->iodref[j] = iodref;
345
346 sat = m->sat[j];
347 if (sat == 0) {
348 continue; /* unsupported satellite (still parsed structurally) */
349 }
350 if (dr == HAS_DR_NA || dit == HAS_DIT_NA || dct == HAS_DCT_NA) {
351 continue; /* data not available */
352 }
353
354 /* sign convention: satpos_ssr applies rs += -R*deph, HAS x~=x+R*dR */
355 has->ssr[sat - 1].deph[0] = -dr * 0.0025;
356 has->ssr[sat - 1].deph[1] = -dit * 0.0080;
357 has->ssr[sat - 1].deph[2] = -dct * 0.0080;
358 has->ssr[sat - 1].ddeph[0] = 0.0;
359 has->ssr[sat - 1].ddeph[1] = 0.0;
360 has->ssr[sat - 1].ddeph[2] = 0.0;
361 has->ssr[sat - 1].iode = iodref;
362 /* Orbit block owns iod[0]/t0[0] only; the clock block owns iod[1]/t0[1].
363 * Keeping them separate makes satpos_ssr's iod[0]==iod[1] gate reject a
364 * stale clock from a previous IOD set against the new IODref ephemeris
365 * at an IOD-set transition (clock-only messages lag the mask+orbit by up
366 * to ~10 s in real broadcast). */
367 has->ssr[sat - 1].iod[0] = iodset;
368 has->ssr[sat - 1].t0[0] = t0;
369 has->ssr[sat - 1].udi[0] = 0.0;
370 has->ssr[sat - 1].update = 1;
371 }
372 io->valid = 1;
373 return i;
374}
375
376/* decode MT1 clock full-set block ([1] 5.2.3) -------------------------------*/
377static int decode_has_clock(has_t* has, const uint8_t* msg, int i, int nbit, int mid, int iodset, gtime_t t0) {
378 const has_mask_t* m = &has->mask[mid];
379 int j, sat, dcm[HAS_MAX_SIG], mult, dcc, prevgid = -1, sysidx = -1;
380
381 if (!has_bits_ok(i, 4, nbit)) {
382 return -1;
383 }
384 i += 4; /* VI */
385
386 /* DCM(2 bits) per GNSS, in mask order. m->nsys <= 15 (4-bit Nsys) and
387 * dcm[] is sized HAS_MAX_SIG (16), so the index is bounded. */
388 for (j = 0; j < m->nsys; j++) {
389 if (!has_bits_ok(i, 2, nbit)) {
390 return -1;
391 }
392 dcm[j] = (int)getbitu(msg, i, 2);
393 i += 2;
394 }
395
396 for (j = 0; j < m->nsat; j++) {
397 if (m->gnssid[j] != prevgid) {
398 prevgid = m->gnssid[j];
399 sysidx++;
400 }
401 mult = dcm[sysidx] + 1; /* DCM value n -> multiplier n+1 ([1] Table 29) */
402
403 if (!has_bits_ok(i, 13, nbit)) {
404 return -1;
405 }
406 dcc = getbits(msg, i, 13);
407 i += 13;
408
409 sat = m->sat[j];
410 if (sat == 0) {
411 continue;
412 }
413 if (dcc == HAS_DCC_NA) {
414 continue; /* data not available */
415 }
416 if (dcc == HAS_DCC_NU) {
417 /* "shall not be used" -> clear clock so satpos_ssr rejects sat */
418 has->ssr[sat - 1].t0[1].time = 0;
419 has->ssr[sat - 1].t0[1].sec = 0.0;
420 continue;
421 }
422
423 has->ssr[sat - 1].dclk[0] = dcc * 0.0025 * mult;
424 has->ssr[sat - 1].dclk[1] = 0.0;
425 has->ssr[sat - 1].dclk[2] = 0.0;
426 has->ssr[sat - 1].hrclk = 0.0;
427 has->ssr[sat - 1].iod[1] = iodset;
428 has->ssr[sat - 1].t0[1] = t0;
429 has->ssr[sat - 1].udi[1] = 0.0;
430 has->ssr[sat - 1].update = 1;
431 }
432 return i;
433}
434
435/* decode MT1 clock subset block ([1] 5.2.4) ---------------------------------*/
436static int decode_has_clock_subset(has_t* has, const uint8_t* msg, int i, int nbit, int mid, int iodset, gtime_t t0) {
437 const has_mask_t* m = &has->mask[mid];
438 int g, j, gnssid, dcm, mult, dcc, sat, nsat_gnss, base, submask_pos, nsys_sub;
439
440 if (!has_bits_ok(i, 4 + 4, nbit)) {
441 return -1;
442 }
443 i += 4; /* VI */
444 nsys_sub = (int)getbitu(msg, i, 4);
445 i += 4;
446
447 for (g = 0; g < nsys_sub; g++) {
448 if (!has_bits_ok(i, 4 + 2, nbit)) {
449 return -1;
450 }
451 gnssid = (int)getbitu(msg, i, 4);
452 i += 4;
453 dcm = (int)getbitu(msg, i, 2);
454 i += 2;
455 mult = dcm + 1;
456
457 /* locate the contiguous mask-slot range for this GNSS in SatM order */
458 base = -1;
459 nsat_gnss = 0;
460 for (j = 0; j < m->nsat; j++) {
461 if (m->gnssid[j] == gnssid) {
462 if (base < 0) {
463 base = j;
464 }
465 nsat_gnss++;
466 }
467 }
468 if (base < 0) {
469 trace(NULL, 2, "decode_has_clock_subset: gnssid=%d not in mask\n", gnssid);
470 return -1;
471 }
472
473 /* subset mask (Nsat bits relative to the GNSS satellites of SatM),
474 * then one DCC(13) per selected satellite */
475 if (!has_bits_ok(i, nsat_gnss, nbit)) {
476 return -1;
477 }
478 submask_pos = i;
479 i += nsat_gnss;
480 for (j = 0; j < nsat_gnss; j++) {
481 if (!getbitu(msg, submask_pos + j, 1)) {
482 continue;
483 }
484 if (!has_bits_ok(i, 13, nbit)) {
485 return -1;
486 }
487 dcc = getbits(msg, i, 13);
488 i += 13;
489
490 sat = m->sat[base + j];
491 if (sat == 0) {
492 continue;
493 }
494 if (dcc == HAS_DCC_NA) {
495 continue;
496 }
497 if (dcc == HAS_DCC_NU) {
498 has->ssr[sat - 1].t0[1].time = 0;
499 has->ssr[sat - 1].t0[1].sec = 0.0;
500 continue;
501 }
502 has->ssr[sat - 1].dclk[0] = dcc * 0.0025 * mult;
503 has->ssr[sat - 1].dclk[1] = 0.0;
504 has->ssr[sat - 1].dclk[2] = 0.0;
505 has->ssr[sat - 1].hrclk = 0.0;
506 has->ssr[sat - 1].iod[1] = iodset;
507 has->ssr[sat - 1].t0[1] = t0;
508 has->ssr[sat - 1].udi[1] = 0.0;
509 has->ssr[sat - 1].update = 1;
510 }
511 }
512 return i;
513}
514
515/* decode MT1 code bias block ([1] 5.2.5) ------------------------------------*/
516static int decode_has_codebias(has_t* has, const uint8_t* msg, int i, int nbit, gtime_t t0, const has_mask_t* m) {
517 int j, c, sat, cb;
518 uint8_t code;
519
520 if (!has_bits_ok(i, 4, nbit)) {
521 return -1;
522 }
523 i += 4; /* VI */
524
525 for (j = 0; j < m->nsat; j++) {
526 sat = m->sat[j];
527 if (sat) {
528 has->ssr[sat - 1].t0[4] = t0;
529 has->ssr[sat - 1].udi[4] = 0.0;
530 }
531 for (c = 0; c < m->nsig[j]; c++) {
532 if (!((m->cellmask[j] >> (m->nsig[j] - 1 - c)) & 0x1)) {
533 continue; /* cell not provided */
534 }
535 if (!has_bits_ok(i, 11, nbit)) {
536 return -1;
537 }
538 cb = getbits(msg, i, 11);
539 i += 11;
540
541 if (sat == 0) {
542 continue;
543 }
544 code = m->code[j][c];
545 if (code == CODE_NONE) {
546 continue;
547 }
548 if (cb == HAS_CB_NA) {
549 /* "data not available" -> clear the bias so corr_meas treats it as
550 * absent (the !vcbias path drops the pseudorange, which is correct:
551 * a HAS code bias replaces BGD and is required). Clearing both the
552 * value and the valid flag also prevents a previously valid bias
553 * from persisting in nav->ssr_ch via the update-gated copy loop. */
554 has->ssr[sat - 1].cbias[code - 1] = 0.0f;
555 has->ssr[sat - 1].vcbias[code - 1] = 0;
556 has->ssr[sat - 1].update = 1;
557 continue;
558 }
559 has->ssr[sat - 1].cbias[code - 1] = (float)(cb * 0.02); /* added to P (Eq. 25) */
560 has->ssr[sat - 1].vcbias[code - 1] = 1;
561 has->ssr[sat - 1].update = 1;
562 }
563 }
564 return i;
565}
566
567/* decode MT1 phase bias block ([1] 5.2.6) -----------------------------------*/
568static int decode_has_phasebias(has_t* has, const uint8_t* msg, int i, int nbit, gtime_t t0, const has_mask_t* m) {
569 int j, c, sat, pb, pdi, sys;
570 uint8_t code;
571 double lam;
572
573 if (!has_bits_ok(i, 4, nbit)) {
574 return -1;
575 }
576 i += 4; /* VI */
577
578 for (j = 0; j < m->nsat; j++) {
579 sat = m->sat[j];
580 sys = sat ? has_gnssid2sys(m->gnssid[j]) : SYS_NONE;
581 if (sat) {
582 has->ssr[sat - 1].t0[5] = t0;
583 has->ssr[sat - 1].udi[5] = 0.0;
584 }
585 for (c = 0; c < m->nsig[j]; c++) {
586 if (!((m->cellmask[j] >> (m->nsig[j] - 1 - c)) & 0x1)) {
587 continue;
588 }
589 if (!has_bits_ok(i, 11 + 2, nbit)) {
590 return -1;
591 }
592 pb = getbits(msg, i, 11);
593 i += 11;
594 pdi = (int)getbitu(msg, i, 2);
595 i += 2;
596
597 if (sat == 0) {
598 continue;
599 }
600 code = m->code[j][c];
601 if (code == CODE_NONE) {
602 continue;
603 }
604 if (pb == HAS_PB_NA) {
605 /* "data not available" -> treat as "no phase bias provided" rather
606 * than an invalid sentinel. With pbias cleared and vpbias=0,
607 * corr_meas keeps the carrier for CORR_GAL_HAS (float PPP absorbs
608 * the unknown satellite phase bias into the float ambiguity);
609 * writing SSR_INVALID_PBIAS would instead force L[i]=0 and drop the
610 * carrier. Clearing also evicts any stale valid bias from
611 * nav->ssr_ch through the update-gated copy. discnt is left as-is
612 * (no new discontinuity is signalled by an NA). */
613 has->ssr[sat - 1].pbias[code - 1] = 0.0;
614 has->ssr[sat - 1].vpbias[code - 1] = 0;
615 has->ssr[sat - 1].update = 1;
616 continue;
617 }
618 lam = has_code2lam(sys, code);
619 has->ssr[sat - 1].pbias[code - 1] = pb * 0.01 * lam; /* cycles -> m (Eq. 26) */
620 has->ssr[sat - 1].vpbias[code - 1] = 1;
621 has->ssr[sat - 1].discnt[code - 1] = pdi;
622 has->ssr[sat - 1].update = 1;
623 }
624 }
625 return i;
626}
627
628/* decode a recovered MT1 message ([1] section 5) ----------------------------*/
629static int decode_has_mt1(has_t* has, const uint8_t* msg, int nbit, gtime_t time) {
630 int i = 0, toh, fmask, forbit, fcfull, fcsub, fcb, fpb, mid, iodset, week;
631 double sow_r, latency;
632 gtime_t t0;
633
634 if (nbit < 32) {
635 return -1;
636 }
637
638 toh = (int)getbitu(msg, i, 12);
639 i += 12;
640 fmask = (int)getbitu(msg, i, 1);
641 i += 1;
642 forbit = (int)getbitu(msg, i, 1);
643 i += 1;
644 fcfull = (int)getbitu(msg, i, 1);
645 i += 1;
646 fcsub = (int)getbitu(msg, i, 1);
647 i += 1;
648 fcb = (int)getbitu(msg, i, 1);
649 i += 1;
650 fpb = (int)getbitu(msg, i, 1);
651 i += 1;
652 i += 4; /* Reserved */
653 mid = (int)getbitu(msg, i, 5);
654 i += 5;
655 iodset = (int)getbitu(msg, i, 5);
656 i += 5;
657
658 /* plausibility gate: TOH is 0..3599 ([1] 5.2, Table 8). A value >=3600 means
659 * the recovered header is garbage (e.g. RS decode over pages mixed from two
660 * content generations of a reused MID within the 150 s window) -> reject. */
661 if (toh >= 3600) {
662 trace(NULL, 2, "decode_has_mt1: invalid toh=%d (>=3600), reject\n", toh);
663 return -1;
664 }
665 /* implied latency = (rec_sow - toh) mod 3600; genuine messages show 1-21 s
666 * on real data, so >HAS_MAX_LATENCY (600 s) kills most MID-reuse garbage. */
667 sow_r = time2gpst(time, &week);
668 latency = fmod(sow_r - (double)toh, 3600.0);
669 if (latency < 0.0) {
670 latency += 3600.0;
671 }
672 if (latency > HAS_MAX_LATENCY) {
673 trace(NULL, 2, "decode_has_mt1: latency=%.0f s > %.0f, reject (toh=%d)\n", latency, HAS_MAX_LATENCY, toh);
674 return -1;
675 }
676
677 t0 = has_t_mt1(time, toh);
678
679 trace(NULL, 4, "decode_has_mt1: %s, toh=%d, flags=%d%d%d%d%d%d, mid=%d, iodset=%d\n", time_str(t0, 3), toh, fmask,
680 forbit, fcfull, fcsub, fcb, fpb, mid, iodset);
681
682 if (fmask) {
683 if ((i = decode_has_mask(has, msg, i, nbit, mid)) < 0) {
684 return -1;
685 }
686 }
687 if (!has->mask[mid].valid) {
688 trace(NULL, 2, "decode_has_mt1: %s, mask id=%d not yet received\n", time_str(t0, 3), mid);
689 return -1; /* cannot interpret body without a cached mask */
690 }
691
692 if (forbit) {
693 if ((i = decode_has_orbit(has, msg, i, nbit, mid, iodset, t0)) < 0) {
694 return -1;
695 }
696 } else if (!has->iodset[mid][iodset].valid) {
697 trace(NULL, 2, "decode_has_mt1: %s, iodset (mid=%d,id=%d) not yet received\n", time_str(t0, 3), mid, iodset);
698 /* IODref cache absent; corrections still applied with cached mask, but
699 * iode reference is unknown -> warn (satpos_ssr will gate on iode). */
700 }
701 if (fcfull) {
702 if ((i = decode_has_clock(has, msg, i, nbit, mid, iodset, t0)) < 0) {
703 return -1;
704 }
705 }
706 if (fcsub) {
707 if ((i = decode_has_clock_subset(has, msg, i, nbit, mid, iodset, t0)) < 0) {
708 return -1;
709 }
710 }
711 if (fcb) {
712 if ((i = decode_has_codebias(has, msg, i, nbit, t0, &has->mask[mid])) < 0) {
713 return -1;
714 }
715 }
716 if (fpb) {
717 if ((i = decode_has_phasebias(has, msg, i, nbit, t0, &has->mask[mid])) < 0) {
718 return -1;
719 }
720 }
721 return 10;
722}
723
724/* run RS erasure decoder on a completed collector and decode MT1 ------------*/
725static int has_complete(has_t* has, has_collect_t* col, gtime_t time) {
726 uint8_t* out;
727 int ret;
728
729 out = (uint8_t*)calloc((size_t)col->ms * HAS_PAGE_BYTES, 1);
730 if (!out) {
731 return -1;
732 }
733 if (has_rs_decode(col->page, col->pid, col->ms, out) != 0) {
734 trace(NULL, 2, "has_complete: RS decode failed mid=%d, k=%d\n", col->mid, col->ms);
735 free(out);
736 return -1;
737 }
738
739 ret = 0;
740 if (col->mt == HAS_MT1) {
741 ret = decode_has_mt1(has, out, col->ms * HAS_PAGE_BYTES * 8, time);
742 }
743 free(out);
744 col->active = 0;
745 return ret;
746}
747
748/* find or allocate an in-flight collector for (mid, mt) ---------------------*/
749static has_collect_t* has_find_collect(has_t* has, int mid, int mt, gtime_t time) {
750 int k, oldest = 0;
751 double age, maxage = -1.0;
752
753 for (k = 0; k < HAS_MAX_INFLIGHT; k++) {
754 if (has->collect[k].active && has->collect[k].mid == mid && has->collect[k].mt == mt) {
755 if (timediff(time, has->collect[k].t0) > HAS_TIMEOUT) {
756 has->collect[k].active = 0; /* expired; restart below */
757 break;
758 }
759 return &has->collect[k];
760 }
761 }
762 /* reuse an idle slot, else the oldest active one */
763 for (k = 0; k < HAS_MAX_INFLIGHT; k++) {
764 if (!has->collect[k].active) {
765 return &has->collect[k];
766 }
767 age = timediff(time, has->collect[k].t0);
768 if (age > maxage) {
769 maxage = age;
770 oldest = k;
771 }
772 }
773 return &has->collect[oldest];
774}
775
776/* clear all collected state ([1] HASS==3 handling) --------------------------*/
777static void has_flush(has_t* has) {
778 int k;
779
780 for (k = 0; k < HAS_MAX_INFLIGHT; k++) {
781 has->collect[k].active = 0;
782 }
783 for (k = 0; k < HAS_MAX_MID; k++) {
784 has->mask[k].valid = 0;
785 }
786 trace(NULL, 2, "has_flush: HAS state discarded\n");
787}
788
789/* ---------------------------------------------------------------------------*/
790extern has_t* has_new(void) {
791 has_t* has;
792
793 if (has_rs_init() != 0) {
794 return NULL;
795 }
796 has = (has_t*)calloc(1, sizeof(has_t));
797 return has;
798}
799
800/* ---------------------------------------------------------------------------*/
801extern void has_free(has_t* has) { free(has); }
802
803/* ---------------------------------------------------------------------------*/
804extern int has_input_page(has_t* has, int prn, const uint8_t* page56, gtime_t time) {
805 has_collect_t* col;
806 uint32_t header;
807 int hass, mt, mid, ms, pid, k;
808
809 (void)prn; /* PRN is a routing hint only; collection keys on (MID, MT) */
810
811 header = getbitu(page56, 0, 24);
812 if (header == HAS_DUMMY_HEADER) {
813 return 0; /* dummy page (defensive; normally filtered upstream) */
814 }
815
816 hass = (int)getbitu(page56, 0, 2);
817 /* getbitu(page56,2,2) = Reserved */
818 mt = (int)getbitu(page56, 4, 2);
819 mid = (int)getbitu(page56, 6, 5);
820 ms = (int)getbitu(page56, 11, 5) + 1; /* MS stored minus 1 */
821 pid = (int)getbitu(page56, 16, 8);
822
823 if (hass == HAS_HASS_DONTUSE) {
824 has_flush(has);
825 return 0;
826 }
827 if (pid == 0 || mt != HAS_MT1) {
828 return 0; /* PID 0 reserved; only MT1 carries corrections */
829 }
830
831 col = has_find_collect(has, mid, mt, time);
832 /* Restart the collection when the slot is fresh, or when an in-flight
833 * collector for this (MID, MT) reports a different MS. A MID is reused
834 * across content generations within the 150 s window, and a new generation
835 * (or a corrupted header) can carry a different MS. Mixing pages from two
836 * generations would feed the RS(255,32) decoder the wrong page count k and
837 * recover garbage, so drop the previously collected pages and begin again
838 * with this page. (PID duplicates that differ in payload across generations
839 * are first-wins; the toh/latency gate in decode_has_mt1 catches any
840 * residue that still RS-decodes.) */
841 if (!col->active || col->mid != mid || col->mt != mt || col->ms != ms) {
842 if (col->active && col->mid == mid && col->mt == mt && col->ms != ms) {
843 trace(NULL, 4, "has_input_page: MS change mid=%d %d->%d, restart collection\n", mid, col->ms, ms);
844 }
845 memset(col, 0, sizeof(has_collect_t));
846 col->active = 1;
847 col->mid = mid;
848 col->mt = mt;
849 col->ms = ms;
850 col->t0 = time;
851 }
852
853 for (k = 0; k < col->npid; k++) {
854 if (col->pid[k] == pid) {
855 return 0; /* duplicate PID already stored */
856 }
857 }
858 if (col->npid >= HAS_MAX_PAGES) {
859 return 0;
860 }
861 col->pid[col->npid] = (uint8_t)pid;
862 memcpy(col->page[col->npid], page56 + 3, HAS_PAGE_BYTES); /* skip 24-bit header */
863 col->npid++;
864
865 if (col->npid >= col->ms) {
866 return has_complete(has, col, time);
867 }
868 return 0;
869}
870
871/* ---------------------------------------------------------------------------*/
872extern int has_input_file(has_t* has, FILE* fp, gtime_t tmax) {
873 uint8_t rec[64];
874 gtime_t time;
875 uint32_t tow_ms;
876 int wnc, prn, ret, completed = 0;
877
878 while (fread(rec, 1, 64, fp) == 64) {
879 /* little-endian record fields ([1] design doc 3) */
880 tow_ms = (uint32_t)rec[0] | ((uint32_t)rec[1] << 8) | ((uint32_t)rec[2] << 16) | ((uint32_t)rec[3] << 24);
881 wnc = (int)((uint16_t)rec[4] | ((uint16_t)rec[5] << 8));
882 prn = rec[6];
883
884 time = gpst2time(wnc, tow_ms * 1.0e-3);
885 if (tmax.time != 0 && timediff(time, tmax) > 0.0) {
886 /* rewind so the next call re-reads this record */
887 fseek(fp, -64, SEEK_CUR);
888 break;
889 }
890
891 ret = has_input_page(has, prn, rec + 8, time);
892 if (ret == 10) {
893 completed = 10;
894 } else if (ret < 0) {
895 trace(NULL, 2, "has_input_file: page decode error\n");
896 }
897 }
898 return completed;
899}
900
901/* ---------------------------------------------------------------------------*/
902extern int has_sel_biascode(int sys, uint8_t code) {
903 if (sys == SYS_GAL) {
904 switch (code) {
905 case CODE_L1B: /* E1-B */
906 case CODE_L1C: /* E1-C */
907 case CODE_L1X:
908 return CODE_L1X; /* E1-B+C */
909 case CODE_L5I: /* E5a-I */
910 case CODE_L5Q: /* E5a-Q */
911 case CODE_L5X:
912 return CODE_L5X; /* E5a-I+Q */
913 case CODE_L7I: /* E5b-I */
914 case CODE_L7Q: /* E5b-Q */
915 case CODE_L7X:
916 return CODE_L7X; /* E5b-I+Q */
917 case CODE_L8I: /* E5-I */
918 case CODE_L8Q: /* E5-Q */
919 case CODE_L8X:
920 return CODE_L8X; /* E5-I+Q */
921 case CODE_L6B: /* E6-B */
922 case CODE_L6C: /* E6-C */
923 case CODE_L6X:
924 return CODE_L6X; /* E6-B+C */
925 }
926 } else if (sys == SYS_GPS) {
927 switch (code) {
928 case CODE_L1C:
929 return CODE_L1C; /* L1 C/A */
930 case CODE_L1S: /* L1C(D) */
931 case CODE_L1L: /* L1C(P) */
932 case CODE_L1X:
933 return CODE_L1X; /* L1C(D+P) */
934 case CODE_L2S: /* L2 CM */
935 case CODE_L2L: /* L2 CL */
936 case CODE_L2X:
937 return CODE_L2X; /* L2 CM+CL */
938 case CODE_L2W:
939 return CODE_L2W; /* L2 P */
940 case CODE_L5I: /* L5 I */
941 case CODE_L5Q: /* L5 Q */
942 case CODE_L5X:
943 return CODE_L5X; /* L5 I+Q */
944 }
945 }
946 return CODE_NONE;
947}
uint32_t getbitu(const uint8_t *buff, int pos, int len)
Extract unsigned bits from byte data.
Definition mrtk_bits.c:99
int32_t getbits(const uint8_t *buff, int pos, int len)
Extract signed bits from byte data.
Definition mrtk_bits.c:107
MRTKLIB Bits Module — Binary bit operations and CRC functions.
MRTKLIB Constants — Physical, navigation, and system constants.
#define CLIGHT
Definition mrtk_coords.c:41
#define MAXSAT
Definition mrtk_coords.c:45
#define SYS_GPS
Definition mrtk_eph.c:65
#define SYS_NONE
Definition mrtk_eph.c:64
#define SYS_GAL
Definition mrtk_eph.c:68
#define CODE_L5Q
#define CODE_L6C
#define CODE_L1S
#define CODE_L5I
#define CODE_L2X
#define CODE_L1L
#define CODE_L5X
#define CODE_L7X
#define CODE_L1C
#define CODE_L7I
#define CODE_L6X
#define CODE_NONE
#define CODE_L8Q
#define CODE_L1B
#define CODE_L2S
#define CODE_L7Q
#define CODE_L6B
#define CODE_L8X
#define CODE_L2W
#define CODE_L2L
#define CODE_L8I
#define CODE_L1X
void satno2id(int sat, char *id)
Definition mrtk_nav.c:817
#define HAS_HASS_DONTUSE
Definition mrtk_has.c:40
static int has_gnssid2sys(int gnssid)
Definition mrtk_has.c:64
static int decode_has_orbit(has_t *has, const uint8_t *msg, int i, int nbit, int mid, int iodset, gtime_t t0)
Definition mrtk_has.c:318
#define HAS_GNSS_GPS
Definition mrtk_has.c:37
static int decode_has_phasebias(has_t *has, const uint8_t *msg, int i, int nbit, gtime_t t0, const has_mask_t *m)
Definition mrtk_has.c:568
static int decode_has_mt1(has_t *has, const uint8_t *msg, int nbit, gtime_t time)
Definition mrtk_has.c:629
#define HAS_SIGM_BITS
Definition mrtk_has.c:43
static int decode_has_clock(has_t *has, const uint8_t *msg, int i, int nbit, int mid, int iodset, gtime_t t0)
Definition mrtk_has.c:377
static int has_bits_ok(int i, int n, int nbit)
Definition mrtk_has.c:160
void has_free(has_t *has)
Free a Galileo HAS decode context.
Definition mrtk_has.c:801
#define HAS_DCC_NU
Definition mrtk_has.c:49
#define HAS_SATM_BITS
Definition mrtk_has.c:42
#define HAS_MAX_LATENCY
Definition mrtk_has.c:165
int satno(int sys, int prn)
Definition mrtk_nav.c:58
#define HAS_MT1
Definition mrtk_has.c:35
static uint8_t has_sigcode(int gnssid, int sigidx)
Definition mrtk_has.c:75
#define HAS_PB_NA
Definition mrtk_has.c:51
static int has_complete(has_t *has, has_collect_t *col, gtime_t time)
Definition mrtk_has.c:725
static const uint8_t has_sig_gal[HAS_MAX_SIG]
Definition mrtk_has.c:54
static double has_code2lam(int sys, uint8_t code)
Definition mrtk_has.c:100
int has_input_file(has_t *has, FILE *fp, gtime_t tmax)
Read .has records from a file and feed them to has_input_page().
Definition mrtk_has.c:872
int has_input_page(has_t *has, int prn, const uint8_t *page56, gtime_t time)
Input one received Galileo HAS page.
Definition mrtk_has.c:804
static int has_iodref_bits(int gnssid)
Definition mrtk_has.c:89
static int popcount_field(const uint8_t *buff, int pos, int len)
Definition mrtk_has.c:168
double time2gpst(gtime_t t, int *week)
Definition mrtk_time.c:181
static int decode_has_clock_subset(has_t *has, const uint8_t *msg, int i, int nbit, int mid, int iodset, gtime_t t0)
Definition mrtk_has.c:436
static void has_flush(has_t *has)
Definition mrtk_has.c:777
#define HAS_CB_NA
Definition mrtk_has.c:50
static const uint8_t has_sig_gps[HAS_MAX_SIG]
Definition mrtk_has.c:59
char * time_str(gtime_t t, int n)
Definition mrtk_time.c:530
static gtime_t has_t_mt1(gtime_t time, int toh)
Definition mrtk_has.c:180
static int decode_has_codebias(has_t *has, const uint8_t *msg, int i, int nbit, gtime_t t0, const has_mask_t *m)
Definition mrtk_has.c:516
#define HAS_DCT_NA
Definition mrtk_has.c:47
has_t * has_new(void)
Allocate and initialize a Galileo HAS decode context.
Definition mrtk_has.c:790
#define HAS_DCC_NA
Definition mrtk_has.c:48
#define HAS_DR_NA
Definition mrtk_has.c:45
static int decode_has_mask(has_t *has, const uint8_t *msg, int i, int nbit, int mid)
Definition mrtk_has.c:195
#define HAS_GNSS_GAL
Definition mrtk_has.c:38
#define HAS_TIMEOUT
Definition mrtk_has.c:33
static has_collect_t * has_find_collect(has_t *has, int mid, int mt, gtime_t time)
Definition mrtk_has.c:749
#define HAS_DIT_NA
Definition mrtk_has.c:46
#define HAS_DUMMY_HEADER
Definition mrtk_has.c:32
int has_sel_biascode(int sys, uint8_t code)
Select the HAS bias slot obs code for an observation code.
Definition mrtk_has.c:902
MRTKLIB Galileo HAS Module — E6-B C/NAV HAS page collection and Message Type 1 (MT1) decode into ssr_...
#define HAS_MAX_MID
Definition mrtk_has.h:43
#define HAS_MAX_SIG
Definition mrtk_has.h:49
#define HAS_MAX_PAGES
Definition mrtk_has.h:45
#define HAS_MAX_INFLIGHT
Definition mrtk_has.h:47
#define HAS_PAGE_BYTES
Definition mrtk_has.h:46
int has_rs_init(void)
Build the GF(256) log/antilog tables and the systematic generator matrix G. Idempotent: safe to call ...
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 FREQ5
Definition mrtk_obs.c:69
#define FREQ2
Definition mrtk_obs.c:68
#define FREQ8
Definition mrtk_obs.c:72
#define FREQ6
Definition mrtk_obs.c:70
#define FREQ7
Definition mrtk_obs.c:71
#define FREQ1
Definition mrtk_obs.c:67
gtime_t gpst2time(int week, double sec)
Convert GPS week/tow to gtime_t.
Definition mrtk_time.c:165
double timediff(gtime_t t1, gtime_t t2)
Compute time difference (t1 - t2).
Definition mrtk_time.c:279
MRTKLIB Time Module — GNSS time types and conversion functions.
void trace(mrtk_ctx_t *ctx, int level, const char *format,...)
Output trace message.
Definition mrtk_trace.c:78
MRTKLIB Trace Module — debug trace and logging functions.
GNSS time representation.
Definition mrtk_time.h:49
double sec
Definition mrtk_time.h:51
time_t time
Definition mrtk_time.h:50
uint8_t page[32][53]
Definition mrtk_has.h:80
uint8_t pid[32]
Definition mrtk_has.h:79
gtime_t t0
Definition mrtk_has.h:78
int iodref[MAXSAT]
Definition mrtk_has.h:69
int gnssid[MAXSAT]
Definition mrtk_has.h:60
int sat[MAXSAT]
Definition mrtk_has.h:59
int navmsg[MAXSAT]
Definition mrtk_has.h:64
int nsig[MAXSAT]
Definition mrtk_has.h:62
uint16_t cellmask[MAXSAT]
Definition mrtk_has.h:63
uint8_t code[MAXSAT][16]
Definition mrtk_has.h:61
int valid
Definition mrtk_has.h:56
has_collect_t collect[2]
Definition mrtk_has.h:84
has_mask_t mask[32]
Definition mrtk_has.h:85
ssr_t ssr[MAXSAT]
Definition mrtk_has.h:87
has_iodset_t iodset[32][32]
Definition mrtk_has.h:86
double dclk[3]
Definition mrtk_nav.h:185
double ddeph[3]
Definition mrtk_nav.h:184
double hrclk
Definition mrtk_nav.h:186
gtime_t t0[6]
Definition mrtk_nav.h:176
int iod[6]
Definition mrtk_nav.h:178
uint8_t update
Definition mrtk_nav.h:194
int vpbias[MAXCODE]
Definition mrtk_nav.h:191
int iode
Definition mrtk_nav.h:179
double deph[3]
Definition mrtk_nav.h:183
int vcbias[MAXCODE]
Definition mrtk_nav.h:190
double pbias[MAXCODE]
Definition mrtk_nav.h:188
float cbias[MAXCODE]
Definition mrtk_nav.h:187
double udi[6]
Definition mrtk_nav.h:177
int discnt[MAXCODE]
Definition mrtk_nav.h:192
#define calloc(x, y)
Definition toml.c:54
#define free(x)
Definition toml.c:53