#include #include #include #include #include "gps/gps_ephemeris_validity.h" #define WEEK_SEC 604800.0 static gps_ephemeris_t make_ephemeris(unsigned int prn, unsigned int iodc) { gps_ephemeris_t eph; memset(&eph, 0, sizeof(eph)); eph.SVID = (unsigned char)prn; eph.ephemeris_valid = true; eph.iodc = iodc; eph.iode = (unsigned char)(iodc & 0xffu); eph.sva = 2u; eph.svhlth = 0u; eph.update = 0u; eph.sqrta = 5153.6; eph.A = eph.sqrta * eph.sqrta; eph.ecc = 0.008; eph.toe.sec = 172800.0; eph.toc.sec = 172800.0; eph.WN = 430u; eph.tow.week = 2430; /* Resolved from WN and the handover word by gps_resolve_toe_week(). */ eph.toe.week = 2430; eph.toe_week_valid = true; return eph; } static void test_fit_interval_table(void) { /* Fit flag clear is always the nominal four hour curve fit. */ assert(gps_ephemeris_fit_interval_hours(0u, false) == 4.0); assert(gps_ephemeris_fit_interval_hours(500u, false) == 4.0); /* IS-GPS-200 Table 20-XII ranges. */ assert(gps_ephemeris_fit_interval_hours(240u, true) == 8.0); assert(gps_ephemeris_fit_interval_hours(247u, true) == 8.0); assert(gps_ephemeris_fit_interval_hours(248u, true) == 14.0); assert(gps_ephemeris_fit_interval_hours(496u, true) == 14.0); assert(gps_ephemeris_fit_interval_hours(497u, true) == 26.0); assert(gps_ephemeris_fit_interval_hours(504u, true) == 50.0); assert(gps_ephemeris_fit_interval_hours(511u, true) == 74.0); /* Untabulated IODC with the flag set falls back conservatively, below * the shortest long fit the ICD defines. */ double fallback = gps_ephemeris_fit_interval_hours(100u, true); assert(fallback == GPS_EPHEMERIS_LONG_FIT_FALLBACK_HOURS); assert(fallback < 8.0); } static void test_static_rules(void) { gps_ephemeris_t eph = make_ephemeris(7u, 40u); assert(gps_ephemeris_check_static(&eph, 7u) == GPS_EPHEMERIS_OK); assert(gps_ephemeris_check_static(NULL, 7u) == GPS_EPHEMERIS_REJECT_NULL); assert(gps_ephemeris_check_static(&eph, 8u) == GPS_EPHEMERIS_REJECT_PRN); gps_ephemeris_t uncommitted = eph; uncommitted.ephemeris_valid = false; assert(gps_ephemeris_check_static(&uncommitted, 7u) == GPS_EPHEMERIS_REJECT_UNCOMMITTED); /* IODE must equal the eight LSBs of IODC. */ gps_ephemeris_t mismatched = eph; mismatched.iode = (unsigned char)(mismatched.iode + 1u); assert(gps_ephemeris_check_static(&mismatched, 7u) == GPS_EPHEMERIS_REJECT_IOD); /* A ten bit IODC whose low byte matches is accepted. */ gps_ephemeris_t wide_iodc = make_ephemeris(7u, 0x123u); assert(wide_iodc.iode == 0x23u); assert(gps_ephemeris_check_static(&wide_iodc, 7u) == GPS_EPHEMERIS_OK); gps_ephemeris_t unhealthy = eph; unhealthy.svhlth = 0x20u; assert(gps_ephemeris_check_static(&unhealthy, 7u) == GPS_EPHEMERIS_REJECT_HEALTH); gps_ephemeris_t no_accuracy = eph; no_accuracy.sva = 15u; assert(gps_ephemeris_check_static(&no_accuracy, 7u) == GPS_EPHEMERIS_REJECT_ACCURACY); gps_ephemeris_t bad_orbit = eph; bad_orbit.ecc = 1.5; assert(gps_ephemeris_check_static(&bad_orbit, 7u) == GPS_EPHEMERIS_REJECT_PARAMETERS); gps_ephemeris_t bad_toe = eph; bad_toe.toe.sec = WEEK_SEC; assert(gps_ephemeris_check_static(&bad_toe, 7u) == GPS_EPHEMERIS_REJECT_PARAMETERS); } static void test_fit_window(void) { gps_ephemeris_t eph = make_ephemeris(7u, 40u); const double toe = eph.toe.sec; /* Nominal four hour fit: usable for two hours either side of Toe. */ assert(gps_ephemeris_max_toe_offset_sec(&eph) == 7200.0); assert(gps_ephemeris_check(&eph, 7u, true, 2430, toe) == GPS_EPHEMERIS_OK); assert(gps_ephemeris_check(&eph, 7u, true, 2430, toe + 7199.0) == GPS_EPHEMERIS_OK); assert(gps_ephemeris_check(&eph, 7u, true, 2430, toe - 7199.0) == GPS_EPHEMERIS_OK); assert(gps_ephemeris_check(&eph, 7u, true, 2430, toe + 7201.0) == GPS_EPHEMERIS_REJECT_AGE); assert(gps_ephemeris_check(&eph, 7u, true, 2430, toe - 7201.0) == GPS_EPHEMERIS_REJECT_AGE); /* An extended fit interval widens the same window. */ gps_ephemeris_t extended = make_ephemeris(7u, 250u); extended.fit_flag = true; assert(gps_ephemeris_max_toe_offset_sec(&extended) == 14.0 * 1800.0); assert(gps_ephemeris_check(&extended, 7u, true, 2430, toe + 20000.0) == GPS_EPHEMERIS_OK); /* Without receiver time only the static rules apply, so a boot-time * flash record stays a usable candidate. */ assert(gps_ephemeris_check(&eph, 7u, false, 0, 0.0) == GPS_EPHEMERIS_OK); } static void test_week_rollover(void) { /* Toe 600 s before the end of the week, receiver time 600 s after the * rollover: the true age is 1200 s, not almost a full week. */ gps_ephemeris_t eph = make_ephemeris(11u, 60u); eph.toe.sec = WEEK_SEC - 600.0; double offset = 0.0; assert(gps_ephemeris_toe_offset_sec(&eph, 2431, 600.0, &offset)); assert(fabs(offset - 1200.0) < 1e-6); assert(gps_ephemeris_check(&eph, 11u, true, 2431, 600.0) == GPS_EPHEMERIS_OK); /* The same record is stale eight hours into the new week. */ assert(gps_ephemeris_check(&eph, 11u, true, 2431, 28800.0) == GPS_EPHEMERIS_REJECT_AGE); /* Toe just after a rollover with receiver time just before it gives a * small negative age rather than a week-long one. */ gps_ephemeris_t fresh = make_ephemeris(11u, 60u); fresh.toe.sec = 600.0; /* Toe belongs to the week after the one it was broadcast in. */ fresh.toe.week = 2431; assert(gps_ephemeris_toe_offset_sec(&fresh, 2430, WEEK_SEC - 600.0, &offset)); assert(fabs(offset + 1200.0) < 1e-6); assert(gps_ephemeris_check(&fresh, 11u, true, 2430, WEEK_SEC - 600.0) == GPS_EPHEMERIS_OK); } /* * Toe is a second-of-week, so before its week was resolved the age check could * only work modulo one week: a record exactly N weeks old, evaluated at a * similar time of week, read as brand new. While ephemeris lives in RAM and * expires in hours that is unreachable. Persisting it to flash makes it * directly reachable - switch the receiver off and on again a week later at a * similar hour and it would accept its own week-old orbits as current, which * is worth kilometres of position error with nothing to flag it. */ static void test_whole_week_staleness_is_not_invisible(void) { gps_ephemeris_t eph = make_ephemeris(11u, 60u); double offset = 0.0; /* Same second of week, one week on. */ assert(gps_ephemeris_toe_offset_sec(&eph, 2431, eph.toe.sec, &offset)); assert(fabs(offset - WEEK_SEC) < 1e-6); assert(gps_ephemeris_check(&eph, 11u, true, 2431, eph.toe.sec) == GPS_EPHEMERIS_REJECT_AGE); /* And four weeks on, which is what a shelved receiver comes back to. */ assert(gps_ephemeris_check(&eph, 11u, true, 2434, eph.toe.sec) == GPS_EPHEMERIS_REJECT_AGE); /* A record whose week could not be resolved has an unknown vintage and * must be refused rather than assumed current. */ gps_ephemeris_t unresolved = make_ephemeris(11u, 60u); unresolved.toe_week_valid = false; assert(!gps_ephemeris_toe_offset_sec(&unresolved, 2430, unresolved.toe.sec + 60.0, &offset)); assert(gps_ephemeris_check(&unresolved, 11u, true, 2430, unresolved.toe.sec + 60.0) == GPS_EPHEMERIS_REJECT_AGE); /* Without receiver time it is still a usable cold-start candidate; the * epoch gate re-checks it the moment time exists. */ assert(gps_ephemeris_check(&unresolved, 11u, false, 0, 0.0) == GPS_EPHEMERIS_OK); printf(" whole-week staleness is visible to the age check\n"); } static void test_supersede(void) { gps_ephemeris_t current = make_ephemeris(19u, 40u); gps_ephemeris_t repeat = current; /* The same data set decoded again is not new information. */ assert(!gps_ephemeris_supersedes(&repeat, ¤t)); /* Health or transmitted accuracy can change inside one issue of data. */ gps_ephemeris_t health_change = current; health_change.svhlth = 0x3fu; assert(gps_ephemeris_supersedes(&health_change, ¤t)); /* A newer upload with a later Toe replaces it. */ gps_ephemeris_t newer = make_ephemeris(19u, 41u); newer.toe.sec = current.toe.sec + 7200.0; assert(gps_ephemeris_supersedes(&newer, ¤t)); /* An older Toe with a different IODE does not: that is a replayed or * out-of-order decode. */ gps_ephemeris_t older = make_ephemeris(19u, 39u); older.toe.sec = current.toe.sec - 7200.0; assert(!gps_ephemeris_supersedes(&older, ¤t)); /* Toe advancing across the week boundary still counts as newer. */ gps_ephemeris_t before_rollover = make_ephemeris(19u, 42u); before_rollover.toe.sec = WEEK_SEC - 1800.0; gps_ephemeris_t after_rollover = make_ephemeris(19u, 43u); after_rollover.toe.sec = 1800.0; assert(gps_ephemeris_supersedes(&after_rollover, &before_rollover)); assert(!gps_ephemeris_supersedes(&before_rollover, &after_rollover)); /* Nothing supersedes across PRNs, and anything replaces an empty slot. */ gps_ephemeris_t other_prn = make_ephemeris(20u, 41u); assert(!gps_ephemeris_supersedes(&other_prn, ¤t)); assert(gps_ephemeris_supersedes(¤t, NULL)); assert(!gps_ephemeris_supersedes(NULL, ¤t)); } int main(void) { test_fit_interval_table(); test_static_rules(); test_fit_window(); test_week_rollover(); test_whole_week_staleness_is_not_invisible(); test_supersede(); puts("gps ephemeris validity tests passed"); return 0; }