@ledgerhq/device-transport-kit-react-native-hid 0.0.0-fix-rn-ble-20250502082216 → 0.0.0-hid-candidate-20250523130730
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +11 -2
- package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +2 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +84 -14
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +70 -29
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +2 -1
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +5 -4
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +4 -3
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +4 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryDevice.kt +1 -1
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectedDevice.kt +1 -1
- package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachineTest.kt +47 -31
- package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +9 -5
- package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js +1 -1
- package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
- package/lib/cjs/api/bridge/mapper.js +1 -1
- package/lib/cjs/api/bridge/mapper.js.map +2 -2
- package/lib/cjs/api/bridge/mapper.test.js +1 -1
- package/lib/cjs/api/bridge/mapper.test.js.map +2 -2
- package/lib/cjs/api/bridge/types.js +1 -1
- package/lib/cjs/api/bridge/types.js.map +1 -1
- package/lib/cjs/api/transport/Errors.js +1 -1
- package/lib/cjs/api/transport/Errors.js.map +3 -3
- package/lib/cjs/api/transport/NativeModuleWrapper.js +1 -1
- package/lib/cjs/api/transport/NativeModuleWrapper.js.map +1 -1
- package/lib/cjs/api/transport/RNHidTransport.js +1 -1
- package/lib/cjs/api/transport/RNHidTransport.js.map +3 -3
- package/lib/cjs/api/transport/RNHidTransport.test.js +1 -1
- package/lib/cjs/api/transport/RNHidTransport.test.js.map +2 -2
- package/lib/cjs/package.json +10 -10
- package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js +1 -1
- package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
- package/lib/esm/api/bridge/mapper.js +1 -1
- package/lib/esm/api/bridge/mapper.js.map +3 -3
- package/lib/esm/api/bridge/mapper.test.js +1 -1
- package/lib/esm/api/bridge/mapper.test.js.map +3 -3
- package/lib/esm/api/bridge/types.js.map +1 -1
- package/lib/esm/api/transport/Errors.js +1 -1
- package/lib/esm/api/transport/Errors.js.map +3 -3
- package/lib/esm/api/transport/RNHidTransport.js +1 -1
- package/lib/esm/api/transport/RNHidTransport.js.map +3 -3
- package/lib/esm/api/transport/RNHidTransport.test.js +1 -1
- package/lib/esm/api/transport/RNHidTransport.test.js.map +3 -3
- package/lib/esm/package.json +10 -10
- package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts +1 -1
- package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts.map +1 -1
- package/lib/types/api/bridge/mapper.d.ts.map +1 -1
- package/lib/types/api/bridge/types.d.ts +2 -2
- package/lib/types/api/bridge/types.d.ts.map +1 -1
- package/lib/types/api/transport/Errors.d.ts +1 -1
- package/lib/types/api/transport/Errors.d.ts.map +1 -1
- package/lib/types/api/transport/NativeModuleWrapper.d.ts +1 -1
- package/lib/types/api/transport/NativeModuleWrapper.d.ts.map +1 -1
- package/lib/types/api/transport/RNHidTransport.d.ts.map +1 -1
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
- package/package.json +12 -12
|
@@ -31,6 +31,7 @@ import kotlinx.coroutines.flow.onEach
|
|
|
31
31
|
import kotlinx.coroutines.launch
|
|
32
32
|
import timber.log.Timber
|
|
33
33
|
import kotlin.random.Random
|
|
34
|
+
import kotlin.time.Duration
|
|
34
35
|
import kotlin.time.Duration.Companion.milliseconds
|
|
35
36
|
|
|
36
37
|
class TransportHidModule(
|
|
@@ -206,7 +207,13 @@ class TransportHidModule(
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
@ReactMethod
|
|
209
|
-
fun sendApdu(
|
|
210
|
+
fun sendApdu(
|
|
211
|
+
sessionId: String,
|
|
212
|
+
apduBase64: String,
|
|
213
|
+
triggersDisconnection: Boolean,
|
|
214
|
+
abortTimeout: Int,
|
|
215
|
+
promise: Promise
|
|
216
|
+
) {
|
|
210
217
|
try {
|
|
211
218
|
val device = connectedDevices.firstOrNull() { it.id == sessionId }
|
|
212
219
|
if (device == null) {
|
|
@@ -216,7 +223,9 @@ class TransportHidModule(
|
|
|
216
223
|
coroutineScope.launch {
|
|
217
224
|
try {
|
|
218
225
|
val apdu: ByteArray = Base64.decode(apduBase64, Base64.DEFAULT)
|
|
219
|
-
val
|
|
226
|
+
val abortTimeoutDuration = if (abortTimeout <= 0) Duration.INFINITE else abortTimeout.milliseconds
|
|
227
|
+
val res =
|
|
228
|
+
device.sendApduFn(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
220
229
|
promise.resolve(res.toWritableMap())
|
|
221
230
|
} catch (e: Exception) {
|
|
222
231
|
Timber.i("$e, ${e.cause}")
|
|
@@ -103,6 +103,8 @@ internal fun SendApduResult.toWritableMap(): WritableMap {
|
|
|
103
103
|
SendApduFailureReason.NoUsbEndpointFound -> "NoUsbEndpointFound"
|
|
104
104
|
SendApduFailureReason.DeviceDisconnected -> "DeviceDisconnected"
|
|
105
105
|
SendApduFailureReason.Unknown -> "Unknown"
|
|
106
|
+
SendApduFailureReason.AbortTimeout -> "SendApduTimeout"
|
|
107
|
+
SendApduFailureReason.EmptyResponse -> "EmptyResponse"
|
|
106
108
|
})
|
|
107
109
|
}
|
|
108
110
|
}
|
|
@@ -97,31 +97,57 @@ internal class DefaultAndroidUsbTransport(
|
|
|
97
97
|
override fun updateUsbState(state: UsbState) {
|
|
98
98
|
when (state) {
|
|
99
99
|
is UsbState.Detached -> {
|
|
100
|
-
loggerService.log(
|
|
100
|
+
loggerService.log(
|
|
101
|
+
buildSimpleDebugLogInfo(
|
|
102
|
+
"AndroidUsbTransport",
|
|
103
|
+
"Detached deviceId=${state.ledgerUsbDevice.uid}"
|
|
104
|
+
)
|
|
105
|
+
)
|
|
101
106
|
usbConnections.entries.find {
|
|
102
107
|
it.value.getApduSender().dependencies.ledgerUsbDevice.uid == state.ledgerUsbDevice.uid
|
|
103
108
|
}.let { item ->
|
|
104
109
|
scope.launch {
|
|
105
110
|
if (item == null) {
|
|
106
|
-
loggerService.log(
|
|
111
|
+
loggerService.log(
|
|
112
|
+
buildSimpleWarningLogInfo(
|
|
113
|
+
"AndroidUsbTransport",
|
|
114
|
+
"No connection found"
|
|
115
|
+
)
|
|
116
|
+
)
|
|
107
117
|
return@launch
|
|
108
118
|
}
|
|
109
119
|
val (key, deviceConnection) = item
|
|
110
|
-
loggerService.log(
|
|
111
|
-
|
|
120
|
+
loggerService.log(
|
|
121
|
+
buildSimpleInfoLogInfo(
|
|
122
|
+
"AndroidUsbTransport",
|
|
123
|
+
"Device disconnected (sessionId=${deviceConnection.sessionId})"
|
|
124
|
+
)
|
|
125
|
+
)
|
|
112
126
|
usbConnections.remove(key)
|
|
113
127
|
usbConnectionsPendingReconnection.add(deviceConnection)
|
|
128
|
+
deviceConnection.handleDeviceDisconnected()
|
|
129
|
+
(deviceConnection.getApduSender() as AndroidUsbApduSender).release()
|
|
114
130
|
}
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
is UsbState.Attached -> {
|
|
119
|
-
loggerService.log(
|
|
135
|
+
loggerService.log(
|
|
136
|
+
buildSimpleDebugLogInfo(
|
|
137
|
+
"AndroidUsbTransport",
|
|
138
|
+
"Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"
|
|
139
|
+
)
|
|
140
|
+
)
|
|
120
141
|
val usbDevice = usbManager.deviceList.values.firstOrNull {
|
|
121
142
|
it.toLedgerUsbDevice()?.uid == state.ledgerUsbDevice.uid
|
|
122
143
|
}
|
|
123
144
|
if (usbDevice == null) {
|
|
124
|
-
loggerService.log(
|
|
145
|
+
loggerService.log(
|
|
146
|
+
buildSimpleWarningLogInfo(
|
|
147
|
+
"AndroidUsbTransport",
|
|
148
|
+
"No UsbDevice found"
|
|
149
|
+
)
|
|
150
|
+
)
|
|
125
151
|
return
|
|
126
152
|
}
|
|
127
153
|
usbConnectionsPendingReconnection.firstOrNull {
|
|
@@ -138,14 +164,29 @@ internal class DefaultAndroidUsbTransport(
|
|
|
138
164
|
)
|
|
139
165
|
return@launch
|
|
140
166
|
}
|
|
141
|
-
loggerService.log(
|
|
167
|
+
loggerService.log(
|
|
168
|
+
buildSimpleDebugLogInfo(
|
|
169
|
+
"AndroidUsbTransport",
|
|
170
|
+
"Found matching device connection $deviceConnection"
|
|
171
|
+
)
|
|
172
|
+
)
|
|
142
173
|
|
|
143
174
|
val permissionResult = checkOrRequestPermission(usbDevice)
|
|
144
175
|
if (permissionResult is PermissionResult.Denied) {
|
|
145
|
-
loggerService.log(
|
|
176
|
+
loggerService.log(
|
|
177
|
+
buildSimpleDebugLogInfo(
|
|
178
|
+
"AndroidUsbTransport",
|
|
179
|
+
"Permission denied"
|
|
180
|
+
)
|
|
181
|
+
)
|
|
146
182
|
return@launch
|
|
147
183
|
}
|
|
148
|
-
loggerService.log(
|
|
184
|
+
loggerService.log(
|
|
185
|
+
buildSimpleInfoLogInfo(
|
|
186
|
+
"AndroidUsbTransport",
|
|
187
|
+
"Reconnecting device (sessionId=${deviceConnection.sessionId})"
|
|
188
|
+
)
|
|
189
|
+
)
|
|
149
190
|
deviceConnection.handleDeviceConnected(
|
|
150
191
|
AndroidUsbApduSender(
|
|
151
192
|
dependencies = AndroidUsbApduSender.Dependencies(
|
|
@@ -195,7 +236,12 @@ internal class DefaultAndroidUsbTransport(
|
|
|
195
236
|
device = usbDevice,
|
|
196
237
|
)
|
|
197
238
|
|
|
198
|
-
loggerService.log(
|
|
239
|
+
loggerService.log(
|
|
240
|
+
buildSimpleDebugLogInfo(
|
|
241
|
+
"AndroidUsbTransport",
|
|
242
|
+
"Waiting for permission result"
|
|
243
|
+
)
|
|
244
|
+
)
|
|
199
245
|
|
|
200
246
|
val result = eventsFlow.first {
|
|
201
247
|
it is UsbPermissionEvent.PermissionGranted ||
|
|
@@ -229,12 +275,33 @@ internal class DefaultAndroidUsbTransport(
|
|
|
229
275
|
}
|
|
230
276
|
|
|
231
277
|
override suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult {
|
|
232
|
-
|
|
233
|
-
|
|
278
|
+
|
|
279
|
+
loggerService.log(
|
|
280
|
+
buildSimpleDebugLogInfo(
|
|
281
|
+
"AndroidUsbTransport",
|
|
282
|
+
"[connect] discoveryDevice=$discoveryDevice"
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
val usbDevices = usbManager.deviceList.values
|
|
287
|
+
|
|
288
|
+
var usbDevice: UsbDevice? =
|
|
289
|
+
usbDevices.firstOrNull { it.deviceId == discoveryDevice.uid.toInt() }
|
|
290
|
+
|
|
291
|
+
if (usbDevice == null) {
|
|
292
|
+
// This is useful for LL during the OS update
|
|
293
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] No device found with matching id, looking for device with matching model"))
|
|
294
|
+
usbDevice =
|
|
295
|
+
usbDevices.firstOrNull { it.toLedgerUsbDevice()?.ledgerDevice == discoveryDevice.ledgerDevice }
|
|
296
|
+
} else {
|
|
297
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] Found device with matching id"))
|
|
298
|
+
}
|
|
234
299
|
|
|
235
300
|
val ledgerUsbDevice = usbDevice?.toLedgerUsbDevice()
|
|
236
301
|
|
|
237
302
|
return if (usbDevice == null || ledgerUsbDevice == null) {
|
|
303
|
+
loggerService.log(
|
|
304
|
+
buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] No device found"))
|
|
238
305
|
InternalConnectionResult.ConnectionError(error = InternalConnectionResult.Failure.DeviceNotFound)
|
|
239
306
|
} else {
|
|
240
307
|
val permissionResult = checkOrRequestPermission(usbDevice)
|
|
@@ -259,9 +326,10 @@ internal class DefaultAndroidUsbTransport(
|
|
|
259
326
|
val deviceConnection = DeviceConnection(
|
|
260
327
|
sessionId = sessionId,
|
|
261
328
|
deviceApduSender = apduSender,
|
|
262
|
-
isFatalSendApduFailure = { false },
|
|
329
|
+
isFatalSendApduFailure = { false },
|
|
263
330
|
reconnectionTimeoutDuration = 5.seconds,
|
|
264
331
|
onTerminated = {
|
|
332
|
+
(it.getApduSender() as AndroidUsbApduSender).release()
|
|
265
333
|
usbConnections.remove(sessionId)
|
|
266
334
|
usbConnectionsPendingReconnection.remove(it)
|
|
267
335
|
eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
|
|
@@ -276,7 +344,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
276
344
|
discoveryDevice.name,
|
|
277
345
|
discoveryDevice.ledgerDevice,
|
|
278
346
|
discoveryDevice.connectivityType,
|
|
279
|
-
sendApduFn = { apdu
|
|
347
|
+
sendApduFn = { apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
|
|
348
|
+
deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
349
|
+
}
|
|
280
350
|
)
|
|
281
351
|
|
|
282
352
|
usbConnections[sessionId] = deviceConnection
|
|
@@ -13,21 +13,21 @@ import android.hardware.usb.UsbInterface
|
|
|
13
13
|
import android.hardware.usb.UsbManager
|
|
14
14
|
import android.hardware.usb.UsbRequest
|
|
15
15
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
|
|
16
|
+
import com.ledger.devicesdk.shared.androidMainInternal.transport.USB_MTU
|
|
17
|
+
import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceApduSender
|
|
16
18
|
import com.ledger.devicesdk.shared.api.apdu.SendApduFailureReason
|
|
17
19
|
import com.ledger.devicesdk.shared.api.apdu.SendApduResult
|
|
18
|
-
import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceApduSender
|
|
19
|
-
import com.ledger.devicesdk.shared.api.utils.toHexadecimalString
|
|
20
|
-
import com.ledger.devicesdk.shared.androidMainInternal.transport.USB_MTU
|
|
21
20
|
import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
|
|
22
21
|
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
|
|
23
|
-
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
|
|
24
22
|
import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
|
|
25
23
|
import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
|
|
26
|
-
import java.nio.ByteBuffer
|
|
27
|
-
import kotlin.random.Random
|
|
28
24
|
import kotlinx.coroutines.CoroutineDispatcher
|
|
25
|
+
import kotlinx.coroutines.delay
|
|
26
|
+
import kotlinx.coroutines.launch
|
|
29
27
|
import kotlinx.coroutines.withContext
|
|
30
|
-
import
|
|
28
|
+
import java.nio.ByteBuffer
|
|
29
|
+
import kotlin.random.Random
|
|
30
|
+
import kotlin.time.Duration
|
|
31
31
|
|
|
32
32
|
private const val USB_TIMEOUT = 500
|
|
33
33
|
|
|
@@ -35,44 +35,75 @@ private const val DEFAULT_USB_INTERFACE = 0
|
|
|
35
35
|
|
|
36
36
|
internal class AndroidUsbApduSender(
|
|
37
37
|
override val dependencies: Dependencies,
|
|
38
|
-
|
|
38
|
+
usbManager: UsbManager,
|
|
39
39
|
private val framerService: FramerService,
|
|
40
40
|
private val request: UsbRequest,
|
|
41
41
|
private val ioDispatcher: CoroutineDispatcher,
|
|
42
42
|
private val loggerService: LoggerService,
|
|
43
43
|
) : DeviceApduSender<AndroidUsbApduSender.Dependencies> {
|
|
44
|
-
|
|
45
44
|
data class Dependencies(
|
|
46
45
|
val usbDevice: UsbDevice,
|
|
47
46
|
val ledgerUsbDevice: LedgerUsbDevice,
|
|
48
47
|
)
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
private val usbDevice = dependencies.usbDevice
|
|
50
|
+
private val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
|
|
51
|
+
private val androidToUsbEndpoint =
|
|
52
|
+
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
|
|
53
|
+
private val usbToAndroidEndpoint =
|
|
54
|
+
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
|
|
55
|
+
private val usbConnection = usbManager.openDevice(usbDevice)
|
|
56
|
+
.apply { claimInterface(usbInterface, true) }
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
fun release() {
|
|
60
|
+
usbConnection.releaseInterface(usbInterface)
|
|
61
|
+
usbConnection.close()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
override suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult =
|
|
51
65
|
try {
|
|
52
|
-
val usbDevice = dependencies.usbDevice
|
|
53
66
|
withContext(context = ioDispatcher) {
|
|
54
|
-
|
|
55
|
-
val
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
|
|
68
|
+
val timeoutJob = launch {
|
|
69
|
+
delay(abortTimeoutDuration)
|
|
70
|
+
throw SendApduTimeoutException
|
|
71
|
+
}
|
|
58
72
|
|
|
59
73
|
transmitApdu(
|
|
60
74
|
usbConnection = usbConnection,
|
|
61
75
|
androidToUsbEndpoint = androidToUsbEndpoint,
|
|
62
76
|
rawApdu = apdu,
|
|
63
77
|
)
|
|
78
|
+
|
|
64
79
|
val apduResponse =
|
|
65
80
|
receiveApdu(
|
|
66
81
|
usbConnection = usbConnection,
|
|
67
82
|
usbToAndroidEndpoint = usbToAndroidEndpoint,
|
|
68
83
|
)
|
|
84
|
+
timeoutJob.cancel()
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
|
|
86
|
+
if (apduResponse.isEmpty()) {
|
|
87
|
+
return@withContext SendApduResult.Failure(reason = SendApduFailureReason.EmptyResponse)
|
|
88
|
+
}
|
|
72
89
|
|
|
73
|
-
SendApduResult.Success(apdu = apduResponse)
|
|
90
|
+
return@withContext SendApduResult.Success(apdu = apduResponse)
|
|
74
91
|
}
|
|
92
|
+
} catch (e: SendApduTimeoutException) {
|
|
93
|
+
loggerService.log(
|
|
94
|
+
buildSimpleErrorLogInfo(
|
|
95
|
+
"AndroidUsbApduSender",
|
|
96
|
+
"timeout in send: $e"
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
SendApduResult.Failure(reason = SendApduFailureReason.AbortTimeout)
|
|
75
100
|
} catch (e: NoSuchElementException) {
|
|
101
|
+
loggerService.log(
|
|
102
|
+
buildSimpleErrorLogInfo(
|
|
103
|
+
"AndroidUsbApduSender",
|
|
104
|
+
"no endpoint found: $e"
|
|
105
|
+
)
|
|
106
|
+
)
|
|
76
107
|
SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
|
|
77
108
|
} catch (e: Exception) {
|
|
78
109
|
loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
|
|
@@ -84,22 +115,27 @@ internal class AndroidUsbApduSender(
|
|
|
84
115
|
androidToUsbEndpoint: UsbEndpoint,
|
|
85
116
|
rawApdu: ByteArray,
|
|
86
117
|
) {
|
|
87
|
-
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
|
|
119
|
+
.forEach { apduFrame ->
|
|
120
|
+
val buffer = apduFrame.toByteArray()
|
|
121
|
+
usbConnection.bulkTransfer(
|
|
122
|
+
androidToUsbEndpoint,
|
|
123
|
+
buffer,
|
|
124
|
+
apduFrame.size(),
|
|
125
|
+
USB_TIMEOUT
|
|
126
|
+
)
|
|
127
|
+
}
|
|
92
128
|
}
|
|
93
129
|
|
|
94
130
|
private fun receiveApdu(
|
|
95
131
|
usbConnection: UsbDeviceConnection,
|
|
96
132
|
usbToAndroidEndpoint: UsbEndpoint,
|
|
97
|
-
): ByteArray
|
|
98
|
-
if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
|
|
133
|
+
): ByteArray {
|
|
134
|
+
return if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
|
|
99
135
|
request.close()
|
|
100
136
|
byteArrayOf()
|
|
101
137
|
} else {
|
|
102
|
-
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
|
|
138
|
+
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true) {
|
|
103
139
|
val buffer = ByteArray(USB_MTU)
|
|
104
140
|
val responseBuffer = ByteBuffer.allocate(USB_MTU)
|
|
105
141
|
|
|
@@ -107,8 +143,7 @@ internal class AndroidUsbApduSender(
|
|
|
107
143
|
if (!queuingResult) {
|
|
108
144
|
request.close()
|
|
109
145
|
byteArrayOf()
|
|
110
|
-
}
|
|
111
|
-
else{
|
|
146
|
+
} else {
|
|
112
147
|
usbConnection.requestWait()
|
|
113
148
|
responseBuffer.rewind()
|
|
114
149
|
responseBuffer.get(buffer, 0, responseBuffer.remaining())
|
|
@@ -117,6 +152,7 @@ internal class AndroidUsbApduSender(
|
|
|
117
152
|
}
|
|
118
153
|
framerService.deserialize(mtu = USB_MTU, frames)
|
|
119
154
|
}
|
|
155
|
+
}
|
|
120
156
|
|
|
121
157
|
private fun UsbInterface.firstEndpointOrThrow(predicate: (Int) -> Boolean): UsbEndpoint {
|
|
122
158
|
for (endp in 0..this.endpointCount) {
|
|
@@ -129,5 +165,10 @@ internal class AndroidUsbApduSender(
|
|
|
129
165
|
throw NoSuchElementException("No endpoint matching the predicate")
|
|
130
166
|
}
|
|
131
167
|
|
|
132
|
-
private fun generateChannelId(): ByteArray =
|
|
168
|
+
private fun generateChannelId(): ByteArray =
|
|
169
|
+
Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
|
|
170
|
+
|
|
171
|
+
private data object SendApduTimeoutException : Exception() {
|
|
172
|
+
private fun readResolve(): Any = SendApduTimeoutException
|
|
173
|
+
}
|
|
133
174
|
}
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
package com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection
|
|
7
7
|
|
|
8
8
|
import com.ledger.devicesdk.shared.api.apdu.SendApduResult
|
|
9
|
+
import kotlin.time.Duration
|
|
9
10
|
|
|
10
11
|
internal interface DeviceApduSender<Dependencies> {
|
|
11
|
-
suspend fun send(apdu: ByteArray): SendApduResult
|
|
12
|
+
suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult
|
|
12
13
|
val dependencies: Dependencies
|
|
13
14
|
}
|
|
@@ -24,9 +24,9 @@ internal class DeviceConnection<Dependencies>(
|
|
|
24
24
|
|
|
25
25
|
init {
|
|
26
26
|
stateMachine = DeviceConnectionStateMachine(
|
|
27
|
-
sendApduFn = {
|
|
27
|
+
sendApduFn = { apdu, abortTimeoutDuration ->
|
|
28
28
|
coroutineScope.launch {
|
|
29
|
-
val res = deviceApduSender.send(
|
|
29
|
+
val res = deviceApduSender.send(apdu, abortTimeoutDuration)
|
|
30
30
|
handleApduResult(res)
|
|
31
31
|
}
|
|
32
32
|
},
|
|
@@ -66,12 +66,13 @@ internal class DeviceConnection<Dependencies>(
|
|
|
66
66
|
stateMachine.handleDeviceDisconnected()
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
public suspend fun requestSendApdu(apdu: ByteArray): SendApduResult =
|
|
69
|
+
public suspend fun requestSendApdu(apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration): SendApduResult =
|
|
70
70
|
suspendCoroutine { cont ->
|
|
71
71
|
stateMachine.requestSendApdu(
|
|
72
72
|
DeviceConnectionStateMachine.SendApduRequestContent(
|
|
73
73
|
apdu = apdu,
|
|
74
|
-
triggersDisconnection = apduTriggersDisconnection(apdu),
|
|
74
|
+
triggersDisconnection = apduTriggersDisconnection(apdu) || triggersDisconnection,
|
|
75
|
+
abortTimeoutDuration = abortTimeoutDuration,
|
|
75
76
|
resultCallback = cont::resume
|
|
76
77
|
)
|
|
77
78
|
)
|
|
@@ -12,7 +12,7 @@ import kotlinx.coroutines.launch
|
|
|
12
12
|
import kotlin.time.Duration
|
|
13
13
|
|
|
14
14
|
internal class DeviceConnectionStateMachine(
|
|
15
|
-
private val sendApduFn: (apdu: ByteArray) -> Unit,
|
|
15
|
+
private val sendApduFn: (apdu: ByteArray, abortTimeoutDuration: Duration) -> Unit,
|
|
16
16
|
private val onTerminated: () -> Unit,
|
|
17
17
|
private val isFatalSendApduFailure: (SendApduResult.Failure) -> Boolean,
|
|
18
18
|
private val reconnectionTimeoutDuration: Duration,
|
|
@@ -29,7 +29,7 @@ internal class DeviceConnectionStateMachine(
|
|
|
29
29
|
when (newState) {
|
|
30
30
|
is State.Connected -> {}
|
|
31
31
|
is State.SendingApdu -> {
|
|
32
|
-
sendApduFn(newState.requestContent.apdu)
|
|
32
|
+
sendApduFn(newState.requestContent.apdu, newState.requestContent.abortTimeoutDuration)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
is State.WaitingForReconnection -> {
|
|
@@ -240,7 +240,7 @@ internal class DeviceConnectionStateMachine(
|
|
|
240
240
|
-> Event: $event
|
|
241
241
|
-> New state: $state
|
|
242
242
|
""".trimIndent()
|
|
243
|
-
loggerService.log(buildSimpleDebugLogInfo("DeviceConnectionStateMachine", logMessage))
|
|
243
|
+
// loggerService.log(buildSimpleDebugLogInfo("DeviceConnectionStateMachine", logMessage))
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
private var timeoutJob: Job? = null
|
|
@@ -280,6 +280,7 @@ internal class DeviceConnectionStateMachine(
|
|
|
280
280
|
data class SendApduRequestContent(
|
|
281
281
|
val apdu: ByteArray,
|
|
282
282
|
val triggersDisconnection: Boolean,
|
|
283
|
+
val abortTimeoutDuration: Duration,
|
|
283
284
|
val resultCallback: (SendApduResult) -> Unit
|
|
284
285
|
)
|
|
285
286
|
|
|
@@ -44,4 +44,8 @@ public sealed class SendApduFailureReason {
|
|
|
44
44
|
public data object DeviceDisconnected : SendApduFailureReason()
|
|
45
45
|
|
|
46
46
|
public data object Unknown : SendApduFailureReason()
|
|
47
|
+
|
|
48
|
+
public data object AbortTimeout : SendApduFailureReason()
|
|
49
|
+
|
|
50
|
+
public data object EmptyResponse : SendApduFailureReason()
|
|
47
51
|
}
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryDevice.kt
CHANGED
|
@@ -8,7 +8,7 @@ package com.ledger.devicesdk.shared.api.discovery
|
|
|
8
8
|
import com.ledger.devicesdk.shared.api.device.LedgerDevice
|
|
9
9
|
import kotlinx.datetime.Clock
|
|
10
10
|
|
|
11
|
-
public class DiscoveryDevice(
|
|
11
|
+
public data class DiscoveryDevice(
|
|
12
12
|
public val uid: String,
|
|
13
13
|
public val name: String,
|
|
14
14
|
public val ledgerDevice: LedgerDevice,
|
|
@@ -9,5 +9,5 @@ internal data class InternalConnectedDevice(
|
|
|
9
9
|
val name: String,
|
|
10
10
|
val ledgerDevice: LedgerDevice,
|
|
11
11
|
val connectivity: ConnectivityType,
|
|
12
|
-
val sendApduFn: suspend (ByteArray) -> SendApduResult,
|
|
12
|
+
val sendApduFn: suspend (apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: kotlin.time.Duration) -> SendApduResult,
|
|
13
13
|
)
|