Files

100 lines
3.7 KiB
C
Raw Permalink Normal View History

// 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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#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;
}