Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FL-3661] Troika layout fixes #3365

Merged
merged 9 commits into from
Feb 6, 2024
2 changes: 1 addition & 1 deletion applications/main/nfc/plugins/supported_cards/plantain.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
175 changes: 163 additions & 12 deletions applications/main/nfc/plugins/supported_cards/troika.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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},
Expand Down Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Loading