Refactor response decoder

Read transactions history
This commit is contained in:
Nikita Vostokov 2024-01-28 03:57:12 +00:00
parent 39055ff701
commit 4b786fb77e
8 changed files with 451 additions and 180 deletions

View File

@ -60,6 +60,56 @@ void nfc_render_emv_application(const EmvApplication* apl, FuriString* str) {
furi_string_cat_printf(str, "\n");
}
void nfc_render_emv_transactions(const EmvApplication* apl, FuriString* str) {
const uint8_t len = apl->active_tr;
if(!len) {
return;
}
furi_string_cat_printf(str, "Transactions:\n");
for(int i = 0; i < len; i++) {
if(!apl->trans[i].amount) continue;
uint8_t* a = (uint8_t*)&apl->trans[i].amount;
furi_string_cat_printf(str, "%d: ", apl->trans[i].atc);
bool top = true;
for(int x = 0; x < 6; x++) {
if(x == 5) {
furi_string_cat_printf(str, ".%02X", a[x]);
break;
}
if(a[x]) {
if(top) {
furi_string_cat_printf(str, "%X", a[x]);
top = false;
} else {
furi_string_cat_printf(str, "%02X", a[x]);
}
}
}
// TODO to string
furi_string_cat_printf(str, " %x\n", apl->trans[i].currency);
// TODO to string
if(apl->trans[i].country)
furi_string_cat_printf(str, "country: %x\n", apl->trans[i].country);
if(apl->trans[i].time)
furi_string_cat_printf(
str,
"%02lx:%02lx:%02lx ",
apl->trans[i].time & 0xff,
(apl->trans[i].time >> 8) & 0xff,
apl->trans[i].time >> 16);
if(apl->trans[i].date)
furi_string_cat_printf(
str,
"%02lx/%02lx/%02lx\n",
apl->trans[i].date >> 16,
(apl->trans[i].date >> 8) & 0xff,
apl->trans[i].date & 0xff);
}
}
void nfc_render_emv_extra(const EmvData* data, FuriString* str) {
nfc_render_emv_application(&data->emv_application, str);
//nfc_render_emv_transactions(&data->emv_application, str);
}

View File

@ -22,3 +22,5 @@ void nfc_render_emv_expired(const EmvApplication* apl, FuriString* str);
void nfc_render_emv_country(const EmvApplication* apl, FuriString* str);
void nfc_render_emv_currency(const EmvApplication* apl, FuriString* str);
void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str);

View File

