Skip to content

Commit 249ba1a

Browse files
committed
fix: reconnect known ble trezors
1 parent 5af487e commit 249ba1a

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

app/src/main/java/to/bitkit/repositories/TrezorRepo.kt

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,8 @@ class TrezorRepo @Inject constructor(
644644
awaitSetup()
645645
TrezorDebugLog.log("RECONNECT", "Setup OK")
646646
TrezorDebugLog.log("RECONNECT", "Scanning for devices...")
647+
val knownDevices = (_state.value.knownDevices + loadKnownDevices()).distinctBy { it.id }
648+
val knownDevice = knownDevices.find { it.matches(deviceId) }
647649
val scannedDevices = trezorService.scan()
648650
TrezorDebugLog.log(
649651
"RECONNECT",
@@ -652,6 +654,7 @@ class TrezorRepo @Inject constructor(
652654
// Honor the transport the user selected — connect to exactly the
653655
// entry they tapped instead of overriding Bluetooth with USB.
654656
val device = scannedDevices.find { it.id == deviceId }
657+
?: knownDevice?.takeIf { it.transportType == TransportType.BLUETOOTH }?.toDeviceInfo()
655658
?: throw AppError("Device not found nearby — is it powered on?")
656659
TrezorDebugLog.log("RECONNECT", "Found matching device: id=${device.id}, name=${device.name}")
657660
TrezorDebugLog.log("RECONNECT", "Calling connectWithThpRetry...")
@@ -952,15 +955,27 @@ class TrezorRepo @Inject constructor(
952955
throw e
953956
}
954957
TrezorDebugLog.log("THPRetry", "Error is retryable, attempting second connect...")
955-
Logger.warn("Connection failed for $deviceId, retrying", e, context = TAG)
958+
Logger.warn("Failed to connect to '$deviceId', retrying", e, context = TAG)
956959
logCredentialFileState(deviceId, "BEFORE 2nd attempt")
957-
val result = connectDevice(deviceId, selection, requestUsbPermission)
960+
val result = runSuspendCatching {
961+
connectDevice(deviceId, selection, requestUsbPermission)
962+
}.onFailure {
963+
disconnectAfterFailedConnect(deviceId)
964+
}.getOrThrow()
958965
logCredentialFileState(deviceId, "AFTER 2nd attempt (success)")
959966
TrezorDebugLog.log("THPRetry", "Second attempt succeeded")
960967
result
961968
}
962969
}
963970

971+
private suspend fun disconnectAfterFailedConnect(deviceId: String) {
972+
runSuspendCatching { trezorService.disconnect() }
973+
.onFailure {
974+
Logger.warn("Failed to disconnect stale Trezor session for '$deviceId'", it, context = TAG)
975+
}
976+
_state.update { it.copy(connected = null) }
977+
}
978+
964979
private suspend fun connectDevice(
965980
deviceId: String,
966981
selection: WalletSelection,
@@ -1034,6 +1049,16 @@ data class KnownDevice(
10341049

10351050
private fun KnownDevice.matches(deviceId: String) = id == deviceId || path == deviceId
10361051

1052+
private fun KnownDevice.toDeviceInfo() = TrezorDeviceInfo(
1053+
id = id,
1054+
transportType = transportType.toCoreTransportType(),
1055+
name = name,
1056+
path = path,
1057+
label = label,
1058+
model = model,
1059+
isBootloader = false,
1060+
)
1061+
10371062
private fun TrezorTransportType.toTransportType(): TransportType = when (this) {
10381063
TrezorTransportType.BLUETOOTH -> TransportType.BLUETOOTH
10391064
TrezorTransportType.USB -> TransportType.USB

app/src/test/java/to/bitkit/repositories/TrezorRepoTest.kt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,21 @@ class TrezorRepoTest : BaseUnitTest() {
634634
verify(trezorService, times(2)).connect(eq(DEVICE_ID), any())
635635
}
636636

637+
@Test
638+
fun `connect should disconnect stale session after retryable THP failures`() = test {
639+
whenever(trezorService.connect(eq(DEVICE_ID), any()))
640+
.thenThrow(RuntimeException("thp timeout"))
641+
.thenThrow(RuntimeException("session timeout"))
642+
sut = createSut()
643+
644+
val result = sut.connect(DEVICE_ID)
645+
646+
assertTrue(result.isFailure)
647+
assertNull(sut.state.value.connected)
648+
verify(trezorService, times(2)).connect(eq(DEVICE_ID), any())
649+
verify(trezorService).disconnect()
650+
}
651+
637652
@Test
638653
fun `connect should not retry non-retryable errors`() = test {
639654
whenever(trezorService.connect(eq(DEVICE_ID), any())).thenThrow(RuntimeException("bad pin"))
@@ -980,6 +995,29 @@ class TrezorRepoTest : BaseUnitTest() {
980995
assertEquals(DEVICE_ID, sut.state.value.connectedDeviceId)
981996
}
982997

998+
@Test
999+
fun `connectKnownDevice should use stored bluetooth device when scan misses active connection`() = test {
1000+
val bleDeviceId = "ble:57:21:A7:F9:DD:AD"
1001+
val knownDevice = mockKnownDevice(
1002+
id = bleDeviceId,
1003+
path = bleDeviceId,
1004+
transportType = TransportType.BLUETOOTH,
1005+
)
1006+
val features = mockFeatures()
1007+
whenever(hwWalletStore.loadKnownDevices()).thenReturn(listOf(knownDevice))
1008+
whenever(trezorService.scan()).thenReturn(emptyList())
1009+
whenever(trezorService.connect(eq(bleDeviceId), any())).thenReturn(features)
1010+
sut = createSut()
1011+
1012+
sut.initialize()
1013+
val result = sut.connectKnownDevice(bleDeviceId)
1014+
1015+
assertTrue(result.isSuccess)
1016+
assertEquals(features, result.getOrNull())
1017+
assertEquals(bleDeviceId, sut.state.value.connectedDeviceId)
1018+
verify(trezorService).connect(eq(bleDeviceId), any())
1019+
}
1020+
9831021
// endregion
9841022

9851023
// region clearError

0 commit comments

Comments
 (0)