@ledgerhq/device-transport-kit-react-native-hid 0.0.0-develop-20250904001204 → 0.0.0-e2e-speculos-20250904125723
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 +3 -12
- package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +0 -2
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +23 -85
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +29 -70
- 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 +33 -103
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +0 -4
- 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 +46 -189
- 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/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 +1 -1
- 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 +1 -1
- 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 +9 -9
|
@@ -31,7 +31,6 @@ 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
|
|
35
34
|
import kotlin.time.Duration.Companion.milliseconds
|
|
36
35
|
|
|
37
36
|
class TransportHidModule(
|
|
@@ -49,7 +48,7 @@ class TransportHidModule(
|
|
|
49
48
|
private val loggerService: LoggerService =
|
|
50
49
|
LoggerService { info ->
|
|
51
50
|
Timber.tag("LDMKTransportHIDModule " + info.tag).d(info.message)
|
|
52
|
-
|
|
51
|
+
sendEvent(reactContext, BridgeEvents.TransportLog(info))
|
|
53
52
|
}
|
|
54
53
|
|
|
55
54
|
private val transport: AndroidUsbTransport? by lazy {
|
|
@@ -207,13 +206,7 @@ class TransportHidModule(
|
|
|
207
206
|
}
|
|
208
207
|
|
|
209
208
|
@ReactMethod
|
|
210
|
-
fun sendApdu(
|
|
211
|
-
sessionId: String,
|
|
212
|
-
apduBase64: String,
|
|
213
|
-
triggersDisconnection: Boolean,
|
|
214
|
-
abortTimeout: Int,
|
|
215
|
-
promise: Promise
|
|
216
|
-
) {
|
|
209
|
+
fun sendApdu(sessionId: String, apduBase64: String, promise: Promise) {
|
|
217
210
|
try {
|
|
218
211
|
val device = connectedDevices.firstOrNull() { it.id == sessionId }
|
|
219
212
|
if (device == null) {
|
|
@@ -223,9 +216,7 @@ class TransportHidModule(
|
|
|
223
216
|
coroutineScope.launch {
|
|
224
217
|
try {
|
|
225
218
|
val apdu: ByteArray = Base64.decode(apduBase64, Base64.DEFAULT)
|
|
226
|
-
val
|
|
227
|
-
val res =
|
|
228
|
-
device.sendApduFn(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
219
|
+
val res = device.sendApduFn(apdu)
|
|
229
220
|
promise.resolve(res.toWritableMap())
|
|
230
221
|
} catch (e: Exception) {
|
|
231
222
|
Timber.i("$e, ${e.cause}")
|
|
@@ -103,8 +103,6 @@ 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"
|
|
108
106
|
})
|
|
109
107
|
}
|
|
110
108
|
}
|
|
@@ -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,53 +138,27 @@ 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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
148
|
+
loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Reconnecting device (sessionId=${deviceConnection.sessionId})"))
|
|
149
|
+
deviceConnection.handleDeviceConnected(
|
|
150
|
+
AndroidUsbApduSender(
|
|
151
|
+
dependencies = AndroidUsbApduSender.Dependencies(
|
|
152
|
+
usbDevice = usbDevice,
|
|
153
|
+
ledgerUsbDevice = state.ledgerUsbDevice,
|
|
154
|
+
),
|
|
155
|
+
usbManager = usbManager,
|
|
156
|
+
ioDispatcher = Dispatchers.IO,
|
|
157
|
+
framerService = FramerService(loggerService),
|
|
158
|
+
request = UsbRequest(),
|
|
159
|
+
loggerService = loggerService
|
|
188
160
|
)
|
|
189
161
|
)
|
|
190
|
-
|
|
191
|
-
val apduSender = AndroidUsbApduSender(
|
|
192
|
-
dependencies = AndroidUsbApduSender.Dependencies(
|
|
193
|
-
usbDevice = usbDevice,
|
|
194
|
-
ledgerUsbDevice = state.ledgerUsbDevice,
|
|
195
|
-
),
|
|
196
|
-
usbManager = usbManager,
|
|
197
|
-
ioDispatcher = Dispatchers.IO,
|
|
198
|
-
framerService = FramerService(loggerService),
|
|
199
|
-
request = UsbRequest(),
|
|
200
|
-
loggerService = loggerService
|
|
201
|
-
)
|
|
202
|
-
|
|
203
|
-
if (!usbConnectionsPendingReconnection.contains(deviceConnection)) {
|
|
204
|
-
/**
|
|
205
|
-
* We check this because maybe by the time we get here,
|
|
206
|
-
* the reconnection has timed out and the session has been terminated.
|
|
207
|
-
* Easy to reproduce for instance if the permission request requires
|
|
208
|
-
* a user interaction.
|
|
209
|
-
*/
|
|
210
|
-
apduSender.release()
|
|
211
|
-
return@launch
|
|
212
|
-
}
|
|
213
|
-
deviceConnection.handleDeviceConnected(apduSender)
|
|
214
162
|
usbConnectionsPendingReconnection.remove(deviceConnection)
|
|
215
163
|
usbConnections[deviceConnection.sessionId] = deviceConnection
|
|
216
164
|
}
|
|
@@ -247,12 +195,7 @@ internal class DefaultAndroidUsbTransport(
|
|
|
247
195
|
device = usbDevice,
|
|
248
196
|
)
|
|
249
197
|
|
|
250
|
-
loggerService.log(
|
|
251
|
-
buildSimpleDebugLogInfo(
|
|
252
|
-
"AndroidUsbTransport",
|
|
253
|
-
"Waiting for permission result"
|
|
254
|
-
)
|
|
255
|
-
)
|
|
198
|
+
loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Waiting for permission result"))
|
|
256
199
|
|
|
257
200
|
val result = eventsFlow.first {
|
|
258
201
|
it is UsbPermissionEvent.PermissionGranted ||
|
|
@@ -316,10 +259,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
316
259
|
val deviceConnection = DeviceConnection(
|
|
317
260
|
sessionId = sessionId,
|
|
318
261
|
deviceApduSender = apduSender,
|
|
319
|
-
isFatalSendApduFailure = { false },
|
|
262
|
+
isFatalSendApduFailure = { false }, // TODO: refine this
|
|
320
263
|
reconnectionTimeoutDuration = 5.seconds,
|
|
321
264
|
onTerminated = {
|
|
322
|
-
(it.getApduSender() as AndroidUsbApduSender).release()
|
|
323
265
|
usbConnections.remove(sessionId)
|
|
324
266
|
usbConnectionsPendingReconnection.remove(it)
|
|
325
267
|
eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
|
|
@@ -334,9 +276,7 @@ internal class DefaultAndroidUsbTransport(
|
|
|
334
276
|
discoveryDevice.name,
|
|
335
277
|
discoveryDevice.ledgerDevice,
|
|
336
278
|
discoveryDevice.connectivityType,
|
|
337
|
-
sendApduFn = { apdu
|
|
338
|
-
deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
339
|
-
}
|
|
279
|
+
sendApduFn = { apdu -> deviceConnection.requestSendApdu(apdu) },
|
|
340
280
|
)
|
|
341
281
|
|
|
342
282
|
usbConnections[sessionId] = deviceConnection
|
|
@@ -346,9 +286,7 @@ internal class DefaultAndroidUsbTransport(
|
|
|
346
286
|
}
|
|
347
287
|
|
|
348
288
|
override suspend fun disconnect(deviceId: String) {
|
|
349
|
-
// The DeviceConnection is either in usbConnections or usbConnectionsPendingReconnection
|
|
350
289
|
usbConnections[deviceId]?.requestCloseConnection()
|
|
351
|
-
usbConnectionsPendingReconnection.find { it.sessionId == deviceId }?.requestCloseConnection()
|
|
352
290
|
}
|
|
353
291
|
|
|
354
292
|
private fun generateSessionId(usbDevice: UsbDevice): String = "usb_${usbDevice.deviceId}"
|
|
@@ -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
|
|
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
22
|
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
|
|
23
|
+
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
|
|
22
24
|
import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
|
|
23
25
|
import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
|
|
24
|
-
import kotlinx.coroutines.CoroutineDispatcher
|
|
25
|
-
import kotlinx.coroutines.delay
|
|
26
|
-
import kotlinx.coroutines.launch
|
|
27
|
-
import kotlinx.coroutines.withContext
|
|
28
26
|
import java.nio.ByteBuffer
|
|
29
27
|
import kotlin.random.Random
|
|
30
|
-
import
|
|
28
|
+
import kotlinx.coroutines.CoroutineDispatcher
|
|
29
|
+
import kotlinx.coroutines.withContext
|
|
30
|
+
import timber.log.Timber
|
|
31
31
|
|
|
32
32
|
private const val USB_TIMEOUT = 500
|
|
33
33
|
|
|
@@ -35,75 +35,44 @@ private const val DEFAULT_USB_INTERFACE = 0
|
|
|
35
35
|
|
|
36
36
|
internal class AndroidUsbApduSender(
|
|
37
37
|
override val dependencies: Dependencies,
|
|
38
|
-
usbManager: UsbManager,
|
|
38
|
+
private val 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
|
+
|
|
44
45
|
data class Dependencies(
|
|
45
46
|
val usbDevice: UsbDevice,
|
|
46
47
|
val ledgerUsbDevice: LedgerUsbDevice,
|
|
47
48
|
)
|
|
48
49
|
|
|
49
|
-
|
|
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 =
|
|
50
|
+
override suspend fun send(apdu: ByteArray): SendApduResult =
|
|
65
51
|
try {
|
|
52
|
+
val usbDevice = dependencies.usbDevice
|
|
66
53
|
withContext(context = ioDispatcher) {
|
|
67
|
-
|
|
68
|
-
val
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
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) }
|
|
72
58
|
|
|
73
59
|
transmitApdu(
|
|
74
60
|
usbConnection = usbConnection,
|
|
75
61
|
androidToUsbEndpoint = androidToUsbEndpoint,
|
|
76
62
|
rawApdu = apdu,
|
|
77
63
|
)
|
|
78
|
-
|
|
79
64
|
val apduResponse =
|
|
80
65
|
receiveApdu(
|
|
81
66
|
usbConnection = usbConnection,
|
|
82
67
|
usbToAndroidEndpoint = usbToAndroidEndpoint,
|
|
83
68
|
)
|
|
84
|
-
timeoutJob.cancel()
|
|
85
69
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
70
|
+
usbConnection.releaseInterface(usbInterface)
|
|
71
|
+
usbConnection.close()
|
|
89
72
|
|
|
90
|
-
|
|
73
|
+
SendApduResult.Success(apdu = apduResponse)
|
|
91
74
|
}
|
|
92
|
-
} catch (e: SendApduTimeoutException) {
|
|
93
|
-
loggerService.log(
|
|
94
|
-
buildSimpleErrorLogInfo(
|
|
95
|
-
"AndroidUsbApduSender",
|
|
96
|
-
"timeout in send: $e"
|
|
97
|
-
)
|
|
98
|
-
)
|
|
99
|
-
SendApduResult.Failure(reason = SendApduFailureReason.AbortTimeout)
|
|
100
75
|
} catch (e: NoSuchElementException) {
|
|
101
|
-
loggerService.log(
|
|
102
|
-
buildSimpleErrorLogInfo(
|
|
103
|
-
"AndroidUsbApduSender",
|
|
104
|
-
"no endpoint found: $e"
|
|
105
|
-
)
|
|
106
|
-
)
|
|
107
76
|
SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
|
|
108
77
|
} catch (e: Exception) {
|
|
109
78
|
loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
|
|
@@ -115,27 +84,22 @@ internal class AndroidUsbApduSender(
|
|
|
115
84
|
androidToUsbEndpoint: UsbEndpoint,
|
|
116
85
|
rawApdu: ByteArray,
|
|
117
86
|
) {
|
|
118
|
-
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
buffer,
|
|
124
|
-
apduFrame.size(),
|
|
125
|
-
USB_TIMEOUT
|
|
126
|
-
)
|
|
127
|
-
}
|
|
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
|
+
}
|
|
128
92
|
}
|
|
129
93
|
|
|
130
94
|
private fun receiveApdu(
|
|
131
95
|
usbConnection: UsbDeviceConnection,
|
|
132
96
|
usbToAndroidEndpoint: UsbEndpoint,
|
|
133
|
-
): ByteArray
|
|
134
|
-
|
|
97
|
+
): ByteArray =
|
|
98
|
+
if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
|
|
135
99
|
request.close()
|
|
136
100
|
byteArrayOf()
|
|
137
101
|
} else {
|
|
138
|
-
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true)
|
|
102
|
+
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
|
|
139
103
|
val buffer = ByteArray(USB_MTU)
|
|
140
104
|
val responseBuffer = ByteBuffer.allocate(USB_MTU)
|
|
141
105
|
|
|
@@ -143,7 +107,8 @@ internal class AndroidUsbApduSender(
|
|
|
143
107
|
if (!queuingResult) {
|
|
144
108
|
request.close()
|
|
145
109
|
byteArrayOf()
|
|
146
|
-
}
|
|
110
|
+
}
|
|
111
|
+
else{
|
|
147
112
|
usbConnection.requestWait()
|
|
148
113
|
responseBuffer.rewind()
|
|
149
114
|
responseBuffer.get(buffer, 0, responseBuffer.remaining())
|
|
@@ -152,7 +117,6 @@ internal class AndroidUsbApduSender(
|
|
|
152
117
|
}
|
|
153
118
|
framerService.deserialize(mtu = USB_MTU, frames)
|
|
154
119
|
}
|
|
155
|
-
}
|
|
156
120
|
|
|
157
121
|
private fun UsbInterface.firstEndpointOrThrow(predicate: (Int) -> Boolean): UsbEndpoint {
|
|
158
122
|
for (endp in 0..this.endpointCount) {
|
|
@@ -165,10 +129,5 @@ internal class AndroidUsbApduSender(
|
|
|
165
129
|
throw NoSuchElementException("No endpoint matching the predicate")
|
|
166
130
|
}
|
|
167
131
|
|
|
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
|
-
}
|
|
132
|
+
private fun generateChannelId(): ByteArray = Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
|
|
174
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
|
)
|