@ -15,6 +15,15 @@ extern "C" {
#define EMV_TAG_CARD_NAME 0x50
#define EMV_TAG_FCI 0xBF0C
#define EMV_TAG_LOG_ENTRY 0x9F4D
#define EMV_TAG_LOG_FMT 0x9F4F
#define EMV_TAG_ATC 0x9F36
#define EMV_TAG_LOG_AMOUNT 0x9F02
#define EMV_TAG_LOG_COUNTRY 0x9F1A
#define EMV_TAG_LOG_CURRENCY 0x5F2A
#define EMV_TAG_LOG_DATE 0x9A
#define EMV_TAG_LOG_TIME 0x9F21
#define EMV_TAG_TRACK_1_EQUIV 0x56
#define EMV_TAG_TRACK_2_EQUIV 0x57
#define EMV_TAG_PAN 0x5A
@ -39,9 +48,22 @@ typedef struct {
uint8_t data[MAX_APDU_LEN];
} APDU;
typedef struct {
uint16_t atc;
uint64_t amount;
uint16_t country;
uint16_t currency;
uint32_t date;
uint32_t time;
} Transaction;
typedef struct {
uint8_t log_sfi;
uint8_t log_records;
uint8_t log_fmt[50];
uint8_t log_fmt_len;
uint8_t active_tr;
Transaction trans[16];
uint8_t priority;
uint8_t aid[16];
uint8_t aid_len;

View File

@ -111,11 +111,14 @@ static NfcCommand emv_poller_handler_get_processing_options(EmvPoller* instance)
}
static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
instance->error = emv_poller_read_files(instance);
instance->error = emv_poller_read_afl(instance);
if(instance->error == EmvErrorNone) {
FURI_LOG_D(TAG, "Read files success");
instance->state = EmvPollerStateReadSuccess;
if(instance->data->emv_application.log_sfi)
instance->state = EmvPollerStateReadLogs;
else
instance->state = EmvPollerStateReadSuccess;
} else {
FURI_LOG_E(TAG, "Failed to read files");
instance->state = EmvPollerStateReadFailed;
@ -124,6 +127,19 @@ static NfcCommand emv_poller_handler_read_files(EmvPoller* instance) {
return NfcCommandContinue;
}
static NfcCommand emv_poller_handler_read_logs(EmvPoller* instance) {
instance->error = emv_poller_read_log_entry(instance);
if(instance->error == EmvErrorNone) {
FURI_LOG_D(TAG, "Log entries had been read");
} else {
FURI_LOG_D(TAG, "No log entry");
}
instance->state = EmvPollerStateReadSuccess;
return NfcCommandContinue;
}
static NfcCommand emv_poller_handler_read_fail(EmvPoller* instance) {
FURI_LOG_D(TAG, "Read failed");
iso14443_4a_poller_halt(instance->iso14443_4a_poller);
@ -147,6 +163,7 @@ static const EmvPollerReadHandler emv_poller_read_handler[EmvPollerStateNum] = {
[EmvPollerStateSelectApplication] = emv_poller_handler_select_application,
[EmvPollerStateGetProcessingOptions] = emv_poller_handler_get_processing_options,
[EmvPollerStateReadFiles] = emv_poller_handler_read_files,
[EmvPollerStateReadLogs] = emv_poller_handler_read_logs,
[EmvPollerStateReadFailed] = emv_poller_handler_read_fail,
[EmvPollerStateReadSuccess] = emv_poller_handler_read_success,
};

View File

@ -46,7 +46,9 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance);
EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t record_num);
EmvError emv_poller_read_files(EmvPoller* instance);
EmvError emv_poller_read_afl(EmvPoller* instance);
EmvError emv_poller_read_log_entry(EmvPoller* instance);
#ifdef __cplusplus
}

View File

