@ledgerhq/device-transport-kit-react-native-hid 0.0.0-try-to-fix-20250429171448 → 0.0.0-v0-transaction-unfunded-20250918091119
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/build.gradle +0 -9
- package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +12 -3
- 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 +85 -23
- 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/androidMain/transport/usb/utils/UsbDeviceMapper.kt +3 -0
- 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 +103 -33
- 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/device/LedgerDevice.kt +16 -0
- 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 +189 -46
- 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 +11 -11
- 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 +11 -11
- 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 +15 -15
package/android/build.gradle
CHANGED
|
@@ -60,15 +60,6 @@ android {
|
|
|
60
60
|
versionCode 1
|
|
61
61
|
versionName "1.0"
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
buildTypes {
|
|
65
|
-
release {
|
|
66
|
-
// Enable ProGuard for release builds
|
|
67
|
-
minifyEnabled false // TODO REMOVE
|
|
68
|
-
// Use ProGuard for code obfuscation and optimization
|
|
69
|
-
//proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
63
|
|
|
73
64
|
compileOptions {
|
|
74
65
|
// Use JavaVersion enums for compatibility
|
|
@@ -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(
|
|
@@ -48,7 +49,7 @@ class TransportHidModule(
|
|
|
48
49
|
private val loggerService: LoggerService =
|
|
49
50
|
LoggerService { info ->
|
|
50
51
|
Timber.tag("LDMKTransportHIDModule " + info.tag).d(info.message)
|
|
51
|
-
sendEvent(reactContext, BridgeEvents.TransportLog(info))
|
|
52
|
+
// sendEvent(reactContext, BridgeEvents.TransportLog(info))
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
private val transport: AndroidUsbTransport? by lazy {
|
|
@@ -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,27 +164,53 @@ 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(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
184
|
+
loggerService.log(
|
|
185
|
+
buildSimpleInfoLogInfo(
|
|
186
|
+
"AndroidUsbTransport",
|
|
187
|
+
"Reconnecting device (sessionId=${deviceConnection.sessionId})"
|
|
160
188
|
)
|
|
161
189
|
)
|
|
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)
|
|
162
214
|
usbConnectionsPendingReconnection.remove(deviceConnection)
|
|
163
215
|
usbConnections[deviceConnection.sessionId] = deviceConnection
|
|
164
216
|
}
|
|
@@ -195,7 +247,12 @@ internal class DefaultAndroidUsbTransport(
|
|
|
195
247
|
device = usbDevice,
|
|
196
248
|
)
|
|
197
249
|
|
|
198
|
-
loggerService.log(
|
|
250
|
+
loggerService.log(
|
|
251
|
+
buildSimpleDebugLogInfo(
|
|
252
|
+
"AndroidUsbTransport",
|
|
253
|
+
"Waiting for permission result"
|
|
254
|
+
)
|
|
255
|
+
)
|
|
199
256
|
|
|
200
257
|
val result = eventsFlow.first {
|
|
201
258
|
it is UsbPermissionEvent.PermissionGranted ||
|
|
@@ -259,9 +316,10 @@ internal class DefaultAndroidUsbTransport(
|
|
|
259
316
|
val deviceConnection = DeviceConnection(
|
|
260
317
|
sessionId = sessionId,
|
|
261
318
|
deviceApduSender = apduSender,
|
|
262
|
-
isFatalSendApduFailure = { false },
|
|
319
|
+
isFatalSendApduFailure = { false },
|
|
263
320
|
reconnectionTimeoutDuration = 5.seconds,
|
|
264
321
|
onTerminated = {
|
|
322
|
+
(it.getApduSender() as AndroidUsbApduSender).release()
|
|
265
323
|
usbConnections.remove(sessionId)
|
|
266
324
|
usbConnectionsPendingReconnection.remove(it)
|
|
267
325
|
eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
|
|
@@ -276,7 +334,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
276
334
|
discoveryDevice.name,
|
|
277
335
|
discoveryDevice.ledgerDevice,
|
|
278
336
|
discoveryDevice.connectivityType,
|
|
279
|
-
sendApduFn = { apdu
|
|
337
|
+
sendApduFn = { apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
|
|
338
|
+
deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
339
|
+
}
|
|
280
340
|
)
|
|
281
341
|
|
|
282
342
|
usbConnections[sessionId] = deviceConnection
|
|
@@ -286,7 +346,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
286
346
|
}
|
|
287
347
|
|
|
288
348
|
override suspend fun disconnect(deviceId: String) {
|
|
349
|
+
// The DeviceConnection is either in usbConnections or usbConnectionsPendingReconnection
|
|
289
350
|
usbConnections[deviceId]?.requestCloseConnection()
|
|
351
|
+
usbConnectionsPendingReconnection.find { it.sessionId == deviceId }?.requestCloseConnection()
|
|
290
352
|
}
|
|
291
353
|
|
|
292
354
|
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
|
|
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
|
)
|