@ledgerhq/device-transport-kit-react-native-hid 0.0.0-rn-hid-improvements-explorations-20250523153215 → 0.0.0-rn-ble-pairing-removed-while-reconnecting-20250731150808
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/BridgeEvents.kt +0 -3
- package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +3 -70
- package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +0 -10
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +14 -84
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +31 -85
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +1 -2
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +4 -5
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +3 -4
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +0 -4
- 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 +31 -47
- package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +5 -9
- package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js +1 -1
- package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
- package/lib/cjs/api/bridge/StubNativeModuleWrapper.js +1 -1
- package/lib/cjs/api/bridge/StubNativeModuleWrapper.js.map +2 -2
- 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 +3 -3
- 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/esm/api/bridge/DefaultNativeModuleWrapper.js +1 -1
- package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
- package/lib/esm/api/bridge/StubNativeModuleWrapper.js +1 -1
- package/lib/esm/api/bridge/StubNativeModuleWrapper.js.map +2 -2
- 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 +1 -1
- package/lib/esm/api/bridge/types.js.map +3 -3
- 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/types/api/bridge/DefaultNativeModuleWrapper.d.ts +1 -4
- package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts.map +1 -1
- package/lib/types/api/bridge/StubNativeModuleWrapper.d.ts +0 -5
- package/lib/types/api/bridge/StubNativeModuleWrapper.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 -9
- 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 -4
- package/lib/types/api/transport/NativeModuleWrapper.d.ts.map +1 -1
- package/lib/types/api/transport/RNHidTransport.d.ts +0 -2
- package/lib/types/api/transport/RNHidTransport.d.ts.map +1 -1
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -26,9 +26,6 @@ internal sealed class BridgeEvents(val eventName: String, val params: EventParam
|
|
|
26
26
|
data class DeviceDisconnected(
|
|
27
27
|
val deviceConnectionLost: TransportEvent.DeviceConnectionLost,
|
|
28
28
|
): BridgeEvents("DeviceDisconnected", EventParams.WMap(deviceConnectionLost.toWritableMap()))
|
|
29
|
-
data class ExchangeBulkApdusEvent(
|
|
30
|
-
val exchangeBulkProgressEvent: TransportHidModule.ExchangeBulkProgressEvent
|
|
31
|
-
): BridgeEvents("ExchangeBulkApdus", EventParams.WMap(exchangeBulkProgressEvent.toWritableMap()))
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
internal fun sendEvent(reactContext: ReactContext, bridgeEvent: BridgeEvents) {
|
|
@@ -10,7 +10,6 @@ import com.facebook.react.bridge.Promise
|
|
|
10
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
11
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
12
12
|
import com.facebook.react.bridge.ReactMethod
|
|
13
|
-
import com.facebook.react.bridge.ReadableArray
|
|
14
13
|
import com.ledger.androidtransporthid.bridge.toWritableMap
|
|
15
14
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.AndroidUsbTransport
|
|
16
15
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.DefaultAndroidUsbTransport
|
|
@@ -18,13 +17,11 @@ import com.ledger.devicesdk.shared.androidMain.transport.usb.controller.ACTION_U
|
|
|
18
17
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.controller.UsbAttachedReceiverController
|
|
19
18
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.controller.UsbDetachedReceiverController
|
|
20
19
|
import com.ledger.devicesdk.shared.androidMain.transport.usb.controller.UsbPermissionReceiver
|
|
21
|
-
import com.ledger.devicesdk.shared.api.apdu.SendApduResult
|
|
22
20
|
import com.ledger.devicesdk.shared.api.discovery.DiscoveryDevice
|
|
23
21
|
import com.ledger.devicesdk.shared.internal.connection.InternalConnectedDevice
|
|
24
22
|
import com.ledger.devicesdk.shared.internal.connection.InternalConnectionResult
|
|
25
23
|
import com.ledger.devicesdk.shared.internal.event.SdkEventDispatcher
|
|
26
24
|
import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
|
|
27
|
-
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleDebugLogInfo
|
|
28
25
|
import com.ledger.devicesdk.shared.internal.transport.TransportEvent
|
|
29
26
|
import kotlinx.coroutines.CoroutineScope
|
|
30
27
|
import kotlinx.coroutines.Dispatchers
|
|
@@ -34,7 +31,6 @@ import kotlinx.coroutines.flow.onEach
|
|
|
34
31
|
import kotlinx.coroutines.launch
|
|
35
32
|
import timber.log.Timber
|
|
36
33
|
import kotlin.random.Random
|
|
37
|
-
import kotlin.time.Duration
|
|
38
34
|
import kotlin.time.Duration.Companion.milliseconds
|
|
39
35
|
|
|
40
36
|
class TransportHidModule(
|
|
@@ -52,7 +48,7 @@ class TransportHidModule(
|
|
|
52
48
|
private val loggerService: LoggerService =
|
|
53
49
|
LoggerService { info ->
|
|
54
50
|
Timber.tag("LDMKTransportHIDModule " + info.tag).d(info.message)
|
|
55
|
-
|
|
51
|
+
sendEvent(reactContext, BridgeEvents.TransportLog(info))
|
|
56
52
|
}
|
|
57
53
|
|
|
58
54
|
private val transport: AndroidUsbTransport? by lazy {
|
|
@@ -210,29 +206,7 @@ class TransportHidModule(
|
|
|
210
206
|
}
|
|
211
207
|
|
|
212
208
|
@ReactMethod
|
|
213
|
-
fun sendApdu(
|
|
214
|
-
sessionId: String,
|
|
215
|
-
apduBase64: String,
|
|
216
|
-
triggersDisconnection: Boolean,
|
|
217
|
-
abortTimeout: Int,
|
|
218
|
-
promise: Promise
|
|
219
|
-
) {
|
|
220
|
-
// Log PERF: "Timestamp of the start of the function"
|
|
221
|
-
loggerService.log(
|
|
222
|
-
buildSimpleDebugLogInfo(
|
|
223
|
-
"AndroidUsbTransport",
|
|
224
|
-
"PERF: [@ReactMethod sendApdu] called at ${System.currentTimeMillis()}",
|
|
225
|
-
)
|
|
226
|
-
)
|
|
227
|
-
fun logEnd() = run {
|
|
228
|
-
loggerService.log(
|
|
229
|
-
buildSimpleDebugLogInfo(
|
|
230
|
-
"AndroidUsbTransport",
|
|
231
|
-
"PERF: [@ReactMethod sendApdu] finished at ${System.currentTimeMillis()}",
|
|
232
|
-
)
|
|
233
|
-
)
|
|
234
|
-
}
|
|
235
|
-
|
|
209
|
+
fun sendApdu(sessionId: String, apduBase64: String, promise: Promise) {
|
|
236
210
|
try {
|
|
237
211
|
val device = connectedDevices.firstOrNull() { it.id == sessionId }
|
|
238
212
|
if (device == null) {
|
|
@@ -242,55 +216,19 @@ class TransportHidModule(
|
|
|
242
216
|
coroutineScope.launch {
|
|
243
217
|
try {
|
|
244
218
|
val apdu: ByteArray = Base64.decode(apduBase64, Base64.DEFAULT)
|
|
245
|
-
val
|
|
246
|
-
val res =
|
|
247
|
-
device.sendApduFn(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
248
|
-
logEnd()
|
|
219
|
+
val res = device.sendApduFn(apdu)
|
|
249
220
|
promise.resolve(res.toWritableMap())
|
|
250
221
|
} catch (e: Exception) {
|
|
251
222
|
Timber.i("$e, ${e.cause}")
|
|
252
|
-
logEnd()
|
|
253
223
|
promise.reject(e)
|
|
254
224
|
}
|
|
255
225
|
}
|
|
256
226
|
} catch (e: Exception) {
|
|
257
227
|
Timber.i("$e, ${e.cause}")
|
|
258
|
-
logEnd()
|
|
259
228
|
promise.reject(e)
|
|
260
229
|
}
|
|
261
230
|
}
|
|
262
231
|
|
|
263
|
-
@ReactMethod
|
|
264
|
-
public fun exchangeBulkApdus(
|
|
265
|
-
sessionId: String,
|
|
266
|
-
apdus: ReadableArray,
|
|
267
|
-
requestId: Int,
|
|
268
|
-
promise: Promise,
|
|
269
|
-
) {
|
|
270
|
-
// find connected device, loop over all apdus and send them to the device, and send back the last result
|
|
271
|
-
val device = connectedDevices.firstOrNull() { it.id == sessionId }
|
|
272
|
-
if (device == null) {
|
|
273
|
-
promise.reject(Exception("[TransportHidModule][exchangeBulkApdus] Device not found"))
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
coroutineScope.launch {
|
|
277
|
-
try {
|
|
278
|
-
val apdusList = apdus.toArrayList()
|
|
279
|
-
val apdusByteArray = apdusList.map { Base64.decode(it as String, Base64.DEFAULT) }
|
|
280
|
-
lateinit var lastResult: SendApduResult
|
|
281
|
-
apdusByteArray.forEachIndexed { index, apdu ->
|
|
282
|
-
lastResult = device.sendApduFn(apdu, false, Duration.INFINITE)
|
|
283
|
-
if (index == apdusByteArray.lastIndex || index % 20 == 0) {
|
|
284
|
-
sendEvent(reactContext, BridgeEvents.ExchangeBulkApdusEvent(ExchangeBulkProgressEvent(requestId, index)))
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
promise.resolve(lastResult.toWritableMap())
|
|
288
|
-
} catch (e: Exception) {
|
|
289
|
-
promise.reject(e)
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
232
|
@ReactMethod
|
|
295
233
|
fun addListener(eventName: String) {
|
|
296
234
|
// Nothing to do in our case, but React Native will issue a warning if this isn't implemented
|
|
@@ -300,9 +238,4 @@ class TransportHidModule(
|
|
|
300
238
|
fun removeListeners(count: Int) {
|
|
301
239
|
// Nothing to do in our case, but React Native will issue a warning if this isn't implemented
|
|
302
240
|
}
|
|
303
|
-
|
|
304
|
-
data class ExchangeBulkProgressEvent(
|
|
305
|
-
val requestId: Int,
|
|
306
|
-
val index: Int,
|
|
307
|
-
)
|
|
308
241
|
}
|
|
@@ -4,7 +4,6 @@ import android.util.Base64
|
|
|
4
4
|
import com.facebook.react.bridge.Arguments
|
|
5
5
|
import com.facebook.react.bridge.WritableArray
|
|
6
6
|
import com.facebook.react.bridge.WritableMap
|
|
7
|
-
import com.ledger.androidtransporthid.TransportHidModule
|
|
8
7
|
import com.ledger.devicesdk.shared.api.apdu.SendApduFailureReason
|
|
9
8
|
import com.ledger.devicesdk.shared.api.apdu.SendApduResult
|
|
10
9
|
import com.ledger.devicesdk.shared.api.device.LedgerDevice
|
|
@@ -104,8 +103,6 @@ internal fun SendApduResult.toWritableMap(): WritableMap {
|
|
|
104
103
|
SendApduFailureReason.NoUsbEndpointFound -> "NoUsbEndpointFound"
|
|
105
104
|
SendApduFailureReason.DeviceDisconnected -> "DeviceDisconnected"
|
|
106
105
|
SendApduFailureReason.Unknown -> "Unknown"
|
|
107
|
-
SendApduFailureReason.AbortTimeout -> "SendApduTimeout"
|
|
108
|
-
SendApduFailureReason.EmptyResponse -> "EmptyResponse"
|
|
109
106
|
})
|
|
110
107
|
}
|
|
111
108
|
}
|
|
@@ -117,13 +114,6 @@ internal fun TransportEvent.DeviceConnectionLost.toWritableMap(): WritableMap =
|
|
|
117
114
|
putString("id", id)
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
internal fun TransportHidModule.ExchangeBulkProgressEvent.toWritableMap(): WritableMap =
|
|
121
|
-
Arguments.createMap().apply {
|
|
122
|
-
putInt("requestId", requestId)
|
|
123
|
-
putInt("index", index)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
|
|
127
117
|
/* lists */
|
|
128
118
|
|
|
129
119
|
fun List<DiscoveryDevice>.toWritableArray(): WritableArray =
|
|
@@ -97,57 +97,31 @@ internal class DefaultAndroidUsbTransport(
|
|
|
97
97
|
override fun updateUsbState(state: UsbState) {
|
|
98
98
|
when (state) {
|
|
99
99
|
is UsbState.Detached -> {
|
|
100
|
-
loggerService.log(
|
|
101
|
-
buildSimpleDebugLogInfo(
|
|
102
|
-
"AndroidUsbTransport",
|
|
103
|
-
"Detached deviceId=${state.ledgerUsbDevice.uid}"
|
|
104
|
-
)
|
|
105
|
-
)
|
|
100
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Detached deviceId=${state.ledgerUsbDevice.uid}"))
|
|
106
101
|
usbConnections.entries.find {
|
|
107
102
|
it.value.getApduSender().dependencies.ledgerUsbDevice.uid == state.ledgerUsbDevice.uid
|
|
108
103
|
}.let { item ->
|
|
109
104
|
scope.launch {
|
|
110
105
|
if (item == null) {
|
|
111
|
-
loggerService.log(
|
|
112
|
-
buildSimpleWarningLogInfo(
|
|
113
|
-
"AndroidUsbTransport",
|
|
114
|
-
"No connection found"
|
|
115
|
-
)
|
|
116
|
-
)
|
|
106
|
+
loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No connection found"))
|
|
117
107
|
return@launch
|
|
118
108
|
}
|
|
119
109
|
val (key, deviceConnection) = item
|
|
120
|
-
loggerService.log(
|
|
121
|
-
|
|
122
|
-
"AndroidUsbTransport",
|
|
123
|
-
"Device disconnected (sessionId=${deviceConnection.sessionId})"
|
|
124
|
-
)
|
|
125
|
-
)
|
|
110
|
+
loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Device disconnected (sessionId=${deviceConnection.sessionId})"))
|
|
111
|
+
deviceConnection.handleDeviceDisconnected()
|
|
126
112
|
usbConnections.remove(key)
|
|
127
113
|
usbConnectionsPendingReconnection.add(deviceConnection)
|
|
128
|
-
deviceConnection.handleDeviceDisconnected()
|
|
129
|
-
(deviceConnection.getApduSender() as AndroidUsbApduSender).release()
|
|
130
114
|
}
|
|
131
115
|
}
|
|
132
116
|
}
|
|
133
117
|
|
|
134
118
|
is UsbState.Attached -> {
|
|
135
|
-
loggerService.log(
|
|
136
|
-
buildSimpleDebugLogInfo(
|
|
137
|
-
"AndroidUsbTransport",
|
|
138
|
-
"Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"
|
|
139
|
-
)
|
|
140
|
-
)
|
|
119
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"))
|
|
141
120
|
val usbDevice = usbManager.deviceList.values.firstOrNull {
|
|
142
121
|
it.toLedgerUsbDevice()?.uid == state.ledgerUsbDevice.uid
|
|
143
122
|
}
|
|
144
123
|
if (usbDevice == null) {
|
|
145
|
-
loggerService.log(
|
|
146
|
-
buildSimpleWarningLogInfo(
|
|
147
|
-
"AndroidUsbTransport",
|
|
148
|
-
"No UsbDevice found"
|
|
149
|
-
)
|
|
150
|
-
)
|
|
124
|
+
loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No UsbDevice found"))
|
|
151
125
|
return
|
|
152
126
|
}
|
|
153
127
|
usbConnectionsPendingReconnection.firstOrNull {
|
|
@@ -164,29 +138,14 @@ internal class DefaultAndroidUsbTransport(
|
|
|
164
138
|
)
|
|
165
139
|
return@launch
|
|
166
140
|
}
|
|
167
|
-
loggerService.log(
|
|
168
|
-
buildSimpleDebugLogInfo(
|
|
169
|
-
"AndroidUsbTransport",
|
|
170
|
-
"Found matching device connection $deviceConnection"
|
|
171
|
-
)
|
|
172
|
-
)
|
|
141
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Found matching device connection $deviceConnection"))
|
|
173
142
|
|
|
174
143
|
val permissionResult = checkOrRequestPermission(usbDevice)
|
|
175
144
|
if (permissionResult is PermissionResult.Denied) {
|
|
176
|
-
loggerService.log(
|
|
177
|
-
buildSimpleDebugLogInfo(
|
|
178
|
-
"AndroidUsbTransport",
|
|
179
|
-
"Permission denied"
|
|
180
|
-
)
|
|
181
|
-
)
|
|
145
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Permission denied"))
|
|
182
146
|
return@launch
|
|
183
147
|
}
|
|
184
|
-
loggerService.log(
|
|
185
|
-
buildSimpleInfoLogInfo(
|
|
186
|
-
"AndroidUsbTransport",
|
|
187
|
-
"Reconnecting device (sessionId=${deviceConnection.sessionId})"
|
|
188
|
-
)
|
|
189
|
-
)
|
|
148
|
+
loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Reconnecting device (sessionId=${deviceConnection.sessionId})"))
|
|
190
149
|
deviceConnection.handleDeviceConnected(
|
|
191
150
|
AndroidUsbApduSender(
|
|
192
151
|
dependencies = AndroidUsbApduSender.Dependencies(
|
|
@@ -236,12 +195,7 @@ internal class DefaultAndroidUsbTransport(
|
|
|
236
195
|
device = usbDevice,
|
|
237
196
|
)
|
|
238
197
|
|
|
239
|
-
loggerService.log(
|
|
240
|
-
buildSimpleDebugLogInfo(
|
|
241
|
-
"AndroidUsbTransport",
|
|
242
|
-
"Waiting for permission result"
|
|
243
|
-
)
|
|
244
|
-
)
|
|
198
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Waiting for permission result"))
|
|
245
199
|
|
|
246
200
|
val result = eventsFlow.first {
|
|
247
201
|
it is UsbPermissionEvent.PermissionGranted ||
|
|
@@ -275,33 +229,12 @@ internal class DefaultAndroidUsbTransport(
|
|
|
275
229
|
}
|
|
276
230
|
|
|
277
231
|
override suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult {
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
}
|
|
232
|
+
val usbDevice: UsbDevice? =
|
|
233
|
+
usbManager.deviceList.values.firstOrNull { it.deviceId == discoveryDevice.uid.toInt() }
|
|
299
234
|
|
|
300
235
|
val ledgerUsbDevice = usbDevice?.toLedgerUsbDevice()
|
|
301
236
|
|
|
302
237
|
return if (usbDevice == null || ledgerUsbDevice == null) {
|
|
303
|
-
loggerService.log(
|
|
304
|
-
buildSimpleDebugLogInfo("AndroidUsbTransport", "[connect] No device found"))
|
|
305
238
|
InternalConnectionResult.ConnectionError(error = InternalConnectionResult.Failure.DeviceNotFound)
|
|
306
239
|
} else {
|
|
307
240
|
val permissionResult = checkOrRequestPermission(usbDevice)
|
|
@@ -326,10 +259,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
326
259
|
val deviceConnection = DeviceConnection(
|
|
327
260
|
sessionId = sessionId,
|
|
328
261
|
deviceApduSender = apduSender,
|
|
329
|
-
isFatalSendApduFailure = { false },
|
|
262
|
+
isFatalSendApduFailure = { false }, // TODO: refine this
|
|
330
263
|
reconnectionTimeoutDuration = 5.seconds,
|
|
331
264
|
onTerminated = {
|
|
332
|
-
(it.getApduSender() as AndroidUsbApduSender).release()
|
|
333
265
|
usbConnections.remove(sessionId)
|
|
334
266
|
usbConnectionsPendingReconnection.remove(it)
|
|
335
267
|
eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
|
|
@@ -344,9 +276,7 @@ internal class DefaultAndroidUsbTransport(
|
|
|
344
276
|
discoveryDevice.name,
|
|
345
277
|
discoveryDevice.ledgerDevice,
|
|
346
278
|
discoveryDevice.connectivityType,
|
|
347
|
-
sendApduFn = { apdu
|
|
348
|
-
deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
349
|
-
}
|
|
279
|
+
sendApduFn = { apdu -> deviceConnection.requestSendApdu(apdu) },
|
|
350
280
|
)
|
|
351
281
|
|
|
352
282
|
usbConnections[sessionId] = deviceConnection
|
|
@@ -13,22 +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
|
|
18
16
|
import com.ledger.devicesdk.shared.api.apdu.SendApduFailureReason
|
|
19
17
|
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
|
|
20
21
|
import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
|
|
21
|
-
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleDebugLogInfo
|
|
22
22
|
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
|
|
23
|
+
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
|
|
23
24
|
import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
|
|
24
25
|
import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
|
|
25
|
-
import kotlinx.coroutines.CoroutineDispatcher
|
|
26
|
-
import kotlinx.coroutines.delay
|
|
27
|
-
import kotlinx.coroutines.launch
|
|
28
|
-
import kotlinx.coroutines.withContext
|
|
29
26
|
import java.nio.ByteBuffer
|
|
30
27
|
import kotlin.random.Random
|
|
31
|
-
import
|
|
28
|
+
import kotlinx.coroutines.CoroutineDispatcher
|
|
29
|
+
import kotlinx.coroutines.withContext
|
|
30
|
+
import timber.log.Timber
|
|
32
31
|
|
|
33
32
|
private const val USB_TIMEOUT = 500
|
|
34
33
|
|
|
@@ -36,116 +35,71 @@ private const val DEFAULT_USB_INTERFACE = 0
|
|
|
36
35
|
|
|
37
36
|
internal class AndroidUsbApduSender(
|
|
38
37
|
override val dependencies: Dependencies,
|
|
39
|
-
usbManager: UsbManager,
|
|
38
|
+
private val usbManager: UsbManager,
|
|
40
39
|
private val framerService: FramerService,
|
|
41
40
|
private val request: UsbRequest,
|
|
42
41
|
private val ioDispatcher: CoroutineDispatcher,
|
|
43
42
|
private val loggerService: LoggerService,
|
|
44
43
|
) : DeviceApduSender<AndroidUsbApduSender.Dependencies> {
|
|
44
|
+
|
|
45
45
|
data class Dependencies(
|
|
46
46
|
val usbDevice: UsbDevice,
|
|
47
47
|
val ledgerUsbDevice: LedgerUsbDevice,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
|
|
54
|
-
private val usbToAndroidEndpoint =
|
|
55
|
-
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
|
|
56
|
-
private val usbConnection = usbManager.openDevice(usbDevice)
|
|
57
|
-
.apply { claimInterface(usbInterface, true) }
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
fun release() {
|
|
61
|
-
usbConnection.releaseInterface(usbInterface)
|
|
62
|
-
usbConnection.close()
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
override suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult {
|
|
66
|
-
val t1 = System.currentTimeMillis()
|
|
67
|
-
return try {
|
|
50
|
+
override suspend fun send(apdu: ByteArray): SendApduResult =
|
|
51
|
+
try {
|
|
52
|
+
val usbDevice = dependencies.usbDevice
|
|
68
53
|
withContext(context = ioDispatcher) {
|
|
69
|
-
|
|
70
|
-
val
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
54
|
+
val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
|
|
55
|
+
val androidToUsbEndpoint = usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
|
|
56
|
+
val usbToAndroidEndpoint = usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
|
|
57
|
+
val usbConnection = usbManager.openDevice(usbDevice).apply { claimInterface(usbInterface, true) }
|
|
74
58
|
|
|
75
59
|
transmitApdu(
|
|
76
60
|
usbConnection = usbConnection,
|
|
77
61
|
androidToUsbEndpoint = androidToUsbEndpoint,
|
|
78
62
|
rawApdu = apdu,
|
|
79
63
|
)
|
|
80
|
-
|
|
81
64
|
val apduResponse =
|
|
82
65
|
receiveApdu(
|
|
83
66
|
usbConnection = usbConnection,
|
|
84
67
|
usbToAndroidEndpoint = usbToAndroidEndpoint,
|
|
85
68
|
)
|
|
86
|
-
timeoutJob.cancel()
|
|
87
69
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
70
|
+
usbConnection.releaseInterface(usbInterface)
|
|
71
|
+
usbConnection.close()
|
|
91
72
|
|
|
92
|
-
|
|
73
|
+
SendApduResult.Success(apdu = apduResponse)
|
|
93
74
|
}
|
|
94
|
-
} catch (e: SendApduTimeoutException) {
|
|
95
|
-
loggerService.log(
|
|
96
|
-
buildSimpleErrorLogInfo(
|
|
97
|
-
"AndroidUsbApduSender",
|
|
98
|
-
"timeout in send: $e"
|
|
99
|
-
)
|
|
100
|
-
)
|
|
101
|
-
SendApduResult.Failure(reason = SendApduFailureReason.AbortTimeout)
|
|
102
75
|
} catch (e: NoSuchElementException) {
|
|
103
|
-
loggerService.log(
|
|
104
|
-
buildSimpleErrorLogInfo(
|
|
105
|
-
"AndroidUsbApduSender",
|
|
106
|
-
"no endpoint found: $e"
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
76
|
SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
|
|
110
77
|
} catch (e: Exception) {
|
|
111
78
|
loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
|
|
112
79
|
SendApduResult.Failure(reason = SendApduFailureReason.Unknown)
|
|
113
|
-
} finally {
|
|
114
|
-
val t2 = System.currentTimeMillis()
|
|
115
|
-
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbApduSender", "PERF: [native sendApdu total]: ${t2 - t1}"))
|
|
116
80
|
}
|
|
117
|
-
}
|
|
118
81
|
|
|
119
82
|
private fun transmitApdu(
|
|
120
83
|
usbConnection: UsbDeviceConnection,
|
|
121
84
|
androidToUsbEndpoint: UsbEndpoint,
|
|
122
85
|
rawApdu: ByteArray,
|
|
123
86
|
) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
androidToUsbEndpoint,
|
|
130
|
-
buffer,
|
|
131
|
-
apduFrame.size(),
|
|
132
|
-
USB_TIMEOUT
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
val t2 = System.currentTimeMillis()
|
|
136
|
-
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbApduSender", "PERF: [transmitApdu]: ${t2 - t1}"))
|
|
87
|
+
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu).forEach { apduFrame ->
|
|
88
|
+
val buffer = apduFrame.toByteArray()
|
|
89
|
+
Timber.i("APDU sent = ${buffer.toHexadecimalString()}")
|
|
90
|
+
usbConnection.bulkTransfer(androidToUsbEndpoint, buffer, apduFrame.size(), USB_TIMEOUT)
|
|
91
|
+
}
|
|
137
92
|
}
|
|
138
93
|
|
|
139
94
|
private fun receiveApdu(
|
|
140
95
|
usbConnection: UsbDeviceConnection,
|
|
141
96
|
usbToAndroidEndpoint: UsbEndpoint,
|
|
142
|
-
): ByteArray
|
|
143
|
-
|
|
97
|
+
): ByteArray =
|
|
98
|
+
if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
|
|
144
99
|
request.close()
|
|
145
100
|
byteArrayOf()
|
|
146
101
|
} else {
|
|
147
|
-
val
|
|
148
|
-
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true) {
|
|
102
|
+
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
|
|
149
103
|
val buffer = ByteArray(USB_MTU)
|
|
150
104
|
val responseBuffer = ByteBuffer.allocate(USB_MTU)
|
|
151
105
|
|
|
@@ -153,19 +107,16 @@ internal class AndroidUsbApduSender(
|
|
|
153
107
|
if (!queuingResult) {
|
|
154
108
|
request.close()
|
|
155
109
|
byteArrayOf()
|
|
156
|
-
}
|
|
110
|
+
}
|
|
111
|
+
else{
|
|
157
112
|
usbConnection.requestWait()
|
|
158
113
|
responseBuffer.rewind()
|
|
159
114
|
responseBuffer.get(buffer, 0, responseBuffer.remaining())
|
|
160
115
|
buffer
|
|
161
116
|
}
|
|
162
117
|
}
|
|
163
|
-
|
|
164
|
-
val t2 = System.currentTimeMillis()
|
|
165
|
-
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbApduSender", "PERF: [receiveApdu]: ${t2 - t1}"))
|
|
166
|
-
result
|
|
118
|
+
framerService.deserialize(mtu = USB_MTU, frames)
|
|
167
119
|
}
|
|
168
|
-
}
|
|
169
120
|
|
|
170
121
|
private fun UsbInterface.firstEndpointOrThrow(predicate: (Int) -> Boolean): UsbEndpoint {
|
|
171
122
|
for (endp in 0..this.endpointCount) {
|
|
@@ -178,10 +129,5 @@ internal class AndroidUsbApduSender(
|
|
|
178
129
|
throw NoSuchElementException("No endpoint matching the predicate")
|
|
179
130
|
}
|
|
180
131
|
|
|
181
|
-
private fun generateChannelId(): ByteArray =
|
|
182
|
-
Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
|
|
183
|
-
|
|
184
|
-
private data object SendApduTimeoutException : Exception() {
|
|
185
|
-
private fun readResolve(): Any = SendApduTimeoutException
|
|
186
|
-
}
|
|
132
|
+
private fun generateChannelId(): ByteArray = Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
|
|
187
133
|
}
|
|
@@ -6,9 +6,8 @@
|
|
|
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
|
|
10
9
|
|
|
11
10
|
internal interface DeviceApduSender<Dependencies> {
|
|
12
|
-
suspend fun send(apdu: ByteArray
|
|
11
|
+
suspend fun send(apdu: ByteArray): SendApduResult
|
|
13
12
|
val dependencies: Dependencies
|
|
14
13
|
}
|
|
@@ -24,9 +24,9 @@ internal class DeviceConnection<Dependencies>(
|
|
|
24
24
|
|
|
25
25
|
init {
|
|
26
26
|
stateMachine = DeviceConnectionStateMachine(
|
|
27
|
-
sendApduFn = {
|
|
27
|
+
sendApduFn = {
|
|
28
28
|
coroutineScope.launch {
|
|
29
|
-
val res = deviceApduSender.send(
|
|
29
|
+
val res = deviceApduSender.send(it)
|
|
30
30
|
handleApduResult(res)
|
|
31
31
|
}
|
|
32
32
|
},
|
|
@@ -66,13 +66,12 @@ internal class DeviceConnection<Dependencies>(
|
|
|
66
66
|
stateMachine.handleDeviceDisconnected()
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
public suspend fun requestSendApdu(apdu: ByteArray
|
|
69
|
+
public suspend fun requestSendApdu(apdu: ByteArray): SendApduResult =
|
|
70
70
|
suspendCoroutine { cont ->
|
|
71
71
|
stateMachine.requestSendApdu(
|
|
72
72
|
DeviceConnectionStateMachine.SendApduRequestContent(
|
|
73
73
|
apdu = apdu,
|
|
74
|
-
triggersDisconnection = apduTriggersDisconnection(apdu)
|
|
75
|
-
abortTimeoutDuration = abortTimeoutDuration,
|
|
74
|
+
triggersDisconnection = apduTriggersDisconnection(apdu),
|
|
76
75
|
resultCallback = cont::resume
|
|
77
76
|
)
|
|
78
77
|
)
|
|
@@ -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
|
|
15
|
+
private val sendApduFn: (apdu: ByteArray) -> 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)
|
|
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
|
-
|
|
243
|
+
loggerService.log(buildSimpleDebugLogInfo("DeviceConnectionStateMachine", logMessage))
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
private var timeoutJob: Job? = null
|
|
@@ -280,7 +280,6 @@ internal class DeviceConnectionStateMachine(
|
|
|
280
280
|
data class SendApduRequestContent(
|
|
281
281
|
val apdu: ByteArray,
|
|
282
282
|
val triggersDisconnection: Boolean,
|
|
283
|
-
val abortTimeoutDuration: Duration,
|
|
284
283
|
val resultCallback: (SendApduResult) -> Unit
|
|
285
284
|
)
|
|
286
285
|
|
|
@@ -44,8 +44,4 @@ 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()
|
|
51
47
|
}
|
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
|
|
11
|
+
public 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 (
|
|
12
|
+
val sendApduFn: suspend (ByteArray) -> SendApduResult,
|
|
13
13
|
)
|