// Replays recorded air traffic through gn_unwrap_its, one pcap record at a time and exactly as the // promiscuous RX callback hands each frame over (the recordings come through the same API), and // writes what came out to a TSV that check_replay.py verifies independently. See README.md. // // Usage: test_replay out.tsv capture.pcap [capture.pcap ...] // // One row per pcap record: // file, record, accepted, port, signed, truncated, area, area_lat, area_lon, area_dist, payload // with everything after `accepted` empty for rejected frames and the payload in hex. Each frame // is placed against the guard page, and an accepted payload that is not inside its frame is a // failure here already. #include #include #include #include "gn_unwrap.h" #include "test_util.h" // main.c's RX_FRAME_MAX_LEN: the promiscuous callback copies at most this many bytes of a frame, // so it is the most gn_unwrap_its ever sees on the board. Keep in step with main.c. #define RX_FRAME_MAX_LEN 800 typedef struct { FILE *out; const char *path; long records, capped, accepted, cam, denm, spatem, signed_frames, truncated, bad; } replay_t; static void on_frame(const uint8_t *frame, int len, int index, void *ctx) { replay_t *r = ctx; r->records++; if (len > RX_FRAME_MAX_LEN) { len = RX_FRAME_MAX_LEN; r->capped++; } tu_set_context("replay %s record %d (%d bytes)", r->path, index, len); const uint8_t *g = tu_guarded(frame, len); gn_rx_t rx; const bool ok = gn_unwrap_its(g, len, &rx); fprintf(r->out, "%s\t%d\t%d", r->path, index, ok); if (ok) { const uintptr_t lo = (uintptr_t)g; const uintptr_t p = (uintptr_t)rx.payload; if (p < lo || rx.payload_len <= 0 || p + (uintptr_t)rx.payload_len > lo + (uintptr_t)len) { fprintf(stderr, "FAIL %s: payload outside the frame\n", tu_context()); r->bad++; } else { fprintf(r->out, "\t%u\t%d\t%d\t%d\t%ld\t%ld\t%u\t", rx.btp_dest_port, rx.signed_unverified, rx.truncated, rx.has_geo_area, (long)rx.geo_area_lat_tenmicrodeg, (long)rx.geo_area_lon_tenmicrodeg, rx.geo_area_distance_a_m); for (int i = 0; i < rx.payload_len; i++) { fprintf(r->out, "%02x", rx.payload[i]); } } r->accepted++; r->cam += rx.btp_dest_port == 2001; r->denm += rx.btp_dest_port == 2002; r->spatem += rx.btp_dest_port == 2004; r->signed_frames += rx.signed_unverified; r->truncated += rx.truncated; } fputc('\n', r->out); } int main(int argc, char **argv) { if (argc < 3) { fprintf(stderr, "usage: test_replay out.tsv capture.pcap [capture.pcap ...]\n"); return 2; } tu_install_crash_handler(); tu_guard_init(); replay_t r = {0}; r.out = fopen(argv[1], "w"); if (!r.out) { fprintf(stderr, "cannot write %s\n", argv[1]); return 2; } fprintf(r.out, "file\trecord\taccepted\tport\tsigned\ttruncated\tarea\tarea_lat\tarea_lon\tarea_dist\tpayload\n"); int unreadable = 0; for (int i = 2; i < argc; i++) { r.path = argv[i]; if (tu_pcap_foreach(argv[i], on_frame, &r) < 0) { fprintf(stderr, "cannot read %s as a pcap\n", argv[i]); unreadable++; } } fclose(r.out); printf("test_replay: %ld records (%ld cut to %d bytes as main.c does), %ld accepted (CAM %ld, " "DENM %ld, SPATEM %ld; %ld signed, %ld truncated), %ld bad\n", r.records, r.capped, RX_FRAME_MAX_LEN, r.accepted, r.cam, r.denm, r.spatem, r.signed_frames, r.truncated, r.bad); return (r.bad || unreadable) ? 1 : 0; }