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]