@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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.apdu
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.utils.isHexadecimal
|
|
9
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.model.MINIMUM_APDU_SIZE
|
|
10
|
+
import com.ledger.devicesdk.shared.internal.transport.utils.isHexadecimal
|
|
11
|
+
import kotlin.contracts.ExperimentalContracts
|
|
12
|
+
import kotlin.contracts.InvocationKind
|
|
13
|
+
import kotlin.contracts.contract
|
|
14
|
+
import kotlin.properties.Delegates
|
|
15
|
+
|
|
16
|
+
@OptIn(ExperimentalContracts::class)
|
|
17
|
+
public fun apdu(init: ApduBuilder.() -> Unit): Apdu {
|
|
18
|
+
contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) }
|
|
19
|
+
val builder = ApduBuilder()
|
|
20
|
+
init.invoke(builder)
|
|
21
|
+
return builder.build()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@OptIn(ExperimentalContracts::class)
|
|
25
|
+
public fun rawApdu(init: ApduRawBuilder.() -> Unit): Apdu {
|
|
26
|
+
contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) }
|
|
27
|
+
val builder = ApduRawBuilder()
|
|
28
|
+
init.invoke(builder)
|
|
29
|
+
return builder.build()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public class ApduBuilder internal constructor() {
|
|
33
|
+
public var classInstruction: Byte by Delegates.notNull()
|
|
34
|
+
public var instructionMethod: Byte by Delegates.notNull()
|
|
35
|
+
public var parameter1: Byte by Delegates.notNull()
|
|
36
|
+
public var parameter2: Byte by Delegates.notNull()
|
|
37
|
+
public var data: ByteArray? = null
|
|
38
|
+
|
|
39
|
+
internal fun build(): Apdu {
|
|
40
|
+
check(classInstruction.isHexadecimal()) { "classInstruction must be a hexadecimal value" }
|
|
41
|
+
check(instructionMethod.isHexadecimal()) { "instructionMethod must be a hexadecimal value" }
|
|
42
|
+
check(parameter1.isHexadecimal()) { "parameter1 must be a hexadecimal value" }
|
|
43
|
+
check(parameter2.isHexadecimal()) { "parameter2 must be a hexadecimal value" }
|
|
44
|
+
if (data != null) {
|
|
45
|
+
val internalData = data!!
|
|
46
|
+
check(internalData.isHexadecimal()) { "data must be filled with a hexadecimal values" }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Apdu(
|
|
50
|
+
classInstruction = classInstruction,
|
|
51
|
+
instructionMethod = instructionMethod,
|
|
52
|
+
parameter1 = parameter1,
|
|
53
|
+
parameter2 = parameter2,
|
|
54
|
+
data = data,
|
|
55
|
+
dataLength = data?.size ?: 0,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public class ApduRawBuilder internal constructor() {
|
|
61
|
+
public var rawApdu: ByteArray? = null
|
|
62
|
+
|
|
63
|
+
internal fun build(): Apdu {
|
|
64
|
+
check(rawApdu != null) { "RawAPDU must be filled" }
|
|
65
|
+
val apdu = rawApdu!!
|
|
66
|
+
check(apdu.isHexadecimal()) { "APDU must be filled with hexadecimal values" }
|
|
67
|
+
check(
|
|
68
|
+
apdu.size >= MINIMUM_APDU_SIZE,
|
|
69
|
+
) {
|
|
70
|
+
"APDU size is not correct : current size = ${apdu.size} / " +
|
|
71
|
+
"minimum size must be = 4 (CLA / INS / P1 / P2)"
|
|
72
|
+
}
|
|
73
|
+
val data =
|
|
74
|
+
if (apdu.size == MINIMUM_APDU_SIZE) {
|
|
75
|
+
null
|
|
76
|
+
} else {
|
|
77
|
+
apdu.sliceArray(MINIMUM_APDU_SIZE..<apdu.size)
|
|
78
|
+
}
|
|
79
|
+
return Apdu(
|
|
80
|
+
classInstruction = apdu[0],
|
|
81
|
+
instructionMethod = apdu[1],
|
|
82
|
+
parameter1 = apdu[2],
|
|
83
|
+
parameter2 = apdu[3],
|
|
84
|
+
data = data,
|
|
85
|
+
dataLength = data?.size ?: 0,
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.apdu
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.utils.extractField
|
|
9
|
+
|
|
10
|
+
public class ApduParser(
|
|
11
|
+
private val response: ByteArray,
|
|
12
|
+
) {
|
|
13
|
+
private var currentIndex = 0
|
|
14
|
+
|
|
15
|
+
public fun extractByteValue(): Byte = extract1BytesValue().first()
|
|
16
|
+
|
|
17
|
+
public fun extract1BytesValue(): ByteArray = extractBytesValue(nbrBytes = 1)
|
|
18
|
+
|
|
19
|
+
public fun extract2BytesValue(): ByteArray = extractBytesValue(nbrBytes = 2)
|
|
20
|
+
|
|
21
|
+
public fun extract3BytesValue(): ByteArray = extractBytesValue(nbrBytes = 3)
|
|
22
|
+
|
|
23
|
+
public fun extract4BytesValue(): ByteArray = extractBytesValue(nbrBytes = 4)
|
|
24
|
+
|
|
25
|
+
public fun extractValueString(nbrBytes: Int): String = extractBytesValue(nbrBytes = nbrBytes).decodeToString()
|
|
26
|
+
|
|
27
|
+
public fun extractRemainingBytesValue(): ByteArray = extractBytesValue(nbrBytes = response.size - currentIndex)
|
|
28
|
+
|
|
29
|
+
public fun extractBytesValue(nbrBytes: Int): ByteArray {
|
|
30
|
+
val index = currentIndex + nbrBytes
|
|
31
|
+
val result = response.extractField(from = currentIndex, to = index, toInclusive = false)
|
|
32
|
+
currentIndex += nbrBytes
|
|
33
|
+
return result
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public fun getCurrentIndex(): Int = currentIndex
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.apdu
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.utils.isHexadecimal
|
|
9
|
+
import com.ledger.devicesdk.shared.internal.transport.utils.isHexadecimal
|
|
10
|
+
|
|
11
|
+
internal fun Apdu.toRawApdu(): ByteArray {
|
|
12
|
+
val dataLength = this.dataLength.toByte()
|
|
13
|
+
return byteArrayOf(
|
|
14
|
+
this.classInstruction,
|
|
15
|
+
this.instructionMethod,
|
|
16
|
+
this.parameter1,
|
|
17
|
+
this.parameter2,
|
|
18
|
+
) + dataLength + (this.data ?: byteArrayOf())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public fun Apdu.isNotWellFormatted(): Boolean = !this.isWellFormatted()
|
|
22
|
+
|
|
23
|
+
public fun Apdu.isWellFormatted(): Boolean = this.isHexadecimalFields()
|
|
24
|
+
|
|
25
|
+
public fun Apdu.isHexadecimalFields(): Boolean =
|
|
26
|
+
try {
|
|
27
|
+
check(this.classInstruction.isHexadecimal())
|
|
28
|
+
check(this.instructionMethod.isHexadecimal())
|
|
29
|
+
check(this.parameter1.isHexadecimal())
|
|
30
|
+
check(this.parameter2.isHexadecimal())
|
|
31
|
+
if (this.data != null) {
|
|
32
|
+
check(this.data.isHexadecimal())
|
|
33
|
+
}
|
|
34
|
+
true
|
|
35
|
+
} catch (e: IllegalStateException) {
|
|
36
|
+
false
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.apdu
|
|
7
|
+
|
|
8
|
+
public sealed class SendApduResult {
|
|
9
|
+
public data class Success(
|
|
10
|
+
val apdu: ByteArray,
|
|
11
|
+
) : SendApduResult() {
|
|
12
|
+
override fun equals(other: Any?): Boolean {
|
|
13
|
+
if (this === other) return true
|
|
14
|
+
if (other == null || this::class != other::class) return false
|
|
15
|
+
|
|
16
|
+
other as Success
|
|
17
|
+
|
|
18
|
+
return apdu.contentEquals(other.apdu)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
override fun hashCode(): Int {
|
|
22
|
+
return apdu.contentHashCode()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public data class Failure(
|
|
27
|
+
val reason: SendApduFailureReason,
|
|
28
|
+
) : SendApduResult()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public sealed class SendApduFailureReason {
|
|
32
|
+
public data object DeviceNotFound : SendApduFailureReason()
|
|
33
|
+
|
|
34
|
+
public data object NoUsbEndpointFound : SendApduFailureReason()
|
|
35
|
+
|
|
36
|
+
public data object ApduNotWellFormatted : SendApduFailureReason()
|
|
37
|
+
|
|
38
|
+
public data object DeviceLocked : SendApduFailureReason()
|
|
39
|
+
|
|
40
|
+
public data object DeviceBusy : SendApduFailureReason()
|
|
41
|
+
|
|
42
|
+
public data object NoResponse : SendApduFailureReason()
|
|
43
|
+
|
|
44
|
+
public data object DeviceDisconnected : SendApduFailureReason()
|
|
45
|
+
|
|
46
|
+
public data object Unknown : SendApduFailureReason()
|
|
47
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2023 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.connection
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.device.LedgerDevice
|
|
9
|
+
import com.ledger.devicesdk.shared.api.discovery.ConnectivityType
|
|
10
|
+
import com.ledger.devicesdk.shared.internal.connection.InternalConnectedDevice
|
|
11
|
+
|
|
12
|
+
public data class ConnectedDevice(
|
|
13
|
+
public val uid: String,
|
|
14
|
+
public val name: String,
|
|
15
|
+
public val ledgerDevice: LedgerDevice,
|
|
16
|
+
public val connectivityType: ConnectivityType,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
internal fun InternalConnectedDevice.toConnectedDevice(): ConnectedDevice =
|
|
20
|
+
ConnectedDevice(
|
|
21
|
+
uid = this.id,
|
|
22
|
+
name = this.name,
|
|
23
|
+
ledgerDevice = this.ledgerDevice,
|
|
24
|
+
connectivityType = this.connectivity,
|
|
25
|
+
)
|
package/android/src/main/kotlin/com/ledger/devicesdk/shared/api/connection/ConnectionResult.kt
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2023 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.connection
|
|
7
|
+
|
|
8
|
+
public sealed class ConnectionResult {
|
|
9
|
+
public data class Connected(
|
|
10
|
+
val device: ConnectedDevice,
|
|
11
|
+
) : ConnectionResult()
|
|
12
|
+
|
|
13
|
+
public data class Disconnected(
|
|
14
|
+
val failure: Failure,
|
|
15
|
+
) : ConnectionResult()
|
|
16
|
+
|
|
17
|
+
// Most of these failures are mapped from the ble library but could be cleaned
|
|
18
|
+
public sealed class Failure {
|
|
19
|
+
public data object PairingFailed : Failure()
|
|
20
|
+
|
|
21
|
+
public data object ConnectionTimeout : Failure()
|
|
22
|
+
|
|
23
|
+
public data object DeviceNotFound : Failure()
|
|
24
|
+
|
|
25
|
+
public data object NoDeviceAddress : Failure()
|
|
26
|
+
|
|
27
|
+
public data object ServiceNotFound : Failure()
|
|
28
|
+
|
|
29
|
+
public data object InternalState : Failure()
|
|
30
|
+
|
|
31
|
+
public data object InitializingFailed : Failure()
|
|
32
|
+
|
|
33
|
+
public data object PermissionNotGranted : Failure()
|
|
34
|
+
|
|
35
|
+
public data object DeviceConnectivityBluetoothDisabled : Failure()
|
|
36
|
+
|
|
37
|
+
public data object DeviceConnectivityLocationDisabled : Failure()
|
|
38
|
+
|
|
39
|
+
public data object BleNotSupported : Failure()
|
|
40
|
+
|
|
41
|
+
public data class Unknown(
|
|
42
|
+
val msg: String?,
|
|
43
|
+
) : Failure()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
package com.ledger.devicesdk.shared.api.device
|
|
2
|
+
|
|
3
|
+
public sealed class LedgerDevice(
|
|
4
|
+
public val name: String,
|
|
5
|
+
public val usbInfo: UsbInfo,
|
|
6
|
+
public val bleInformation: BleInformation? = null,
|
|
7
|
+
) {
|
|
8
|
+
public data object Flex :
|
|
9
|
+
LedgerDevice(
|
|
10
|
+
name = "Ledger Flex",
|
|
11
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x70", "0x0007"),
|
|
12
|
+
bleInformation =
|
|
13
|
+
BleInformation(
|
|
14
|
+
serviceUuid = "13d63400-2c97-3004-0000-4c6564676572",
|
|
15
|
+
notifyCharacteristicUuid =
|
|
16
|
+
"13d63400-2c97-3004-0001-4c6564676572",
|
|
17
|
+
writeWithResponseCharacteristicUuid =
|
|
18
|
+
"13d63400-2c97-3004-0002-4c6564676572",
|
|
19
|
+
writeWithoutResponseCharacteristicUuid =
|
|
20
|
+
"13d63400-2c97-3004-0003-4c6564676572",
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
public data object Stax :
|
|
25
|
+
LedgerDevice(
|
|
26
|
+
name = "Ledger Stax",
|
|
27
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x60", "0x0006"),
|
|
28
|
+
bleInformation =
|
|
29
|
+
BleInformation(
|
|
30
|
+
serviceUuid = "13d63400-2c97-6004-0000-4c6564676572",
|
|
31
|
+
notifyCharacteristicUuid =
|
|
32
|
+
"13d63400-2c97-6004-0001-4c6564676572",
|
|
33
|
+
writeWithResponseCharacteristicUuid =
|
|
34
|
+
"13d63400-2c97-6004-0002-4c6564676572",
|
|
35
|
+
writeWithoutResponseCharacteristicUuid =
|
|
36
|
+
"13d63400-2c97-6004-0003-4c6564676572",
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
public data object NanoX :
|
|
41
|
+
LedgerDevice(
|
|
42
|
+
name = "Nano X",
|
|
43
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x40", "0x0004"),
|
|
44
|
+
bleInformation =
|
|
45
|
+
BleInformation(
|
|
46
|
+
serviceUuid = "13d63400-2c97-0004-0000-4c6564676572",
|
|
47
|
+
notifyCharacteristicUuid =
|
|
48
|
+
"13d63400-2c97-0004-0001-4c6564676572",
|
|
49
|
+
writeWithResponseCharacteristicUuid =
|
|
50
|
+
"13d63400-2c97-0004-0002-4c6564676572",
|
|
51
|
+
writeWithoutResponseCharacteristicUuid =
|
|
52
|
+
"13d63400-2c97-0004-0003-4c6564676572",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
public data object NanoSPlus :
|
|
57
|
+
LedgerDevice(
|
|
58
|
+
name = "Nano S Plus",
|
|
59
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x50", "0x0005"),
|
|
60
|
+
bleInformation = null,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
public data object NanoS :
|
|
64
|
+
LedgerDevice(
|
|
65
|
+
name = "Nano S",
|
|
66
|
+
usbInfo = UsbInfo(LEDGER_USB_VENDOR_ID, "0x10", "0x0001"),
|
|
67
|
+
bleInformation = null,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
public companion object {
|
|
71
|
+
public const val LEDGER_USB_VENDOR_ID: String = "0x2c97"
|
|
72
|
+
|
|
73
|
+
// Cannot use reflexion here to get all subclasses as it depends of the JVM
|
|
74
|
+
private val subclasses = buildList {
|
|
75
|
+
add(Flex)
|
|
76
|
+
add(Stax)
|
|
77
|
+
add(NanoX)
|
|
78
|
+
add(NanoSPlus)
|
|
79
|
+
add(NanoS)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
public fun getAllDevices(): List<LedgerDevice> {
|
|
83
|
+
return subclasses
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public fun getAllDevicesWithBluetooth(): List<LedgerDevice> =
|
|
87
|
+
getAllDevices().filter { it.bleInformation != null }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -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.api.discovery
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.api.device.LedgerDevice
|
|
9
|
+
import kotlinx.datetime.Clock
|
|
10
|
+
|
|
11
|
+
public class DiscoveryDevice(
|
|
12
|
+
public val uid: String,
|
|
13
|
+
public val name: String,
|
|
14
|
+
public val ledgerDevice: LedgerDevice,
|
|
15
|
+
public val connectivityType: ConnectivityType,
|
|
16
|
+
) {
|
|
17
|
+
public val timestamp: Long = Clock.System.now().toEpochMilliseconds()
|
|
18
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2023 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.discovery
|
|
7
|
+
|
|
8
|
+
public sealed class DiscoveryResult {
|
|
9
|
+
public data class DeviceDiscovered(
|
|
10
|
+
val devices: List<DiscoveryDevice>,
|
|
11
|
+
) : DiscoveryResult()
|
|
12
|
+
|
|
13
|
+
public data object Ended : DiscoveryResult()
|
|
14
|
+
|
|
15
|
+
public sealed class Failure : DiscoveryResult() {
|
|
16
|
+
public data object LocationDisabled : Failure()
|
|
17
|
+
|
|
18
|
+
public data object BluetoothDisabled : Failure()
|
|
19
|
+
|
|
20
|
+
public data object BluetoothPermissionNotGranted : Failure()
|
|
21
|
+
|
|
22
|
+
public data object BluetoothBleNotSupported : Failure()
|
|
23
|
+
|
|
24
|
+
public data class Unknown(
|
|
25
|
+
val message: String,
|
|
26
|
+
) : Failure()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* SPDX-FileCopyrightText: 2024 Ledger SAS
|
|
3
|
+
* SPDX-License-Identifier: LicenseRef-LEDGER
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
package com.ledger.devicesdk.shared.api.utils
|
|
7
|
+
|
|
8
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.toInt
|
|
9
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.toIntOn2Bytes
|
|
10
|
+
import com.ledger.devicesdk.shared.internal.transport.framer.toUInt
|
|
11
|
+
import com.ledger.devicesdk.shared.api.utils.isNotHexadecimal
|
|
12
|
+
import com.ledger.devicesdk.shared.internal.transport.utils.isNotHexadecimal
|
|
13
|
+
|
|
14
|
+
public fun ByteArray.isHexadecimal(): Boolean {
|
|
15
|
+
return if (isEmpty()) {
|
|
16
|
+
false
|
|
17
|
+
} else {
|
|
18
|
+
var result = true
|
|
19
|
+
this.forEach {
|
|
20
|
+
if (it.isNotHexadecimal()) {
|
|
21
|
+
result = false
|
|
22
|
+
return@forEach
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
result
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@OptIn(ExperimentalStdlibApi::class)
|
|
30
|
+
public fun ByteArray.toHexadecimalString(uppercase: Boolean = true): String {
|
|
31
|
+
var result = ""
|
|
32
|
+
forEach { result += it.toHexString() }
|
|
33
|
+
return if (uppercase) result.uppercase() else result
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public fun ByteArray.extractField(
|
|
37
|
+
from: Int,
|
|
38
|
+
to: Int,
|
|
39
|
+
fromInclusive: Boolean = true,
|
|
40
|
+
toInclusive: Boolean = true,
|
|
41
|
+
): ByteArray =
|
|
42
|
+
when (fromInclusive) {
|
|
43
|
+
true -> {
|
|
44
|
+
when (toInclusive) {
|
|
45
|
+
true -> this.sliceArray(from..to)
|
|
46
|
+
false -> this.sliceArray(from..<to)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
false -> {
|
|
51
|
+
when (toInclusive) {
|
|
52
|
+
true -> this.sliceArray(from + 1..to)
|
|
53
|
+
false -> this.sliceArray(from + 1..<to)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public fun ByteArray.extractFieldAsIntOn2Bytes(
|
|
59
|
+
from: Int,
|
|
60
|
+
to: Int,
|
|
61
|
+
fromInclusive: Boolean = true,
|
|
62
|
+
toInclusive: Boolean = true,
|
|
63
|
+
): Int =
|
|
64
|
+
this
|
|
65
|
+
.extractField(
|
|
66
|
+
from = from,
|
|
67
|
+
to = to,
|
|
68
|
+
fromInclusive = fromInclusive,
|
|
69
|
+
toInclusive = toInclusive,
|
|
70
|
+
).toIntOn2Bytes()
|
|
71
|
+
|
|
72
|
+
public fun ByteArray.extractFieldAsUInt(
|
|
73
|
+
from: Int,
|
|
74
|
+
to: Int,
|
|
75
|
+
fromInclusive: Boolean = true,
|
|
76
|
+
toInclusive: Boolean = true,
|
|
77
|
+
): UInt =
|
|
78
|
+
this
|
|
79
|
+
.extractField(
|
|
80
|
+
from = from,
|
|
81
|
+
to = to,
|
|
82
|
+
fromInclusive = fromInclusive,
|
|
83
|
+
toInclusive = toInclusive,
|
|
84
|
+
).toUInt()
|
|
85
|
+
|
|
86
|
+
public fun ByteArray.extractFieldAsInt(
|
|
87
|
+
from: Int,
|
|
88
|
+
to: Int,
|
|
89
|
+
fromInclusive: Boolean = true,
|
|
90
|
+
toInclusive: Boolean = true,
|
|
91
|
+
): Int =
|
|
92
|
+
this
|
|
93
|
+
.extractField(
|
|
94
|
+
from = from,
|
|
95
|
+
to = to,
|
|
96
|
+
fromInclusive = fromInclusive,
|
|
97
|
+
toInclusive = toInclusive,
|
|
98
|
+
).toInt()
|
|
99
|
+
|
|
100
|
+
public fun ByteArray.decodeString(
|
|
101
|
+
from: Int,
|
|
102
|
+
to: Int,
|
|
103
|
+
fromInclusive: Boolean = true,
|
|
104
|
+
toInclusive: Boolean = true,
|
|
105
|
+
): String =
|
|
106
|
+
this
|
|
107
|
+
.extractField(
|
|
108
|
+
from = from,
|
|
109
|
+
to = to,
|
|
110
|
+
fromInclusive = fromInclusive,
|
|
111
|
+
toInclusive = toInclusive,
|
|
112
|
+
).decodeToString()
|
|
113
|
+
|
|
114
|
+
public fun ByteArray.extractFieldAtIndexAsInt(at: Int): Int = this[at].toInt()
|
|
115
|
+
|
|
116
|
+
public fun ByteArray.extractFieldAtIndexAsString(at: Int): String = this[at].toString()
|
|
@@ -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.api.utils
|
|
7
|
+
|
|
8
|
+
@Throws(IllegalStateException::class)
|
|
9
|
+
public fun String.fromHexStringToBytesOrThrow(): ByteArray {
|
|
10
|
+
check(this.isHexadecimal()) { "$this is not in hexadecimal format" }
|
|
11
|
+
return chunked(2)
|
|
12
|
+
.map(String::fromHexStringToByteOrThrow)
|
|
13
|
+
.toByteArray()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@Throws(NumberFormatException::class)
|
|
17
|
+
public fun String.fromHexStringToByteOrThrow(): Byte = this.toInt(16).toByte()
|
|
18
|
+
|
|
19
|
+
public fun String.isHexadecimal(): Boolean = this.isNotEmpty() && this.matches(Regex("^[A-Fa-f0-9]+$"))
|
|
20
|
+
|
|
21
|
+
public fun String.isNotHexadecimal(): Boolean = !this.isHexadecimal()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package com.ledger.devicesdk.shared.internal.connection
|
|
2
|
+
|
|
3
|
+
import com.ledger.devicesdk.shared.api.apdu.SendApduResult
|
|
4
|
+
import com.ledger.devicesdk.shared.api.device.LedgerDevice
|
|
5
|
+
import com.ledger.devicesdk.shared.api.discovery.ConnectivityType
|
|
6
|
+
|
|
7
|
+
internal data class InternalConnectedDevice(
|
|
8
|
+
val id: String,
|
|
9
|
+
val name: String,
|
|
10
|
+
val ledgerDevice: LedgerDevice,
|
|
11
|
+
val connectivity: ConnectivityType,
|
|
12
|
+
val sendApduFn: suspend (ByteArray) -> SendApduResult,
|
|
13
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
package com.ledger.devicesdk.shared.internal.connection
|
|
2
|
+
|
|
3
|
+
internal sealed class InternalConnectionResult {
|
|
4
|
+
data class Connected(
|
|
5
|
+
val device: InternalConnectedDevice,
|
|
6
|
+
val sessionId: String,
|
|
7
|
+
) : InternalConnectionResult()
|
|
8
|
+
|
|
9
|
+
data class ConnectionError(
|
|
10
|
+
val error: Failure,
|
|
11
|
+
) : InternalConnectionResult()
|
|
12
|
+
|
|
13
|
+
// Most of these failures are mapped from the ble library but could be cleaned
|
|
14
|
+
sealed class Failure {
|
|
15
|
+
data object PairingFailed : Failure()
|
|
16
|
+
|
|
17
|
+
data object ConnectionTimeout : Failure()
|
|
18
|
+
|
|
19
|
+
data object DeviceNotFound : Failure()
|
|
20
|
+
|
|
21
|
+
data object NoDeviceAddress : Failure()
|
|
22
|
+
|
|
23
|
+
data object ServiceNotFound : Failure()
|
|
24
|
+
|
|
25
|
+
data object InternalState : Failure()
|
|
26
|
+
|
|
27
|
+
data object InitializingFailed : Failure()
|
|
28
|
+
|
|
29
|
+
data object PermissionNotGranted : Failure()
|
|
30
|
+
|
|
31
|
+
data object DeviceConnectivityBluetoothDisabled : Failure()
|
|
32
|
+
|
|
33
|
+
data object DeviceConnectivityLocationDisabled : Failure()
|
|
34
|
+
|
|
35
|
+
data object BleNotSupported : Failure()
|
|
36
|
+
|
|
37
|
+
data class Unknown(
|
|
38
|
+
val msg: String?,
|
|
39
|
+
) : Failure()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 kotlin.coroutines.CoroutineContext
|
|
9
|
+
import kotlinx.coroutines.CoroutineScope
|
|
10
|
+
import kotlinx.coroutines.cancelChildren
|
|
11
|
+
|
|
12
|
+
internal val sdkScope: SdkCloseableScope
|
|
13
|
+
get() = getSdkScope()
|
|
14
|
+
|
|
15
|
+
internal class SdkCloseableScope(
|
|
16
|
+
context: CoroutineContext,
|
|
17
|
+
) : CoroutineScope{
|
|
18
|
+
override val coroutineContext: CoroutineContext = context
|
|
19
|
+
|
|
20
|
+
override fun toString(): String = "CoroutineScope(context = $coroutineContext)"
|
|
21
|
+
|
|
22
|
+
fun close() {
|
|
23
|
+
coroutineContext.cancelChildren()
|
|
24
|
+
}
|
|
25
|
+
}
|