[FL-3661] Troika layout fixes (#3365)

* Add support for different troika layouts

* Display additional data if debug is enabled in settings

* Support for layout 2, where there's no balance

* nfc app plugins: fix mfc read error processing

* nfc app: clean up troika plugin

* nfc app: troika parser more clean up

---------

Co-authored-by: gornekich <n.gorbadey@gmail.com>
Co-authored-by: hedger <hedger@users.noreply.github.com>
This commit is contained in:
Astra 2024-02-06 21:29:17 +04:00 committed by GitHub
parent ed34dfa1c6
commit e0782966d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 165 additions and 14 deletions

View file

@ -135,7 +135,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error != MfClassicErrorNotPresent) {
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data");
break;
}

View file

@ -5,6 +5,7 @@
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include "furi_hal_rtc.h"
#define TAG "Troika"
@ -18,6 +19,19 @@ typedef struct {
uint32_t data_sector;
} TroikaCardConfig;
typedef enum {
TroikaLayoutUnknown = 0x0,
TroikaLayout2 = 0x2,
TroikaLayoutE = 0xE,
} TroikaLayout;
typedef enum {
TroikaSublayoutUnknown = 0x0,
TroikaSublayout3 = 0x3,
TroikaSublayout5 = 0x5,
TroikaSublayout6 = 0x6,
} TroikaSubLayout;
static const MfClassicKeyPair troika_1k_keys[] = {
{.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58},
{.a = 0xa82607b01c0d, .b = 0x2910989b6880},
@ -67,7 +81,7 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type)
config->data_sector = 8;
config->keys = troika_1k_keys;
} else if(type == MfClassicType4k) {
config->data_sector = 4;
config->data_sector = 8; // Further testing needed
config->keys = troika_4k_keys;
} else {
success = false;
@ -76,6 +90,126 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type)
return success;
}
static TroikaLayout troika_get_layout(const MfClassicData* data, uint8_t start_block_num) {
furi_assert(data);
// Layout is stored in byte 6 of block, length 4 bits (bits 52 - 55), second nibble.
const uint8_t* layout_ptr = &data->block[start_block_num].data[6];
const uint8_t layout = (*layout_ptr & 0x0F);
TroikaLayout result = TroikaLayoutUnknown;
switch(layout) {
case TroikaLayout2:
case TroikaLayoutE:
result = layout;
break;
default:
// If debug is enabled - pass the actual layout value for the debug text
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
return layout;
} else {
return TroikaLayoutUnknown;
}
}
return result;
}
static TroikaSubLayout troika_get_sub_layout(const MfClassicData* data, uint8_t start_block_num) {
furi_assert(data);
// Sublayout is stored in byte 7 (bits 56 - 60) of block, length 5 bits (first nibble and one bit from second nibble)
const uint8_t* sub_layout_ptr = &data->block[start_block_num].data[7];
const uint8_t sub_layout = (*sub_layout_ptr & 0x3F) >> 3;
TroikaSubLayout result = TroikaSublayoutUnknown;
switch(sub_layout) {
case TroikaSublayout3:
case TroikaSublayout5:
case TroikaSublayout6:
result = sub_layout;
break;
default:
// If debug is enabled - pass the actual sublayout value for the debug text
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
return sub_layout;
} else {
return TroikaSublayoutUnknown;
}
}
return result;
}
static bool troika_has_balance(TroikaLayout layout, TroikaSubLayout sub_layout) {
UNUSED(sub_layout);
// Layout 0x2 has no balance
if(layout == TroikaLayout2) {
return false;
}
return true;
}
static uint16_t troika_get_balance(
const MfClassicData* data,
uint8_t start_block_num,
TroikaLayout layout,
TroikaSubLayout sub_layout) {
furi_assert(data);
// In layout 0x3 balance in bits 188:209 ( from sector start, length 22).
// In layout 0x5 balance in bits 165:185 ( from sector start, length 20).
uint32_t balance = 0;
uint8_t balance_data_offset = 0;
bool supported_layout = false;
if(layout == TroikaLayoutE && sub_layout == TroikaSublayout3) {
balance_data_offset = 7;
supported_layout = true;
} else if(layout == TroikaLayoutE && sub_layout == TroikaSublayout5) {
balance_data_offset = 4;
supported_layout = true;
}
if(supported_layout) {
const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[balance_data_offset];
balance |= (temp_ptr[0] & 0x3) << 18;
balance |= temp_ptr[1] << 10;
balance |= temp_ptr[2] << 2;
balance |= (temp_ptr[3] & 0xC0) >> 6;
}
return balance / 100;
}
static uint32_t troika_get_number(
const MfClassicData* data,
uint8_t start_block_num,
TroikaLayout layout,
TroikaSubLayout sub_layout) {
furi_assert(data);
UNUSED(sub_layout);
if(layout == TroikaLayoutE || layout == TroikaLayout2) {
const uint8_t* temp_ptr = &data->block[start_block_num].data[2];
uint32_t number = 0;
for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
}
number >>= 4;
number |= (temp_ptr[0] & 0xf) << 28;
return number;
} else {
return 0;
}
}
static bool troika_verify_type(Nfc* nfc, MfClassicType type) {
bool verified = false;
@ -171,22 +305,39 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
if(key != cfg.keys[cfg.data_sector].a) break;
// Parse data
// Get the block number of the block that contains the data
const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5];
uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
temp_ptr = &data->block[start_block_num].data[2];
// Get layout, sublayout, balance and number
TroikaLayout layout = troika_get_layout(data, start_block_num);
TroikaSubLayout sub_layout = troika_get_sub_layout(data, start_block_num);
uint32_t number = 0;
for(size_t i = 1; i < 5; i++) {
number <<= 8;
number |= temp_ptr[i];
if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
// If debug is enabled - proceed even if layout or sublayout is unknown, that will make collecting data easier
if(layout == TroikaLayoutUnknown || sub_layout == TroikaSublayoutUnknown) break;
}
uint32_t number = troika_get_number(data, start_block_num, layout, sub_layout);
furi_string_printf(parsed_data, "\e#Troika\nNum: %lu", number);
if(troika_has_balance(layout, sub_layout) ||
furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
uint16_t balance = troika_get_balance(data, start_block_num, layout, sub_layout);
furi_string_cat_printf(parsed_data, "\nBalance: %u RUR", balance);
} else {
furi_string_cat_printf(parsed_data, "\nBalance: Not available");
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
furi_string_cat_printf(
parsed_data,
"\nLayout: %02x\nSublayout: %02x\nData Block: %u",
layout,
sub_layout,
start_block_num);
}
number >>= 4;
number |= (temp_ptr[0] & 0xf) << 28;
furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance);
parsed = true;
} while(false);

View file

@ -85,7 +85,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
}
error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error != MfClassicErrorNotPresent) {
if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data");
break;
}