Receive signed ITS messages and forward each at its declared length
Signed packets. A GeoNetworking Basic Header NextHeader of 2 means a TS 103 097 (IEEE 1609.2) envelope follows, with the Common Header inside it. gn_unwrap_its rejected all of these, and most real traffic is signed: the 2026-08-17 capture holds 157 signed frames from 15 source MACs against 2 unsecured stations. It now opens a COER-encoded signedData, or a bare unsecuredData, and parses the inner packet as before. The inner packet comes first inside tbsData, so the certificate and signature are never parsed, and the signature is not verified - the firmware has no trust store. Such messages reach the phone with the new V2X_RX flags bit1, signed but not verified. The app reads only bit0 and is unaffected until it learns the flag. Encrypted payloads, nested signing and the legacy v1.2.1 envelope are still rejected. All 157 recorded signed frames have the layout this reads, in all three COER length forms, and asn1tools decodes every envelope to the same inner packet. Payload bounds. Every frame recorded through the ESP32-C5's promiscuous RX, about 15 000 of them, ends in 8 bytes that are not part of the 802.11 frame and not a valid FCS. obu-firmware reads frames through the same API and took the rest of the frame as the message, so it forwarded those 8 bytes to the phone after every message. UPER decoders stop where the message ends, so nothing visibly broke, but the bytes cost serial bandwidth and 8 bytes of the DENM's headroom, and they stayed attached wherever raw payloads were stored or passed on. The payload is now exactly what the Common Header's payload-length field declares, which is also what separates a signed message from its signature. A frame longer than main.c's 800-byte capture buffer is now reported as truncated instead of being forwarded cut off, and counted as an oversize drop through the new serial_link_note_oversize_drop, as it was when the cut-off frame failed serial_link's size check. Host tests in obu-firmware/test/host build the firmware sources unmodified with MSYS2 gcc; `make` runs all three. - test_chain: frames from the firmware's TX code checked byte by byte against EN 302 636-4-1 and parsed back, including hand-built signed frames, the payload-length rule, the RX trailer, and every truncation length against a no-access guard page. 1731 checks, 0 failures. - test_replay and check_replay.py: all 15 145 recorded frames through gn_unwrap_its, cut to 800 bytes as on the board, and re-derived independently in Python with the envelope decoded by asn1tools. They agree on every record; 15 131 accepted, 157 of them signed. 11 043 of the 11 106 distinct messages re-encode byte-identically. The other 63 fail the same way with the old 8 bytes put back, so the boundary is not the cause: 5 are our own CAMs from before the 2026-08-20 yawRateConfidence fix, and the rest, from other stations, are a follow-up in TODO.md. - fuzz_gn_unwrap: random edits of every recorded frame, each run against the guard page. 50 000 000 iterations, no crash. obu-firmware/test/pcap_gn_tally.py tallies GeoNetworking header fields per station over captures; it is how the other stations' lifetimes were measured. TODO.md collects what is still open, including the on-air check for this change: it builds on IDF 6.1 but has not been flashed.
This commit is contained in:
+131
-15
@@ -28,9 +28,20 @@
|
||||
#define GN_HEADER_TYPE_TSB (5) // Topologically-Scoped Broadcast
|
||||
#define GN_HEADER_SUBTYPE_SINGLE_HOP (0)
|
||||
|
||||
#define GN_NEXT_HEADER_COMMON (1) // unsecured; 2 would be a secured packet
|
||||
#define GN_NEXT_HEADER_COMMON (1) // unsecured: the Common Header follows
|
||||
#define GN_NEXT_HEADER_SECURED (2) // a TS 103 097 envelope follows, Common Header inside it
|
||||
#define GN_COMMON_NEXT_HEADER_BTP_B (2)
|
||||
|
||||
// Common Header field (clause 9.7): length of everything after the GeoNetworking headers, i.e.
|
||||
// the BTP-B header plus the ITS payload.
|
||||
#define GN_COMMON_PAYLOAD_LEN_OFFSET (4)
|
||||
|
||||
// IEEE 1609.2 / TS 103 097 envelope, COER encoded - see unwrap_secured().
|
||||
#define IEEE1609DOT2_VERSION (3)
|
||||
#define CONTENT_TAG_UNSECURED_DATA (0x80) // Ieee1609Dot2Content CHOICE, context tag 0
|
||||
#define CONTENT_TAG_SIGNED_DATA (0x81) // context tag 1
|
||||
#define SIGNED_PAYLOAD_HAS_DATA (0x40) // SignedDataPayload preamble: `data` present
|
||||
|
||||
#define BTP_DEST_PORT_CAM (2001) // ETSI TS 103 248
|
||||
#define BTP_DEST_PORT_DENM (2002)
|
||||
// NOTE the crossover: SPATEM is BTP port 2004 but ItsPduHeader messageID 4, while MAPEM is port
|
||||
@@ -51,6 +62,90 @@ static uint16_t be16(const uint8_t *p)
|
||||
return (uint16_t)(((uint16_t)p[0] << 8) | (uint16_t)p[1]);
|
||||
}
|
||||
|
||||
// COER length determinant (ITU-T X.696): a first byte below 0x80 is the length itself; otherwise
|
||||
// its low 7 bits count the big-endian length bytes that follow. Two of them cover anything this
|
||||
// radio can deliver. Returns how many bytes the determinant occupies, or 0 if it does not fit in
|
||||
// `avail` or uses a form this does not read.
|
||||
static int coer_length(const uint8_t *p, int avail, int *len)
|
||||
{
|
||||
if (avail < 1) {
|
||||
return 0;
|
||||
}
|
||||
if (p[0] < 0x80) {
|
||||
*len = p[0];
|
||||
return 1;
|
||||
}
|
||||
const int n = p[0] & 0x7F;
|
||||
if (n < 1 || n > 2 || avail < 1 + n) {
|
||||
return 0;
|
||||
}
|
||||
int v = 0;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
v = (v << 8) | p[i];
|
||||
}
|
||||
*len = v;
|
||||
return 1 + n;
|
||||
}
|
||||
|
||||
// Locates the GeoNetworking packet inside a secured one. `offset` points just past the Basic
|
||||
// Header. Returns the offset of the inner Common Header and sets *inner_end to where the envelope
|
||||
// says the inner packet ends - which lies beyond frame_len if the capture was cut short - or
|
||||
// returns -1 for anything this does not unwrap.
|
||||
//
|
||||
// The envelope is an Ieee1609Dot2Data (IEEE 1609.2, profiled by TS 103 097 v1.3.1 and later),
|
||||
// COER encoded. A signed message starts:
|
||||
//
|
||||
// 03 protocolVersion 3
|
||||
// 81 content = signedData
|
||||
// 00 hashId (sha256; any one-byte value is accepted - the hash is not checked)
|
||||
// 40 tbsData.payload preamble: `data` present (bit 6)
|
||||
// 03 80 <len> payload.data: an Ieee1609Dot2Data holding unsecuredData of <len> bytes, which
|
||||
// are the Common Header, extended header, BTP-B header and ITS payload
|
||||
// ... headerInfo, signer, signature: not read
|
||||
//
|
||||
// The inner packet comes first inside tbsData, so it is found without parsing the certificate
|
||||
// or the signature, and its explicit length is what separates it from them. The shape is
|
||||
// measured, not only read from the standard: all 157 signed frames in
|
||||
// capture_20260817_171055.pcap have it (150 CAM, 7 GeoBroadcast DENM; <len> in all three COER
|
||||
// forms), and asn1tools decodes every one of them to the same unsecuredData. A top-level
|
||||
// unsecuredData (03 80 <len>, no signature at all) is accepted too.
|
||||
static int unwrap_secured(const uint8_t *frame, int offset, int frame_len,
|
||||
int *inner_end, bool *is_signed)
|
||||
{
|
||||
const uint8_t *p = frame + offset;
|
||||
const int avail = frame_len - offset;
|
||||
int i;
|
||||
|
||||
if (avail < 2 || p[0] != IEEE1609DOT2_VERSION) {
|
||||
return -1; // includes the legacy TS 103 097 v1.2.1 envelope, protocolVersion 2
|
||||
}
|
||||
if (p[1] == CONTENT_TAG_SIGNED_DATA) {
|
||||
if (avail < 6 ||
|
||||
p[2] >= 0x80 || // hashId: a one-byte enumerated value
|
||||
!(p[3] & SIGNED_PAYLOAD_HAS_DATA) || // signs only a hash of data sent elsewhere
|
||||
p[4] != IEEE1609DOT2_VERSION ||
|
||||
p[5] != CONTENT_TAG_UNSECURED_DATA) { // nested signing or encryption
|
||||
return -1;
|
||||
}
|
||||
i = 6;
|
||||
*is_signed = true;
|
||||
} else if (p[1] == CONTENT_TAG_UNSECURED_DATA) {
|
||||
i = 2;
|
||||
*is_signed = false;
|
||||
} else {
|
||||
return -1; // encryptedData, certificate requests
|
||||
}
|
||||
|
||||
int len;
|
||||
const int used = coer_length(p + i, avail - i, &len);
|
||||
if (used == 0) {
|
||||
return -1;
|
||||
}
|
||||
i += used;
|
||||
*inner_end = offset + i + len;
|
||||
return offset + i;
|
||||
}
|
||||
|
||||
bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out)
|
||||
{
|
||||
if (!frame || !out || frame_len < IEEE80211_HEADER_LEN) {
|
||||
@@ -91,22 +186,33 @@ bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out)
|
||||
if (frame_len < offset + GN_BASIC_HEADER_LEN) {
|
||||
return false;
|
||||
}
|
||||
// NextHeader distinguishes an unsecured packet (1 = Common Header follows) from a secured one
|
||||
// (2 = a TS 103 097 SecuredMessage follows, with the Common Header buried inside it at a
|
||||
// variable offset). Checking this rather than blindly skipping means a secured packet is
|
||||
// rejected cleanly instead of having its security envelope misread as a Common Header.
|
||||
if ((frame[offset] & 0x0F) != GN_NEXT_HEADER_COMMON) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t basic_next_header = frame[offset] & 0x0F;
|
||||
offset += GN_BASIC_HEADER_LEN;
|
||||
|
||||
// The headers from here on must end before `limit`: the end of the frame, or for a secured
|
||||
// packet the end of the envelope's inner packet if that comes first.
|
||||
int limit = frame_len;
|
||||
int envelope_end = -1;
|
||||
if (basic_next_header == GN_NEXT_HEADER_SECURED) {
|
||||
offset = unwrap_secured(frame, offset, frame_len, &envelope_end, &out->signed_unverified);
|
||||
if (offset < 0) {
|
||||
return false;
|
||||
}
|
||||
if (envelope_end < limit) {
|
||||
limit = envelope_end;
|
||||
}
|
||||
} else if (basic_next_header != GN_NEXT_HEADER_COMMON) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---- GN Common Header (8 bytes) ----
|
||||
if (frame_len < offset + GN_COMMON_HEADER_LEN) {
|
||||
if (limit < offset + GN_COMMON_HEADER_LEN) {
|
||||
return false;
|
||||
}
|
||||
uint8_t next_header = (frame[offset + 0] >> 4) & 0x0F;
|
||||
uint8_t header_type = (frame[offset + 1] >> 4) & 0x0F;
|
||||
uint8_t header_subtype = frame[offset + 1] & 0x0F;
|
||||
const int gn_payload_len = be16(frame + offset + GN_COMMON_PAYLOAD_LEN_OFFSET);
|
||||
if (next_header != GN_COMMON_NEXT_HEADER_BTP_B) {
|
||||
return false;
|
||||
}
|
||||
@@ -126,7 +232,7 @@ bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out)
|
||||
} else {
|
||||
return false; // Beacon / GeoUnicast / GeoAnycast / multi-hop TSB - see header comment
|
||||
}
|
||||
if (frame_len < offset + ext_len) {
|
||||
if (limit < offset + ext_len) {
|
||||
return false;
|
||||
}
|
||||
if (is_gbc) {
|
||||
@@ -138,7 +244,7 @@ bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out)
|
||||
offset += ext_len;
|
||||
|
||||
// ---- BTP-B header (4 bytes) ----
|
||||
if (frame_len < offset + BTP_B_HEADER_LEN) {
|
||||
if (limit < offset + BTP_B_HEADER_LEN) {
|
||||
return false;
|
||||
}
|
||||
uint16_t dest_port = be16(frame + offset);
|
||||
@@ -146,16 +252,26 @@ bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out)
|
||||
dest_port != BTP_DEST_PORT_SPATEM) {
|
||||
return false;
|
||||
}
|
||||
offset += BTP_B_HEADER_LEN;
|
||||
|
||||
// ---- Whatever's left is the ITS UPER payload ----
|
||||
int payload_len = frame_len - offset;
|
||||
// ---- ITS payload: exactly as long as the Common Header declares ----
|
||||
// Not "whatever is left of the frame": see "Payload bounds" in gn_unwrap.h for the 8 trailing
|
||||
// bytes every received frame carries and the signature that follows a secured packet.
|
||||
if (gn_payload_len <= BTP_B_HEADER_LEN) {
|
||||
return false; // no ITS payload at all
|
||||
}
|
||||
const int payload_start = offset + BTP_B_HEADER_LEN;
|
||||
const int payload_end = offset + gn_payload_len;
|
||||
if (envelope_end >= 0 && payload_end > envelope_end) {
|
||||
return false; // the inner packet claims more than its envelope holds
|
||||
}
|
||||
out->truncated = payload_end > frame_len;
|
||||
const int payload_len = (out->truncated ? frame_len : payload_end) - payload_start;
|
||||
if (payload_len <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out->btp_dest_port = dest_port;
|
||||
out->payload = frame + offset;
|
||||
out->payload = frame + payload_start;
|
||||
out->payload_len = payload_len;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
// Inverse of geonet_wrap_shb() + dot11p_build_frame(): takes a raw 802.11 frame as delivered by
|
||||
// the WiFi driver's promiscuous RX callback and strips 802.11 header -> LLC/SNAP ->
|
||||
// GeoNetworking Basic/Common/extended header -> BTP-B header, leaving the ITS payload (a UPER
|
||||
// message) plus the metadata the phone needs to know what it received.
|
||||
// GeoNetworking Basic Header -> [security envelope] -> Common/extended header -> BTP-B header,
|
||||
// leaving the ITS payload (a UPER message) plus the metadata the phone needs to know what it
|
||||
// received.
|
||||
//
|
||||
// ---- Supported GeoNetworking header types --------------------------------------------------
|
||||
// Two shapes, chosen by the Common Header's HeaderType, with DIFFERENT extended-header lengths:
|
||||
@@ -28,6 +29,23 @@
|
||||
// Beacon, GeoUnicast, GeoAnycast and multi-hop TSB are still rejected - nothing this project
|
||||
// talks to sends them, and each has its own extended-header length that would need measuring.
|
||||
//
|
||||
// ---- Secured packets -----------------------------------------------------------------------
|
||||
// A Basic Header NextHeader of 2 means an ETSI TS 103 097 (IEEE 1609.2) envelope follows, with
|
||||
// the Common Header onward inside it. Signed messages are unwrapped WITHOUT verifying the
|
||||
// signature or the certificate - this firmware has no trust store - and are reported with
|
||||
// signed_unverified set so the phone can tell. Most real traffic is signed: the 2026-08-17
|
||||
// capture held 157 signed frames from 15 source MACs. Encrypted payloads, nested signing and the
|
||||
// legacy v1.2.1 envelope are rejected. The layout is documented at unwrap_secured() in
|
||||
// gn_unwrap.c. Before 2026-09-11 every secured packet was rejected.
|
||||
//
|
||||
// ---- Payload bounds ------------------------------------------------------------------------
|
||||
// The payload is exactly as long as the Common Header's payload-length field says, minus the
|
||||
// BTP-B header - not "the rest of the frame". After the message comes, in a signed packet, the
|
||||
// signature; and every frame recorded through this chip's promiscuous RX API (~15 000 of them)
|
||||
// ends in 8 more bytes that are not part of the 802.11 frame and not a valid FCS. Until
|
||||
// 2026-09-11 those 8 bytes were forwarded to the phone as the tail of every message. UPER
|
||||
// decoders stop where the message ends, which is why nothing visibly broke.
|
||||
//
|
||||
// ---- Accepted BTP-B ports (ETSI TS 103 248) ------------------------------------------------
|
||||
// 2001 (CAM), 2002 (DENM) and 2004 (SPATEM). MAPEM (2003) and the rest are deliberately not
|
||||
// accepted yet: the phone has no decoder for them, so forwarding would just burn serial
|
||||
@@ -38,17 +56,11 @@
|
||||
// counted as an oversize drop rather than forwarded. The bench RSU trigger emits ~58-byte SPATEMs
|
||||
// and is unaffected, but real road RSUs measured 555 bytes median and 1243 max (2026-03-18 drive,
|
||||
// 79k messages), i.e. roughly 70% would be dropped. Raising the cap is deliberately deferred: it
|
||||
// also requires enlarging RX_FRAME_MAX_LEN and moving rx_item_t off the WiFi callback stack,
|
||||
// which at that size would overflow it.
|
||||
// also requires enlarging main.c's RX_FRAME_MAX_LEN.
|
||||
//
|
||||
// ---- What is NOT handled -------------------------------------------------------------------
|
||||
// Secured packets (GN Basic Header NextHeader=2, i.e. ETSI TS 103 097 signed messages). The
|
||||
// units on this bench run with ItsGnSecurity=0 so everything observed is unsecured; a secured
|
||||
// packet is rejected rather than mis-parsed.
|
||||
//
|
||||
// No FCS/CRC check: the WiFi driver has already validated and stripped it.
|
||||
// No FCS/CRC check here: the WiFi driver has already validated the frame.
|
||||
typedef struct {
|
||||
// BTP-B destination port, identifying the service: 2001 = CAM, 2002 = DENM.
|
||||
// BTP-B destination port, identifying the service: 2001 = CAM, 2002 = DENM, 2004 = SPATEM.
|
||||
uint16_t btp_dest_port;
|
||||
|
||||
// ITS payload (UPER message bytes). Points INTO the caller's `frame` buffer - NOT a copy, so
|
||||
@@ -64,11 +76,20 @@ typedef struct {
|
||||
int32_t geo_area_lat_tenmicrodeg;
|
||||
int32_t geo_area_lon_tenmicrodeg;
|
||||
uint16_t geo_area_distance_a_m;
|
||||
|
||||
// The packet arrived inside a TS 103 097 signed envelope. The signature was NOT checked.
|
||||
bool signed_unverified;
|
||||
|
||||
// The frame ended before the payload its headers declare. On the board only main.c's
|
||||
// RX_FRAME_MAX_LEN capture limit causes this (the driver drops frames that fail their FCS).
|
||||
// payload/payload_len then cover just the part that arrived, so it must not be forwarded.
|
||||
bool truncated;
|
||||
} gn_rx_t;
|
||||
|
||||
// Returns true and fills *out if this was a well-formed, supported ITS frame. Returns false
|
||||
// otherwise (wrong ethertype, secured, unsupported header type, unaccepted BTP port, truncated,
|
||||
// or promiscuous-capture garbage) - all common and expected on an open-air capture, so the caller
|
||||
// Returns true and fills *out if this was a well-formed, supported ITS frame - check `truncated`
|
||||
// before using the payload. Returns false otherwise (wrong ethertype, encrypted or unsupported
|
||||
// envelope, unsupported header type, unaccepted BTP port, headers cut short, or
|
||||
// promiscuous-capture garbage) - all common and expected on an open-air capture, so the caller
|
||||
// should treat false as "not for us", not as an error worth logging per frame.
|
||||
bool gn_unwrap_its(const uint8_t *frame, int frame_len, gn_rx_t *out);
|
||||
|
||||
|
||||
@@ -280,8 +280,16 @@ static void rx_forward_task(void *arg)
|
||||
// returning false here is the common case, not an error, so it isn't logged per frame.
|
||||
gn_rx_t rx;
|
||||
if (gn_unwrap_its(item.data, item.len, &rx)) {
|
||||
if (rx.truncated) {
|
||||
// Longer than the RX_FRAME_MAX_LEN bytes captured above, so it cannot be forwarded
|
||||
// whole - and at that size it could not cross the serial link either. Counted as
|
||||
// an oversize drop, as it was when the cut-off frame still reached
|
||||
// serial_link_send_v2x_rx() and failed the size check there.
|
||||
serial_link_note_oversize_drop(rx.btp_dest_port);
|
||||
continue;
|
||||
}
|
||||
serial_link_send_v2x_rx(rx.btp_dest_port, item.rssi,
|
||||
rx.has_geo_area,
|
||||
rx.has_geo_area, rx.signed_unverified,
|
||||
rx.geo_area_lat_tenmicrodeg,
|
||||
rx.geo_area_lon_tenmicrodeg,
|
||||
rx.geo_area_distance_a_m,
|
||||
|
||||
@@ -38,6 +38,13 @@ void serial_link_note_tx_failure(void)
|
||||
bump(&s_tx_failures);
|
||||
}
|
||||
|
||||
void serial_link_note_oversize_drop(uint16_t btp_dest_port)
|
||||
{
|
||||
bump(&s_oversize_drops);
|
||||
ESP_LOGW(TAG, "port %u message larger than the RX capture buffer, total oversize drops %u",
|
||||
btp_dest_port, s_oversize_drops);
|
||||
}
|
||||
|
||||
// ---- CRC-16/CCITT-FALSE (poly 0x1021, init 0xFFFF, no reflect, no xorout) ----
|
||||
// Bytewise (no table) - frames here are at most SERIAL_LINK_MAX_PAYLOAD + 3 bytes, so table
|
||||
// lookup isn't worth the flash/RAM tradeoff. MUST match the Kotlin-side implementation exactly
|
||||
@@ -114,7 +121,7 @@ static bool send_frame(uint8_t type, const uint8_t *payload, int len)
|
||||
}
|
||||
|
||||
bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
bool has_geo_area,
|
||||
bool has_geo_area, bool signed_unverified,
|
||||
int32_t geo_area_lat_tenmicrodeg,
|
||||
int32_t geo_area_lon_tenmicrodeg,
|
||||
uint16_t geo_area_distance_a_m,
|
||||
@@ -138,7 +145,7 @@ bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
s_v2x_payload[0] = (uint8_t)(btp_dest_port & 0xFF);
|
||||
s_v2x_payload[1] = (uint8_t)((btp_dest_port >> 8) & 0xFF);
|
||||
s_v2x_payload[2] = (uint8_t)rssi;
|
||||
s_v2x_payload[3] = has_geo_area ? 0x01 : 0x00;
|
||||
s_v2x_payload[3] = (uint8_t)((has_geo_area ? 0x01 : 0x00) | (signed_unverified ? 0x02 : 0x00));
|
||||
uint32_t lat = (uint32_t)geo_area_lat_tenmicrodeg;
|
||||
uint32_t lon = (uint32_t)geo_area_lon_tenmicrodeg;
|
||||
s_v2x_payload[4] = (uint8_t)(lat & 0xFF);
|
||||
|
||||
@@ -37,10 +37,13 @@
|
||||
// [0..1] btp_dest_port uint16 LE 2001 = CAM, 2002 = DENM (ETSI TS 103 248)
|
||||
// [2] rssi int8 dBm, from the promiscuous RX metadata
|
||||
// [3] flags uint8 bit0: geo area fields below are valid
|
||||
// bit1: arrived signed (TS 103 097), signature NOT
|
||||
// verified. An app that tests only bit0 ignores it.
|
||||
// [4..7] geo_area_lat int32 LE 1/10 microdegree, GeoBroadcast destination area
|
||||
// [8..11] geo_area_lon int32 LE 1/10 microdegree
|
||||
// [12..13] geo_area_dist uint16 LE Distance A, metres (relevance radius for a circle)
|
||||
// [14..] UPER message bytes
|
||||
// [14..] UPER message bytes - exactly the message. Before 2026-09-11 they were followed by
|
||||
// the 8 bytes the chip's promiscuous RX appends (gn_unwrap.h, "Payload bounds").
|
||||
//
|
||||
// All prefix fields are LITTLE-endian, matching this framing's own length field - note the
|
||||
// GeoNetworking wire format they came from is big-endian, so gn_unwrap.c converts.
|
||||
@@ -138,10 +141,11 @@ void serial_link_init(serial_link_cam_tx_cb_t on_cam_tx,
|
||||
// Sends a SERIAL_MSG_V2X_RX frame: the metadata prefix plus the UPER bytes gn_unwrap.c extracted
|
||||
// from an over-the-air frame. Pass has_geo_area=false and zeroes for the area fields when the
|
||||
// source frame carried no destination area (i.e. it was single-hop broadcast, not GeoBroadcast).
|
||||
// signed_unverified is gn_rx_t's flag of the same name; it sets bit1 of the prefix flags.
|
||||
// Returns true if the frame was written to the USB endpoint - not an end-to-end ack, the phone
|
||||
// may still drop it.
|
||||
bool serial_link_send_v2x_rx(uint16_t btp_dest_port, int8_t rssi,
|
||||
bool has_geo_area,
|
||||
bool has_geo_area, bool signed_unverified,
|
||||
int32_t geo_area_lat_tenmicrodeg,
|
||||
int32_t geo_area_lon_tenmicrodeg,
|
||||
uint16_t geo_area_distance_a_m,
|
||||
@@ -156,4 +160,9 @@ bool serial_link_send_status(uint8_t status);
|
||||
// otherwise indistinguishable, from the phone's side, from one that transmitted fine.
|
||||
void serial_link_note_tx_failure(void);
|
||||
|
||||
// Counts an ITS message that cannot be forwarded because it is too large, in the same heartbeat
|
||||
// counter serial_link_send_v2x_rx() uses for its own size check. For main.c's rx_forward_task,
|
||||
// whose capture buffer is smaller than the largest frames on air.
|
||||
void serial_link_note_oversize_drop(uint16_t btp_dest_port);
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user