@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.
Files changed (62) hide show
  1. package/android/src/main/kotlin/com/ledger/androidtransporthid/BridgeEvents.kt +0 -3
  2. package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +3 -70
  3. package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +0 -10
  4. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +14 -84
  5. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +31 -85
  6. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +1 -2
  7. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +4 -5
  8. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +3 -4
  9. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +0 -4
  10. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryDevice.kt +1 -1
  11. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectedDevice.kt +1 -1
  12. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachineTest.kt +31 -47
  13. package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +5 -9
  14. package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js +1 -1
  15. package/lib/cjs/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
  16. package/lib/cjs/api/bridge/StubNativeModuleWrapper.js +1 -1
  17. package/lib/cjs/api/bridge/StubNativeModuleWrapper.js.map +2 -2
  18. package/lib/cjs/api/bridge/mapper.js +1 -1
  19. package/lib/cjs/api/bridge/mapper.js.map +2 -2
  20. package/lib/cjs/api/bridge/mapper.test.js +1 -1
  21. package/lib/cjs/api/bridge/mapper.test.js.map +2 -2
  22. package/lib/cjs/api/bridge/types.js +1 -1
  23. package/lib/cjs/api/bridge/types.js.map +3 -3
  24. package/lib/cjs/api/transport/Errors.js +1 -1
  25. package/lib/cjs/api/transport/Errors.js.map +3 -3
  26. package/lib/cjs/api/transport/NativeModuleWrapper.js +1 -1
  27. package/lib/cjs/api/transport/NativeModuleWrapper.js.map +1 -1
  28. package/lib/cjs/api/transport/RNHidTransport.js +1 -1
  29. package/lib/cjs/api/transport/RNHidTransport.js.map +3 -3
  30. package/lib/cjs/api/transport/RNHidTransport.test.js +1 -1
  31. package/lib/cjs/api/transport/RNHidTransport.test.js.map +2 -2
  32. package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js +1 -1
  33. package/lib/esm/api/bridge/DefaultNativeModuleWrapper.js.map +3 -3
  34. package/lib/esm/api/bridge/StubNativeModuleWrapper.js +1 -1
  35. package/lib/esm/api/bridge/StubNativeModuleWrapper.js.map +2 -2
  36. package/lib/esm/api/bridge/mapper.js +1 -1
  37. package/lib/esm/api/bridge/mapper.js.map +3 -3
  38. package/lib/esm/api/bridge/mapper.test.js +1 -1
  39. package/lib/esm/api/bridge/mapper.test.js.map +3 -3
  40. package/lib/esm/api/bridge/types.js +1 -1
  41. package/lib/esm/api/bridge/types.js.map +3 -3
  42. package/lib/esm/api/transport/Errors.js +1 -1
  43. package/lib/esm/api/transport/Errors.js.map +3 -3
  44. package/lib/esm/api/transport/RNHidTransport.js +1 -1
  45. package/lib/esm/api/transport/RNHidTransport.js.map +3 -3
  46. package/lib/esm/api/transport/RNHidTransport.test.js +1 -1
  47. package/lib/esm/api/transport/RNHidTransport.test.js.map +3 -3
  48. package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts +1 -4
  49. package/lib/types/api/bridge/DefaultNativeModuleWrapper.d.ts.map +1 -1
  50. package/lib/types/api/bridge/StubNativeModuleWrapper.d.ts +0 -5
  51. package/lib/types/api/bridge/StubNativeModuleWrapper.d.ts.map +1 -1
  52. package/lib/types/api/bridge/mapper.d.ts.map +1 -1
  53. package/lib/types/api/bridge/types.d.ts +2 -9
  54. package/lib/types/api/bridge/types.d.ts.map +1 -1
  55. package/lib/types/api/transport/Errors.d.ts +1 -1
  56. package/lib/types/api/transport/Errors.d.ts.map +1 -1
  57. package/lib/types/api/transport/NativeModuleWrapper.d.ts +1 -4
  58. package/lib/types/api/transport/NativeModuleWrapper.d.ts.map +1 -1
  59. package/lib/types/api/transport/RNHidTransport.d.ts +0 -2
  60. package/lib/types/api/transport/RNHidTransport.d.ts.map +1 -1
  61. package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
  62. 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
- // sendEvent(reactContext, BridgeEvents.TransportLog(info))
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 abortTimeoutDuration = if (abortTimeout <= 0) Duration.INFINITE else abortTimeout.milliseconds
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
- buildSimpleInfoLogInfo(
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
- 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
- }
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: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
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 kotlin.time.Duration
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
- private val usbDevice = dependencies.usbDevice
51
- private val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
52
- private val androidToUsbEndpoint =
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 timeoutJob = launch {
71
- delay(abortTimeoutDuration)
72
- throw SendApduTimeoutException
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
- if (apduResponse.isEmpty()) {
89
- return@withContext SendApduResult.Failure(reason = SendApduFailureReason.EmptyResponse)
90
- }
70
+ usbConnection.releaseInterface(usbInterface)
71
+ usbConnection.close()
91
72
 
92
- return@withContext SendApduResult.Success(apdu = apduResponse)
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
- val t1 = System.currentTimeMillis()
125
- framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
126
- .forEach { apduFrame ->
127
- val buffer = apduFrame.toByteArray()
128
- usbConnection.bulkTransfer(
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
- return if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
97
+ ): ByteArray =
98
+ if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
144
99
  request.close()
145
100
  byteArrayOf()
146
101
  } else {
147
- val t1 = System.currentTimeMillis()
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
- } else {
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
- val result = framerService.deserialize(mtu = USB_MTU, frames)
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, abortTimeoutDuration: Duration): SendApduResult
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 = { apdu, abortTimeoutDuration ->
27
+ sendApduFn = {
28
28
  coroutineScope.launch {
29
- val res = deviceApduSender.send(apdu, abortTimeoutDuration)
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, triggersDisconnection: Boolean, abortTimeoutDuration: Duration): SendApduResult =
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) || triggersDisconnection,
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, abortTimeoutDuration: Duration) -> Unit,
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, newState.requestContent.abortTimeoutDuration)
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
- // loggerService.log(buildSimpleDebugLogInfo("DeviceConnectionStateMachine", logMessage))
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
  }
@@ -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 data class DiscoveryDevice(
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 (apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: kotlin.time.Duration) -> SendApduResult,
12
+ val sendApduFn: suspend (ByteArray) -> SendApduResult,
13
13
  )