Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,7 @@ region save
---

#### GPS anti-spoofing guard (When built with `GPS_GUARD`)
With `GPS_GUARD`, a fix becomes the node's last fix (position, telemetry, adverts) only after it passes plausibility checks: at least `GPS_GUARD_MIN_SATS` satellites, HDOP ≤ `GPS_GUARD_MAX_HDOP10`/10, inside the optional build-time region (`GPS_GUARD_LAT_MIN/MAX`, `GPS_GUARD_LON_MIN/MAX`), within the fence around the node's configured `lat`/`lon` (if set), no jump faster than `GPS_GUARD_MAX_SPEED_KMH`, and `GPS_GUARD_STABLE_FIXES` consistent fixes in a row. An unverified position is never reported.
With `GPS_GUARD`, a fix becomes the node's last fix (position, telemetry, adverts) only after it passes plausibility checks: a position on Earth (latitude within ±90°, longitude within ±180°), at least `GPS_GUARD_MIN_SATS` satellites, HDOP ≤ `GPS_GUARD_MAX_HDOP10`/10, inside the optional build-time region (`GPS_GUARD_LAT_MIN/MAX`, `GPS_GUARD_LON_MIN/MAX`), within the fence around the node's configured `lat`/`lon` (if set), no jump faster than `GPS_GUARD_MAX_SPEED_KMH`, and `GPS_GUARD_STABLE_FIXES` consistent fixes in a row. An unverified position is never reported.

GPS time is written to the clock only while such a fix is current, never earlier than the firmware build date, and, once the clock holds a plausible time (from the build date to 10 years after it), only within `GPS_GUARD_TIME_MAX_STEP` seconds for the first sync and within clock drift (`GPS_GUARD_TIME_DRIFT_BASE` s + `GPS_GUARD_TIME_DRIFT_PPM`) afterwards. Setting the clock manually (`time`, `clock sync`) resets that anchor. A clock outside that window (e.g. a bogus date in 2080) is corrected by the first trusted fix.

Expand Down
4 changes: 3 additions & 1 deletion src/helpers/sensors/GpsGuard.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ class GpsGuard {

const char* why = NULL;
double dist = 0;
if (sats < GPS_GUARD_MIN_SATS) {
if (lat_e6 < -90000000L || lat_e6 > 90000000L || lon_e6 < -180000000L || lon_e6 > 180000000L) {
why = "coord"; // not a position on Earth (e.g. a garbled sentence that still passes its checksum)
} else if (sats < GPS_GUARD_MIN_SATS) {
why = "sats";
} else if (hdop10 == 255 || hdop10 > GPS_GUARD_MAX_HDOP10) {
why = "hdop";
Expand Down
27 changes: 27 additions & 0 deletions test/mocks/RTClib.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#pragma once

#include <cstdint>
#include <cstdlib>
#include <cstring>

// Just enough of RTClib's DateTime for GpsGuard: the firmware build date as an epoch.
class DateTime {
uint32_t _unix = 0;

public:
// __DATE__ ("Mmm dd yyyy") and __TIME__ ("hh:mm:ss")
DateTime(const char* date, const char* time) {
static const char* months = "JanFebMarAprMayJunJulAugSepOctNovDec";
int mon = 1;
for (int i = 0; i < 12; ++i) if (strncmp(date, months + 3 * i, 3) == 0) mon = i + 1;
const int day = atoi(date + 4), year = atoi(date + 7);
// days from civil (Howard Hinnant)
const int y = year - (mon <= 2);
const int era = y / 400, yoe = y - era * 400;
const int doy = (153 * (mon + (mon > 2 ? -3 : 9)) + 2) / 5 + day - 1;
const int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
const long days = (long)era * 146097 + doe - 719468;
_unix = (uint32_t)(days * 86400L + atoi(time) * 3600L + atoi(time + 3) * 60L + atoi(time + 6));
}
uint32_t unixtime() const { return _unix; }
};
44 changes: 44 additions & 0 deletions test/test_gps_guard/test_gps_guard_range.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#include <gtest/gtest.h>
#include <helpers/sensors/GpsGuard.h>

// A fix is judged on its position alone here: 8 satellites, HDOP 0.9, no reference
// position, no earlier fix. Positions are in micro-degrees, as MicroNMEA reports them.
static bool rejectedAsCoord(long lat_e6, long lon_e6) {
GpsGuard guard;
guard.acceptFix(lat_e6, lon_e6, 8, 9, 1800000000UL);
char status[64];
guard.formatStatus(status, sizeof(status));
return strstr(status, "rej coord") != nullptr;
}

TEST(GpsGuardRange, ordinaryPositionIsNotRejected) {
EXPECT_FALSE(rejectedAsCoord(47268723L, -122559463L));
EXPECT_FALSE(rejectedAsCoord(0L, 0L));
}

TEST(GpsGuardRange, limitsThemselvesAreAccepted) {
EXPECT_FALSE(rejectedAsCoord(90000000L, 180000000L));
EXPECT_FALSE(rejectedAsCoord(-90000000L, -180000000L));
}

TEST(GpsGuardRange, latitudeOutsideRangeIsRejected) {
EXPECT_TRUE(rejectedAsCoord(90000001L, 0L));
EXPECT_TRUE(rejectedAsCoord(-90000001L, 0L));
EXPECT_TRUE(rejectedAsCoord(100666665L, 181650000L)); // what a garbled "9999.9999,N" reports
}

TEST(GpsGuardRange, longitudeOutsideRangeIsRejected) {
EXPECT_TRUE(rejectedAsCoord(0L, 180000001L));
EXPECT_TRUE(rejectedAsCoord(0L, -180000001L));
}

TEST(GpsGuardRange, guardOffAcceptsEverything) {
GpsGuard guard;
guard.setEnabled(false);
EXPECT_TRUE(guard.acceptFix(100666665L, 181650000L, 8, 9, 1800000000UL));
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}