@ledgerhq/device-transport-kit-react-native-hid 0.0.0-try-to-fix-20250429171448 → 0.0.0-web-ble-29-08---20250829104351
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 +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 +57 -10
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +75 -33
- 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 +3 -2
- 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 +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/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/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 +2 -2
- 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/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 +13 -13
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(
|
|
@@ -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,17 +97,32 @@ 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(
|
|
120
|
+
loggerService.log(
|
|
121
|
+
buildSimpleInfoLogInfo(
|
|
122
|
+
"AndroidUsbTransport",
|
|
123
|
+
"Device disconnected (sessionId=${deviceConnection.sessionId})"
|
|
124
|
+
)
|
|
125
|
+
)
|
|
111
126
|
deviceConnection.handleDeviceDisconnected()
|
|
112
127
|
usbConnections.remove(key)
|
|
113
128
|
usbConnectionsPendingReconnection.add(deviceConnection)
|
|
@@ -116,12 +131,22 @@ internal class DefaultAndroidUsbTransport(
|
|
|
116
131
|
}
|
|
117
132
|
|
|
118
133
|
is UsbState.Attached -> {
|
|
119
|
-
loggerService.log(
|
|
134
|
+
loggerService.log(
|
|
135
|
+
buildSimpleDebugLogInfo(
|
|
136
|
+
"AndroidUsbTransport",
|
|
137
|
+
"Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"
|
|
138
|
+
)
|
|
139
|
+
)
|
|
120
140
|
val usbDevice = usbManager.deviceList.values.firstOrNull {
|
|
121
141
|
it.toLedgerUsbDevice()?.uid == state.ledgerUsbDevice.uid
|
|
122
142
|
}
|
|
123
143
|
if (usbDevice == null) {
|
|
124
|
-
loggerService.log(
|
|
144
|
+
loggerService.log(
|
|
145
|
+
buildSimpleWarningLogInfo(
|
|
146
|
+
"AndroidUsbTransport",
|
|
147
|
+
"No UsbDevice found"
|
|
148
|
+
)
|
|
149
|
+
)
|
|
125
150
|
return
|
|
126
151
|
}
|
|
127
152
|
usbConnectionsPendingReconnection.firstOrNull {
|
|
@@ -138,14 +163,29 @@ internal class DefaultAndroidUsbTransport(
|
|
|
138
163
|
)
|
|
139
164
|
return@launch
|
|
140
165
|
}
|
|
141
|
-
loggerService.log(
|
|
166
|
+
loggerService.log(
|
|
167
|
+
buildSimpleDebugLogInfo(
|
|
168
|
+
"AndroidUsbTransport",
|
|
169
|
+
"Found matching device connection $deviceConnection"
|
|
170
|
+
)
|
|
171
|
+
)
|
|
142
172
|
|
|
143
173
|
val permissionResult = checkOrRequestPermission(usbDevice)
|
|
144
174
|
if (permissionResult is PermissionResult.Denied) {
|
|
145
|
-
loggerService.log(
|
|
175
|
+
loggerService.log(
|
|
176
|
+
buildSimpleDebugLogInfo(
|
|
177
|
+
"AndroidUsbTransport",
|
|
178
|
+
"Permission denied"
|
|
179
|
+
)
|
|
180
|
+
)
|
|
146
181
|
return@launch
|
|
147
182
|
}
|
|
148
|
-
loggerService.log(
|
|
183
|
+
loggerService.log(
|
|
184
|
+
buildSimpleInfoLogInfo(
|
|
185
|
+
"AndroidUsbTransport",
|
|
186
|
+
"Reconnecting device (sessionId=${deviceConnection.sessionId})"
|
|
187
|
+
)
|
|
188
|
+
)
|
|
149
189
|
deviceConnection.handleDeviceConnected(
|
|
150
190
|
AndroidUsbApduSender(
|
|
151
191
|
dependencies = AndroidUsbApduSender.Dependencies(
|
|
@@ -195,7 +235,12 @@ internal class DefaultAndroidUsbTransport(
|
|
|
195
235
|
device = usbDevice,
|
|
196
236
|
)
|
|
197
237
|
|
|
198
|
-
loggerService.log(
|
|
238
|
+
loggerService.log(
|
|
239
|
+
buildSimpleDebugLogInfo(
|
|
240
|
+
"AndroidUsbTransport",
|
|
241
|
+
"Waiting for permission result"
|
|
242
|
+
)
|
|
243
|
+
)
|
|
199
244
|
|
|
200
245
|
val result = eventsFlow.first {
|
|
201
246
|
it is UsbPermissionEvent.PermissionGranted ||
|
|
@@ -276,7 +321,9 @@ internal class DefaultAndroidUsbTransport(
|
|
|
276
321
|
discoveryDevice.name,
|
|
277
322
|
discoveryDevice.ledgerDevice,
|
|
278
323
|
discoveryDevice.connectivityType,
|
|
279
|
-
sendApduFn = { apdu
|
|
324
|
+
sendApduFn = { apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: Duration ->
|
|
325
|
+
deviceConnection.requestSendApdu(apdu, triggersDisconnection, abortTimeoutDuration)
|
|
326
|
+
}
|
|
280
327
|
)
|
|
281
328
|
|
|
282
329
|
usbConnections[sessionId] = deviceConnection
|
|
@@ -13,21 +13,22 @@ 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.cancel
|
|
26
|
+
import kotlinx.coroutines.delay
|
|
27
|
+
import kotlinx.coroutines.launch
|
|
29
28
|
import kotlinx.coroutines.withContext
|
|
30
|
-
import
|
|
29
|
+
import java.nio.ByteBuffer
|
|
30
|
+
import kotlin.random.Random
|
|
31
|
+
import kotlin.time.Duration
|
|
31
32
|
|
|
32
33
|
private const val USB_TIMEOUT = 500
|
|
33
34
|
|
|
@@ -41,38 +42,70 @@ internal class AndroidUsbApduSender(
|
|
|
41
42
|
private val ioDispatcher: CoroutineDispatcher,
|
|
42
43
|
private val loggerService: LoggerService,
|
|
43
44
|
) : DeviceApduSender<AndroidUsbApduSender.Dependencies> {
|
|
44
|
-
|
|
45
45
|
data class Dependencies(
|
|
46
46
|
val usbDevice: UsbDevice,
|
|
47
47
|
val ledgerUsbDevice: LedgerUsbDevice,
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
override suspend fun send(apdu: ByteArray): SendApduResult =
|
|
50
|
+
override suspend fun send(apdu: ByteArray, abortTimeoutDuration: Duration): SendApduResult =
|
|
51
51
|
try {
|
|
52
52
|
val usbDevice = dependencies.usbDevice
|
|
53
53
|
withContext(context = ioDispatcher) {
|
|
54
54
|
val usbInterface = usbDevice.getInterface(DEFAULT_USB_INTERFACE)
|
|
55
|
-
val androidToUsbEndpoint =
|
|
56
|
-
|
|
57
|
-
val
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
55
|
+
val androidToUsbEndpoint =
|
|
56
|
+
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_OUT }
|
|
57
|
+
val usbToAndroidEndpoint =
|
|
58
|
+
usbInterface.firstEndpointOrThrow { it == UsbConstants.USB_DIR_IN }
|
|
59
|
+
val usbConnection = usbManager.openDevice(usbDevice)
|
|
60
|
+
.apply { claimInterface(usbInterface, true) }
|
|
61
|
+
|
|
62
|
+
val timeoutJob = launch {
|
|
63
|
+
delay(abortTimeoutDuration)
|
|
64
|
+
usbConnection.releaseInterface(usbInterface)
|
|
65
|
+
usbConnection.close()
|
|
66
|
+
throw SendApduTimeoutException
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
|
|
71
|
+
transmitApdu(
|
|
66
72
|
usbConnection = usbConnection,
|
|
67
|
-
|
|
73
|
+
androidToUsbEndpoint = androidToUsbEndpoint,
|
|
74
|
+
rawApdu = apdu,
|
|
68
75
|
)
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
val apduResponse =
|
|
78
|
+
receiveApdu(
|
|
79
|
+
usbConnection = usbConnection,
|
|
80
|
+
usbToAndroidEndpoint = usbToAndroidEndpoint,
|
|
81
|
+
)
|
|
82
|
+
timeoutJob.cancel()
|
|
83
|
+
|
|
84
|
+
if (apduResponse.isEmpty()) {
|
|
85
|
+
return@withContext SendApduResult.Failure(reason = SendApduFailureReason.EmptyResponse)
|
|
86
|
+
}
|
|
72
87
|
|
|
73
|
-
|
|
88
|
+
return@withContext SendApduResult.Success(apdu = apduResponse)
|
|
89
|
+
} finally {
|
|
90
|
+
usbConnection.releaseInterface(usbInterface)
|
|
91
|
+
usbConnection.close()
|
|
92
|
+
}
|
|
74
93
|
}
|
|
94
|
+
} catch (e: SendApduTimeoutException) {
|
|
95
|
+
loggerService.log(
|
|
96
|
+
buildSimpleErrorLogInfo(
|
|
97
|
+
"AndroidUsbApduSender",
|
|
98
|
+
"timeout in send: $e"
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
SendApduResult.Failure(reason = SendApduFailureReason.AbortTimeout)
|
|
75
102
|
} catch (e: NoSuchElementException) {
|
|
103
|
+
loggerService.log(
|
|
104
|
+
buildSimpleErrorLogInfo(
|
|
105
|
+
"AndroidUsbApduSender",
|
|
106
|
+
"no endpoint found: $e"
|
|
107
|
+
)
|
|
108
|
+
)
|
|
76
109
|
SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
|
|
77
110
|
} catch (e: Exception) {
|
|
78
111
|
loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
|
|
@@ -84,11 +117,16 @@ internal class AndroidUsbApduSender(
|
|
|
84
117
|
androidToUsbEndpoint: UsbEndpoint,
|
|
85
118
|
rawApdu: ByteArray,
|
|
86
119
|
) {
|
|
87
|
-
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
framerService.serialize(mtu = USB_MTU, channelId = generateChannelId(), rawApdu = rawApdu)
|
|
121
|
+
.forEach { apduFrame ->
|
|
122
|
+
val buffer = apduFrame.toByteArray()
|
|
123
|
+
usbConnection.bulkTransfer(
|
|
124
|
+
androidToUsbEndpoint,
|
|
125
|
+
buffer,
|
|
126
|
+
apduFrame.size(),
|
|
127
|
+
USB_TIMEOUT
|
|
128
|
+
)
|
|
129
|
+
}
|
|
92
130
|
}
|
|
93
131
|
|
|
94
132
|
private fun receiveApdu(
|
|
@@ -99,7 +137,7 @@ internal class AndroidUsbApduSender(
|
|
|
99
137
|
request.close()
|
|
100
138
|
byteArrayOf()
|
|
101
139
|
} else {
|
|
102
|
-
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
|
|
140
|
+
val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true) {
|
|
103
141
|
val buffer = ByteArray(USB_MTU)
|
|
104
142
|
val responseBuffer = ByteBuffer.allocate(USB_MTU)
|
|
105
143
|
|
|
@@ -107,8 +145,7 @@ internal class AndroidUsbApduSender(
|
|
|
107
145
|
if (!queuingResult) {
|
|
108
146
|
request.close()
|
|
109
147
|
byteArrayOf()
|
|
110
|
-
}
|
|
111
|
-
else{
|
|
148
|
+
} else {
|
|
112
149
|
usbConnection.requestWait()
|
|
113
150
|
responseBuffer.rewind()
|
|
114
151
|
responseBuffer.get(buffer, 0, responseBuffer.remaining())
|
|
@@ -129,5 +166,10 @@ internal class AndroidUsbApduSender(
|
|
|
129
166
|
throw NoSuchElementException("No endpoint matching the predicate")
|
|
130
167
|
}
|
|
131
168
|
|
|
132
|
-
private fun generateChannelId(): ByteArray =
|
|
169
|
+
private fun generateChannelId(): ByteArray =
|
|
170
|
+
Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
|
|
171
|
+
|
|
172
|
+
private data object SendApduTimeoutException: Exception() {
|
|
173
|
+
private fun readResolve(): Any = SendApduTimeoutException
|
|
174
|
+
}
|
|
133
175
|
}
|
|
@@ -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
|
)
|
|
@@ -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) -> Unit,
|
|
15
|
+
private val sendApduFn: (apdu: ByteArray, abortTimeoutDuration: Duration) -> Unit,
|
|
16
16
|
private val onTerminated: () -> Unit,
|
|
17
17
|
private val isFatalSendApduFailure: (SendApduResult.Failure) -> Boolean,
|
|
18
18
|
private val reconnectionTimeoutDuration: Duration,
|
|
@@ -29,7 +29,7 @@ internal class DeviceConnectionStateMachine(
|
|
|
29
29
|
when (newState) {
|
|
30
30
|
is State.Connected -> {}
|
|
31
31
|
is State.SendingApdu -> {
|
|
32
|
-
sendApduFn(newState.requestContent.apdu)
|
|
32
|
+
sendApduFn(newState.requestContent.apdu, newState.requestContent.abortTimeoutDuration)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
is State.WaitingForReconnection -> {
|
|
@@ -280,6 +280,7 @@ internal class DeviceConnectionStateMachine(
|
|
|
280
280
|
data class SendApduRequestContent(
|
|
281
281
|
val apdu: ByteArray,
|
|
282
282
|
val triggersDisconnection: Boolean,
|
|
283
|
+
val abortTimeoutDuration: Duration,
|
|
283
284
|
val resultCallback: (SendApduResult) -> Unit
|
|
284
285
|
)
|
|
285
286
|
|
|
@@ -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
|
}
|
|
@@ -5,6 +5,21 @@ public sealed class LedgerDevice(
|
|
|
5
5
|
public val usbInfo: UsbInfo,
|
|
6
6
|
public val bleInformation: BleInformation? = null,
|
|
7
7
|
) {
|
|
8
|
+
public data object Apex :
|
|
9
|
+
LedgerDevice(
|
|
10
|
+
name = "Ledger Apex",
|
|
11
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x80", "0x0008"),
|
|
12
|
+
bleInformation =
|
|
13
|
+
BleInformation(
|
|
14
|
+
serviceUuid = "13d63400-2c97-6004-0000-4c6564676572",
|
|
15
|
+
notifyCharacteristicUuid =
|
|
16
|
+
"13d63400-2c97-6004-0001-4c6564676572",
|
|
17
|
+
writeWithResponseCharacteristicUuid =
|
|
18
|
+
"13d63400-2c97-6004-0002-4c6564676572",
|
|
19
|
+
writeWithoutResponseCharacteristicUuid =
|
|
20
|
+
"13d63400-2c97-6004-0003-4c6564676572",
|
|
21
|
+
),
|
|
22
|
+
)
|
|
8
23
|
public data object Flex :
|
|
9
24
|
LedgerDevice(
|
|
10
25
|
name = "Ledger Flex",
|
|
@@ -77,6 +92,7 @@ public sealed class LedgerDevice(
|
|
|
77
92
|
add(NanoX)
|
|
78
93
|
add(NanoSPlus)
|
|
79
94
|
add(NanoS)
|
|
95
|
+
add(Apex)
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
public fun getAllDevices(): List<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 (ByteArray) -> SendApduResult,
|
|
12
|
+
val sendApduFn: suspend (apdu: ByteArray, triggersDisconnection: Boolean, abortTimeoutDuration: kotlin.time.Duration) -> SendApduResult,
|
|
13
13
|
)
|