Improvements on reading - optional on last movements
This commit is contained in:
@@ -209,6 +209,17 @@ fun CardReaderScreen(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
|
||||
// Mostrar información adicional si está disponible
|
||||
if (cardData.lastOperation != null) {
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
Text(
|
||||
text = cardData.lastOperation,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.nfc.tech.IsoDep
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
data class CardData(
|
||||
val id: String,
|
||||
@@ -36,9 +38,20 @@ class CardRepositoryImpl(
|
||||
// Obtener saldo (bytes 29-31, 3 bytes, big endian, en céntimos)
|
||||
val balance = toIntBigEndian(contract, 29, 3) / 100.0
|
||||
|
||||
// Intentar extraer información de última validación del contrato
|
||||
val lastValidation = try {
|
||||
extractLastValidation(contract)
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
isoDep.close()
|
||||
|
||||
Result.success(CardData(cardId, balance))
|
||||
Result.success(CardData(
|
||||
id = cardId,
|
||||
balance = balance,
|
||||
lastOperation = lastValidation
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
@@ -78,10 +91,50 @@ class CardRepositoryImpl(
|
||||
return sendApdu(isoDep, byteArrayOf(0x80.toByte(), 0x2E, 0x01, 0x00, 0x20), 34)
|
||||
}
|
||||
|
||||
private fun extractLastValidation(contract: ByteArray): String? {
|
||||
return try {
|
||||
// El contrato tiene 32 bytes de datos útiles (34 total con SW)
|
||||
// Intentar extraer fecha de última validación si está disponible
|
||||
|
||||
// Bytes 8-9: posible fecha de última validación (días desde epoch Calypso)
|
||||
if (contract.size >= 11) {
|
||||
val days = toIntBigEndian(contract, 8, 2)
|
||||
if (days > 0 && days < 20000) { // Validación: entre 1997 y ~2050
|
||||
val epochDate = Calendar.getInstance().apply {
|
||||
set(1997, Calendar.JANUARY, 1, 0, 0, 0)
|
||||
set(Calendar.MILLISECOND, 0)
|
||||
add(Calendar.DAY_OF_YEAR, days)
|
||||
}
|
||||
val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault())
|
||||
return "Última validación: ${dateFormat.format(epochDate.time)}"
|
||||
}
|
||||
}
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendApdu(isoDep: IsoDep, command: ByteArray, responseSize: Int, statusWord: String = "9000"): ByteArray {
|
||||
val response = isoDep.transceive(command)
|
||||
requireSize(response, responseSize)
|
||||
requireStatusWord(response, statusWord)
|
||||
|
||||
// Verificar status word primero
|
||||
if (response.size >= 2) {
|
||||
val sw = String.format("%02X%02X", response[response.size - 2], response[response.size - 1])
|
||||
// 6A83 = Record not found (es normal para registros vacíos)
|
||||
if (sw == "6A83" || sw == "6A82") {
|
||||
throw IllegalStateException("Registro no encontrado")
|
||||
}
|
||||
if (sw != statusWord) {
|
||||
throw IllegalArgumentException("APDU error: expected status word $statusWord but was $sw")
|
||||
}
|
||||
}
|
||||
|
||||
// El tamaño puede variar ligeramente, ser flexible
|
||||
if (response.size < responseSize - 2 || response.size > responseSize + 2) {
|
||||
throw IllegalArgumentException("APDU response size error: expected ~$responseSize bytes but was ${response.size}")
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user