Skip to content

Commit 7e67352

Browse files
malysclaude
andcommitted
feat(nav): ask transactions 38 and 39 what they actually reply
CP-051 has a transaction number and no answer. CP-040 found 38 and 39 declared on `IMapService` while surveying the navigation adapter and never called them, and the reason to call them now is worth stating plainly: the destination a driver has already entered in the car's own navigation costs no keyboard on a head unit, no geocoding request, no second quota, and no request carrying text the driver typed off the vehicle. `probeTransaction` records the reply and does not interpret it. Reading the bytes as a typed value would be guessing the shape, and the failure mode of guessing wrong is not obvious: a latitude read as a longitude routes to the wrong continent and looks like an ordinary number while doing it. So the probe answers with the payload's length and its first 128 bytes in hex, and a session at a desk decides what they mean. "Unbound", "no answer" and "unmarshallable" are answers too, and each is returned as plainly as a payload is — a probe that cannot distinguish silence from failure teaches nothing. Read-only, like every vehicle access in this library. The reply may contain the destination that is set in the car's navigation, which is the point of the probe and the reason the caller's artifact says so: that file leaves the car on a USB stick. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 2f9a7ff commit 7e67352

2 files changed

Lines changed: 50 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ All notable changes to this project are documented here. Format follows
88

99
### Added
1010

11+
- **`SaicNav.probeTransaction` — what a navigation transaction replies, recorded rather than
12+
interpreted.** CP-040 saw transactions 38 and 39 declared on `IMapService` and never called
13+
them; CP-051 wants to know whether they carry the destination the driver has already entered
14+
in the car's own navigation, because that destination costs no keyboard and no geocoding
15+
request. The probe returns the reply's bytes and its length, and refuses to read them as a
16+
typed value: a latitude read as a longitude routes to the wrong continent while looking like
17+
an ordinary number. Read-only, capped at 128 bytes, and it answers "unbound" or "no answer"
18+
as plainly as it answers with a payload. Called only by the unstable channel's validation
19+
probe (CP-055).
20+
1121
- **A consumption model for a car that publishes no battery power — which is this car.**
1222
`CarPropertyEvidence` has recorded since 2026-09-04 that SWI68 never publishes battery power:
1323
three diagnostic bundles, 51 samples, zero readings. The consequence was that the kWh model

‎lib/src/main/java/com/evsuite/hardware/saic/SaicNav.kt‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,44 @@ object SaicNav {
4646
*/
4747
fun totalMileageKm(): Int? =
4848
SaicAidl.callInt(binder(), DESCRIPTOR, TX_GET_TOTAL_MILEAGE)?.takeIf { it > 0 }
49+
50+
/**
51+
* The two transactions CP-040 saw declared on this interface and never called.
52+
*
53+
* They are the candidates for "where is the driver already going", which would remove the
54+
* typed address, the keyboard and a geocoding request from the charge-stop screen.
55+
*/
56+
val DESTINATION_TRANSACTIONS = listOf(38, 39)
57+
58+
/**
59+
* What a transaction actually replies, recorded rather than interpreted.
60+
*
61+
* CP-051 exists because the transaction numbers are known and the answer is not. Reading
62+
* the reply as a typed value would be guessing the shape; a latitude read as a longitude
63+
* routes to the wrong continent and looks like an ordinary number while doing it. So this
64+
* returns the bytes, and a session at a desk decides what they mean.
65+
*
66+
* **The reply may contain the destination that is set in the car's navigation.** That is
67+
* the point of the probe, and it is why the artifact carrying this says so — the file
68+
* leaves the car on a USB stick.
69+
*
70+
* @return one line, always: unbound, unanswered, or the payload in hex.
71+
*/
72+
fun probeTransaction(code: Int, maxBytes: Int = MAX_PROBE_BYTES): String {
73+
val target = binder() ?: return "tx=$code unbound"
74+
return SaicAidl.callParcel(target, DESCRIPTOR, code) { reply ->
75+
val start = reply.dataPosition()
76+
val available = reply.dataAvail()
77+
val hex = runCatching {
78+
reply.marshall()
79+
.drop(start)
80+
.take(maxBytes)
81+
.joinToString("") { byte -> "%02x".format(byte.toInt() and 0xff) }
82+
}.getOrElse { "unmarshallable(${it.javaClass.simpleName})" }
83+
"tx=$code answered bytes=$available hex=$hex"
84+
} ?: "tx=$code no answer"
85+
}
86+
87+
/** Enough for a coordinate pair, a name and their headers; not enough to be a log dump. */
88+
private const val MAX_PROBE_BYTES = 128
4989
}

0 commit comments

Comments
 (0)