mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-12 00:32:26 +01:00
A car that hangs up on the full service discovery request gets the short one next time
The 2019 Fiat 500's Uconnect drops the link within a second of the request with icons that 0.3.39 started sending, while a 2017 Hyundai needs them. Each car is remembered by its head unit's certificate.
This commit is contained in:
1 parent
7fd19758a4
commit
5f87840e7e
3 files changed
+54
-4
No files matched your search
@@ -69,6 +69,8 @@ class GearslipRunner(
|
||||
|
||||
/** Waits for the certificate only when TLS is first needed; see [run]. */
|
||||
private val tls: PhoneTls by lazy { identityTask.get() }
|
||||
/** This session sent the short service discovery request. */
|
||||
@Volatile private var plainDiscovery = false
|
||||
private var authenticationFailed = false
|
||||
private var drainWriterOnExit = false
|
||||
@Volatile private var stopped = false
|
||||
@@ -174,6 +176,9 @@ class GearslipRunner(
|
||||
SessionReport.fail(link, "transport failed", t, state.name)
|
||||
reportDisconnect()
|
||||
} finally {
|
||||
// The car hung up between our service discovery request and its answer: try the
|
||||
// other kind of request with this car next time (see [ServiceDiscoveryQuirks]).
|
||||
if (!stopped && state == State.WAIT_SDR) ServiceDiscoveryQuirks.droppedAfter(tls.headUnitKey, plainDiscovery)
|
||||
running = false
|
||||
// The car is gone, but nothing calls stop() when a wireless session drops: it waits
|
||||
// for the next one. Close the music channel now, so the audio capture behind it,
|
||||
@@ -496,14 +501,17 @@ class GearslipRunner(
|
||||
* never answered the request when it had only the label and model (issue #47).
|
||||
*/
|
||||
private fun sendServiceDiscoveryRequest() {
|
||||
val icons = runCatching(phoneIcons).getOrDefault(emptyList())
|
||||
// A 2019 Fiat 500's Uconnect hangs up on the full request (issue #91), so a car that has
|
||||
// done that before gets the short one Gearslip sent until 0.3.39: label and model only.
|
||||
plainDiscovery = ServiceDiscoveryQuirks.wantsPlain(tls.headUnitKey)
|
||||
val icons = if (plainDiscovery) emptyList() else runCatching(phoneIcons).getOrDefault(emptyList())
|
||||
var body = ByteArray(0)
|
||||
icons.take(3).forEachIndexed { i, png -> body += Protobuf.bytesField(i + 1, png) } // small, medium, large icon
|
||||
body += Protobuf.stringField(4, "Gearslip") + // label_text
|
||||
Protobuf.stringField(5, Build.MODEL) + // device_name
|
||||
Protobuf.bytesField(6, Protobuf.stringField(1, java.util.UUID.randomUUID().toString())) // session ID
|
||||
Protobuf.stringField(5, Build.MODEL) // device_name
|
||||
if (!plainDiscovery) body += Protobuf.bytesField(6, Protobuf.stringField(1, java.util.UUID.randomUUID().toString())) // session ID
|
||||
send(MSG_SERVICE_DISCOVERY_REQUEST, body, encrypted = true)
|
||||
log.i("-> ServiceDiscoveryRequest (encrypted, ${icons.size} icons, ${body.size} bytes)")
|
||||
log.i("-> ServiceDiscoveryRequest (encrypted, ${if (plainDiscovery) "short, as this car asked" else "${icons.size} icons"}, ${body.size} bytes)")
|
||||
}
|
||||
|
||||
private fun onServiceDiscoveryResponse(body: ByteArray) {
|
||||
|
||||
@@ -25,6 +25,12 @@ class PhoneTls(keyStore: KeyStore, password: CharArray) {
|
||||
private var netBuffer = ByteBuffer.allocate(32 * 1024)
|
||||
|
||||
@Volatile var handshakeComplete: Boolean = false
|
||||
|
||||
/**
|
||||
* A short fingerprint of the head unit's certificate, known once TLS completes: the first
|
||||
* thing that tells one car model from another, before it has described itself.
|
||||
*/
|
||||
@Volatile var headUnitKey: String? = null
|
||||
private set
|
||||
|
||||
init {
|
||||
@@ -166,6 +172,9 @@ class PhoneTls(keyStore: KeyStore, password: CharArray) {
|
||||
try {
|
||||
val peers = session.peerCertificates
|
||||
log.i("head unit presented ${peers.size} certificate(s):")
|
||||
headUnitKey = peers.firstOrNull()?.encoded?.let { bytes ->
|
||||
java.security.MessageDigest.getInstance("SHA-256").digest(bytes).take(8).joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
peers.forEachIndexed { index, certificate ->
|
||||
if (certificate is X509Certificate) {
|
||||
log.i(" [$index] subject=${certificate.subjectX500Principal}")
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.seb3thehacker.gearslip
|
||||
|
||||
import app.seb3thehacker.gearslip.car.CarEnvironment
|
||||
|
||||
/**
|
||||
* Which cars want the short service discovery request. Since 0.3.39 Gearslip sends what a real
|
||||
* phone does: its icon at three sizes and a session ID. A 2017 Hyundai on protocol 1.0 ignored
|
||||
* the request without them (issue #47), but a 2019 Fiat 500's Uconnect hangs up on it within a
|
||||
* second (issue #91). A car that hangs up after one kind is sent the other next time, remembered
|
||||
* by its head unit's certificate, so each car settles on whichever it answers.
|
||||
*/
|
||||
object ServiceDiscoveryQuirks {
|
||||
|
||||
private const val KEY = "sdr_plain_cars"
|
||||
|
||||
fun wantsPlain(car: String?): Boolean = car != null && car in cars()
|
||||
|
||||
/** The car hung up after [plain] (or the full) request: switch it to the other one. */
|
||||
fun droppedAfter(car: String?, plain: Boolean) {
|
||||
val context = CarEnvironment.appContext ?: return
|
||||
car ?: return
|
||||
val now = cars()
|
||||
val next = if (plain) now - car else now + car
|
||||
if (next == now) return
|
||||
AppSettings.putString(context, KEY, next.joinToString(","))
|
||||
GearslipLog.i("service discovery: the car hung up after the ${if (plain) "short" else "full"} request, so next time it gets the ${if (plain) "full" else "short"} one", "PROTO")
|
||||
}
|
||||
|
||||
private fun cars(): Set<String> {
|
||||
val context = CarEnvironment.appContext ?: return emptySet()
|
||||
return AppSettings.getString(context, KEY, "").split(",").filter { it.isNotEmpty() }.toSet()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user