Fix UPER encoding of CurvatureCalculationMode; verified on hardware

CurvatureCalculationMode is the one extensible ENUMERATED in CAM:
  ENUMERATED {yawRateUsed(0), yawRateNotUsed(1), unavailable(2), ...}
UPER encodes an extensible ENUMERATED as an extension bit followed by the root
index - 1 + 2 = 3 bits. All three of our encoders wrote only the 2-bit index,
shifting yawRate and the entire low-frequency container one bit early for any
standards-compliant receiver.

It went unnoticed because every end of this project shared the mistake: the
Kotlin codec was ported bit-for-bit from cam.c, so phone and ESP32 agreed
perfectly with each other and with nothing else. Confirmed against the ETSI
ASN.1 in the C-ITS-Parser checkout, where rasn marks this type - and only this
type - #[non_exhaustive].

Fixed in all three copies of the encoder (app CamUperCodec.kt,
obu-firmware/main/cam.c, obu-cam-transmistter/main/cam.c) plus the decoder,
which now rejects rather than misreads a set extension bit. Frame size is
unchanged at 43 bytes. Transmitter reflashed and the phone decodes its CAMs.

Also in this change:

- serial_link: skip send_frame entirely when no USB host is attached, and raise
  the tx mutex timeout above the worst-case hold. With the phone unplugged every
  write blocked its full timeout while holding the lock, so forwarded CAM_RX
  traffic starved the 1 Hz heartbeat - observed as "tx mutex timeout, dropping
  frame" on the console, and it would have tripped the phone's link watchdog.
  Verified gone on hardware.
- Log decoded and failed CAMs in CamUseCaseRepository. "The app shows nothing"
  had two indistinguishable causes; a silent `?: return` made this bug much
  harder to find than it needed to be.
- Remove the ESP32 send-only/send-and-receive toggle. Reception can't be
  disabled in firmware (raw TX only works while promiscuous), so it was an
  app-side filter pretending to be a radio control.
- V2X monitor follows the serial link state on the ESP32 path instead of MQTT,
  which is permanently disconnected there; CAM intake is gated on the link being
  up, and engine state is cleared when it drops.
- About screen: 0.5.0, Phase 03.
- Track obu-cam-transmistter, the bench CAM transmitter. Its cam.c is compiled
  (unlike obu-firmware's reference copy) and must stay bit-identical to the other
  two - this commit is what that coupling costs when it's broken.
- Document the two-toolchain split: this project builds on IDF 5.5.4, obu-firmware
  on the pinned 6.1. Exporting both in one shell fails confusingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Ashin Walpola
2026-08-11 14:50:35 +02:00
co-authored by Claude Opus 5
parent f3ae81a8fe
commit 528637dab6
32 changed files with 3852 additions and 133 deletions
+49
View File
@@ -0,0 +1,49 @@
#include <stdint.h>
// RETIRED - no longer built (removed from main/CMakeLists.txt SRCS), kept only
// for history. Confirmed not to work: linked cleanly with -Wl,-zmuldefs but
// the QoS-frame rejection persisted identically. Also turned out to be based
// on the wrong function signature - the real ieee80211_raw_frame_sanity_check
// takes (wifi_interface_t ifx, const void *buffer, int32_t len, bool
// en_sys_seq), confirmed from opentrafficmap/its-g5-receiver-firmware_txenabled's
// main/tx_custom.c, not the 3x int32_t guessed below. Superseded by
// tx_custom.c, which bypasses esp_wifi_80211_tx() (and the function that
// calls this check) entirely instead of trying to neutralize the check.
// See docs/04-transmit-setup.md.
// Overrides a function inside the closed-source WiFi library that gates
// which raw 802.11 frame types esp_wifi_80211_tx() will accept. By default
// it only allows beacon/probe-request/probe-response/action and non-QoS
// data frames - it explicitly rejects QoS Data (subtype 8), which is what
// real ITS-G5/802.11p hardware actually transmits and expects.
//
// This is the same technique used by ESP32 WiFi-security tools (deauther/
// injection projects) to unlock raw frame injection: define a function with
// the exact same name as the library's gate, and link with -Wl,-zmuldefs
// (see CMakeLists.txt) so the linker accepts having two definitions of the
// same symbol instead of erroring with "multiple definition of
// `ieee80211_raw_frame_sanity_check'" - and takes this one instead of the
// library's.
//
// Confirmed present for THIS target/IDF version: `nm` on
// components/esp_wifi/lib/esp32c5/libnet80211.a (IDF v5.5.4) shows
// `ieee80211_raw_frame_sanity_check` as a normal (non-weak) global text
// symbol in ieee80211_node.o. The exact argument count/meaning is
// reverse-engineered from community ESP32 (Xtensa) deauther tools, not
// confirmed byte-for-byte against esp32c5's actual implementation - if
// frames still get rejected, or this crashes, the real signature may take
// different arguments than assumed here.
//
// Real risk, not just an inconvenience: this disables ALL sanity checking
// on raw frames going through esp_wifi_80211_tx(), not just the QoS-type
// gate. Whatever else that check validates (frame length bounds, etc.) is
// now unchecked. Malformed frames from a bug elsewhere in this codebase
// could behave worse (silent corruption, crash) than they would have with
// the check in place, where they'd have just been rejected cleanly.
int ieee80211_raw_frame_sanity_check(int32_t arg1, int32_t arg2, int32_t arg3)
{
(void)arg1;
(void)arg2;
(void)arg3;
return 0; // 0 = "frame is sane" - always pass
}