Updated on 2026-08-14
This commit is contained in:
commit
45b640dcec
7 changed files with 323 additions and 0 deletions
75
tangem-card-new/src/main/java/com/tangem/data/CommandApdu.kt
Normal file
75
tangem-card-new/src/main/java/com/tangem/data/CommandApdu.kt
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package com.tangem.data
|
||||
|
||||
import com.tangem.enums.Instruction
|
||||
|
||||
class CommandApdu(
|
||||
val tlvList: List<Tlv>,
|
||||
val instruction: Instruction,
|
||||
val p1: Int = 0x00,
|
||||
val p2: Int = 0x00,
|
||||
val encryptionKey: ByteArray? = null,
|
||||
val rawInstruction: ByteArray = byteArrayOf()) {
|
||||
|
||||
val cla = ISO_CLA
|
||||
|
||||
|
||||
fun serialize(): ByteArray {
|
||||
|
||||
var length = 4 // CLA, INS, P1, P2
|
||||
|
||||
val data = if (tlvList.isNotEmpty()) {
|
||||
tlvList.toBytes()
|
||||
} else {
|
||||
byteArrayOf()
|
||||
}
|
||||
|
||||
val lc = data.size
|
||||
|
||||
if (data.isNotEmpty()) {
|
||||
length += 1 // LC
|
||||
if (lc >= 256)
|
||||
length += 2
|
||||
length += data.size // DATA
|
||||
}
|
||||
|
||||
val apdu = ByteArray(length)
|
||||
|
||||
var index = 0
|
||||
apdu[index] = cla
|
||||
index++
|
||||
apdu[index] = instruction.code.toByte()
|
||||
index++
|
||||
apdu[index] = p1.toByte()
|
||||
index++
|
||||
apdu[index] = p2.toByte()
|
||||
index++
|
||||
if (lc != 0) {
|
||||
if (lc < 256) {
|
||||
apdu[index] = lc.toByte()
|
||||
index++
|
||||
} else {
|
||||
apdu[index] = 0
|
||||
index++
|
||||
apdu[index] = (lc shr 8).toByte()
|
||||
index++
|
||||
apdu[index] = (lc and 0xFF).toByte()
|
||||
index++
|
||||
}
|
||||
|
||||
System.arraycopy(data, 0, apdu, index, data.size)
|
||||
index += data.size
|
||||
}
|
||||
return apdu
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private fun encrypt() {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ISO_CLA = 0x00.toByte()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.data
|
||||
|
||||
import com.tangem.enums.Status
|
||||
|
||||
class ResponseApdu(val data: ByteArray) {
|
||||
|
||||
private val tlvParser = TlvParser()
|
||||
|
||||
fun deserialize(encryptionKey: ByteArray? = null): ResponseApduParsed? {
|
||||
|
||||
if (data.size < 2) return null
|
||||
|
||||
if (data.size == 2) return ResponseApduParsed(parseStatus(data[0], data[1]), emptyList())
|
||||
|
||||
val tlvList = tlvParser.fromBytes(data.copyOf(data.size - 2))
|
||||
val sw1 = data[data.size - 2]
|
||||
val sw2 = data[data.size -1]
|
||||
return ResponseApduParsed(parseStatus(sw1, sw2), tlvList)
|
||||
}
|
||||
|
||||
private fun parseStatus(sw1: Byte, sw2: Byte): Status {
|
||||
val code = (0x00FF and sw1.toInt()) shl 8 or (0x00FF and sw2.toInt())
|
||||
return Status.byCode(code)
|
||||
}
|
||||
|
||||
private fun decrypt(encryptionKey: ByteArray) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class ResponseApduParsed(val status: Status, val tlvList: List<Tlv>? = null, val parsingError: String? = null) {
|
||||
fun statusCompleted(): Boolean = status == Status.ProcessCompleted
|
||||
}
|
||||
31
tangem-card-new/src/main/java/com/tangem/data/Tlv.kt
Normal file
31
tangem-card-new/src/main/java/com/tangem/data/Tlv.kt
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.data
|
||||
|
||||
import com.tangem.enums.TlvTag
|
||||
|
||||
class Tlv(val tag: TlvTag, val tagCode: Int, val value: ByteArray = byteArrayOf())
|
||||
|
||||
fun List<Tlv>.toBytes(): ByteArray =
|
||||
this.map { it.toBytes() }.reduce { arr1, arr2 -> arr1 + arr2 }
|
||||
|
||||
fun Tlv.toBytes(): ByteArray {
|
||||
val tag = byteArrayOf(this.tag.code.toByte())
|
||||
val length = getLengthInBytes(this.value.size)
|
||||
val value = if (this.value.isNotEmpty()) this.value else byteArrayOf(0x00)
|
||||
return tag + length + value
|
||||
}
|
||||
|
||||
private fun getLengthInBytes(tlvLength: Int): ByteArray {
|
||||
return if (tlvLength < 0) {
|
||||
if (tlvLength > 0xFE) {
|
||||
byteArrayOf(
|
||||
0xFF.toByte(),
|
||||
(tlvLength shr 8 and 0xFF).toByte(),
|
||||
(tlvLength and 0xFF).toByte()
|
||||
)
|
||||
} else {
|
||||
byteArrayOf((tlvLength and 0xFF).toByte())
|
||||
}
|
||||
} else {
|
||||
byteArrayOf()
|
||||
}
|
||||
}
|
||||
55
tangem-card-new/src/main/java/com/tangem/data/TlvParser.kt
Normal file
55
tangem-card-new/src/main/java/com/tangem/data/TlvParser.kt
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package com.tangem.data
|
||||
|
||||
import com.tangem.enums.TlvTag
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
||||
class TlvParser {
|
||||
|
||||
fun readFromStream(stream: ByteArrayInputStream): Tlv? {
|
||||
val code = stream.read()
|
||||
if (code == -1) return null
|
||||
var len = stream.read()
|
||||
if (len == -1)
|
||||
throw IOException("Can't read TLV")
|
||||
if (len == 0xFF) {
|
||||
val lenH = stream.read()
|
||||
if (lenH == -1)
|
||||
throw IOException("Can't read TLV")
|
||||
len = stream.read()
|
||||
if (len == -1)
|
||||
throw IOException("Can't read TLV")
|
||||
len = len or (lenH shl 8)
|
||||
}
|
||||
val value = ByteArray(len)
|
||||
if (len > 0) {
|
||||
if (len != stream.read(value)) {
|
||||
throw IOException("Can't read TLV")
|
||||
}
|
||||
}
|
||||
val tag = TlvTag.byCode(code)
|
||||
return Tlv(tag, code, value)
|
||||
}
|
||||
|
||||
fun fromBytes(data: ByteArray): List<Tlv> {
|
||||
val tlvList = mutableListOf<Tlv>()
|
||||
val stream = ByteArrayInputStream(data)
|
||||
var tlv: Tlv? = null
|
||||
do {
|
||||
try {
|
||||
tlv = readFromStream(stream)
|
||||
if (tlv != null) tlvList.add(tlv)
|
||||
} catch (e: IOException) {
|
||||
throw IOException("TLVError: " + e.message)
|
||||
}
|
||||
|
||||
} while (tlv != null)
|
||||
return tlvList
|
||||
}
|
||||
|
||||
|
||||
fun parseTlv(data: ByteArray) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.tangem.enums
|
||||
|
||||
enum class Instruction(var code: Int) {
|
||||
Unknown(0x00),
|
||||
Read(0xF2),
|
||||
VerifyCard(0xF3),
|
||||
ValidateCard(0xF4),
|
||||
VerifyCode(0xF5),
|
||||
WriteIssuerData(0xF6),
|
||||
GetIssuerData(0xF7),
|
||||
CreateWallet(0xF8),
|
||||
CheckWallet(0xF9),
|
||||
SwapPIN(0xFA),
|
||||
Sign(0xFB),
|
||||
PurgeWallet(0xFC),
|
||||
Activate(0xFE),
|
||||
OpenSession(0xFF);
|
||||
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): Instruction = values().find { it.code == code } ?: Unknown
|
||||
}
|
||||
}
|
||||
21
tangem-card-new/src/main/java/com/tangem/enums/Status.kt
Normal file
21
tangem-card-new/src/main/java/com/tangem/enums/Status.kt
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.enums
|
||||
|
||||
enum class Status (val code: Int, val description: String){
|
||||
|
||||
ProcessCompleted(0x9000, "SW_PROCESS_COMPLETED"),
|
||||
InvalidParams(0x6A86, "SW_INVALID_PARAMS"),
|
||||
ErrorProcessingCommand(0x6286, "SW_ERROR_PROCESSING_COMMAND"),
|
||||
InvalidState(0x6985, "SW_INVALID_STATE"),
|
||||
// PinsNotChanged(ProcessCompleted.code, ProcessCompleted.description),
|
||||
Pin1Changed(ProcessCompleted.code + 0x0001, "SW_PIN1_CHANGED"),
|
||||
Pin2Changed(ProcessCompleted.code + 0x0002, "SW_PIN2_CHANGED"),
|
||||
PinsChanged(ProcessCompleted.code + 0x0003, "SW_PINS_CHANGED"),
|
||||
InsNotSupported(0x6D00, "SW_INS_NOT_SUPPORTED"),
|
||||
NeedEnctryption(0x6982, "SW_NEED_ENCRYPTION"),
|
||||
NeedPause(0x9789, "SW_NEED_PAUSE");
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): Status = values().find { it.code == code } ?: InvalidParams
|
||||
}
|
||||
|
||||
}
|
||||
84
tangem-card-new/src/main/java/com/tangem/enums/TlvTag.kt
Normal file
84
tangem-card-new/src/main/java/com/tangem/enums/TlvTag.kt
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
package com.tangem.enums
|
||||
|
||||
enum class TlvTag(val code: Int) {
|
||||
Unknown(0x00),
|
||||
CardID(0x01),
|
||||
Status(0x02),
|
||||
CardPublicKey(0x03),
|
||||
CardSignature(0x04),
|
||||
CurveID(0x05),
|
||||
HashAlgID(0x06),
|
||||
SigningMethod(0x07),
|
||||
MaxSignatures(0x08),
|
||||
PauseBeforePin2(0x09),
|
||||
SettingsMask(0x0A),
|
||||
CardData(0x0C),
|
||||
NdefData(0x0D),
|
||||
Health(0x0F),
|
||||
|
||||
Pin(0x10),
|
||||
Pin2(0x11),
|
||||
NewPin(0x12),
|
||||
NewPin2(0x13),
|
||||
NewPinHash(0x14),
|
||||
NewPin2Hash(0x15),
|
||||
Challenge(0x16),
|
||||
Salt(0x17),
|
||||
ValidationCounter(0x18),
|
||||
Cvc(0x19),
|
||||
|
||||
SessionKeyA(0x1A),
|
||||
SessionKeyB(0x1B),
|
||||
Pause(0x1C),
|
||||
|
||||
ManufactureId(0x20),
|
||||
ManufacturerSignature(0x21),
|
||||
|
||||
IssuerDataPublicKey(0x30),
|
||||
IssuerTransactionPublicKey(0x31),
|
||||
IssuerData(0x32),
|
||||
IssuerDataSignature(0x33),
|
||||
IssuerTransactionSignature(0x34),
|
||||
IssuerDataCounter(0x35),
|
||||
|
||||
IsActivated(0x3A),
|
||||
ActivationSeed(0x3B),
|
||||
ResetPin(0x36),
|
||||
|
||||
CodePageAddress(0x40),
|
||||
CodePageCount(0x41),
|
||||
CodeHash(0x42),
|
||||
|
||||
TrOutHash(0x50),
|
||||
TrOutHashSize(0x51),
|
||||
TrOutRaw(0x52),
|
||||
|
||||
WalletPublicKey(0x60),
|
||||
Signature(0x61),
|
||||
RemainingSignatures(0x62),
|
||||
SignedHashes(0x63),
|
||||
|
||||
Firmware(0x80),
|
||||
Batch(0x81),
|
||||
ManufactureDateTime(0x82),
|
||||
IssuerId(0x83),
|
||||
BlockchainId(0x84),
|
||||
ManufacturerPublicKey(0x85),
|
||||
CardIdManufacturerSignature(0x86),
|
||||
|
||||
TokenSymbol(0xA0),
|
||||
TokenContractAddress(0xA1),
|
||||
TokenDecimal(0xA2),
|
||||
Denomination(0xC0),
|
||||
ValidatedBalance(0xC1),
|
||||
LastSignDate(0xC2),
|
||||
DenominationText(0xC3),
|
||||
|
||||
TerminalIsLinked(0x58),
|
||||
TerminalPublicKey(0x5C),
|
||||
TerminalTransactionSignature(0x57);
|
||||
|
||||
companion object {
|
||||
fun byCode(code: Int): TlvTag = values().find { it.code == code } ?: Unknown
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue