# BNC regression tests Run BNC headless on fixed inputs and compare its outputs with stored reference outputs. No network access is needed. ``` tests/run_tests.py # all cases (parallel), exit code 1 on failure tests/run_tests.py ssr_ rinex_qc # only cases whose name contains a pattern tests/run_tests.py --list tests/run_tests.py --update NAME # accept current output as new reference # (removes references of outputs no longer produced) ``` Build `./bnc` first. Each case runs in `tests/work//`, which has an `Input` symlink to `Example_Configs/Input` and an empty `Output` directory, so the example configurations run unchanged. On a failure, the full diff is at `tests/work//.diff` and the exact command line is in `tests/work//command.txt`. ## Layout | Path | Content | |---|---| | `run_tests.py` | runner; the `case.json` format is documented in its header | | `cases//case.json` | config, extra `-key` arguments, raw capture, outputs to compare | | `cases//expected/*.gz` | normalised reference outputs | | `data/*.raw` | short raw captures (BNC `rawOutFile` format) replayed via `--file` | | `tools/fakecaster.py` | local Ntrip caster recording an upload as a BNC raw file (case key `fakecaster`); optionally TLS and serving a stream | | `tools/bncraw.py` | `info` (streams, time span, RTCM3 message types), `slice` and `crs` (decode 1300/1301/1302) for raw captures | | `tools/mkcrafted.py` | writes `data/crafted_limits.raw`: crafted frames at the upper field limits | | `tools/ssr_harness.sh` | builds `tools/ssr_harness.cpp` with the SSR decoders under ASan/UBSan and runs it | | `tools/ssl_check.py` | TLS checks of the Ntrip 2s client and upload (not part of `run_tests.py`) | | `work/` | scratch output, not versioned | Before comparing, the runner removes content that changes with each run or BNC version: wall-clock log prefixes, `Start BNC` lines, the BNC version string, the RINEX `PGM / RUN BY / DATE` line, and the `Run by`/`Date` lines in reqc logs. ## Cases * File-based examples: `rinex_concat`, `rinex_qc`, `sp3_compare`, `ppp_postproc`, `ppp_osm` (04, 05, 21, 11, 12), plus the command-line variants `*_cli` and `sp3_compare_exclude` (scripts 24, 23, 28). The QC cases use the 1 h BRUX files instead of the full-day file. * `rinex_edit_minele`: Edit/Concatenate with a 10 deg elevation mask (1 h BRUX files), covering the ephemeris selection for the elevation mask. * Stream replays of the real-time examples (`replay_*`), all from the 5 min slice `data/wtzr_cnes_5min.raw` (BCEP00BKG0, WTZR00DEU0, SSRA00CNE1): `replay_rinex_obs` (01, skeleton not mandatory as there is no caster offline), `replay_rinex_eph` (02), `replay_feed_engine` (07, IP port disabled) and `replay_save_sp3` (14, with SSRA00CNE1 instead of SSRA00BKG1). * `replay_save_sp3_has`: 14 with Galileo HAS (SSRA00EUH0) only; its epochs are off the SP3/CLK sampling grid and are written at the preceding grid epoch. * Real-time PPP replays (`ppp_rt_*`) from 11 min slices of the CNES and WHU captures (`data/wtzr_cnes_11min.raw`, `data/wtzr_whu_11min.raw`): `ppp_rt_float` (08), `ppp_rt_quickstart` (10, NMEA port disabled), `ppp_rt_ar_cnes` (29) and `ppp_rt_ar_whu` (30), compared exactly. * `ppp_rt_net` (09, two stations) and `ppp_rt_has` (13, Galileo HAS via SSRA00EUH0) from `data/merged_260930_11min.raw`, a slice of two simultaneous recordings merged by time (BCEP00BKG0, WTZR00DEU0, FFMJ00DEU0, SSRA00CNE1, SSRA00EUH0). * Combination (`combi`, `combi_ppp`; 17 and 18 unchanged) from `data/combi_261005_11min.raw` (SSRA00BKG1, SSRA00CNE1, SSRA00CHC1, BCEP00BKG0, FFMJ00DEU0), upload to the caster disabled, SP3/CLK/BIA written to files. * `combi_multi`: 17 with the 7 ACs of an operational combination (BKG1, CHC1, CNE1, GFZ1, GMV0, NRC0, SHA1; IGS-SSR and RTCM-SSR) from `data/combi_multi_261002_5min.raw`. Covers legacy and erroneous epoch encodings, missing reference-signal biases, inconsistent biases and persistent outliers. The longest case (about 4 min). * `combi_refsig`: `combi` with non-default reference signals (`ssrRefSignals C:1P&5P`): BDS clock referral and code biases from the Bias SINEX file on C1P/C5P (generic DCB derivation), CLK/BIA and the combination log compared. * Upload (`upload_*`; 16, 19, 20) from `data/upload_rtnet_5min.raw` (RTNET engine output + BCEP00BKG0): BNC uploads to a local fake caster (`tools/fakecaster.py`, Ntrip 1/2, chunked), and a `post` step decodes the received stream again: `upload_rtnet` (IGS-SSR encoding, SP3/CLK/BIA files, decoded `.ssr`, CRS messages 1300/1302 decoded by `bncraw.py crs` in a `tool` post step), `upload_eph` (RTCM3 ephemerides to RINEX nav) and `upload_raw` (raw forwarding to RINEX nav). `upload_rtnet_rtcm` encodes the same input to RTCM-SSR (message numbers 1057-1068, 1240-1270) and decodes it again. * SSR decoding (`ssr_*`): 3 min slices of real streams replayed through `03_BrdcCorr.bnc`. They compare the `.ssr` files and the `BNC_DEBUG_SSR` stderr trace. On a build without that trace, the trace comparison is reported as SKIP. * `ssr_igs_SSRA00CNE1`: IGS-SSR 4076, `SsrCorrIgs` * `ssr_rtcm_SSRA00CNE0`, `ssr_rtcm_WHU1_OSBC`: RTCM 1059-1270; these test whichever class `RTCM3Decoder.cpp` selects for non-4076 SSR. * `ssr_weekchange`: 2 min across the GPS week change (SSRA00CNE1, SSRA00CHC1); corrections of Saturday received after midnight must keep their epochs. * `ssr_qc`, `ssr_qc_select`: SSR QC (31_SsrQC, one file per stream, 5 min interval; all streams resp. `ssrQcStreams SSRA00CNE1,SSRA00GMV0`) of the 7 AC streams of `data/combi_multi_261002_5min.raw`. `ssr_qc_bias` adds the CAS DCB file as 'QC Bias File' (code bias check against the other ACs and the file). * `decoder_limits`: crafted, CRC-valid frames from `tools/mkcrafted.py` using the largest values the formats allow: VTEC with degree/order 16 (IGS-SSR 4076/201 and RTCM-SSR 1264, 2 and 4 layers) and RTCM 1300/1301/1302 with 31-character names, listed by the RTCM scan in the log. The coefficients are C(d,o) = (100*d+o)/200 TECU and S = -C, both `.ssr` files are identical apart from the station ID. These frames overflowed the coefficient and name arrays; the 4-layer VTEC crashed BNC. ## Sanitizer check of the SSR decoders ``` tests/tools/ssr_harness.sh # crafted_limits.raw tests/tools/ssr_harness.sh tests/data/*.raw # real captures ``` In BNC all SSR target structs are members of one `RTCM3coDecoder` object, so a write beyond one struct corrupts the next member without any AddressSanitizer report. The harness decodes every frame with all three format classes into separately allocated structs, so ASan reports it. It also covers the new RTCM-SSR metadata (message 60 in `crafted_limits.raw`: 7 entries with additional parameters of 40 and 0 bits, and 31 entries, which must be rejected), which `RTCM3Decoder.cpp` does not dispatch at present. Every decoded VTEC and metadata message is encoded again with the `Make*` function of the same class and decoded once more; the result must be identical. Exit code 0 only without sanitizer errors and differences. ## TLS check ``` tests/tools/ssl_check.py [--bnc PATH] ``` Runs BNC against `tools/fakecaster.py` with TLS on port 443 (BNC uploads with Ntrip 2s always to 443) inside an unprivileged user and network namespace (`unshare -rn`), with a test CA, server and client certificates created by openssl in `work/ssl_check/`. Checks for upload and download: certificate of an unknown CA rejected, accepted with the CA in `sslCaCertPath`, accepted with `sslIgnoreErrors` and the ignored errors logged; for upload also a certificate for another host name (rejected) and a client certificate required by the caster, with and without `sslIgnoreErrors`. Takes about 3 min. ## Adding a replay case ``` tests/tools/bncraw.py info capture.raw tests/tools/bncraw.py slice capture.raw tests/data/new.raw --stations SSRA00XYZ0 \ --start 2026-09-28T12:00:00 --end 2026-09-28T12:03:00 mkdir tests/cases/new && $EDITOR tests/cases/new/case.json # copy an ssr_* case tests/run_tests.py --update new ``` Before accepting a reference with `--update`, check it by hand; for decoder cases, hand-decode the raw bits. The reference records what BNC does, not what is correct. ## Reproducibility of replays In batch post-processing (`--file`), BNC delivers decoded ephemerides and corrections synchronously to the PPP (`t_bncCore::decoderConnectionType`) and starts reading only after all PPP instances are connected (`t_pppThread::waitUntilReady`). Replays are therefore reproducible run to run, including PPP with ambiguity resolution. Live operation is not affected.