@ledgerhq/device-transport-kit-react-native-hid 0.0.0-rn-hid-20250221112139 → 0.0.0-rn-hid-20250221115747

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/android/.settings/org.eclipse.buildship.core.prefs +13 -0
  2. package/android/build.gradle +99 -0
  3. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  4. package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  5. package/android/gradle.properties +1 -0
  6. package/android/gradlew +252 -0
  7. package/android/gradlew.bat +94 -0
  8. package/android/src/main/AndroidManifest.xml +3 -0
  9. package/android/src/main/kotlin/com/ledger/androidtransporthid/BridgeEvents.kt +42 -0
  10. package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +241 -0
  11. package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidPackage.kt +25 -0
  12. package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +124 -0
  13. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/AndroidUsbTransport.kt +16 -0
  14. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +304 -0
  15. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/UsbPermissionRequester.kt +18 -0
  16. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +133 -0
  17. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbAttachedReceiverController.kt +59 -0
  18. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbDetachedReceiverController.kt +58 -0
  19. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbPermissionReceiver.kt +92 -0
  20. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/LedgerUsbDevice.kt +16 -0
  21. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/ProductId.kt +11 -0
  22. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/UsbPermissionEvent.kt +14 -0
  23. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/UsbState.kt +16 -0
  24. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/VendorId.kt +11 -0
  25. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbDeviceMapper.kt +55 -0
  26. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbMapper.kt +56 -0
  27. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/UsbConst.android.kt +8 -0
  28. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +13 -0
  29. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +96 -0
  30. package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +311 -0
  31. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/DeviceAction.kt +23 -0
  32. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/Apdu.kt +44 -0
  33. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduBuilder.kt +88 -0
  34. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduParser.kt +37 -0
  35. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduUtils.kt +37 -0
  36. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +47 -0
  37. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/connection/ConnectedDevice.kt +25 -0
  38. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/connection/ConnectionResult.kt +45 -0
  39. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/BleInformation.kt +8 -0
  40. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/LedgerDevice.kt +74 -0
  41. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/UsbInfo.kt +6 -0
  42. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/disconnection/DisconnectionResult.kt +10 -0
  43. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/ConnectivityType.kt +10 -0
  44. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryDevice.kt +18 -0
  45. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryResult.kt +28 -0
  46. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/utils/ByteArrayExtension.kt +116 -0
  47. package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/utils/StringExtension.kt +21 -0
  48. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectedDevice.kt +13 -0
  49. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectionResult.kt +41 -0
  50. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/coroutine/SDKScope.kt +25 -0
  51. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/coroutine/SDKScopeHandler.kt +18 -0
  52. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/event/SdkEventDispatcher.kt +19 -0
  53. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/DisableLoggerService.kt +12 -0
  54. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LogInfo.kt +52 -0
  55. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LogLevel.kt +13 -0
  56. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LoggerService.kt +10 -0
  57. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/Transport.kt +21 -0
  58. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/TransportEvent.kt +18 -0
  59. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/FramerService.kt +210 -0
  60. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/FramerUtils.kt +35 -0
  61. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduConst.kt +9 -0
  62. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduFrame.kt +66 -0
  63. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduFramerHeader.kt +74 -0
  64. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/FramerConst.kt +14 -0
  65. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/utils/ByteExtension.kt +21 -0
  66. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/utils/InternalByteArrayExtension.kt +18 -0
  67. package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/utils/Controller.kt +12 -0
  68. package/lib/cjs/package.json +1 -0
  69. package/lib/esm/package.json +1 -0
  70. package/package.json +5 -4
