diff --git a/app/consapp/convbin/convbin.c b/app/consapp/convbin/convbin.c index 6e11b4be9..f4be47d2a 100644 --- a/app/consapp/convbin/convbin.c +++ b/app/consapp/convbin/convbin.c @@ -114,6 +114,9 @@ static const char *help[]={ " -l lfile output RINEX LNAV file", " -s sfile output SBAS message file", " -trace level output trace level [off]", +" -safeephemtime refuse to use wall clock and refuse to increment", +" ephemeris week numbers when decoding ephemerides;", +" requires -tr; affects RTCM3 and SBP eph decoders", "", " If not any output file is specified, default output files .obs,", " .nav, .gnav, .hnav, .qnav, .lnav and", @@ -337,7 +340,8 @@ static int get_filetime(const char *file, gtime_t *time) } /* parse command line options ------------------------------------------------*/ static int cmdopts(int argc, char **argv, rnxopt_t *opt, char **ifile, - char **ofile, char **dir, int *trace) + char **ofile, char **dir, int *trace, int *safeephemtime, + int *has_tr) { double eps[]={1980,1,1,0,0,0},epe[]={2037,12,31,0,0,0}; double epr[]={2010,1,1,0,0,0},span=0.0; @@ -365,6 +369,7 @@ static int cmdopts(int argc, char **argv, rnxopt_t *opt, char **ifile, sscanf(argv[++i],"%lf/%lf/%lf",epr,epr+1,epr+2); sscanf(argv[++i],"%lf:%lf:%lf",epr+3,epr+4,epr+5); opt->trtcm=epoch2time(epr); + *has_tr=1; } else if (!strcmp(argv[i],"-ti")&&i+1tint=atof(argv[++i]); @@ -485,6 +490,9 @@ static int cmdopts(int argc, char **argv, rnxopt_t *opt, char **ifile, else if (!strcmp(argv[i],"-trace" )&&i+10)?" -SAFEEPHEMTIME":"-SAFEEPHEMTIME"; + toklen=strlen(tok); + if (used+toklen+1>sizeof(opt.rcvopt)) { + fprintf(stderr,"receiver option string too long to add -SAFEEPHEMTIME\n"); + return -1; + } + memcpy(opt.rcvopt+used,tok,toklen+1); + } + if (trace>0) { traceopen(TRACEFILE); tracelevel(trace); diff --git a/app/consapp/convbin/gcc/makefile b/app/consapp/convbin/gcc/makefile index ee3244265..3fc081b15 100644 --- a/app/consapp/convbin/gcc/makefile +++ b/app/consapp/convbin/gcc/makefile @@ -129,7 +129,9 @@ install: clean: rm -f sbp2rinex sbp2rinex.exe *.o *.obs *.nav *.gnav *.hnav *.qnav *.sbs *.stackdump -test : test1 test2 test3 test4 test5 test6 testclean +test : test1 test2 test3 test4 test5 test6 \ + test_safeephem_guard test_safeephem_sbp_anchors test_safeephem_rtcm3_anchors \ + testclean test1: # binary sbp @echo -e "\n\n****** Test1: sbp binary ********\n" @@ -155,5 +157,42 @@ test6: # BDS + GAL + GLO + GPS diff --strip-trailing-cr ./2018-06-15-json.trunc.obs $(shell pwd)$(SEP)$(DATDIR)$(SEP)2018-06-15-json-ref.obs tail -n +5 ./2018-06-15-json.nav > 2018-06-15-json.trunc.nav diff --strip-trailing-cr ./2018-06-15-json.trunc.nav $(shell pwd)$(SEP)$(DATDIR)$(SEP)2018-06-15-json-ref.nav +test_safeephem_guard: + @echo -e "\n\n****** test_safeephem_guard: -safeephemtime without -tr ******\n" + @if ./sbp2rinex -safeephemtime $(DATDIR)$(SEP)2017-05-12-v1.1.26.sbp \ + -o se_guard.obs >/dev/null 2>&1; then \ + echo "FAIL: expected non-zero exit"; exit 1; \ + else \ + echo "OK: rejected as expected"; \ + fi + +test_safeephem_sbp_anchors: + @echo -e "\n\n****** test_safeephem_sbp_anchors: 2017 SBP fixture anchored to 2017 ******\n" + ./sbp2rinex -safeephemtime -tr 2017/05/12 0:0:0 \ + $(DATDIR)$(SEP)2017-05-12-v1.1.26.sbp \ + -o se_sbp.obs -n se_sbp.nav + @if ! grep -qE '^[A-Z][0-9]+ +2017 ' se_sbp.nav; then \ + echo "FAIL: no 2017 epochs in SBP nav file"; exit 1; \ + fi + @if grep -qE '^[A-Z][0-9]+ +202[5-9] ' se_sbp.nav; then \ + echo "FAIL: SBP nav contains current-era epochs (rollover regression)"; exit 1; \ + fi + @echo "OK: SBP nav epochs anchored to 2017" + +test_safeephem_rtcm3_anchors: + @echo -e "\n\n****** test_safeephem_rtcm3_anchors: 2026 IGS RTCM3 fixture ******\n" + ./sbp2rinex -r rtcm3 -safeephemtime -tr 2026/05/01 0:0:0 \ + $(DATDIR)$(SEP)igs_ephem_2026-05-01.rtcm \ + -o se_rtcm.obs -n se_rtcm.nav + @# C45's first eph in this fixture is at BDS week 1058, toe 597600s, + @# more than 3.5 days from the wall clock. Legacy code nudges + @# eph.week++ producing "2026 04 25"; safemode preserves on-wire bits + @# producing "2026 04 18". + @if ! grep -q '^C45 2026 04 18 22 00 00' se_rtcm.nav; then \ + echo "FAIL: C45 first eph not at 2026 04 18 22 00 00"; \ + grep '^C45' se_rtcm.nav | head -2; exit 1; \ + fi + @echo "OK: C45 first eph at 2026 04 18 22 00 00 (on-wire week preserved)" + testclean: rm -f *.o *.obs *.nav *.gnav *.hnav *.qnav *.sbs diff --git a/src/convrnx.c b/src/convrnx.c index 5abb49526..a86100c15 100644 --- a/src/convrnx.c +++ b/src/convrnx.c @@ -1275,7 +1275,8 @@ static int convrnx_s(int sess, int format, rnxopt_t *opt, const char *file, for (i=0;ircvopt,"-SAFEEPHEMTIME")) { str->time=opt->trtcm; } else if (opt->ts.time) { diff --git a/src/rcv/swiftnav.c b/src/rcv/swiftnav.c index cf1572eab..a0bd5996a 100644 --- a/src/rcv/swiftnav.c +++ b/src/rcv/swiftnav.c @@ -666,7 +666,8 @@ static void decode_gpsnav_common_dep1(uint8_t *_pBuff, eph_t *_pEph) { _pEph->iode = U1(_pBuff + 182); _pEph->iodc = U2(_pBuff + 183); - _pEph->week = adjgpsweek(uWeekE); + /* SBP carries a full 16-bit GPS week; trust it directly. */ + _pEph->week = uWeekE; _pEph->toe = gpst2time(_pEph->week, _pEph->toes); _pEph->toc = gpst2time(uWeekC, dToc); } @@ -711,7 +712,8 @@ static void decode_gpsnav_common(uint8_t *_pBuff, eph_t *_pEph) { _pEph->iode = U1(_pBuff + 138); _pEph->iodc = U2(_pBuff + 139); - _pEph->week = adjgpsweek(uWeekE); + /* SBP carries a full 16-bit GPS week; trust it directly. */ + _pEph->week = uWeekE; _pEph->code = 2; /* SBP payload does not have the "code on L2" flag */ _pEph->toe = gpst2time(_pEph->week, _pEph->toes); _pEph->toc = gpst2time(uWeekC, dToc); @@ -757,8 +759,11 @@ static void decode_bdsnav_common(uint8_t *_pBuff, eph_t *_pEph) { _pEph->iode = U1(_pBuff + 146); _pEph->iodc = U2(_pBuff + 147); - _pEph->week = adjgpsweek(uWeekE) - BDS_WEEK_TO_GPS_WEEK; - _pEph->toe = gpst2time(_pEph->week, _pEph->toes); + /* SBP carries full GPS week numbers for BDS messages. Internal rtklib + BDS convention: eph.week is BDS-relative; eph.toe/eph.toc are GPST + gtime_t derived from BDT (week, tow) -- matches rtcm3.c MT 1042. */ + _pEph->week = uWeekE - BDS_WEEK_TO_GPS_WEEK; + _pEph->toe = bdt2gpst(bdt2time(_pEph->week, _pEph->toes)); _pEph->toc = gpst2time(uWeekC, dToc); } @@ -802,7 +807,8 @@ static void decode_galnav_common(uint8_t *_pBuff, eph_t *_pEph) { _pEph->iode = U2(_pBuff + 150); _pEph->iodc = U2(_pBuff + 152); - _pEph->week = adjgpsweek(uWeekE); + /* SBP carries a full 16-bit GPS week; trust it directly. */ + _pEph->week = uWeekE; _pEph->toe = gpst2time(_pEph->week, _pEph->toes); _pEph->toc = gpst2time(uWeekC, dToc); } @@ -835,10 +841,11 @@ static int decode_gpsnav_dep_e(raw_t *raw) { decode_gpsnav_common_dep1(puiTmp, &eph); - if (0 == timediff(raw->time, time0)) { - eph.ttr = timeget(); - } else { + if (strstr(raw->opt, "-SAFEEPHEMTIME") || + timediff(raw->time, time0) != 0) { eph.ttr = raw->time; + } else { + eph.ttr = timeget(); } if (!strstr(raw->opt, "EPHALL")) { @@ -897,10 +904,11 @@ static int decode_gpsnav_dep_f(raw_t *raw) { decode_gpsnav_common_dep1(puiTmp - 2, &eph); - if (0 == timediff(raw->time, time0)) { - eph.ttr = timeget(); - } else { + if (strstr(raw->opt, "-SAFEEPHEMTIME") || + timediff(raw->time, time0) != 0) { eph.ttr = raw->time; + } else { + eph.ttr = timeget(); } if (!strstr(raw->opt, "EPHALL")) { @@ -952,10 +960,11 @@ static int decode_gpsnav(raw_t *raw) { decode_gpsnav_common(puiTmp - 2, &eph); - if (0 == timediff(raw->time, time0)) { - eph.ttr = timeget(); - } else { + if (strstr(raw->opt, "-SAFEEPHEMTIME") || + timediff(raw->time, time0) != 0) { eph.ttr = raw->time; + } else { + eph.ttr = timeget(); } if (!strstr(raw->opt, "EPHALL")) { @@ -1015,10 +1024,11 @@ static int decode_qzssnav(raw_t *raw) { decode_gpsnav_common(puiTmp - 2, &eph); - if (0 == timediff(raw->time, time0)) { - eph.ttr = timeget(); - } else { + if (strstr(raw->opt, "-SAFEEPHEMTIME") || + timediff(raw->time, time0) != 0) { eph.ttr = raw->time; + } else { + eph.ttr = timeget(); } if (!strstr(raw->opt, "EPHALL")) { diff --git a/src/rtcm3.c b/src/rtcm3.c index 6446e450f..33cecd946 100644 --- a/src/rtcm3.c +++ b/src/rtcm3.c @@ -64,6 +64,11 @@ #define P2_59 1.734723475976810E-18 /* 2^-59 */ #define P2_66 1.355252715606880E-20 /* 2^-66 */ +/* Threshold (seconds) for the safemode stale-eph trace warning. Independent + of the legacy +/-1 week nudge boundary (302400s = 3.5 days), which this + PR removes from the safemode path. */ +#define SAFEEPHEM_STALE_THRESHOLD_S (86400.0*3.0) + /* type definition -----------------------------------------------------------*/ typedef struct { /* multi-signal-message header type */ @@ -181,12 +186,27 @@ static void adjweek(rtcm_t *rtcm, double tow) rtcm->time=gpst2time(week,tow); } /* adjust weekly rollover of BDS time ----------------------------------------*/ +/* BDS WN field on the wire is 13 bits (range 0..8191); mod-8192 matches that. + See rtklibexplorer/RTKLIB@1b6036e. */ static int adjbdtweek(int week) { int w; (void)time2bdt(gpst2bdt(utc2gpst(timeget())),&w); if (w<1) w=1; /* use 2006/1/1 if time is earlier than 2006/1/1 */ - return week+(w-week+512)/1024*1024; + return week+(w-week+4095)/8192*8192; +} +/* Trace a SAFEEPHEMTIME warning if the decoded TOE is too far from the + current rtcm reference time. No-op when safemode is off. */ +static void trace_safeephem_stale(int safeephem, gtime_t toe, gtime_t time, + int prn, int msg_id) +{ + double tt; + if (!safeephem) return; + tt=timediff(toe,time); + if (fabs(tt)>SAFEEPHEM_STALE_THRESHOLD_S) { + trace(2,"rtcm3 %d -SAFEEPHEMTIME: stale eph prn=%d age=%.1f days\n", + msg_id,prn,-tt/86400.0); + } } /* adjust daily rollover of GLONASS time -------------------------------------*/ static void adjday_glot(rtcm_t *rtcm, double tod) @@ -796,13 +816,23 @@ static int decode_type1019(rtcm_t *rtcm) return -1; } eph.sat=sat; - eph.week=adjgpsweek(week); - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=gpst2time(eph.week,eph.toes); - eph.toc=gpst2time(eph.week,toc); + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1019 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + eph.week=safeephem?adjgpsweek_ref(week,rtcm->time):adjgpsweek(week); + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=gpst2time(eph.week,eph.toes); + eph.toc=gpst2time(eph.week,toc); + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1019); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; if (!strstr(rtcm->opt,"-EPHALL")) { @@ -1113,13 +1143,23 @@ static int decode_type1041(rtcm_t *rtcm) return -1; } eph.sat=sat; - eph.week=adjgpsweek(week); - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=gpst2time(eph.week,eph.toes); - eph.toc=gpst2time(eph.week,toc); + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1041 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + eph.week=safeephem?adjgpsweek_ref(week,rtcm->time):adjgpsweek(week); + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=gpst2time(eph.week,eph.toes); + eph.toc=gpst2time(eph.week,toc); + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1041); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; eph.iodc=eph.iode; @@ -1186,13 +1226,23 @@ static int decode_type1044(rtcm_t *rtcm) return -1; } eph.sat=sat; - eph.week=adjgpsweek(week); - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=gpst2time(eph.week,eph.toes); - eph.toc=gpst2time(eph.week,toc); + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1044 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + eph.week=safeephem?adjgpsweek_ref(week,rtcm->time):adjgpsweek(week); + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=gpst2time(eph.week,eph.toes); + eph.toc=gpst2time(eph.week,toc); + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1044); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; eph.flag=1; /* fixed to 1 */ @@ -1265,12 +1315,22 @@ static int decode_type1045(rtcm_t *rtcm) } eph.sat=sat; eph.week=week+1024; /* gal-week = gst-week + 1024 */ - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=gpst2time(eph.week,eph.toes); - eph.toc=gpst2time(eph.week,toc); + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1045 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=gpst2time(eph.week,eph.toes); + eph.toc=gpst2time(eph.week,toc); + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1045); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; eph.svh=(e5a_hs<<4)+(e5a_dvs<<3); @@ -1346,12 +1406,22 @@ static int decode_type1046(rtcm_t *rtcm) } eph.sat=sat; eph.week=week+1024; /* gal-week = gst-week + 1024 */ - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=gpst2time(eph.week,eph.toes); - eph.toc=gpst2time(eph.week,toc); + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1046 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(gpst2time(eph.week,eph.toes),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=gpst2time(eph.week,eph.toes); + eph.toc=gpst2time(eph.week,toc); + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1046); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; eph.svh=(e5b_hs<<7)+(e5b_dvs<<6)+(e1_hs<<1)+(e1_dvs<<0); @@ -1419,13 +1489,25 @@ static int decode_type1042(rtcm_t *rtcm) return -1; } eph.sat=sat; - eph.week=adjbdtweek(week); - if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); - tt=timediff(bdt2gpst(bdt2time(eph.week,eph.toes)),rtcm->time); - if (tt<-302400.0) eph.week++; - else if (tt>=302400.0) eph.week--; - eph.toe=bdt2gpst(bdt2time(eph.week,eph.toes)); /* bdt -> gpst */ - eph.toc=bdt2gpst(bdt2time(eph.week,toc)); /* bdt -> gpst */ + { + int safeephem=strstr(rtcm->opt,"-SAFEEPHEMTIME")!=NULL; + if (safeephem&&rtcm->time.time==0) { + trace(2,"rtcm3 1042 -SAFEEPHEMTIME: rtcm->time not set; rejecting eph\n"); + return -1; + } + /* BDS WN in MT1042 is 13 bits, unambiguous for ~140 years; under + -SAFEEPHEMTIME use it directly without rollover resolution. */ + eph.week=safeephem?week:adjbdtweek(week); + if (!safeephem) { + if (rtcm->time.time==0) rtcm->time=utc2gpst(timeget()); + tt=timediff(bdt2gpst(bdt2time(eph.week,eph.toes)),rtcm->time); + if (tt<-302400.0) eph.week++; + else if (tt>=302400.0) eph.week--; + } + eph.toe=bdt2gpst(bdt2time(eph.week,eph.toes)); /* bdt -> gpst */ + eph.toc=bdt2gpst(bdt2time(eph.week,toc)); /* bdt -> gpst */ + trace_safeephem_stale(safeephem,eph.toe,rtcm->time,prn,1042); + } eph.ttr=rtcm->time; eph.A=sqrtA*sqrtA; if (!strstr(rtcm->opt,"-EPHALL")) { diff --git a/src/rtkcmn.c b/src/rtkcmn.c index 9a113be1e..092ed83b4 100644 --- a/src/rtkcmn.c +++ b/src/rtkcmn.c @@ -1840,6 +1840,32 @@ extern int adjgpsweek(int week) if (w<1560) w=1560; /* use 2009/12/1 if time is earlier than 2009/12/1 */ return week+(w-week+1)/1024*1024; } +/* adjust 10-bit gps week using a caller-supplied reference time -------------- +* takes the reference time explicitly so callers can avoid timeget(). intended +* for offline replay where the user supplies a known epoch (e.g. -tr). +* +* Uses round-to-nearest centering (+512) rather than the past-biased +1 used +* by adjgpsweek. The legacy +1 formula gives error window (-1023, +1] weeks +* -- fine for live receivers (eph TOE <= current week) but breaks on offline +* captures whose ephs are valid into the *next* week: a 10-bit field of +* ((ref_w + 1) mod 1024 + 1) gets rolled 1024 weeks back to the previous +* rollover (e.g. ref_w=2415, input=369 -> 1393 = year 2006) instead of +* forward by one week. Centering via +512 gives error window (-512, +512], +* which handles forward-broadcast ephs symmetrically. Caveat: for ephs +* >9.8 years older than -tr, +512 rounds toward the future side; callers +* with truly archival data should set -tr to match the data's epoch. +*-----------------------------------------------------------------------------*/ +extern int adjgpsweek_ref(int week, gtime_t ref) +{ + int w,adj; + (void)time2gpst(ref,&w); + adj=(w-week+512)/1024; + if (adj!=0) { + trace(3,"adjgpsweek_ref: rolled raw=%d -> full=%d (ref_w=%d)\n", + week,week+adj*1024,w); + } + return week+adj*1024; +} /* get tick time --------------------------------------------------------------- * get current tick in ms * args : none diff --git a/src/rtklib.h b/src/rtklib.h index 87d28f830..5dc50f4c9 100644 --- a/src/rtklib.h +++ b/src/rtklib.h @@ -1382,6 +1382,7 @@ EXPORT double utc2gmst (gtime_t t, double ut1_utc); EXPORT int read_leaps(const char *file); EXPORT int adjgpsweek(int week); +EXPORT int adjgpsweek_ref(int week, gtime_t ref); EXPORT uint32_t tickget(void); EXPORT void sleepms(int ms); diff --git a/test/data/rcvraw/igs_ephem_2026-05-01.rtcm b/test/data/rcvraw/igs_ephem_2026-05-01.rtcm new file mode 100644 index 000000000..054fac5a7 Binary files /dev/null and b/test/data/rcvraw/igs_ephem_2026-05-01.rtcm differ