@ledgerhq/device-transport-kit-react-native-hid 0.0.0-rn-hid-20250221112139 → 0.0.0-rnhid-transport-20250411151739
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/README.md +1 -1
- package/android/build.gradle +101 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +1 -0
- package/android/gradlew +252 -0
- package/android/gradlew.bat +94 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/kotlin/com/ledger/androidtransporthid/BridgeEvents.kt +42 -0
- package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidModule.kt +241 -0
- package/android/src/main/kotlin/com/ledger/androidtransporthid/TransportHidPackage.kt +25 -0
- package/android/src/main/kotlin/com/ledger/androidtransporthid/bridge/serialization.kt +124 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/AndroidUsbTransport.kt +16 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/DefaultAndroidUsbTransport.kt +298 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/UsbPermissionRequester.kt +18 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/connection/AndroidUsbApduSender.kt +133 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbAttachedReceiverController.kt +59 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbDetachedReceiverController.kt +58 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/controller/UsbPermissionReceiver.kt +92 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/LedgerUsbDevice.kt +16 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/ProductId.kt +11 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/UsbPermissionEvent.kt +14 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/UsbState.kt +16 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/model/VendorId.kt +11 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbDeviceMapper.kt +46 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMain/transport/usb/utils/UsbMapper.kt +56 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/UsbConst.android.kt +8 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceApduSender.kt +13 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnection.kt +95 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachine.kt +314 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/Apdu.kt +44 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduBuilder.kt +88 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduParser.kt +37 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/ApduUtils.kt +37 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/apdu/SendApduResult.kt +47 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/connection/ConnectedDevice.kt +25 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/connection/ConnectionResult.kt +45 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/BleInformation.kt +8 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/LedgerDevice.kt +89 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/device/UsbInfo.kt +7 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/disconnection/DisconnectionResult.kt +10 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/ConnectivityType.kt +10 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryDevice.kt +18 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/discovery/DiscoveryResult.kt +28 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/utils/ByteArrayExtension.kt +116 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/utils/StringExtension.kt +21 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectedDevice.kt +13 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/connection/InternalConnectionResult.kt +41 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/coroutine/SDKScope.kt +25 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/coroutine/SDKScopeHandler.kt +18 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/event/SdkEventDispatcher.kt +19 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/DisableLoggerService.kt +12 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LogInfo.kt +52 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LogLevel.kt +13 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LoggerService.kt +10 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/Transport.kt +21 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/TransportEvent.kt +18 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/FramerService.kt +210 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/FramerUtils.kt +35 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduConst.kt +9 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduFrame.kt +66 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/ApduFramerHeader.kt +74 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/model/FramerConst.kt +14 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/utils/ByteExtension.kt +21 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/utils/InternalByteArrayExtension.kt +18 -0
- package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/utils/Controller.kt +12 -0
- package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionStateMachineTest.kt +713 -0
- package/android/src/test/kotlin/com/ledger/devicesdk/shared/androidMainInternal/transport/deviceconnection/DeviceConnectionTest.kt +218 -0
- package/lib/cjs/package.json +2 -1
- package/lib/esm/package.json +2 -1
- package/lib/types/tsconfig.prod.tsbuildinfo +1 -1
- package/package.json +6 -5
package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/coroutine/SDKScopeHandler.kt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.coroutine
|
|
7
|
+
|
|
8
|
+
import kotlinx.coroutines.Dispatchers
|
|
9
|
+
import kotlinx.coroutines.SupervisorJob
|
|
10
|
+
|
|
11
|
+
private lateinit var scope: SdkCloseableScope
|
|
12
|
+
|
|
13
|
+
internal fun getSdkScope(): SdkCloseableScope {
|
|
14
|
+
if (!::scope.isInitialized) {
|
|
15
|
+
scope = SdkCloseableScope(context = Dispatchers.Default + SupervisorJob())
|
|
16
|
+
}
|
|
17
|
+
return scope
|
|
18
|
+
}
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/event/SdkEventDispatcher.kt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.ledger.devicesdk.shared.internal.event
|
|
2
|
+
|
|
3
|
+
import com.ledger.devicesdk.shared.internal.coroutine.sdkScope
|
|
4
|
+
import com.ledger.devicesdk.shared.internal.transport.TransportEvent
|
|
5
|
+
import kotlinx.coroutines.flow.Flow
|
|
6
|
+
import kotlinx.coroutines.flow.MutableSharedFlow
|
|
7
|
+
import kotlinx.coroutines.launch
|
|
8
|
+
|
|
9
|
+
internal class SdkEventDispatcher {
|
|
10
|
+
private val eventFlow: MutableSharedFlow<TransportEvent> = MutableSharedFlow()
|
|
11
|
+
|
|
12
|
+
fun listen(): Flow<TransportEvent> = eventFlow
|
|
13
|
+
|
|
14
|
+
fun dispatch(event: TransportEvent) {
|
|
15
|
+
sdkScope.launch {
|
|
16
|
+
eventFlow.emit(value = event)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.service.logger
|
|
7
|
+
|
|
8
|
+
internal class DisableLoggerService : LoggerService {
|
|
9
|
+
override fun log(info: LogInfo) {
|
|
10
|
+
//DO NOTING
|
|
11
|
+
}
|
|
12
|
+
}
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/service/logger/LogInfo.kt
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.service.logger
|
|
7
|
+
|
|
8
|
+
internal data class LogInfo(
|
|
9
|
+
val level: LogLevel,
|
|
10
|
+
val tag: String,
|
|
11
|
+
val message: String,
|
|
12
|
+
val jsonPayLoad: Map<String, String>,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
internal fun buildSimpleDebugLogInfo(
|
|
16
|
+
tag: String,
|
|
17
|
+
message: String,
|
|
18
|
+
): LogInfo =
|
|
19
|
+
LogInfo(level = LogLevel.DEBUG, tag = tag, message = message, emptyMap())
|
|
20
|
+
|
|
21
|
+
internal fun buildSimpleErrorLogInfo(
|
|
22
|
+
tag: String,
|
|
23
|
+
message: String,
|
|
24
|
+
): LogInfo =
|
|
25
|
+
LogInfo(level = LogLevel.ERROR, tag = tag, message = message, emptyMap())
|
|
26
|
+
|
|
27
|
+
internal fun buildSimpleWarningLogInfo(
|
|
28
|
+
tag: String,
|
|
29
|
+
message: String,
|
|
30
|
+
): LogInfo =
|
|
31
|
+
LogInfo(level = LogLevel.WARNING, tag = tag, message = message, emptyMap())
|
|
32
|
+
|
|
33
|
+
internal fun buildSimpleInfoLogInfo(
|
|
34
|
+
tag: String,
|
|
35
|
+
message: String,
|
|
36
|
+
): LogInfo =
|
|
37
|
+
LogInfo(level = LogLevel.INFO, tag = tag, message = message, emptyMap())
|
|
38
|
+
|
|
39
|
+
private fun buildSimpleLogInfo(
|
|
40
|
+
level: LogLevel,
|
|
41
|
+
tag: String,
|
|
42
|
+
message: String,
|
|
43
|
+
): LogInfo =
|
|
44
|
+
LogInfo(level = level, tag = tag, message = message, emptyMap())
|
|
45
|
+
|
|
46
|
+
internal fun buildComplexLogInfo(
|
|
47
|
+
level: LogLevel,
|
|
48
|
+
tag: String,
|
|
49
|
+
message: String,
|
|
50
|
+
jsonPayLoad: Map<String, String>,
|
|
51
|
+
): LogInfo =
|
|
52
|
+
LogInfo(level = level, tag = tag, message = message, jsonPayLoad = jsonPayLoad)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.discovery.DiscoveryDevice
|
|
9
|
+
import com.ledger.devicesdk.shared.internal.connection.InternalConnectionResult
|
|
10
|
+
import kotlinx.coroutines.flow.Flow
|
|
11
|
+
|
|
12
|
+
internal interface Transport {
|
|
13
|
+
fun startScan(): Flow<List<DiscoveryDevice>>
|
|
14
|
+
|
|
15
|
+
fun stopScan()
|
|
16
|
+
|
|
17
|
+
// TODO change by Flow<ConnectedDeviceState> or add observe device connection for listening device state flow through?
|
|
18
|
+
suspend fun connect(discoveryDevice: DiscoveryDevice): InternalConnectionResult
|
|
19
|
+
|
|
20
|
+
suspend fun disconnect(deviceId: String)
|
|
21
|
+
}
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/TransportEvent.kt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport
|
|
7
|
+
|
|
8
|
+
internal sealed class TransportEvent {
|
|
9
|
+
data class DeviceConnectionLost(
|
|
10
|
+
val id: String,
|
|
11
|
+
) : TransportEvent()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
internal sealed class BluetoothTransportEvent : TransportEvent() {
|
|
15
|
+
data object BluetoothDisable : BluetoothTransportEvent()
|
|
16
|
+
|
|
17
|
+
data object BluetoothEnable : BluetoothTransportEvent()
|
|
18
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.framer
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.apdu.ApduParser
|
|
9
|
+
import com.ledger.devicesdk.shared.api.utils.toHexadecimalString
|
|
10
|
+
import com.ledger.devicesdk.shared.internal.service.logger.LoggerService
|
|
11
|
+
import com.ledger.devicesdk.shared.internal.service.logger.buildSimpleInfoLogInfo
|
|
12
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.ApduFrame
|
|
13
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.ApduFramerHeader
|
|
14
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.HEADER_SIZE
|
|
15
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.MAXIMUM_HEADER_SIZE
|
|
16
|
+
import com.ledger.devicesdk.shared.internal.transport.utils.extractApduSize
|
|
17
|
+
import com.ledger.devicesdk.shared.internal.transport.utils.extractFrameHeader
|
|
18
|
+
|
|
19
|
+
private const val TAG = "FramerService"
|
|
20
|
+
|
|
21
|
+
internal class FramerService(
|
|
22
|
+
private val loggerService: LoggerService
|
|
23
|
+
) {
|
|
24
|
+
fun serialize(
|
|
25
|
+
mtu: Int,
|
|
26
|
+
channelId: ByteArray?,
|
|
27
|
+
rawApdu: ByteArray,
|
|
28
|
+
): List<ApduFrame> {
|
|
29
|
+
val header = ApduFramerHeader(channelId = channelId, frameId = byteArrayOf(0x00, 0x00))
|
|
30
|
+
val apduSize = rawApdu.size.to2BytesArray()
|
|
31
|
+
val frames =
|
|
32
|
+
if (rawApdu.isShortApdu(mtu = mtu)) {
|
|
33
|
+
val paddingSize = mtu - header.size() - apduSize.size - rawApdu.size
|
|
34
|
+
val finalApdu = rawApdu + ByteArray(paddingSize)
|
|
35
|
+
listOf(
|
|
36
|
+
ApduFrame(
|
|
37
|
+
header = header,
|
|
38
|
+
apduSize = apduSize,
|
|
39
|
+
apdu = finalApdu,
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
} else {
|
|
43
|
+
var isBuildingFrames = true
|
|
44
|
+
var counter = 0
|
|
45
|
+
var startIndex = 0
|
|
46
|
+
var endIndex = mtu - header.size() - apduSize.size
|
|
47
|
+
buildList {
|
|
48
|
+
while (isBuildingFrames) {
|
|
49
|
+
if (counter == 0) {
|
|
50
|
+
// loggerService.log(
|
|
51
|
+
// info = buildSimpleInfoLogInfo(
|
|
52
|
+
// tag = TAG,
|
|
53
|
+
// message = "-- IF -- startIndex = $startIndex / endIndex = $endIndex",
|
|
54
|
+
// ),
|
|
55
|
+
// )
|
|
56
|
+
add(
|
|
57
|
+
ApduFrame(
|
|
58
|
+
header = header,
|
|
59
|
+
apduSize = apduSize,
|
|
60
|
+
apdu = rawApdu.slice(startIndex..<endIndex).toByteArray(),
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
} else {
|
|
64
|
+
startIndex = endIndex
|
|
65
|
+
endIndex = (mtu - header.size()) + startIndex
|
|
66
|
+
// loggerService.log(
|
|
67
|
+
// info = buildSimpleInfoLogInfo(
|
|
68
|
+
// tag = TAG,
|
|
69
|
+
// message = "-- ELSE -- startIndex = $startIndex / endIndex = $endIndex",
|
|
70
|
+
// ),
|
|
71
|
+
// )
|
|
72
|
+
if (endIndex <= rawApdu.size) {
|
|
73
|
+
add(
|
|
74
|
+
ApduFrame(
|
|
75
|
+
ApduFramerHeader(
|
|
76
|
+
channelId = channelId,
|
|
77
|
+
frameId = counter.to2BytesArray(),
|
|
78
|
+
),
|
|
79
|
+
apduSize = null,
|
|
80
|
+
apdu = rawApdu.slice(startIndex..<endIndex).toByteArray(),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
} else {
|
|
84
|
+
val apduExtracted = rawApdu.slice(startIndex..<rawApdu.size).toByteArray()
|
|
85
|
+
val paddingSize = mtu - header.size() - apduExtracted.size
|
|
86
|
+
val finalApdu =
|
|
87
|
+
apduExtracted + ByteArray(paddingSize)
|
|
88
|
+
add(
|
|
89
|
+
ApduFrame(
|
|
90
|
+
header =
|
|
91
|
+
ApduFramerHeader(
|
|
92
|
+
channelId = channelId,
|
|
93
|
+
frameId = counter.to2BytesArray(),
|
|
94
|
+
),
|
|
95
|
+
apduSize = null,
|
|
96
|
+
apdu = finalApdu,
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
isBuildingFrames = false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
counter += 1
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// loggerService.log(
|
|
107
|
+
// info = buildSimpleInfoLogInfo(
|
|
108
|
+
// tag = TAG,
|
|
109
|
+
// message = "APDU SERIALIZATION RESULT : \n${frames.toHexadecimalString()}",
|
|
110
|
+
// ),
|
|
111
|
+
// )
|
|
112
|
+
return frames
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fun deserialize(
|
|
116
|
+
mtu: Int,
|
|
117
|
+
frames: List<ApduFrame>,
|
|
118
|
+
): ByteArray {
|
|
119
|
+
// loggerService.log(
|
|
120
|
+
// info = buildSimpleInfoLogInfo(
|
|
121
|
+
// tag = TAG,
|
|
122
|
+
// message = "APDU DESERIALIZATION RESULT : \n",
|
|
123
|
+
// ),
|
|
124
|
+
// )
|
|
125
|
+
var payload = byteArrayOf()
|
|
126
|
+
return if (frames.isEmpty()) {
|
|
127
|
+
payload
|
|
128
|
+
} else {
|
|
129
|
+
var rawApdu: ByteArray
|
|
130
|
+
var offset = mtu - MAXIMUM_HEADER_SIZE
|
|
131
|
+
var apduSize = frames.first().apduSize!!.toIntOn2Bytes()
|
|
132
|
+
// loggerService.log(
|
|
133
|
+
// info = buildSimpleInfoLogInfo(
|
|
134
|
+
// tag = TAG,
|
|
135
|
+
// message =
|
|
136
|
+
// "Header: ${frames.first().header}\n" +
|
|
137
|
+
// "ApduSize : $apduSize",
|
|
138
|
+
// ),
|
|
139
|
+
// )
|
|
140
|
+
for (apduFrame in frames) {
|
|
141
|
+
if (offset < apduSize) {
|
|
142
|
+
rawApdu = apduFrame.apdu.extractApdu(toExclusive = offset)
|
|
143
|
+
payload += rawApdu
|
|
144
|
+
apduSize -= offset
|
|
145
|
+
offset = mtu - HEADER_SIZE
|
|
146
|
+
} else {
|
|
147
|
+
rawApdu = apduFrame.apdu.extractApdu(toExclusive = apduSize)
|
|
148
|
+
payload += rawApdu
|
|
149
|
+
break
|
|
150
|
+
}
|
|
151
|
+
// loggerService.log(
|
|
152
|
+
// info = buildSimpleInfoLogInfo(
|
|
153
|
+
// tag = TAG,
|
|
154
|
+
// message = "Apdu : ${rawApdu.toHexadecimalString()}",
|
|
155
|
+
// ),
|
|
156
|
+
// )
|
|
157
|
+
}
|
|
158
|
+
return payload
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fun createApduFrames(
|
|
163
|
+
mtu: Int,
|
|
164
|
+
isUsbTransport: Boolean,
|
|
165
|
+
onCreateBuffer: ()->ByteArray,
|
|
166
|
+
): List<ApduFrame> {
|
|
167
|
+
var firstFrame = true
|
|
168
|
+
var nbrDataRead = 0
|
|
169
|
+
var apduSizeInt = 0
|
|
170
|
+
return buildList {
|
|
171
|
+
do {
|
|
172
|
+
val rawApdu: ByteArray
|
|
173
|
+
val apduSize: ByteArray?
|
|
174
|
+
val buffer = onCreateBuffer()
|
|
175
|
+
if(buffer.isEmpty()){
|
|
176
|
+
return emptyList()
|
|
177
|
+
}
|
|
178
|
+
val parser = ApduParser(response = buffer)
|
|
179
|
+
val header = buffer.extractFrameHeader(isUsbTransport = isUsbTransport, parser = parser)
|
|
180
|
+
if (firstFrame) {
|
|
181
|
+
apduSize = buffer.extractApduSize(parser = parser)
|
|
182
|
+
apduSizeInt = apduSize.toIntOn2Bytes()
|
|
183
|
+
rawApdu = parser.extractRemainingBytesValue()
|
|
184
|
+
firstFrame = false
|
|
185
|
+
} else {
|
|
186
|
+
apduSize = null
|
|
187
|
+
rawApdu = parser.extractRemainingBytesValue()
|
|
188
|
+
}
|
|
189
|
+
nbrDataRead += mtu
|
|
190
|
+
add(
|
|
191
|
+
ApduFrame(
|
|
192
|
+
header = header,
|
|
193
|
+
apduSize = apduSize,
|
|
194
|
+
apdu = rawApdu,
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
// loggerService.log(
|
|
198
|
+
// buildSimpleInfoLogInfo(
|
|
199
|
+
// tag = TAG,
|
|
200
|
+
// message = "APDU received = ${rawApdu.toHexadecimalString()}",
|
|
201
|
+
// ),
|
|
202
|
+
// )
|
|
203
|
+
} while (nbrDataRead < apduSizeInt)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun ByteArray.isShortApdu(mtu: Int): Boolean = this.size < mtu - MAXIMUM_HEADER_SIZE
|
|
208
|
+
|
|
209
|
+
private fun ByteArray.extractApdu(toExclusive: Int) = this.sliceArray(0..<toExclusive)
|
|
210
|
+
}
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/internal/transport/framer/FramerUtils.kt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.framer
|
|
7
|
+
|
|
8
|
+
import com.ditchoom.buffer.ByteOrder
|
|
9
|
+
import com.ditchoom.buffer.PlatformBuffer
|
|
10
|
+
import com.ditchoom.buffer.allocate
|
|
11
|
+
import com.ditchoom.buffer.wrap
|
|
12
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.ApduFrame
|
|
13
|
+
|
|
14
|
+
internal fun Int.to2BytesArray(): ByteArray {
|
|
15
|
+
val result = PlatformBuffer.allocate(Int.SIZE_BYTES).apply { writeShort(this@to2BytesArray.toShort()) }
|
|
16
|
+
|
|
17
|
+
return byteArrayOf(result[0], result[1])
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
internal fun List<ApduFrame>.toHexadecimalString(): String {
|
|
21
|
+
var result = ""
|
|
22
|
+
forEach { result += it.toString() }
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
internal fun ByteArray.toIntOn2Bytes(): Int =
|
|
27
|
+
PlatformBuffer.wrap(this).readShort().toInt()
|
|
28
|
+
|
|
29
|
+
internal fun ByteArray.toUInt(bo: ByteOrder = ByteOrder.LITTLE_ENDIAN) = this.toInt(bo = bo).toUInt()
|
|
30
|
+
|
|
31
|
+
internal fun ByteArray.toInt(bo: ByteOrder = ByteOrder.LITTLE_ENDIAN): Int {
|
|
32
|
+
val paddedByteArray = ByteArray(Int.SIZE_BYTES)
|
|
33
|
+
this.copyInto(paddedByteArray)
|
|
34
|
+
return PlatformBuffer.wrap(array = paddedByteArray, byteOrder = bo).readInt()
|
|
35
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.framer.model
|
|
7
|
+
|
|
8
|
+
@OptIn(ExperimentalStdlibApi::class) // Use of Byte.toHexString()
|
|
9
|
+
internal data class ApduFrame(
|
|
10
|
+
val header: ApduFramerHeader,
|
|
11
|
+
val apduSize: ByteArray?,
|
|
12
|
+
val apdu: ByteArray,
|
|
13
|
+
) {
|
|
14
|
+
init {
|
|
15
|
+
apduSize?.let { require(it.size == APDU_SIZE_SIZE) }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fun toByteArray(): ByteArray =
|
|
19
|
+
header.toByteArray() +
|
|
20
|
+
(apduSize?.let { byteArrayOf(apduSize[0], apduSize[1]) } ?: byteArrayOf()) +
|
|
21
|
+
apdu
|
|
22
|
+
|
|
23
|
+
fun size(): Int {
|
|
24
|
+
val apduSize = apduSize?.size ?: 0
|
|
25
|
+
return header.size() +
|
|
26
|
+
apduSize +
|
|
27
|
+
apdu.size
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun toString(): String {
|
|
31
|
+
var result = "$header\napduSize = "
|
|
32
|
+
apduSize?.map {
|
|
33
|
+
result += "${it.toHexString().uppercase()} "
|
|
34
|
+
}
|
|
35
|
+
result += "\napdu = "
|
|
36
|
+
apdu.map {
|
|
37
|
+
result += "${it.toHexString().uppercase()} "
|
|
38
|
+
}
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
override fun equals(other: Any?): Boolean {
|
|
43
|
+
if (this === other) return true
|
|
44
|
+
if (other == null || this::class != other::class) return false
|
|
45
|
+
|
|
46
|
+
other as ApduFrame
|
|
47
|
+
|
|
48
|
+
if (header != other.header) return false
|
|
49
|
+
if (apduSize != null) {
|
|
50
|
+
if (other.apduSize == null) return false
|
|
51
|
+
if (!apduSize.contentEquals(other.apduSize)) return false
|
|
52
|
+
} else if (other.apduSize != null) {
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
if (!apdu.contentEquals(other.apdu)) return false
|
|
56
|
+
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
override fun hashCode(): Int {
|
|
61
|
+
var result = header.hashCode()
|
|
62
|
+
result = 31 * result + (apduSize?.contentHashCode() ?: 0)
|
|
63
|
+
result = 31 * result + apdu.contentHashCode()
|
|
64
|
+
return result
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.framer.model
|
|
7
|
+
|
|
8
|
+
@OptIn(ExperimentalStdlibApi::class)
|
|
9
|
+
internal data class ApduFramerHeader(
|
|
10
|
+
val channelId: ByteArray?,
|
|
11
|
+
val tagId: Byte = 0x05.toByte(),
|
|
12
|
+
val frameId: ByteArray,
|
|
13
|
+
) {
|
|
14
|
+
init {
|
|
15
|
+
if(channelId != null){
|
|
16
|
+
require(channelId.size == CHANNEL_ID_SIZE)
|
|
17
|
+
}
|
|
18
|
+
require(frameId.size == FRAME_ID_SIZE)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun toByteArray(): ByteArray {
|
|
22
|
+
return if(channelId != null){
|
|
23
|
+
channelId +
|
|
24
|
+
tagId +
|
|
25
|
+
frameId
|
|
26
|
+
}
|
|
27
|
+
else{
|
|
28
|
+
byteArrayOf(tagId) + frameId
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
override fun toString(): String {
|
|
33
|
+
var result = "--HEADER FRAME--\nchannelId = "
|
|
34
|
+
channelId?.map {
|
|
35
|
+
result += "${it.toHexString().uppercase()} "
|
|
36
|
+
}
|
|
37
|
+
result += "\ntagId = ${tagId.toHexString().uppercase()}"
|
|
38
|
+
result += "\nframeId = "
|
|
39
|
+
frameId.map {
|
|
40
|
+
result += "${it.toHexString().uppercase()} "
|
|
41
|
+
}
|
|
42
|
+
return result
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fun size(): Int {
|
|
46
|
+
val size = frameId.size + 1 // 1 byte relating to the tagId
|
|
47
|
+
return if(channelId != null){
|
|
48
|
+
size + channelId.size
|
|
49
|
+
}
|
|
50
|
+
else{
|
|
51
|
+
size
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override fun equals(other: Any?): Boolean {
|
|
56
|
+
if (this === other) return true
|
|
57
|
+
if (other == null || this::class != other::class) return false
|
|
58
|
+
|
|
59
|
+
other as ApduFramerHeader
|
|
60
|
+
|
|
61
|
+
if (!channelId.contentEquals(other.channelId)) return false
|
|
62
|
+
if (tagId != other.tagId) return false
|
|
63
|
+
if (!frameId.contentEquals(other.frameId)) return false
|
|
64
|
+
|
|
65
|
+
return true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun hashCode(): Int {
|
|
69
|
+
var result = channelId.contentHashCode()
|
|
70
|
+
result = 31 * result + tagId
|
|
71
|
+
result = 31 * result + frameId.contentHashCode()
|
|
72
|
+
return result
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.framer.model
|
|
7
|
+
|
|
8
|
+
internal const val HEADER_SIZE = 5
|
|
9
|
+
internal const val CHANNEL_ID_INDEX = 0
|
|
10
|
+
internal const val CHANNEL_ID_SIZE = 2
|
|
11
|
+
internal const val TAG_ID_INDEX = 2
|
|
12
|
+
internal const val FRAME_ID_INDEX = 3
|
|
13
|
+
internal const val FRAME_ID_SIZE = 2
|
|
14
|
+
internal const val MAXIMUM_HEADER_SIZE = HEADER_SIZE + APDU_SIZE_SIZE
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.utils
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.utils.isHexadecimal
|
|
9
|
+
|
|
10
|
+
internal fun Byte.isNotHexadecimal(): Boolean = !this.isHexadecimal()
|
|
11
|
+
|
|
12
|
+
@OptIn(ExperimentalStdlibApi::class)
|
|
13
|
+
internal fun Byte.isHexadecimal(): Boolean {
|
|
14
|
+
return try {
|
|
15
|
+
this.toHexString().isHexadecimal()
|
|
16
|
+
true
|
|
17
|
+
}
|
|
18
|
+
catch (e: NumberFormatException){
|
|
19
|
+
false
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.internal.transport.utils
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.apdu.ApduParser
|
|
9
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.ApduFramerHeader
|
|
10
|
+
|
|
11
|
+
internal fun ByteArray.extractFrameHeader(isUsbTransport: Boolean, parser: ApduParser) =
|
|
12
|
+
ApduFramerHeader(
|
|
13
|
+
channelId = if(isUsbTransport) parser.extract2BytesValue() else null,
|
|
14
|
+
tagId = parser.extractByteValue(),
|
|
15
|
+
frameId = parser.extract2BytesValue(),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
internal fun ByteArray.extractApduSize(parser: ApduParser) = parser.extract2BytesValue()
|