2026-08-04 17:58:07 +02:00
|
|
|
#ifndef GN_UNWRAP_H
|
|
|
|
|
#define GN_UNWRAP_H
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
#include <stddef.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
|
|
|
|
|
// Inverse of geonet_wrap_shb() + dot11p_build_frame(): takes a raw 802.11 frame as delivered by
|
2026-08-17 18:42:48 +02:00
|
|
|
// the WiFi driver's promiscuous RX callback and strips 802.11 header -> LLC/SNAP ->
|
2026-09-11 20:19:40 +02:00
|
|
|
// 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.
|
2026-08-04 17:58:07 +02:00
|
|
|
//
|
2026-08-17 18:42:48 +02:00
|
|
|
// ---- Supported GeoNetworking header types --------------------------------------------------
|
|
|
|
|
// Two shapes, chosen by the Common Header's HeaderType, with DIFFERENT extended-header lengths:
|
2026-08-04 17:58:07 +02:00
|
|
|
//
|
2026-08-17 18:42:48 +02:00
|
|
|
// TSB/SINGLE_HOP (HT=5, HST=0) - 28 bytes: Source Position Vector (24) + Reserved (4).
|
|
|
|
|
// What CAM uses, and what geonet_wrap_shb() builds.
|
|
|
|
|
// GEOBROADCAST (HT=4) - 44 bytes: SeqNum (2) + Reserved (2) + SO PV (24) +
|
|
|
|
|
// GeoArea lat (4) + lon (4) + DistanceA (2) + DistanceB (2) + Angle (2) + Reserved (2).
|
|
|
|
|
// What DENM uses in practice - real RSUs and OBUs disseminate DENM by GeoBroadcast so it
|
|
|
|
|
// can be forwarded across an area, not by single-hop broadcast.
|
2026-08-04 17:58:07 +02:00
|
|
|
//
|
2026-08-17 18:42:48 +02:00
|
|
|
// Both lengths are measured facts, not spec-table guesses: verified against live air capture on
|
|
|
|
|
// 2026-08-17 (its-g5-receiver-firmware/recordings/capture_20260817_171055.pcap) by locating the
|
|
|
|
|
// BTP port and ItsPduHeader and checking they agree. An earlier version of this file used 24 for
|
|
|
|
|
// the SHB case, four bytes short, which read the BTP port out of the Reserved field and silently
|
|
|
|
|
// dropped EVERY real CAM. Do not "simplify" these constants without re-measuring.
|
2026-08-04 17:58:07 +02:00
|
|
|
//
|
2026-08-17 18:42:48 +02:00
|
|
|
// 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.
|
2026-08-04 17:58:07 +02:00
|
|
|
//
|
2026-09-11 20:19:40 +02:00
|
|
|
// ---- 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.
|
|
|
|
|
//
|
2026-08-17 18:42:48 +02:00
|
|
|
// ---- Accepted BTP-B ports (ETSI TS 103 248) ------------------------------------------------
|
2026-08-20 15:47:14 +02:00
|
|
|
// 2001 (CAM), 2002 (DENM) and 2004 (SPATEM). MAPEM (2003) and the rest are deliberately not
|
2026-08-17 18:42:48 +02:00
|
|
|
// accepted yet: the phone has no decoder for them, so forwarding would just burn serial
|
|
|
|
|
// bandwidth. Adding one is a one-line change here plus a decoder on the phone - the serial
|
|
|
|
|
// protocol itself is already generic (see SERIAL_MSG_V2X_RX in serial_link.h).
|
|
|
|
|
//
|
2026-08-20 15:47:14 +02:00
|
|
|
// SPATEM size caveat: SERIAL_LINK_MAX_PAYLOAD is 512, so a SPATEM whose UPER exceeds 498 bytes is
|
|
|
|
|
// 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
|
2026-09-11 20:19:40 +02:00
|
|
|
// also requires enlarging main.c's RX_FRAME_MAX_LEN.
|
2026-08-20 15:47:14 +02:00
|
|
|
//
|
2026-09-11 20:19:40 +02:00
|
|
|
// No FCS/CRC check here: the WiFi driver has already validated the frame.
|
2026-08-17 18:42:48 +02:00
|
|
|
typedef struct {
|
2026-09-11 20:19:40 +02:00
|
|
|
// BTP-B destination port, identifying the service: 2001 = CAM, 2002 = DENM, 2004 = SPATEM.
|
2026-08-17 18:42:48 +02:00
|
|
|
uint16_t btp_dest_port;
|
|
|
|
|
|
|
|
|
|
// ITS payload (UPER message bytes). Points INTO the caller's `frame` buffer - NOT a copy, so
|
|
|
|
|
// it is only valid while `frame` is.
|
|
|
|
|
const uint8_t *payload;
|
|
|
|
|
int payload_len;
|
|
|
|
|
|
|
|
|
|
// GeoBroadcast destination area, when this frame carried one (GEOBROADCAST only; false for
|
|
|
|
|
// TSB/SHB). This is the hazard's relevance area - for a DENM it says "this warning applies
|
|
|
|
|
// within DistanceA metres of this point", which is materially more useful on a map than the
|
|
|
|
|
// originator's own position.
|
|
|
|
|
bool has_geo_area;
|
|
|
|
|
int32_t geo_area_lat_tenmicrodeg;
|
|
|
|
|
int32_t geo_area_lon_tenmicrodeg;
|
|
|
|
|
uint16_t geo_area_distance_a_m;
|
2026-09-11 20:19:40 +02:00
|
|
|
|
|
|
|
|
// 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;
|
2026-08-17 18:42:48 +02:00
|
|
|
} gn_rx_t;
|
|
|
|
|
|
2026-09-11 20:19:40 +02:00
|
|
|
// 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
|
2026-08-17 18:42:48 +02:00
|
|
|
// 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);
|
2026-08-04 17:58:07 +02:00
|
|
|
|
|
|
|
|
#endif
|