@@ -0,0 +1,304 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2024 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb
7
+
8
+ import android.app.Application
9
+ import android.hardware.usb.UsbDevice
10
+ import android.hardware.usb.UsbManager
11
+ import android.hardware.usb.UsbRequest
12
+ import com.ledger.devicesdk.shared.api.discovery.DiscoveryDevice
13
+ import com.ledger.devicesdk.shared.internal.connection.InternalConnectedDevice
14
+ import com.ledger.devicesdk.shared.internal.connection.InternalConnectionResult
15
+ import com.ledger.devicesdk.shared.internal.event.SdkEventDispatcher
16
+ import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
17
+ import com.ledger.devicesdk.shared.internal.transport.TransportEvent
18
+ import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
19
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.connection.AndroidUsbApduSender
20
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
21
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.UsbPermissionEvent
22
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.UsbState
23
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toLedgerUsbDevice
24
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toScannedDevice
25
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toUsbDevices
26
+ import com.ledger.devicesdk.shared.androidMainInternal.transport.deviceconnection.DeviceConnection
27
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleDebugLogInfo
28
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
29
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
30
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleWarningLogInfo
31
+ import kotlin.time.Duration
32
+ import kotlinx.coroutines.CoroutineDispatcher
33
+ import kotlinx.coroutines.CoroutineScope
34
+ import kotlinx.coroutines.Dispatchers
35
+ import kotlinx.coroutines.Job
36
+ import kotlinx.coroutines.SupervisorJob
37
+ import kotlinx.coroutines.delay
38
+ import kotlinx.coroutines.flow.Flow
39
+ import kotlinx.coroutines.flow.MutableSharedFlow
40
+ import kotlinx.coroutines.flow.MutableStateFlow
41
+ import kotlinx.coroutines.flow.SharingStarted
42
+ import kotlinx.coroutines.flow.first
43
+ import kotlinx.coroutines.flow.merge
44
+ import kotlinx.coroutines.flow.onStart
45
+ import kotlinx.coroutines.flow.shareIn
46
+ import kotlinx.coroutines.isActive
47
+ import kotlinx.coroutines.launch
48
+ import kotlin.math.log
49
+ import kotlin.time.Duration.Companion.seconds
50
+
51
+ internal class DefaultAndroidUsbTransport(
52
+ private val application: Application,
53
+ private val usbManager: UsbManager,
54
+ private val permissionRequester: UsbPermissionRequester,
55
+ private val eventDispatcher: SdkEventDispatcher,
56
+ private val loggerService: LoggerService,
57
+ private val scanDelay: Duration,
58
+ coroutineDispatcher: CoroutineDispatcher,
59
+ ) : AndroidUsbTransport {
60
+ private val scope = CoroutineScope(coroutineDispatcher + SupervisorJob())
61
+ private val internalUsbEventFlow: MutableSharedFlow<UsbState> = MutableSharedFlow()
62
+ private val internalUsbPermissionEventFlow: MutableSharedFlow<UsbPermissionEvent> =
63
+ MutableSharedFlow()
64
+
65
+ @Suppress("BackingPropertyName")
66
+ private var _scanStateFlow: MutableStateFlow<List<DiscoveryDevice>> =
67
+ MutableStateFlow(emptyList())
68
+ private var discoveryJob: Job? = null
69
+ private val usbConnections: MutableMap<String, DeviceConnection<AndroidUsbApduSender.Dependencies>> =
70
+ mutableMapOf()
71
+ private val usbConnectionsPendingReconnection: MutableSet<DeviceConnection<AndroidUsbApduSender.Dependencies>> =
72
+ mutableSetOf()
73
+
74
+ override fun startScan(): Flow<List<DiscoveryDevice>> {
75
+ discoveryJob?.cancel()
76
+ _scanStateFlow.value = emptyList()
77
+ discoveryJob =
78
+ scope.launch {
79
+ while (isActive) {
80
+ val usbDevices = usbManager.deviceList.values.toList()
81
+ val devices =
82
+ usbDevices
83
+ .filter { device ->
84
+ usbConnections.filter {
85
+ device == it.value.getApduSender().dependencies.usbDevice
86
+ }.isEmpty()
87
+ }.toUsbDevices()
88
+
89
+ _scanStateFlow.value = devices.toScannedDevices()
90
+
91
+ delay(scanDelay)
92
+ }
93
+ }
94
+ return _scanStateFlow
95
+ }
96
+
97
+ override fun stopScan() {
98
+ discoveryJob?.cancel()
99
+ discoveryJob = null
100
+ }
101
+
102
+ override fun updateUsbState(state: UsbState) {
103
+ when (state) {
104
+ is UsbState.Detached -> {
105
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Detached deviceId=${state.ledgerUsbDevice.uid}"))
106
+ usbConnections.entries.find {
107
+ it.value.getApduSender().dependencies.ledgerUsbDevice.uid == state.ledgerUsbDevice.uid
108
+ }.let { item ->
109
+ scope.launch {
110
+ if (item == null) {
111
+ loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No connection found"))
112
+ return@launch
113
+ }
114
+ val (key, deviceConnection) = item
115
+ loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Device disconnected (sessionId=${deviceConnection.sessionId})"))
116
+ deviceConnection.handleDeviceDisconnected()
117
+ usbConnections.remove(key)
118
+ usbConnectionsPendingReconnection.add(deviceConnection)
119
+ }
120
+ }
121
+ }
122
+
123
+ is UsbState.Attached -> {
124
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Attached deviceId=${state.ledgerUsbDevice.uid}, pendingReconnections=${usbConnectionsPendingReconnection}"))
125
+ val usbDevice = usbManager.deviceList.values.firstOrNull {
126
+ it.toLedgerUsbDevice()?.uid == state.ledgerUsbDevice.uid
127
+ }
128
+ if (usbDevice == null) {
129
+ loggerService.log(buildSimpleWarningLogInfo("AndroidUsbTransport", "No UsbDevice found"))
130
+ return
131
+ }
132
+ usbConnectionsPendingReconnection.firstOrNull {
133
+ it.getApduSender()
134
+ .dependencies.ledgerUsbDevice.ledgerDevice == state.ledgerUsbDevice.ledgerDevice // we just find a similar device model since there is no way to uniquely identify a device between 2 connections
135
+ }.let { deviceConnection ->
136
+ scope.launch {
137
+ if (deviceConnection == null) {
138
+ loggerService.log(
139
+ buildSimpleWarningLogInfo(
140
+ "AndroidUsbTransport",
141
+ "No pending connection found"
142
+ )
143
+ )
144
+ return@launch
145
+ }
146
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Found matching device connection $deviceConnection"))
147
+
148
+ val permissionResult = checkOrRequestPermission(usbDevice)
149
+ if (permissionResult is PermissionResult.Denied) {
150
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Permission denied"))
151
+ return@launch
152
+ }
153
+ loggerService.log(buildSimpleInfoLogInfo("AndroidUsbTransport", "Reconnecting device (sessionId=${deviceConnection.sessionId})"))
154
+ deviceConnection.setApduSender(
155
+ AndroidUsbApduSender(
156
+ dependencies = AndroidUsbApduSender.Dependencies(
157
+ usbDevice = usbDevice,
158
+ ledgerUsbDevice = state.ledgerUsbDevice,
159
+ ),
160
+ usbManager = usbManager,
161
+ ioDispatcher = Dispatchers.IO,
162
+ framerService = FramerService(loggerService),
163
+ request = UsbRequest(),
164
+ loggerService = loggerService
165
+ )
166
+ )
167
+ deviceConnection.handleDeviceConnected()
168
+ usbConnectionsPendingReconnection.remove(deviceConnection)
169
+ usbConnections[deviceConnection.sessionId] = deviceConnection
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ override fun updateUsbEvent(event: UsbPermissionEvent) {
177
+ scope.launch {
178
+ internalUsbPermissionEventFlow.emit(event)
179
+ }
180
+ }
181
+
182
+ sealed class PermissionResult {
183
+ data object Granted : PermissionResult()
184
+ data class Denied(val connectionError: InternalConnectionResult.ConnectionError) :
185
+ PermissionResult()
186
+ }
187
+
188
+ private suspend fun checkOrRequestPermission(usbDevice: UsbDevice): PermissionResult {
189
+ if (usbManager.hasPermission(usbDevice)) {
190
+ return PermissionResult.Granted
191
+ }
192
+
193
+ val eventsFlow = merge(
194
+ internalUsbPermissionEventFlow,
195
+ internalUsbEventFlow,
196
+ ).shareIn(scope = scope, started = SharingStarted.Eagerly)
197
+
198
+ permissionRequester.requestPermission(
199
+ context = application,
200
+ manager = usbManager,
201
+ device = usbDevice,
202
+ )
203
+
204
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Waiting for permission result"))
205
+
206
+ val result = eventsFlow.first {
207
+ it is UsbPermissionEvent.PermissionGranted ||
208
+ it is UsbPermissionEvent.PermissionDenied ||
209
+ it is UsbState.Detached
210
+ }
211
+
212
+ loggerService.log(buildSimpleDebugLogInfo("AndroidUsbTransport", "Got permission result"))
213
+
214
+ return when (result) {
215
+ is UsbPermissionEvent -> {
216
+ return when (result) {
217
+ is UsbPermissionEvent.PermissionDenied -> {
218
+ PermissionResult.Denied(
219
+ InternalConnectionResult.ConnectionError(
220
+ error = InternalConnectionResult.Failure.PermissionNotGranted,
221
+ )
222
+ )
223
+ }
224
+
225
+ is UsbPermissionEvent.PermissionGranted -> {
226
+ PermissionResult.Granted
227
+ }
228
+ }
229
+ }
230
+
231
+ else -> {
232
+ PermissionResult.Denied(InternalConnectionResult.ConnectionError(error = InternalConnectionResult.Failure.DeviceNotFound))
233
+ }
234
+ }
235
+ }
236
+
237
+ override suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult {
238
+ val usbDevice: UsbDevice? =
239
+ usbManager.deviceList.values.firstOrNull { it.deviceId == discoveryDevice.uid.toInt() }
240
+
241
+ val ledgerUsbDevice = usbDevice?.toLedgerUsbDevice()
242
+
243
+ return if (usbDevice == null || ledgerUsbDevice == null) {
244
+ InternalConnectionResult.ConnectionError(error = InternalConnectionResult.Failure.DeviceNotFound)
245
+ } else {
246
+ val permissionResult = checkOrRequestPermission(usbDevice)
247
+ if (permissionResult is PermissionResult.Denied) {
248
+ return permissionResult.connectionError
249
+ }
250
+
251
+ val sessionId = generateSessionId(usbDevice)
252
+ val apduSender =
253
+ AndroidUsbApduSender(
254
+ dependencies = AndroidUsbApduSender.Dependencies(
255
+ usbDevice = usbDevice,
256
+ ledgerUsbDevice = ledgerUsbDevice,
257
+ ),
258
+ usbManager = usbManager,
259
+ ioDispatcher = Dispatchers.IO,
260
+ framerService = FramerService(loggerService),
261
+ request = UsbRequest(),
262
+ loggerService = loggerService,
263
+ )
264
+
265
+ val deviceConnection = DeviceConnection(
266
+ sessionId = sessionId,
267
+ deviceApduSender = apduSender,
268
+ isFatalSendApduFailure = { false }, // TODO: refine this
269
+ reconnectionTimeoutDuration = 5.seconds,
270
+ onTerminated = {
271
+ usbConnections.remove(sessionId)
272
+ usbConnectionsPendingReconnection.remove(it)
273
+ eventDispatcher.dispatch(TransportEvent.DeviceConnectionLost(sessionId))
274
+ },
275
+ coroutineScope = scope,
276
+ loggerService = loggerService,
277
+ )
278
+
279
+ val connectedDevice =
280
+ InternalConnectedDevice(
281
+ sessionId,
282
+ discoveryDevice.name,
283
+ discoveryDevice.ledgerDevice,
284
+ discoveryDevice.connectivityType,
285
+ sendApduFn = { apdu -> deviceConnection.requestSendApdu(apdu) },
286
+ )
287
+
288
+ usbConnections[sessionId] = deviceConnection
289
+
290
+ InternalConnectionResult.Connected(device = connectedDevice, sessionId = sessionId)
291
+ }
292
+ }
293
+
294
+ override suspend fun disconnect(deviceId: String) {
295
+ usbConnections[deviceId]?.requestCloseConnection()
296
+ }
297
+
298
+ private fun generateSessionId(usbDevice: UsbDevice): String = "usb_${usbDevice.deviceId}"
299
+ }
300
+
301
+ private fun List<LedgerUsbDevice>.toScannedDevices(): List<DiscoveryDevice> =
302
+ this.map {
303
+ it.toScannedDevice()
304
+ }
@@ -0,0 +1,18 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2023 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb
7
+
8
+ import android.content.Context
9
+ import android.hardware.usb.UsbManager
10
+ import android.hardware.usb.UsbDevice as AndroidUsbDevice
11
+
12
+ internal fun interface UsbPermissionRequester {
13
+ fun requestPermission(
14
+ context: Context,
15
+ manager: UsbManager,
16
+ device: AndroidUsbDevice,
17
+ )
18
+ }
@@ -0,0 +1,133 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2024 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb.connection
7
+
8
+ import android.hardware.usb.UsbConstants
9
+ import android.hardware.usb.UsbDevice
10
+ import android.hardware.usb.UsbDeviceConnection
11
+ import android.hardware.usb.UsbEndpoint
12
+ import android.hardware.usb.UsbInterface
13
+ import android.hardware.usb.UsbManager
14
+ import android.hardware.usb.UsbRequest
15
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
16
+ import com.ledger.devicesdk.shared.api.apdu.SendApduFailureReason
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
21
+ import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
22
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleErrorLogInfo
23
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
24
+ import com.ledger.devicesdk.shared.internal.transport.framer.FramerService
25
+ import com.ledger.devicesdk.shared.internal.transport.framer.to2BytesArray
26
+ import java.nio.ByteBuffer
27
+ import kotlin.random.Random
28
+ import kotlinx.coroutines.CoroutineDispatcher
29
+ import kotlinx.coroutines.withContext
30
+ import timber.log.Timber
31
+
32
+ private const val USB_TIMEOUT = 500
33
+
34
+ private const val DEFAULT_USB_INTERFACE = 0
35
+
36
+ internal class AndroidUsbApduSender(
37
+ override val dependencies: Dependencies,
38
+ private val usbManager: UsbManager,
39
+ private val framerService: FramerService,
40
+ private val request: UsbRequest,
41
+ private val ioDispatcher: CoroutineDispatcher,
42
+ private val loggerService: LoggerService,
43
+ ) : DeviceApduSender<AndroidUsbApduSender.Dependencies> {
44
+
45
+ data class Dependencies(
46
+ val usbDevice: UsbDevice,
47
+ val ledgerUsbDevice: LedgerUsbDevice,
48
+ )
49
+
50
+ override suspend fun send(apdu: ByteArray): SendApduResult =
51
+ try {
52
+ val usbDevice = dependencies.usbDevice
53
+ withContext(context = ioDispatcher) {
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) }
58
+
59
+ transmitApdu(
60
+ usbConnection = usbConnection,
61
+ androidToUsbEndpoint = androidToUsbEndpoint,
62
+ rawApdu = apdu,
63
+ )
64
+ val apduResponse =
65
+ receiveApdu(
66
+ usbConnection = usbConnection,
67
+ usbToAndroidEndpoint = usbToAndroidEndpoint,
68
+ )
69
+
70
+ usbConnection.releaseInterface(usbInterface)
71
+ usbConnection.close()
72
+
73
+ SendApduResult.Success(apdu = apduResponse)
74
+ }
75
+ } catch (e: NoSuchElementException) {
76
+ SendApduResult.Failure(reason = SendApduFailureReason.NoUsbEndpointFound)
77
+ } catch (e: Exception) {
78
+ loggerService.log(buildSimpleErrorLogInfo("AndroidUsbApduSender", "error in send: $e"))
79
+ SendApduResult.Failure(reason = SendApduFailureReason.Unknown)
80
+ }
81
+
82
+ private fun transmitApdu(
83
+ usbConnection: UsbDeviceConnection,
84
+ androidToUsbEndpoint: UsbEndpoint,
85
+ rawApdu: ByteArray,
86
+ ) {
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
+ }
92
+ }
93
+
94
+ private fun receiveApdu(
95
+ usbConnection: UsbDeviceConnection,
96
+ usbToAndroidEndpoint: UsbEndpoint,
97
+ ): ByteArray =
98
+ if (!request.initialize(usbConnection, usbToAndroidEndpoint)) {
99
+ request.close()
100
+ byteArrayOf()
101
+ } else {
102
+ val frames = framerService.createApduFrames(mtu = USB_MTU, isUsbTransport = true){
103
+ val buffer = ByteArray(USB_MTU)
104
+ val responseBuffer = ByteBuffer.allocate(USB_MTU)
105
+
106
+ val queuingResult = request.queue(responseBuffer)
107
+ if (!queuingResult) {
108
+ request.close()
109
+ byteArrayOf()
110
+ }
111
+ else{
112
+ usbConnection.requestWait()
113
+ responseBuffer.rewind()
114
+ responseBuffer.get(buffer, 0, responseBuffer.remaining())
115
+ buffer
116
+ }
117
+ }
118
+ framerService.deserialize(mtu = USB_MTU, frames)
119
+ }
120
+
121
+ private fun UsbInterface.firstEndpointOrThrow(predicate: (Int) -> Boolean): UsbEndpoint {
122
+ for (endp in 0..this.endpointCount) {
123
+ val endpoint = this.getEndpoint(endp)
124
+ val endpointDirection = endpoint.direction
125
+ if (predicate(endpointDirection)) {
126
+ return endpoint
127
+ }
128
+ }
129
+ throw NoSuchElementException("No endpoint matching the predicate")
130
+ }
131
+
132
+ private fun generateChannelId(): ByteArray = Random.nextInt(0, until = Int.MAX_VALUE).to2BytesArray()
133
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2023 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb.controller
7
+
8
+ import android.content.BroadcastReceiver
9
+ import android.content.Context
10
+ import android.content.Intent
11
+ import android.content.IntentFilter
12
+ import android.hardware.usb.UsbDevice
13
+ import android.hardware.usb.UsbManager
14
+ import androidx.core.content.ContextCompat
15
+ import com.ledger.devicesdk.shared.internal.utils.Controller
16
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.AndroidUsbTransport
17
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
18
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.UsbState
19
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.getAndroidUsbDevice
20
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toLedgerUsbDevice
21
+ import timber.log.Timber
22
+
23
+ internal class UsbAttachedReceiverController(
24
+ private val context: Context,
25
+ private val androidUsbTransport: AndroidUsbTransport,
26
+ ) : BroadcastReceiver(),
27
+ Controller {
28
+ override fun start() {
29
+ Timber.i("start UsbAttachedReceiverController")
30
+ ContextCompat.registerReceiver(
31
+ context,
32
+ this,
33
+ IntentFilter(UsbManager.ACTION_USB_DEVICE_ATTACHED),
34
+ ContextCompat.RECEIVER_NOT_EXPORTED,
35
+ )
36
+ }
37
+
38
+ override fun stop() {
39
+ Timber.i("stop UsbAttachedReceiverController")
40
+ context.unregisterReceiver(this)
41
+ }
42
+
43
+ override fun onReceive(
44
+ context: Context,
45
+ intent: Intent,
46
+ ) {
47
+ Timber.i("UsbAttachedReceiverController:onReceive")
48
+ val androidUsbDevice: UsbDevice = intent.getAndroidUsbDevice()
49
+ val ledgerUsbDevice: LedgerUsbDevice? = androidUsbDevice.toLedgerUsbDevice()
50
+ if (ledgerUsbDevice != null) {
51
+ androidUsbTransport.updateUsbState(
52
+ state = UsbState.Attached(
53
+ ledgerUsbDevice = ledgerUsbDevice,
54
+ )
55
+ )
56
+ }
57
+
58
+ }
59
+ }
@@ -0,0 +1,58 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2023 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb.controller
7
+
8
+ import android.content.BroadcastReceiver
9
+ import android.content.Context
10
+ import android.content.Intent
11
+ import android.content.IntentFilter
12
+ import android.hardware.usb.UsbDevice
13
+ import android.hardware.usb.UsbManager
14
+ import androidx.core.content.ContextCompat
15
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.AndroidUsbTransport
16
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.LedgerUsbDevice
17
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.UsbState
18
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.getAndroidUsbDevice
19
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toLedgerUsbDevice
20
+ import com.ledger.devicesdk.shared.internal.utils.Controller
21
+ import timber.log.Timber
22
+
23
+ internal class UsbDetachedReceiverController(
24
+ private val context: Context,
25
+ private val androidUsbTransport: AndroidUsbTransport,
26
+ ) : BroadcastReceiver(),
27
+ Controller {
28
+ override fun start() {
29
+ Timber.i("start UsbDetachedReceiverController")
30
+ ContextCompat.registerReceiver(
31
+ context,
32
+ this,
33
+ IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED),
34
+ ContextCompat.RECEIVER_NOT_EXPORTED,
35
+ )
36
+ }
37
+
38
+ override fun stop() {
39
+ Timber.i("stop UsbDetachedReceiverController")
40
+ context.unregisterReceiver(this)
41
+ }
42
+
43
+ override fun onReceive(
44
+ context: Context,
45
+ intent: Intent,
46
+ ) {
47
+ Timber.i("UsbDetachedReceiverController:onReceive")
48
+ val usbDevice: UsbDevice = intent.getAndroidUsbDevice()
49
+ val ledgerUsbDevice: LedgerUsbDevice? = usbDevice.toLedgerUsbDevice()
50
+ if (ledgerUsbDevice != null) {
51
+ androidUsbTransport.updateUsbState(
52
+ state = UsbState.Detached(
53
+ ledgerUsbDevice = ledgerUsbDevice,
54
+ )
55
+ )
56
+ }
57
+ }
58
+ }
@@ -0,0 +1,92 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2024 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb.controller
7
+
8
+ import android.content.BroadcastReceiver
9
+ import android.content.Context
10
+ import android.content.Intent
11
+ import android.content.IntentFilter
12
+ import android.hardware.usb.UsbManager
13
+ import android.os.Build
14
+ import androidx.core.content.ContextCompat
15
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.AndroidUsbTransport
16
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.model.UsbPermissionEvent
17
+ import com.ledger.devicesdk.shared.androidMain.transport.usb.utils.toLedgerUsbDevice
18
+ import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
19
+ import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleDebugLogInfo
20
+ import com.ledger.devicesdk.shared.internal.utils.Controller
21
+ import timber.log.Timber
22
+
23
+ internal const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
24
+
25
+ internal class UsbPermissionReceiver(
26
+ private val context: Context,
27
+ private val androidUsbTransport: AndroidUsbTransport,
28
+ private val usbManager: UsbManager,
29
+ private val loggerService: LoggerService,
30
+ ) : BroadcastReceiver(),
31
+ Controller {
32
+ override fun start() {
33
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
34
+ ContextCompat.registerReceiver(
35
+ context,
36
+ this,
37
+ IntentFilter(ACTION_USB_PERMISSION),
38
+ ContextCompat.RECEIVER_NOT_EXPORTED,
39
+ )
40
+ } else {
41
+ ContextCompat.registerReceiver(
42
+ context,
43
+ this,
44
+ IntentFilter(ACTION_USB_PERMISSION),
45
+ ACTION_USB_PERMISSION,
46
+ null,
47
+ ContextCompat.RECEIVER_NOT_EXPORTED,
48
+ )
49
+ }
50
+ }
51
+
52
+ override fun stop() {
53
+ context.unregisterReceiver(this)
54
+ }
55
+
56
+ override fun onReceive(
57
+ context: Context,
58
+ intent: Intent,
59
+ ) {
60
+ Timber.i("UsbPermissionReceiver:onReceive")
61
+ if (ACTION_USB_PERMISSION == intent.action) {
62
+ synchronized(this) {
63
+ val androidUsbDevice = usbManager.deviceList.values.firstOrNull {
64
+ usbManager.hasPermission(it) && it.toLedgerUsbDevice() != null
65
+ }
66
+ val ledgerUsbDevice = androidUsbDevice?.toLedgerUsbDevice()
67
+ if (ledgerUsbDevice != null) {
68
+ loggerService.log(
69
+ buildSimpleDebugLogInfo(
70
+ "UsbPermissionReceiver:onReceive",
71
+ "permission granted"
72
+ )
73
+ )
74
+ androidUsbTransport.updateUsbEvent(
75
+ UsbPermissionEvent.PermissionGranted(ledgerUsbDevice = ledgerUsbDevice)
76
+ )
77
+ } else {
78
+ loggerService.log(
79
+ buildSimpleDebugLogInfo(
80
+ "UsbPermissionReceiver:onReceive",
81
+ "permission denied"
82
+ )
83
+ )
84
+ androidUsbTransport.updateUsbEvent(
85
+ UsbPermissionEvent.PermissionDenied
86
+ )
87
+ }
88
+
89
+ }
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,16 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: 2023 Ledger SAS
3
+ * SPDX-License-Identifier: LicenseRef-LEDGER
4
+ */
5
+
6
+ package com.ledger.devicesdk.shared.androidMain.transport.usb.model
7
+
8
+ import com.ledger.devicesdk.shared.api.device.LedgerDevice
9
+
10
+ internal class LedgerUsbDevice(
11
+ val uid: String,
12
+ val name: String,
13
+ val vendorId: VendorId,
14
+ val productId: ProductId,
15
+ val ledgerDevice: LedgerDevice,
16
+ )