@ -109,179 +109,277 @@ static uint16_t emv_prepare_pdol(APDU* dest, APDU* src) {
return dest->size;
}
static bool emv_decode_response(const uint8_t* buff, uint16_t len, EmvApplication* app) {
uint16_t i = 0;
uint16_t tag = 0, first_byte = 0;
uint16_t tlen = 0;
static bool
emv_decode_tlv_tag(const uint8_t* buff, uint16_t tag, uint8_t tlen, EmvApplication* app) {
uint8_t i = 0;
bool success = false;
switch(tag) {
case EMV_TAG_LOG_FMT:
furi_check(tlen < sizeof(app->log_fmt));
memcpy(app->log_fmt, &buff[i], tlen);
app->log_fmt_len = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_LOG_FMT %X: len %d", tag, tlen);
break;
case EMV_TAG_GPO_FMT1:
// skip AIP
i += 2;
tlen -= 2;
furi_check(tlen < sizeof(app->afl.data));
memcpy(app->afl.data, &buff[i], tlen);
app->afl.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag);
break;
case EMV_TAG_AID:
app->aid_len = tlen;
memcpy(app->aid, &buff[i], tlen);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag);
for(size_t x = 0; x < tlen; x++) {
FURI_LOG_RAW_T("%02X ", app->aid[x]);
}
FURI_LOG_RAW_T("\r\n");
break;
case EMV_TAG_PRIORITY:
memcpy(&app->priority, &buff[i], tlen);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
break;
case EMV_TAG_CARD_NAME:
memcpy(app->name, &buff[i], tlen);
app->name[tlen] = '\0';
app->name_found = true;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
break;
case EMV_TAG_PDOL:
memcpy(app->pdol.data, &buff[i], tlen);
app->pdol.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
break;
case EMV_TAG_AFL:
memcpy(app->afl.data, &buff[i], tlen);
app->afl.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
break;
// Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf
case EMV_TAG_TRACK_1_EQUIV: {
// Contain PAN and expire date
char track_1_equiv[80];
memcpy(track_1_equiv, &buff[i], tlen);
track_1_equiv[tlen] = '\0';
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
break;
}
case EMV_TAG_TRACK_2_DATA:
case EMV_TAG_TRACK_2_EQUIV: {
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag);
// 0xD0 delimits PAN from expiry (YYMM)
for(int x = 1; x < tlen; x++) {
if(buff[i + x + 1] > 0xD0) {
memcpy(app->pan, &buff[i], x + 1);
app->pan_len = x + 1;
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
break;
}
}
// Convert 4-bit to ASCII representation
char track_2_equiv[41];
uint8_t track_2_equiv_len = 0;
for(int x = 0; x < tlen; x++) {
char top = (buff[i + x] >> 4) + '0';
char bottom = (buff[i + x] & 0x0F) + '0';
track_2_equiv[x * 2] = top;
track_2_equiv_len++;
if(top == '?') break;
track_2_equiv[x * 2 + 1] = bottom;
track_2_equiv_len++;
if(bottom == '?') break;
}
track_2_equiv[track_2_equiv_len] = '\0';
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv);
success = true;
break;
}
case EMV_TAG_PAN:
memcpy(app->pan, &buff[i], tlen);
app->pan_len = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
break;
case EMV_TAG_EXP_DATE:
app->exp_year = buff[i];
app->exp_month = buff[i + 1];
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
break;
case EMV_TAG_CURRENCY_CODE:
app->currency_code = (buff[i] << 8 | buff[i + 1]);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
break;
case EMV_TAG_COUNTRY_CODE:
app->country_code = (buff[i] << 8 | buff[i + 1]);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
break;
case EMV_TAG_LOG_ENTRY:
app->log_sfi = buff[i];
app->log_records = buff[i + 1];
success = true;
FURI_LOG_T(
TAG,
"found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d",
tag,
app->log_sfi,
app->log_records);
break;
case EMV_TAG_ATC:
app->trans[app->active_tr].atc = (buff[i] << 8 | buff[i + 1]);
success = true;
break;
case EMV_TAG_LOG_AMOUNT:
memcpy(&app->trans[app->active_tr].amount, &buff[i], tlen);
success = true;
break;
case EMV_TAG_LOG_COUNTRY:
app->trans[app->active_tr].country = (buff[i] << 8 | buff[i + 1]);
success = true;
break;
case EMV_TAG_LOG_CURRENCY:
app->trans[app->active_tr].currency = (buff[i] << 8 | buff[i + 1]);
success = true;
break;
case EMV_TAG_LOG_DATE:
memcpy(&app->trans[app->active_tr].date, &buff[i], tlen);
success = true;
break;
case EMV_TAG_LOG_TIME:
memcpy(&app->trans[app->active_tr].time, &buff[i], tlen);
success = true;
break;
}
return success;
}
static bool emv_response_error(const uint8_t* buff, uint16_t len) {
uint8_t i = 0;
uint8_t first_byte = 0;
bool error = true;
first_byte = buff[i];
if((len == 2) && ((first_byte >> 4) == 6)) {
switch(buff[i]) {
case EMV_TAG_RESP_BUF_SIZE:
FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]);
// Need to request SFI again with this length value
return error;
case EMV_TAG_RESP_BYTES_AVAILABLE:
FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]);
// Need to request one more time
return error;
default:
FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]);
return error;
}
}
return false;
}
static bool
emv_parse_tag(const uint8_t* buff, uint16_t len, uint16_t* t, uint8_t* tl, uint8_t* off) {
uint8_t i = *off;
uint16_t tag = 0;
uint8_t first_byte = 0;
uint8_t tlen = 0;
bool success = false;
first_byte = buff[i];
if(emv_response_error(buff, len)) return success;
if((first_byte & 31) == 31) { // 2-byte tag
tag = buff[i] << 8 | buff[i + 1];
i++;
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
} else {
tag = buff[i];
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
}
i++;
tlen = buff[i];
if((tlen & 128) == 128) { // long length value
i++;
tlen = buff[i];
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
} else {
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
}
i++;
*off = i;
*t = tag;
*tl = tlen;
success = true;
return success;
}
static bool emv_decode_tl(
const uint8_t* buff,
uint16_t len,
const uint8_t* fmt,
uint8_t fmt_len,
EmvApplication* app) {
uint8_t i = 0;
uint8_t f = 0;
uint16_t tag = 0;
uint8_t tlen = 0;
bool success = false;
if(emv_response_error(buff, len)) return success;
while(f < fmt_len && i < len) {
success = emv_parse_tag(fmt, fmt_len, &tag, &tlen, &f);
if(!success) return success;
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
i += tlen;
}
success = true;
return success;
}
static bool emv_decode_response_tlv(const uint8_t* buff, uint8_t len, EmvApplication* app) {
uint8_t i = 0;
uint16_t tag = 0;
uint8_t first_byte = 0;
uint8_t tlen = 0;
bool success = false;
while(i < len) {
first_byte = buff[i];
if((len == 2) && ((first_byte >> 4) == 6)) {
switch(buff[i]) {
case EMV_TAG_RESP_BUF_SIZE:
FURI_LOG_T(TAG, " Wrong length. Read %02X bytes", buff[i + 1]);
// Need to request SFI again with this length value
return success;
case EMV_TAG_RESP_BYTES_AVAILABLE:
FURI_LOG_T(TAG, " Bytes available: %02X", buff[i + 1]);
// Need to request one more time
return success;
success = emv_parse_tag(buff, len, &tag, &tlen, &i);
if(!success) return success;
default:
FURI_LOG_T(TAG, " Error/warning code: %02X %02X", buff[i], buff[i + 1]);
return success;
}
}
if((first_byte & 31) == 31) { // 2-byte tag
tag = buff[i] << 8 | buff[i + 1];
i++;
FURI_LOG_T(TAG, " 2-byte TLV EMV tag: %x", tag);
} else {
tag = buff[i];
FURI_LOG_T(TAG, " 1-byte TLV EMV tag: %x", tag);
}
i++;
tlen = buff[i];
if((tlen & 128) == 128) { // long length value
i++;
tlen = buff[i];
FURI_LOG_T(TAG, " 2-byte TLV length: %d", tlen);
} else {
FURI_LOG_T(TAG, " 1-byte TLV length: %d", tlen);
}
i++;
if((first_byte & 32) == 32) { // "Constructed" -- contains more TLV data to parse
FURI_LOG_T(TAG, "Constructed TLV %x", tag);
if(!emv_decode_response(&buff[i], tlen, app)) {
if(!emv_decode_response_tlv(&buff[i], tlen, app)) {
FURI_LOG_T(TAG, "Failed to decode response for %x", tag);
// return false;
} else {
success = true;
}
} else {
switch(tag) {
case EMV_TAG_GPO_FMT1:
// skip AIP
i += 2;
tlen -= 2;
memcpy(app->afl.data, &buff[i], tlen);
app->afl.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_GPO_FMT1 %X: ", tag);
break;
case EMV_TAG_AID:
app->aid_len = tlen;
memcpy(app->aid, &buff[i], tlen);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_AID %X: ", tag);
for(size_t x = 0; x < tlen; x++) {
FURI_LOG_RAW_T("%02X ", app->aid[x]);
}
FURI_LOG_RAW_T("\r\n");
break;
case EMV_TAG_PRIORITY:
memcpy(&app->priority, &buff[i], tlen);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_APP_PRIORITY %X: %d", tag, app->priority);
break;
case EMV_TAG_CARD_NAME:
memcpy(app->name, &buff[i], tlen);
app->name[tlen] = '\0';
app->name_found = true;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_CARD_NAME %x : %s", tag, app->name);
break;
case EMV_TAG_PDOL:
memcpy(app->pdol.data, &buff[i], tlen);
app->pdol.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_PDOL %x (len=%d)", tag, tlen);
break;
case EMV_TAG_AFL:
memcpy(app->afl.data, &buff[i], tlen);
app->afl.size = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
break;
// Tracks data https://murdoch.is/papers/defcon20emvdecode.pdf
case EMV_TAG_TRACK_1_EQUIV: {
// Contain PAN and expire date
char track_1_equiv[80];
memcpy(track_1_equiv, &buff[i], tlen);
track_1_equiv[tlen] = '\0';
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
break;
}
case EMV_TAG_TRACK_2_DATA:
case EMV_TAG_TRACK_2_EQUIV: {
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X", tag);
// 0xD0 delimits PAN from expiry (YYMM)
for(int x = 1; x < tlen; x++) {
if(buff[i + x + 1] > 0xD0) {
memcpy(app->pan, &buff[i], x + 1);
app->pan_len = x + 1;
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
break;
}
}
// Convert 4-bit to ASCII representation
char track_2_equiv[41];
uint8_t track_2_equiv_len = 0;
for(int x = 0; x < tlen; x++) {
char top = (buff[i + x] >> 4) + '0';
char bottom = (buff[i + x] & 0x0F) + '0';
track_2_equiv[x * 2] = top;
track_2_equiv_len++;
if(top == '?') break;
track_2_equiv[x * 2 + 1] = bottom;
track_2_equiv_len++;
if(bottom == '?') break;
}
track_2_equiv[track_2_equiv_len] = '\0';
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2 %X : %s", tag, track_2_equiv);
success = true;
break;
}
case EMV_TAG_PAN:
memcpy(app->pan, &buff[i], tlen);
app->pan_len = tlen;
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_PAN %x", tag);
break;
case EMV_TAG_EXP_DATE:
app->exp_year = buff[i];
app->exp_month = buff[i + 1];
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_EXP_DATE %x", tag);
break;
case EMV_TAG_CURRENCY_CODE:
app->currency_code = (buff[i] << 8 | buff[i + 1]);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_CURRENCY_CODE %x", tag);
break;
case EMV_TAG_COUNTRY_CODE:
app->country_code = (buff[i] << 8 | buff[i + 1]);
success = true;
FURI_LOG_T(TAG, "found EMV_TAG_COUNTRY_CODE %x", tag);
break;
case EMV_TAG_LOG_ENTRY:
app->log_sfi = buff[i];
app->log_records = buff[i + 1];
success = true;
FURI_LOG_T(
TAG,
"found EMV_TAG_LOG_ENTRY %x: sfi 0x%x, records %d",
tag,
app->log_sfi,
app->log_records);
break;
}
emv_decode_tlv_tag(&buff[i], tag, tlen, app);
}
i += tlen;
}
@ -320,7 +418,7 @@ EmvError emv_poller_select_ppse(EmvPoller* instance) {
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
if(!emv_decode_response(
if(!emv_decode_response_tlv(
buff,
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
@ -372,7 +470,7 @@ EmvError emv_poller_select_application(EmvPoller* instance) {
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
if(!emv_decode_response(
if(!emv_decode_response_tlv(
buff,
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
@ -424,7 +522,7 @@ EmvError emv_poller_get_processing_options(EmvPoller* instance) {
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
if(!emv_decode_response(
if(!emv_decode_response_tlv(
buff,
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
@ -466,17 +564,6 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
error = emv_process_error(iso14443_4a_error);
break;
}
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
if(!emv_decode_response(
buff,
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
// It's ok while bruteforcing
//error = EmvErrorProtocol;
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record_num);
}
} while(false);
furi_string_free(text);
@ -484,7 +571,7 @@ EmvError emv_poller_read_sfi_record(EmvPoller* instance, uint8_t sfi, uint8_t re
return error;
}
EmvError emv_poller_read_files(EmvPoller* instance) {
EmvError emv_poller_read_afl(EmvPoller* instance) {
EmvError error = EmvErrorNone;
APDU* afl = &instance->data->emv_application.afl;
@ -504,6 +591,14 @@ EmvError emv_poller_read_files(EmvPoller* instance) {
for(uint8_t record = record_start; record <= record_end; ++record) {
error = emv_poller_read_sfi_record(instance, sfi, record);
if(error != EmvErrorNone) break;
if(!emv_decode_response_tlv(
bit_buffer_get_data(instance->rx_buffer),
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
error = EmvErrorProtocol;
FURI_LOG_T(TAG, "Failed to parse SFI 0x%X record %d", sfi, record);
}
if(instance->data->emv_application.pan_len != 0)
return EmvErrorNone; // Card number fetched
}
@ -511,4 +606,85 @@ EmvError emv_poller_read_files(EmvPoller* instance) {
}
return error;
}
}
static EmvError emv_poller_get_log_format(EmvPoller* instance) {
EmvError error = EmvErrorNone;
const uint8_t cla_ins[] = {0x80, 0xCA};
bit_buffer_reset(instance->tx_buffer);
bit_buffer_reset(instance->rx_buffer);
bit_buffer_copy_bytes(instance->tx_buffer, cla_ins, sizeof(cla_ins));
bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT >> 8);
bit_buffer_append_byte(instance->tx_buffer, EMV_TAG_LOG_FMT & 0xFF);
bit_buffer_append_byte(instance->tx_buffer, 0x00); //Length
do {
FURI_LOG_D(TAG, "Get log format");
Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block_pwt_ext(
instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer);
emv_trace(instance, "Get log format answer:");
if(iso14443_4a_error != Iso14443_4aErrorNone) {
FURI_LOG_E(TAG, "Failed to get log format, error %u", iso14443_4a_error);
error = emv_process_error(iso14443_4a_error);
break;
}
const uint8_t* buff = bit_buffer_get_data(instance->rx_buffer);
if(!emv_decode_response_tlv(
buff,
bit_buffer_get_size_bytes(instance->rx_buffer),
&instance->data->emv_application)) {
error = EmvErrorProtocol;
FURI_LOG_E(TAG, "Failed to parse log format");
}
} while(false);
return error;
}
EmvError emv_poller_read_log_entry(EmvPoller* instance) {
EmvError error = EmvErrorProtocol;
uint8_t records = instance->data->emv_application.log_records;
if(records == 0) {
return false;
}
error = emv_poller_get_log_format(instance);
if(error != EmvErrorNone) return false;
FURI_LOG_D(TAG, "Read Transaction logs");
uint8_t sfi = instance->data->emv_application.log_sfi;
uint8_t record_start = 1;
uint8_t record_end = records;
// Iterate through all records in file
for(uint8_t record = record_start; record <= record_end; ++record) {
error = emv_poller_read_sfi_record(instance, sfi, record);
if(error != EmvErrorNone) break;
if(!emv_decode_tl(
bit_buffer_get_data(instance->rx_buffer),
bit_buffer_get_size_bytes(instance->rx_buffer),
instance->data->emv_application.log_fmt,
instance->data->emv_application.log_fmt_len,
&instance->data->emv_application)) {
error = EmvErrorProtocol;
FURI_LOG_T(TAG, "Failed to parse log SFI 0x%X record %d", sfi, record);
break;
}
instance->data->emv_application.active_tr++;
furi_check(
instance->data->emv_application.active_tr <
COUNT_OF(instance->data->emv_application.trans));
}
return error;
}

View File

@ -14,6 +14,7 @@ typedef enum {
EmvPollerStateSelectApplication,
EmvPollerStateGetProcessingOptions,
EmvPollerStateReadFiles,
EmvPollerStateReadLogs,
EmvPollerStateReadFailed,
EmvPollerStateReadSuccess,

View File

@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,52.0,,
Version,+,52.1,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
Header,+,applications/services/cli/cli.h,,
@ -888,7 +888,8 @@ Function,+,emv_get_uid,const uint8_t*,"const EmvData*, size_t*"
Function,+,emv_is_equal,_Bool,"const EmvData*, const EmvData*"
Function,+,emv_load,_Bool,"EmvData*, FlipperFormat*, uint32_t"
Function,+,emv_poller_get_processing_options,EmvError,EmvPoller*
Function,+,emv_poller_read_files,EmvError,EmvPoller*
Function,+,emv_poller_read_afl,EmvError,EmvPoller*
Function,+,emv_poller_read_log_entry,EmvError,EmvPoller*
Function,+,emv_poller_read_sfi_record,EmvError,"EmvPoller*, uint8_t, uint8_t"
Function,+,emv_poller_select_application,EmvError,EmvPoller*
Function,+,emv_poller_select_ppse,EmvError,EmvPoller*

1 entry status name type params
2 Version + 52.0 52.1
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/cli/cli.h
888 Function + emv_is_equal _Bool const EmvData*, const EmvData*
889 Function + emv_load _Bool EmvData*, FlipperFormat*, uint32_t
890 Function + emv_poller_get_processing_options EmvError EmvPoller*
891 Function + emv_poller_read_files emv_poller_read_afl EmvError EmvPoller*
892 Function + emv_poller_read_log_entry EmvError EmvPoller*
893 Function + emv_poller_read_sfi_record EmvError EmvPoller*, uint8_t, uint8_t
894 Function + emv_poller_select_application EmvError EmvPoller*
895 Function + emv_poller_select_ppse EmvError EmvPoller*