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