From 95fddf5a550771391d4ab7fcb0f5ade8e2e63927 Mon Sep 17 00:00:00 2001 From: Seb3thehacker Date: Wed, 4 Nov 2026 22:27:21 -0800 Subject: [PATCH] GM cars also get their Bluetooth and Wi-Fi projection channels opened The 2021 Bolt still hangs up about 20 seconds in with pings answered and GM's channel open. These are the two channels it offers that Gearslip had never opened. What arrives on them is logged. Without an address typed in Settings, no PairingRequest is sent. --- .../seb3thehacker/gearslip/GearslipRunner.kt | 21 +++++++++++++++++++ .../gearslip/ServiceDiscovery.kt | 7 +++++++ .../gearslip/car/CarBluetooth.kt | 18 ++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt index f572cc4..206d42b 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/GearslipRunner.kt @@ -107,6 +107,8 @@ class GearslipRunner( @Volatile private var audioLink: AudioLink? = null @Volatile private var guidanceLink: AudioLink? = null @Volatile private var vendorChannelId = -1 + /** The car's Wi-Fi projection channel, opened in GM cars only (see [startVendorChannel]); -1 when closed. */ + @Volatile private var wifiChannelId = -1 private var audioMessagesSeen = 0 private var touchWidth = 0 private var touchHeight = 0 @@ -260,6 +262,8 @@ class GearslipRunner( CarBluetooth.onMessage(messageId, body) } else if (frame.channel == vendorChannelId) { onVendorMessage(messageId, body) + } else if (frame.channel == wifiChannelId) { + onWifiProjectionMessage(messageId, body) } else if (frame.channel == CarMic.current?.channelId) { CarMic.current?.onMessage(messageId, body) } else if (frame.channel == audioLink?.channelId) { @@ -726,6 +730,23 @@ class GearslipRunner( vendorChannelId = gm.serviceId log.i("-> ChannelOpenRequest on GM's channel ${gm.serviceId}") openChannel(gm.serviceId) + // Opening GM's channel and pinging the car didn't stop the Bolt hanging up, so a GM car + // also gets the two channels Gearslip had never opened: Bluetooth and Wi-Fi projection. + // Only what arrives on them is logged, by message id and size, until one turns out to matter. + CarBluetooth.announce("a GM car, issue #18") + ServiceDiscovery.findWifiProjectionService(serviceDiscoveryResponse)?.let { channel -> + wifiChannelId = channel + log.i("-> ChannelOpenRequest on the Wi-Fi projection channel $channel (a GM car, issue #18)") + openChannel(channel) + } + } + + private fun onWifiProjectionMessage(messageId: Int, body: ByteArray) { + if (messageId == MSG_CHANNEL_OPEN_RESPONSE) { + log.i("<- Wi-Fi projection channel open: status=${Protobuf.readInt32Field(body, 1) ?: 0}") + } else { + log.i("<- Wi-Fi projection message id=$messageId (${body.size} bytes)") + } } private fun onVendorMessage(messageId: Int, body: ByteArray) { diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt index 690db76..fc6892b 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/ServiceDiscovery.kt @@ -200,6 +200,13 @@ object ServiceDiscovery { VendorService(id, name) } + /** The channel of the car's Wi-Fi projection service (Service field 14), or null. */ + fun findWifiProjectionService(response: ByteArray): Int? = + Wire.allBytes(Wire.fields(response), 1).firstNotNullOfOrNull { channel -> + val service = Wire.fields(channel) + if (Wire.bytes(service, 14) == null) null else Wire.varint(service, 1)?.toInt() + } + fun findBluetoothService(response: ByteArray): BluetoothService? { for (channel in Wire.allBytes(Wire.fields(response), 1)) { val service = Wire.fields(channel) diff --git a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarBluetooth.kt b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarBluetooth.kt index 5d78d5a..5748435 100644 --- a/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarBluetooth.kt +++ b/gearslip/src/main/java/app/seb3thehacker/gearslip/car/CarBluetooth.kt @@ -105,6 +105,20 @@ object CarBluetooth { } } + /** + * Opens the car's Bluetooth channel though there's no pairing to do, as a real phone does on + * every connection. Issue #18: a 2021 Bolt hangs up about 20 seconds in, and this is one of + * the two channels it offers that Gearslip never opened. Without an address the driver typed, + * it only opens the channel: a made-up address could set the car pairing with the wrong phone. + */ + fun announce(why: String) { + val c = car ?: return + if (pairing || announcing) return + announcing = true + log.i("-> ChannelOpenRequest on channel ${c.service.serviceId} ($why)") + c.open() + } + fun onCarDisconnected() { car = null pairing = false @@ -157,6 +171,10 @@ object CarBluetooth { else main.post { startBond("the car wouldn't open its Bluetooth channel") } return } + if (announcing && typedAddress() == null) { + log.i("Bluetooth channel open; no address typed in Settings, so no PairingRequest") + return + } val method = c.service.pairingMethods.firstOrNull { it == METHOD_NUMERIC_COMPARISON } ?: c.service.pairingMethods.firstOrNull() ?: METHOD_NUMERIC_COMPARISON c.send(