/* * Malformed/edge-case NMEA regression test. * * Feeds soft/nmea.c's real gps_parse() (through the same PC_BUILD shim as * the rest of this test tool) a fixture of real and synthetic sentences * that are known to be awkward: invalid epochs from an actual hike log, * truncated/bad-checksum/short-dialect sentences, and a run long enough to * exercise the HDOP-based speed-sample gating added after that hike showed * a single poor-quality fix (HDOP 16, 44.8 km/h) skewing the whole-session * average. * * Nothing in malformed_test.nmea should crash the parser (run under a tool * like valgrind or ASan for a stronger guarantee than "the process exited * zero"), and the checks below assert the speed average is actually left * alone by the invalid/poor-quality samples, and is capped rather than * excluded for a genuinely fast, good-quality one. * * Usage: ./test-malformed [malformed_test.nmea] */ #define PC_BUILD #include "main.h" volatile struct system_s System; struct location_s location; time_t utc; unsigned char FLAGS = 0; volatile unsigned char reset_cause = 0; FILE *debug_output = NULL; __flash const char gnss_name_stub[] = "n/a"; __flash const char *gnss_names[] = { gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub, gnss_name_stub }; __flash const char uart_log_mode_stub[] = "n/a"; __flash const char *uart_log_mode_names[] = { uart_log_mode_stub, uart_log_mode_stub, uart_log_mode_stub }; time_t gps_parse(const char *nmea); char *get_iso_time(time_t time, unsigned char local) { (void)local; static char output[32]; struct tm *ct = gmtime(&time); sprintf(output, "%04d-%02d-%02dT%02d:%02d:%02d.000Z", ct->tm_year + 1900, ct->tm_mon + 1, ct->tm_mday, ct->tm_hour, ct->tm_min, ct->tm_sec); return output; } void iso_time_to_filename(char *time) { (void)time; } static int failures; static void check(int cond, const char *what) { if (!cond) { fprintf(stderr, "FAIL: %s\n", what); failures++; } else { printf("ok: %s\n", what); } } /* Recognize the known-invalid lines from malformed_test.nmea's section 1, * to check precisely that they leave the speed average alone - not just * that nothing crashes. */ static int is_invalid_epoch_line(const char *line) { return strstr(line, ",V,,,,,") != NULL /* RMC: empty lat/lon, status V */ || (strstr(line, "VTG") && strstr(line, ",N*")); /* VTG: mode indicator N (not valid) */ } int main(int argc, char *argv[]) { const char *path = argc > 1 ? argv[1] : "malformed_test.nmea"; FILE *f = fopen(path, "r"); char raw[512]; char line[512]; int total_lines = 0; if (!f) { fprintf(stderr, "Cannot open %s\n", path); return 1; } memset((void *)&System, 0, sizeof(System)); memset(&location, 0, sizeof(location)); System.conf.min_sats = 4; utc = 0; while (fgets(raw, sizeof raw, f)) { unsigned long int count_before, accum_before; size_t len = strlen(raw); while (len && (raw[len - 1] == '\n' || raw[len - 1] == '\r')) raw[--len] = '\0'; if (len == 0 || raw[0] == '#') continue; /* blank line or fixture comment, not fed to the parser */ snprintf(line, sizeof line, "%s\r\n", raw); total_lines++; count_before = System.speed_sample_count; accum_before = System.speed_accum_x100; gps_parse(line); /* must not crash, regardless of how malformed the line is */ if (is_invalid_epoch_line(raw)) { char what[560]; snprintf(what, sizeof what, "no speed sample added by: %s", raw); check(System.speed_sample_count == count_before && System.speed_accum_x100 == accum_before, what); } } fclose(f); printf("\nParsed %d lines without crashing.\n", total_lines); check(total_lines > 250, "fixture file actually has content"); /* Section 3: the HDOP-16.2, 44.8 km/h sample must be excluded outright - * the average right after it must still equal the ~4.3 km/h warm-up * average, not be pulled up towards 44.8. */ check(System.speed_sample_count >= 64, "warm-up samples were accepted"); { double avg = (double)System.speed_accum_x100 / System.speed_sample_count / 100.0; char what[128]; snprintf(what, sizeof what, "average (%.2f km/h) unaffected by the HDOP-16.2 44.8 km/h sample", avg); check(avg > 2.0 && avg < 6.0, what); } /* The 200 km/h, good-HDOP spike must be capped (added to the sum at * something well below 200, per SPEED_SAMPLE_CAP_MULT/FLOOR), not * excluded outright: a real fast segment (car/train) must still count. */ { double avg_after = (double)System.speed_accum_x100 / System.speed_sample_count / 100.0; char what[160]; snprintf(what, sizeof what, "200 km/h spike was capped, not excluded nor let through raw (average now %.2f km/h)", avg_after); check(avg_after > 2.5 && avg_after < 15.0, what); } if (failures) { printf("\n%d check(s) FAILED\n", failures); return 1; } printf("\nAll checks passed.\n"); return 0; }