From 78ac3832a8cb630b1cf8661372c1ccdb09d4a1dd Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Feb 2020 16:05:34 +0300 Subject: [PATCH 01/11] Updated on 2026-08-14 --- .gitignore | 32 ++++++++++--------- .idea/codeStyles/codeStyleConfig.xml | 2 +- .../tangem/commands/ReadUserDataCommand.kt | 5 +++ .../tangem/commands/WriteUserDataCommand.kt | 5 +++ 4 files changed, 28 insertions(+), 16 deletions(-) create mode 100644 tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt create mode 100644 tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt diff --git a/.gitignore b/.gitignore index 16e0697372..ae9ed9c524 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,22 @@ *.iml -.gradle -/local.properties -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -.DS_Store + +# Built application files /build -/captures -.externalNativeBuild -.idea/vcs.xml -.idea/caches -.idea/dictionaries -.idea/runConfigurations.xml -.idea/encodings.xml -.idea/codeStyles/codeStyleConfig.xml -.idea/assetWizardSettings.xml + +# Local configuration file (sdk path, etc) +local.properties + +# Gradle generated files +.gradle + +# User-specific configurations +.idea/caches/ +.idea/libraries/ +.idea/*.xml + +# OS-specific files +.DS_Store +.DS_Store? # fastlane files **/fastlane/report.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml index a55e7a179b..eaa8acfd78 100644 --- a/.idea/codeStyles/codeStyleConfig.xml +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -1,5 +1,5 @@ - \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt new file mode 100644 index 0000000000..e09977bd46 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -0,0 +1,5 @@ +package com.tangem.commands + +/** +[REDACTED_AUTHOR] + */ \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt new file mode 100644 index 0000000000..e09977bd46 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -0,0 +1,5 @@ +package com.tangem.commands + +/** +[REDACTED_AUTHOR] + */ \ No newline at end of file From 5de9753f52a5ae5e416dbb7e6707e9414eefa4e3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sat, 22 Feb 2020 22:25:02 +0300 Subject: [PATCH 02/11] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 43 +++++++++- .../tangem/commands/ReadUserDataCommand.kt | 80 ++++++++++++++++++- .../tangem/commands/WriteUserDataCommand.kt | 61 +++++++++++++- .../com/tangem/common/apdu/Instruction.kt | 4 +- .../main/java/com/tangem/common/tlv/TlvTag.kt | 8 +- 5 files changed, 189 insertions(+), 7 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 8211251100..3e1d706b5c 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -177,6 +177,46 @@ class CardManager( runTask(task, cardId, callback) } + /** + * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * Writing of User_Counter and User_Data protected only by PIN1. + * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. + */ + fun writeUserData( + cardId: String, + userData: ByteArray, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + callback: (result: TaskEvent) -> Unit + ) { + val writeIssuerDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + val task = SingleCommandTask(writeIssuerDataCommand) + runTask(task, cardId, callback) + } + + /** + * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and + * User_Protected_Counter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + */ + fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { + val task = SingleCommandTask(ReadUserDataCommand()) + runTask(task, cardId, callback) + } + /** * This command will create a new wallet on the card having ‘Empty’ state. * A key pair WalletPublicKey / WalletPrivateKey is generated and securely stored in the card. @@ -211,8 +251,7 @@ class CardManager( /** */ - fun runTask(task: Task, cardId: String? = null, - callback: (result: TaskEvent) -> Unit) { + fun runTask(task: Task, cardId: String? = null, callback: (result: TaskEvent) -> Unit) { if (isBusy) { callback(TaskEvent.Completion(TaskError.Busy())) return diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index e09977bd46..d0d6e85f60 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -1,5 +1,83 @@ package com.tangem.commands +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + /** [REDACTED_AUTHOR] - */ \ No newline at end of file + */ +class ReadUserDataResponse( + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, + + /** + * Data defined by user's App. + */ + val userData: ByteArray, + + /** + * Data defined by user's App (confirmed by PIN2). + */ + val userProtectedData: ByteArray, + + /** + * Counter initialized by user's App and increased on every signing of new transaction + */ + val userCounter: Int, + + /** + * Counter initialized by user's App (confirmed by PIN2) and increased on every signing of new transaction + */ + val userProtectedCounter: Int + +): CommandResponse { + companion object { + internal fun fromMapper(mapper: TlvMapper): ReadUserDataResponse = ReadUserDataResponse( + cardId = mapper.map(TlvTag.CardId), + userData = mapper.map(TlvTag.IssuerData), + userProtectedData = mapper.map(TlvTag.IssuerDataSignature), + userCounter = mapper.map(TlvTag.IssuerDataCounter), + userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) + ) + } +} + +/** + * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and + * User_Protected_Counter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + */ +class ReadUserDataCommand: CommandSerializer() { + + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val serializedTlv = TlvBuilder().apply { + append(TlvTag.CardId, cardEnvironment.cardId) + append(TlvTag.Pin, cardEnvironment.pin1) + }.serialize() + + return CommandApdu(Instruction.ReadIssuerData, serializedTlv) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + ReadUserDataResponse.fromMapper(TlvMapper(tlvData)) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index e09977bd46..93ff44f311 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -1,5 +1,64 @@ package com.tangem.commands +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + /** [REDACTED_AUTHOR] - */ \ No newline at end of file + */ + +class WriteUserDataResponse( + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String +): CommandResponse + +/** + * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * Writing of User_Counter and User_Data protected only by PIN1. + * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. + */ +class WriteUserDataCommand( + private val userData: ByteArray? = null, + private val userProtectedData: ByteArray? = null, + private val userCounter: Int? = null, + private val userProtectedCounter: Int? = null +): CommandSerializer() { + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val serializedTlv = TlvBuilder().apply { + append(TlvTag.CardId, cardEnvironment.cardId) + append(TlvTag.Pin, cardEnvironment.pin1) + append(TlvTag.UserData, userData) + append(TlvTag.UserCounter, userCounter) + append(TlvTag.UserProtectedData, userProtectedData) + append(TlvTag.UserProtectedCounter, userProtectedCounter) + if (userProtectedCounter != null || userProtectedData != null) + append(TlvTag.Pin2, cardEnvironment.pin2) + }.serialize() + return CommandApdu(Instruction.ReadIssuerData, serializedTlv) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index f0743aa0dc..34e04959fc 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -18,7 +18,9 @@ enum class Instruction(var code: Int) { Sign(0xFB), PurgeWallet(0xFC), Activate(0xFE), - OpenSession(0xFF); + OpenSession(0xFF), + WriteUserData(0xE0), + ReadUserData(0xE1); companion object { diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index e5320b77b1..19d0713c7d 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -94,7 +94,6 @@ enum class TlvTag(val code: Int) { ProductMask(0x8A), PaymentFlowVersion(0x54), - UserCounter(0x2C), TokenSymbol(0xA0), @@ -107,7 +106,12 @@ enum class TlvTag(val code: Int) { TerminalIsLinked(0x58), TerminalPublicKey(0x5C), - TerminalTransactionSignature(0x57); + TerminalTransactionSignature(0x57), + + UserData(0x2A), + UserProtectedData(0x2B), + UserCounter(0x2C), + UserProtectedCounter(0x2D); /** * @return [TlvValueType] associated with a [TlvTag] From 250fea9baa4c7f1700061f13fffcd07ceeb4ed6b Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Feb 2020 00:02:15 +0300 Subject: [PATCH 03/11] Updated on 2026-08-14 --- .../src/main/java/com/tangem/commands/ReadUserDataCommand.kt | 2 +- .../src/main/java/com/tangem/commands/WriteUserDataCommand.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index d0d6e85f60..e1fbaa972a 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -68,7 +68,7 @@ class ReadUserDataCommand: CommandSerializer() { append(TlvTag.Pin, cardEnvironment.pin1) }.serialize() - return CommandApdu(Instruction.ReadIssuerData, serializedTlv) + return CommandApdu(Instruction.ReadUserData, serializedTlv) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 93ff44f311..64d5359e2d 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -49,7 +49,7 @@ class WriteUserDataCommand( if (userProtectedCounter != null || userProtectedData != null) append(TlvTag.Pin2, cardEnvironment.pin2) }.serialize() - return CommandApdu(Instruction.ReadIssuerData, serializedTlv) + return CommandApdu(Instruction.WriteUserData, serializedTlv) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { From 8f04c58c6908da8394161d2b5166b37d69c12fcd Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Feb 2020 08:52:58 +0300 Subject: [PATCH 04/11] Updated on 2026-08-14 --- .../src/main/java/com/tangem/common/apdu/Instruction.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt index 34e04959fc..a913813eb2 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/Instruction.kt @@ -19,8 +19,8 @@ enum class Instruction(var code: Int) { PurgeWallet(0xFC), Activate(0xFE), OpenSession(0xFF), - WriteUserData(0xE0), - ReadUserData(0xE1); + WriteUserData(0xE0), + ReadUserData(0xE1); companion object { From 1fa4b6061412674002275f2237021a7ae117cc0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 23 Feb 2020 08:57:45 +0300 Subject: [PATCH 05/11] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 76 ++++++++-------- .../tangem/commands/ReadUserDataCommand.kt | 88 +++++++++---------- .../tangem/commands/WriteUserDataCommand.kt | 59 +++++++------ .../main/java/com/tangem/common/tlv/TlvTag.kt | 8 +- 4 files changed, 116 insertions(+), 115 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 3e1d706b5c..b679c726ce 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -177,45 +177,45 @@ class CardManager( runTask(task, cardId, callback) } - /** - * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - * - * Writing of User_Counter and User_Data protected only by PIN1. - * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. - */ - fun writeUserData( - cardId: String, - userData: ByteArray, - userProtectedData: ByteArray? = null, - userCounter: Int? = null, - userProtectedCounter: Int? = null, - callback: (result: TaskEvent) -> Unit - ) { - val writeIssuerDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) - val task = SingleCommandTask(writeIssuerDataCommand) - runTask(task, cardId, callback) - } + /** + * This command write some of User_Data, User_ProtectedData, User_Counter and User_ProtectedCounter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + * + * Writing of User_Counter and User_Data protected only by PIN1. + * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. + */ + fun writeUserData( + cardId: String, + userData: ByteArray, + userProtectedData: ByteArray? = null, + userCounter: Int? = null, + userProtectedCounter: Int? = null, + callback: (result: TaskEvent) -> Unit + ) { + val writeIssuerDataCommand = WriteUserDataCommand(userData, userProtectedData, userCounter, userProtectedCounter) + val task = SingleCommandTask(writeIssuerDataCommand) + runTask(task, cardId, callback) + } - /** - * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and - * User_Protected_Counter fields. - * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. - * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information - * from blockchain to accelerate preparing new transaction. - * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing - * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. - * For example, this fields may contain blockchain nonce value. - */ - fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { - val task = SingleCommandTask(ReadUserDataCommand()) - runTask(task, cardId, callback) - } + /** + * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and + * User_Protected_Counter fields. + * User_Data and User_ProtectedData are never changed or parsed by the executable code the Tangem COS. + * The App defines purpose of use, format and it's payload. For example, this field may contain cashed information + * from blockchain to accelerate preparing new transaction. + * User_Counter and User_ProtectedCounter are counters, that initial values can be set by App and increased on every signing + * of new transaction (on SIGN command that calculate new signatures). The App defines purpose of use. + * For example, this fields may contain blockchain nonce value. + */ + fun readUserData(cardId: String, callback: (result: TaskEvent) -> Unit) { + val task = SingleCommandTask(ReadUserDataCommand()) + runTask(task, cardId, callback) + } /** * This command will create a new wallet on the card having ‘Empty’ state. diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index e1fbaa972a..a321876aee 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -13,41 +13,41 @@ import com.tangem.tasks.TaskError [REDACTED_AUTHOR] */ class ReadUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String, + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String, - /** - * Data defined by user's App. - */ - val userData: ByteArray, + /** + * Data defined by user's App. + */ + val userData: ByteArray, - /** - * Data defined by user's App (confirmed by PIN2). - */ - val userProtectedData: ByteArray, + /** + * Data defined by user's App (confirmed by PIN2). + */ + val userProtectedData: ByteArray, - /** - * Counter initialized by user's App and increased on every signing of new transaction - */ - val userCounter: Int, + /** + * Counter initialized by user's App and increased on every signing of new transaction + */ + val userCounter: Int, - /** - * Counter initialized by user's App (confirmed by PIN2) and increased on every signing of new transaction - */ - val userProtectedCounter: Int + /** + * Counter initialized by user's App (confirmed by PIN2) and increased on every signing of new transaction + */ + val userProtectedCounter: Int ): CommandResponse { - companion object { - internal fun fromMapper(mapper: TlvMapper): ReadUserDataResponse = ReadUserDataResponse( - cardId = mapper.map(TlvTag.CardId), - userData = mapper.map(TlvTag.IssuerData), - userProtectedData = mapper.map(TlvTag.IssuerDataSignature), - userCounter = mapper.map(TlvTag.IssuerDataCounter), - userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) - ) - } + companion object { + internal fun fromMapper(mapper: TlvMapper): ReadUserDataResponse = ReadUserDataResponse( + cardId = mapper.map(TlvTag.CardId), + userData = mapper.map(TlvTag.IssuerData), + userProtectedData = mapper.map(TlvTag.IssuerDataSignature), + userCounter = mapper.map(TlvTag.IssuerDataCounter), + userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) + ) + } } /** @@ -62,22 +62,22 @@ class ReadUserDataResponse( */ class ReadUserDataCommand: CommandSerializer() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val serializedTlv = TlvBuilder().apply { - append(TlvTag.CardId, cardEnvironment.cardId) - append(TlvTag.Pin, cardEnvironment.pin1) - }.serialize() + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val serializedTlv = TlvBuilder().apply { + append(TlvTag.CardId, cardEnvironment.cardId) + append(TlvTag.Pin, cardEnvironment.pin1) + }.serialize() - return CommandApdu(Instruction.ReadUserData, serializedTlv) - } + return CommandApdu(Instruction.ReadUserData, serializedTlv) + } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null - return try { - ReadUserDataResponse.fromMapper(TlvMapper(tlvData)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } - } + return try { + ReadUserDataResponse.fromMapper(TlvMapper(tlvData)) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 64d5359e2d..689e274dea 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -14,10 +14,10 @@ import com.tangem.tasks.TaskError */ class WriteUserDataResponse( - /** - * CID, Unique Tangem card ID number. - */ - val cardId: String + /** + * CID, Unique Tangem card ID number. + */ + val cardId: String ): CommandResponse /** @@ -33,32 +33,33 @@ class WriteUserDataResponse( * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ class WriteUserDataCommand( - private val userData: ByteArray? = null, - private val userProtectedData: ByteArray? = null, - private val userCounter: Int? = null, - private val userProtectedCounter: Int? = null + private val userData: ByteArray? = null, + private val userProtectedData: ByteArray? = null, + private val userCounter: Int? = null, + private val userProtectedCounter: Int? = null ): CommandSerializer() { - override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val serializedTlv = TlvBuilder().apply { - append(TlvTag.CardId, cardEnvironment.cardId) - append(TlvTag.Pin, cardEnvironment.pin1) - append(TlvTag.UserData, userData) - append(TlvTag.UserCounter, userCounter) - append(TlvTag.UserProtectedData, userProtectedData) - append(TlvTag.UserProtectedCounter, userProtectedCounter) - if (userProtectedCounter != null || userProtectedData != null) - append(TlvTag.Pin2, cardEnvironment.pin2) - }.serialize() - return CommandApdu(Instruction.WriteUserData, serializedTlv) - } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val serializedTlv = TlvBuilder().apply { + append(TlvTag.CardId, cardEnvironment.cardId) + append(TlvTag.Pin, cardEnvironment.pin1) + append(TlvTag.UserData, userData) + append(TlvTag.UserCounter, userCounter) + append(TlvTag.UserProtectedData, userProtectedData) + append(TlvTag.UserProtectedCounter, userProtectedCounter) + if (userProtectedCounter != null || userProtectedData != null) + append(TlvTag.Pin2, cardEnvironment.pin2) + }.serialize() + return CommandApdu(Instruction.WriteUserData, serializedTlv) + } - return try { - WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) - } catch (exception: Exception) { - throw TaskError.SerializeCommandError() - } - } + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index 19d0713c7d..43732307e6 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -108,10 +108,10 @@ enum class TlvTag(val code: Int) { TerminalPublicKey(0x5C), TerminalTransactionSignature(0x57), - UserData(0x2A), - UserProtectedData(0x2B), - UserCounter(0x2C), - UserProtectedCounter(0x2D); + UserData(0x2A), + UserProtectedData(0x2B), + UserCounter(0x2C), + UserProtectedCounter(0x2D); /** * @return [TlvValueType] associated with a [TlvTag] From f51329384e77e70be7c3318db9a0b3a4f237247f Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Feb 2020 12:11:44 +0300 Subject: [PATCH 06/11] Updated on 2026-08-14 --- .../tangem/commands/ReadUserDataCommand.kt | 9 +++--- .../tangem/commands/WriteUserDataCommand.kt | 29 +++++++++---------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index a321876aee..1bdd871b8f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -63,12 +63,11 @@ class ReadUserDataResponse( class ReadUserDataCommand: CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val serializedTlv = TlvBuilder().apply { - append(TlvTag.CardId, cardEnvironment.cardId) - append(TlvTag.Pin, cardEnvironment.pin1) - }.serialize() + val builder = TlvBuilder() + builder.append(TlvTag.CardId, cardEnvironment.cardId) + builder.append(TlvTag.Pin, cardEnvironment.pin1) - return CommandApdu(Instruction.ReadUserData, serializedTlv) + return CommandApdu(Instruction.ReadUserData, builder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index 689e274dea..d512c4ded7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -32,25 +32,22 @@ class WriteUserDataResponse( * Writing of User_Counter and User_Data protected only by PIN1. * User_ProtectedCounter and User_ProtectedData additionaly need PIN2 to confirmation. */ -class WriteUserDataCommand( - private val userData: ByteArray? = null, - private val userProtectedData: ByteArray? = null, +class WriteUserDataCommand(private val userData: ByteArray? = null, private val userProtectedData: ByteArray? = null, private val userCounter: Int? = null, - private val userProtectedCounter: Int? = null -): CommandSerializer() { + private val userProtectedCounter: Int? = null): CommandSerializer() { override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - val serializedTlv = TlvBuilder().apply { - append(TlvTag.CardId, cardEnvironment.cardId) - append(TlvTag.Pin, cardEnvironment.pin1) - append(TlvTag.UserData, userData) - append(TlvTag.UserCounter, userCounter) - append(TlvTag.UserProtectedData, userProtectedData) - append(TlvTag.UserProtectedCounter, userProtectedCounter) - if (userProtectedCounter != null || userProtectedData != null) - append(TlvTag.Pin2, cardEnvironment.pin2) - }.serialize() - return CommandApdu(Instruction.WriteUserData, serializedTlv) + val builder = TlvBuilder() + builder.append(TlvTag.CardId, cardEnvironment.cardId) + builder.append(TlvTag.Pin, cardEnvironment.pin1) + builder.append(TlvTag.UserData, userData) + builder.append(TlvTag.UserCounter, userCounter) + builder.append(TlvTag.UserProtectedData, userProtectedData) + builder.append(TlvTag.UserProtectedCounter, userProtectedCounter) + if (userProtectedCounter != null || userProtectedData != null) + builder.append(TlvTag.Pin2, cardEnvironment.pin2) + + return CommandApdu(Instruction.WriteUserData, builder.serialize()) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { From d2e1acdee8c38697fea32fb0d0ebd5231ac432a0 Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Feb 2020 12:43:06 +0300 Subject: [PATCH 07/11] Updated on 2026-08-14 --- .../tangem/commands/ReadUserDataCommand.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 1bdd871b8f..ae301c2230 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -38,17 +38,7 @@ class ReadUserDataResponse( */ val userProtectedCounter: Int -): CommandResponse { - companion object { - internal fun fromMapper(mapper: TlvMapper): ReadUserDataResponse = ReadUserDataResponse( - cardId = mapper.map(TlvTag.CardId), - userData = mapper.map(TlvTag.IssuerData), - userProtectedData = mapper.map(TlvTag.IssuerDataSignature), - userCounter = mapper.map(TlvTag.IssuerDataCounter), - userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) - ) - } -} +): CommandResponse /** * This command returns two up to 512-byte User_Data, User_Protected_Data and two counters User_Counter and @@ -74,7 +64,14 @@ class ReadUserDataCommand: CommandSerializer() { val tlvData = responseApdu.getTlvData() ?: return null return try { - ReadUserDataResponse.fromMapper(TlvMapper(tlvData)) + val mapper = TlvMapper(tlvData) + ReadUserDataResponse( + cardId = mapper.map(TlvTag.CardId), + userData = mapper.map(TlvTag.IssuerData), + userProtectedData = mapper.map(TlvTag.IssuerDataSignature), + userCounter = mapper.map(TlvTag.IssuerDataCounter), + userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) + ) } catch (exception: Exception) { throw TaskError.SerializeCommandError() } From 8ee3bf8aacd5b3f86c35f5191786882a659c900b Mon Sep 17 00:00:00 2001 From: Tangem Date: Tue, 25 Feb 2020 17:00:28 +0300 Subject: [PATCH 08/11] Updated on 2026-08-14 --- .../src/main/java/com/tangem/CardManager.kt | 2 +- .../tangem/commands/ReadUserDataCommand.kt | 8 +- .../main/java/com/tangem/common/tlv/TlvTag.kt | 2 +- tangem-demo/src/main/AndroidManifest.xml | 27 ++ .../com/tangem/tangemtest/MainActivity.kt | 7 +- .../tangem/tangemtest/TestUserDataActivity.kt | 180 +++++++++++++ .../src/main/res/layout/a_test_user_data.xml | 238 ++++++++++++++++++ .../src/main/res/layout/activity_main.xml | 171 +++++++------ .../src/main/res/layout/m_delimiter_h.xml | 7 + tangem-demo/src/main/res/values/colors.xml | 2 + tangem-demo/src/main/res/values/dimens.xml | 6 + 11 files changed, 561 insertions(+), 89 deletions(-) create mode 100644 tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt create mode 100644 tangem-demo/src/main/res/layout/a_test_user_data.xml create mode 100644 tangem-demo/src/main/res/layout/m_delimiter_h.xml create mode 100644 tangem-demo/src/main/res/values/dimens.xml diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index b679c726ce..99ddc6ce35 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -191,7 +191,7 @@ class CardManager( */ fun writeUserData( cardId: String, - userData: ByteArray, + userData: ByteArray? = null, userProtectedData: ByteArray? = null, userCounter: Int? = null, userProtectedCounter: Int? = null, diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index ae301c2230..3628d9cad7 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -67,10 +67,10 @@ class ReadUserDataCommand: CommandSerializer() { val mapper = TlvMapper(tlvData) ReadUserDataResponse( cardId = mapper.map(TlvTag.CardId), - userData = mapper.map(TlvTag.IssuerData), - userProtectedData = mapper.map(TlvTag.IssuerDataSignature), - userCounter = mapper.map(TlvTag.IssuerDataCounter), - userProtectedCounter = mapper.map(TlvTag.IssuerDataCounter) + userData = mapper.map(TlvTag.UserData), + userProtectedData = mapper.map(TlvTag.UserProtectedData), + userCounter = mapper.map(TlvTag.UserCounter), + userProtectedCounter = mapper.map(TlvTag.UserProtectedCounter) ) } catch (exception: Exception) { throw TaskError.SerializeCommandError() diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index 43732307e6..d37500e221 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -125,7 +125,7 @@ enum class TlvTag(val code: Int) { MaxSignatures, PauseBeforePin2, RemainingSignatures, SignedHashes, Health, TokenDecimal, Offset, Size -> TlvValueType.Uint16 - UserCounter, IssuerDataCounter -> TlvValueType.Uint32 + UserCounter, UserProtectedCounter, IssuerDataCounter -> TlvValueType.Uint32 IsActivated, TerminalIsLinked -> TlvValueType.BoolValue ManufactureDateTime -> TlvValueType.DateTime ProductMask -> TlvValueType.ProductMask diff --git a/tangem-demo/src/main/AndroidManifest.xml b/tangem-demo/src/main/AndroidManifest.xml index 2235a4e852..f90fa6c597 100644 --- a/tangem-demo/src/main/AndroidManifest.xml +++ b/tangem-demo/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> + @@ -46,6 +47,32 @@ android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" /> + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt index 967c62c688..01207d703d 100644 --- a/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/MainActivity.kt @@ -1,13 +1,9 @@ package com.tangem.tangemtest +import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import com.tangem.CardManager -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.extensions.toByteArray -import com.tangem.common.extensions.toHexString -import com.tangem.crypto.CryptoUtils -import com.tangem.crypto.sign import com.tangem.tangem_sdk_new.extensions.init import com.tangem.tasks.ScanEvent import com.tangem.tasks.TaskError @@ -154,6 +150,7 @@ class MainActivity : AppCompatActivity() { } } } + btn_read_write_user_data?.setOnClickListener { startActivity(Intent(this, TestUserDataActivity::class.java)) } } private fun createSampleHashes(): Array { diff --git a/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt new file mode 100644 index 0000000000..2c31306bba --- /dev/null +++ b/tangem-demo/src/main/java/com/tangem/tangemtest/TestUserDataActivity.kt @@ -0,0 +1,180 @@ +package com.tangem.tangemtest + +import android.os.Bundle +import android.view.View +import android.widget.CompoundButton +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.tangem.CardManager +import com.tangem.commands.ReadUserDataResponse +import com.tangem.commands.WriteUserDataResponse +import com.tangem.common.CardEnvironment +import com.tangem.tangem_sdk_new.extensions.init +import com.tangem.tasks.ScanEvent +import com.tangem.tasks.TaskError +import com.tangem.tasks.TaskEvent +import kotlinx.android.synthetic.main.a_test_user_data.* +import java.nio.charset.StandardCharsets + +/** +[REDACTED_AUTHOR] + */ +class TestUserDataActivity: AppCompatActivity() { + + private lateinit var cardManager: CardManager + private lateinit var writeOptions: WriteOptions + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.a_test_user_data) + + init() + initWriteOptions() + } + + private fun init() { + cardManager = CardManager.init(this) + + btn_scan?.setOnClickListener { _ -> + cardManager.scanCard { taskEvent -> + when (taskEvent) { + is TaskEvent.Event -> { + when (taskEvent.data) { + is ScanEvent.OnReadEvent -> { + // Handle returned card data + writeOptions.cardId = (taskEvent.data as ScanEvent.OnReadEvent).card.cardId + runOnUiThread { showReadWriteSection(true) } + } + is ScanEvent.OnVerifyEvent -> { + //Handle card verification + } + } + } + is TaskEvent.Completion -> { + if (taskEvent.error != null) { + if (taskEvent.error is TaskError.UserCancelled) { + // Handle case when user cancelled manually + } + // Handle other errors + } + // Handle completion + } + } + } + } + + btn_write.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.writeUserData( + writeOptions.cardId !!, + writeOptions.userData, + writeOptions.userProtectedData, + writeOptions.userCounter, + writeOptions.userProtectedCounter + ) { + when (it) { + is TaskEvent.Completion -> handleError(tv_write_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? WriteUserDataResponse + if (data == null) { + tv_write_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_write_result?.text = "Success" + } + } + } + } + } + + btn_read.setOnClickListener { + if (writeOptions.cardId == null) return@setOnClickListener + + cardManager.readUserData(writeOptions.cardId !!) { + when (it) { + is TaskEvent.Completion -> handleError(tv_read_result, it.error) + is TaskEvent.Event -> { + runOnUiThread { + val data = it.data as? ReadUserDataResponse + if (data == null) { + tv_read_result.text = "Response doesn't match" + return@runOnUiThread + } + tv_read_result?.text = "Success" + + writeOptions.userData = data.userData + writeOptions.userProtectedData = data.userProtectedData + writeOptions.userCounter = data.userCounter + writeOptions.userProtectedCounter = data.userProtectedCounter + + tv_card_cid.text = data.cardId + tv_data.text = String(data.userData, StandardCharsets.US_ASCII) + tv_protected_data.text = String(data.userProtectedData, StandardCharsets.US_ASCII) + tv_counter.text = data.userCounter.toString() + tv_protected_counter.text = data.userProtectedCounter.toString() + } + + } + } + } + } + } + + private fun handleError(tv: TextView, error: TaskError?) { + val er = error ?: return + if (er is TaskError.UserCancelled) return + + runOnUiThread { tv.text = er::class.simpleName } + } + + private fun initWriteOptions() { + writeOptions = WriteOptions() + + chb_with_ud.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateData(buttonView) } + chb_with_ud_protected.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedData(buttonView) } + chb_with_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateCounter(buttonView) } + chb_with_protected_counter.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updateProtectedCounter(buttonView) } + chb_with_pin2.setOnCheckedChangeListener { buttonView, isChecked -> writeOptions.updatePin2(buttonView) } + } + + private fun showReadWriteSection(show: Boolean) { + val state = if (show) View.VISIBLE else View.GONE + cl_read_write.visibility = state + } +} + +class WriteOptions { + var cardId: String? = null + var userData: ByteArray? = null + var userProtectedData: ByteArray? = null + var userCounter: Int? = null + var userProtectedCounter: Int? = null + var pin2: String? = null + + fun updateData(chbx: CompoundButton) { + val value = "simple user data".toByteArray() + userData = if (chbx.isChecked) value else null + } + + fun updateProtectedData(chbx: CompoundButton) { + val value = "protected user data".toByteArray() + userProtectedData = if (chbx.isChecked) value else null + } + + fun updateCounter(chbx: CompoundButton) { + val value = if (userCounter == null) 0 else userCounter !! + 1 + userCounter = if (chbx.isChecked) value else null + } + + fun updateProtectedCounter(chbx: CompoundButton) { + val value = if (userProtectedCounter == null) 0 else userProtectedCounter !! + 1 + userProtectedCounter = if (chbx.isChecked) value else null + } + + fun updatePin2(chbx: CompoundButton) { + val value = CardEnvironment.DEFAULT_PIN2 + pin2 = if (chbx.isChecked) value else null + } +} \ No newline at end of file diff --git a/tangem-demo/src/main/res/layout/a_test_user_data.xml b/tangem-demo/src/main/res/layout/a_test_user_data.xml new file mode 100644 index 0000000000..261a6efd64 --- /dev/null +++ b/tangem-demo/src/main/res/layout/a_test_user_data.xml @@ -0,0 +1,238 @@ + + + +