Updated on 2026-08-14

This commit is contained in:
Tangem 2020-02-22 22:25:02 +03:00
parent 78ac3832a8
commit 5de9753f52
5 changed files with 189 additions and 7 deletions

View file

@ -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<WriteUserDataResponse>) -> 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<ReadUserDataResponse>) -> 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 <T> runTask(task: Task<T>, cardId: String? = null,
callback: (result: TaskEvent<T>) -> Unit) {
fun <T> runTask(task: Task<T>, cardId: String? = null, callback: (result: TaskEvent<T>) -> Unit) {
if (isBusy) {
callback(TaskEvent.Completion(TaskError.Busy()))
return

View file

@ -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]
*/
*/
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<ReadUserDataResponse>() {
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()
}
}
}

View file

@ -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]
*/
*/
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<WriteUserDataResponse>() {
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()
}
}
}

View file

@ -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 {

View file

@ -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]