bip parser refactor

This commit is contained in:
Methodius 2024-03-09 01:53:02 +09:00
parent 4e7f25a539
commit 0c5a78a59f
No known key found for this signature in database
GPG key ID: 122FA99A00B41679

View file

@ -1,71 +1,140 @@
#include "nfc_supported_card_plugin.h" #include "nfc_supported_card_plugin.h"
#include <flipper_application.h>
#include <bit_lib/bit_lib.h> #include "protocols/mf_classic/mf_classic.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h> #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <bit_lib.h>
#include <locale/locale.h>
#define TAG "Bip" #define TAG "Bip"
#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block)) #define BIP_CARD_ID_SECTOR_NUMBER (0)
#define BIP_BALANCE_SECTOR_NUMBER (8)
#define BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER (5)
#define BIP_LAST_TOP_UPS_SECTOR_NUMBER (10)
#define BIP_TRIPS_INFO_SECTOR_NUMBER (11)
static const uint64_t bip_keys_a[] = { typedef struct {
0x3a42f33af429, DateTime datetime;
0x6338a371c0ed, uint16_t amount;
0xf124c2578ad0, } BipTransaction;
0x32ac3b90ac13,
0x4ad1e273eaf1, typedef struct {
0xe2c42591368a, uint64_t a;
0x2a3c347a1200, uint64_t b;
0x16f3d5ab1139, } MfClassicKeyPair;
0x937a4fff3011,
0x35c3d2caee88, static const MfClassicKeyPair bip_1k_keys[] = {
0x693143f10368, {.a = 0x3a42f33af429, .b = 0x1fc235ac1309},
0xa3f97428dd01, {.a = 0x6338a371c0ed, .b = 0x243f160918d1},
0x63f17a449af0, {.a = 0xf124c2578ad0, .b = 0x9afc42372af1},
0xc4652c54261c, {.a = 0x32ac3b90ac13, .b = 0x682d401abb09},
0xd49e2826664f, {.a = 0x4ad1e273eaf1, .b = 0x067db45454a9},
0x3df14c8000a1, {.a = 0xe2c42591368a, .b = 0x15fc4c7613fe},
{.a = 0x2a3c347a1200, .b = 0x68d30288910a},
{.a = 0x16f3d5ab1139, .b = 0xf59a36a2546d},
{.a = 0x937a4fff3011, .b = 0x64e3c10394c2},
{.a = 0x35c3d2caee88, .b = 0xb736412614af},
{.a = 0x693143f10368, .b = 0x324f5df65310},
{.a = 0xa3f97428dd01, .b = 0x643fb6de2217},
{.a = 0x63f17a449af0, .b = 0x82f435dedf01},
{.a = 0xc4652c54261c, .b = 0x0263de1278f3},
{.a = 0xd49e2826664f, .b = 0x51284c3686a6},
{.a = 0x3df14c8000a1, .b = 0x6a470d54127c},
}; };
static const uint64_t bip_keys_b[] = { static void bip_parse_datetime(const MfClassicBlock* block, DateTime* parsed_data) {
0x1fc235ac1309, furi_assert(block);
0x243f160918d1, furi_assert(parsed_data);
0x9afc42372af1,
0x682d401abb09, parsed_data->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
0x067db45454a9, parsed_data->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
0x15fc4c7613fe, parsed_data->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
0x68d30288910a, parsed_data->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
0xf59a36a2546d, parsed_data->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
0x64e3c10394c2, parsed_data->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
0xb736412614af, }
0x324f5df65310,
0x643fb6de2217, static void bip_print_datetime(const DateTime* datetime, FuriString* str) {
0x82f435dedf01, furi_assert(datetime);
0x0263de1278f3, furi_assert(str);
0x51284c3686a6,
0x6a470d54127c, LocaleDateFormat date_format = locale_get_date_format();
}; const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
FuriString* date_str = furi_string_alloc();
locale_format_date(date_str, datetime, date_format, separator);
FuriString* time_str = furi_string_alloc();
locale_format_time(time_str, datetime, locale_get_time_format(), false);
furi_string_cat_printf(
str, "%s %s", furi_string_get_cstr(date_str), furi_string_get_cstr(time_str));
furi_string_free(date_str);
furi_string_free(time_str);
}
static int datetime_cmp(const DateTime* dt_1, const DateTime* dt_2) {
furi_assert(dt_1);
furi_assert(dt_2);
if(dt_1->year != dt_2->year) {
return dt_1->year - dt_2->year;
}
if(dt_1->month != dt_2->month) {
return dt_1->month - dt_2->month;
}
if(dt_1->day != dt_2->day) {
return dt_1->day - dt_2->day;
}
if(dt_1->hour != dt_2->hour) {
return dt_1->hour - dt_2->hour;
}
if(dt_1->minute != dt_2->minute) {
return dt_1->minute - dt_2->minute;
}
if(dt_1->second != dt_2->second) {
return dt_1->second - dt_2->second;
}
return 0;
}
static bool is_bip_block_empty(const MfClassicBlock* block) {
furi_assert(block);
// check if all but last byte are zero (last is checksum)
for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
if(block->data[i] != 0) {
return false;
}
}
return true;
}
bool bip_verify(Nfc* nfc) { bool bip_verify(Nfc* nfc) {
bool verified = true; bool verified = false;
do {
const uint8_t verify_sector = 0; const uint8_t verify_sector = 0;
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
MfClassicKey key_a_0 = {}; MfClassicKey key = {};
bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data); bit_lib_num_to_bytes_be(bip_1k_keys[0].a, COUNT_OF(key.data), key.data);
MfClassicAuthContext auth_ctx = {}; MfClassicAuthContext auth_ctx = {};
MfClassicError error = MfClassicError error =
mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
if(error == MfClassicErrorNotPresent) { if(error == MfClassicErrorNone) {
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
verified = false; break;
} }
verified = true;
} while(false);
return verified; return verified;
} }
@ -79,31 +148,33 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) {
nfc_device_copy_data(device, NfcProtocolMfClassic, data); nfc_device_copy_data(device, NfcProtocolMfClassic, data);
do { do {
MfClassicType type = MfClassicType1k; MfClassicType type = MfClassicTypeMini;
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
if(error == MfClassicErrorNotPresent) { if(error != MfClassicErrorNone) break;
FURI_LOG_W(TAG, "Card not MIFARE Classic 1k");
break;
}
data->type = type; data->type = type;
MfClassicDeviceKeys keys = {}; if(type != MfClassicType1k) break;
MfClassicDeviceKeys keys = {
.key_a_mask = 0,
.key_b_mask = 0,
};
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data); bit_lib_num_to_bytes_be(bip_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
FURI_BIT_SET(keys.key_a_mask, i); FURI_BIT_SET(keys.key_a_mask, i);
bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data); bit_lib_num_to_bytes_be(bip_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
FURI_BIT_SET(keys.key_b_mask, i); FURI_BIT_SET(keys.key_b_mask, i);
} }
error = mf_classic_poller_sync_read(nfc, &keys, data); error = mf_classic_poller_sync_read(nfc, &keys, data);
if(error == MfClassicErrorNotPresent) { if(error == MfClassicErrorNotPresent) {
FURI_LOG_W(TAG, "Failed to read data. Bad keys?"); FURI_LOG_W(TAG, "Failed to read data");
break; break;
} }
nfc_device_set_data(device, NfcProtocolMfClassic, data); nfc_device_set_data(device, NfcProtocolMfClassic, data);
is_read = true; is_read = (error == MfClassicErrorNone);
} while(false); } while(false);
mf_classic_free(data); mf_classic_free(data);
@ -111,183 +182,91 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) {
return is_read; return is_read;
} }
typedef struct {
uint16_t year;
uint8_t month;
uint8_t day;
uint8_t hour;
uint8_t minute;
uint8_t second;
} BipTimestamp;
static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) {
furi_assert(block);
furi_assert(timestamp);
timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
}
static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) {
furi_assert(t1);
furi_assert(t2);
if(t1->year != t2->year) {
return t1->year - t2->year;
}
if(t1->month != t2->month) {
return t1->month - t2->month;
}
if(t1->day != t2->day) {
return t1->day - t2->day;
}
if(t1->hour != t2->hour) {
return t1->hour - t2->hour;
}
if(t1->minute != t2->minute) {
return t1->minute - t2->minute;
}
if(t1->second != t2->second) {
return t1->second - t2->second;
}
return 0;
}
static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) {
furi_assert(timestamp);
furi_assert(str);
furi_string_cat_printf(
str,
"%04u-%02u-%02u %02u:%02u:%02u",
timestamp->year,
timestamp->month,
timestamp->day,
timestamp->hour,
timestamp->minute,
timestamp->second);
}
static bool is_bip_block_empty(const MfClassicBlock* block) {
furi_assert(block);
// check if all but last byte are zero (last is checksum)
for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
if(block->data[i] != 0) {
return false;
}
}
return true;
}
static void parse_uint16_le(const uint8_t* data, uint16_t* value) {
furi_assert(data);
furi_assert(value);
*value = (data[0]) | (data[1] << 8);
}
static void parse_uint32_le(const uint8_t* data, uint32_t* value) {
furi_assert(data);
furi_assert(value);
*value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
}
static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) {
furi_assert(data);
furi_assert(value);
parse_uint16_le(data, value);
*value = *value >> 2;
}
typedef struct {
BipTimestamp timestamp;
uint16_t amount;
} BipTransaction;
static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device); furi_assert(device);
furi_assert(parsed_data); furi_assert(parsed_data);
bool parsed = true;
struct { struct {
uint32_t card_id; uint32_t card_id;
uint16_t balance; uint16_t balance;
uint16_t flags; uint16_t flags;
BipTimestamp trip_time_window; DateTime trip_time_window;
BipTransaction top_ups[3]; BipTransaction top_ups[3];
BipTransaction charges[3]; BipTransaction charges[3];
} bip_data = { } bip_data = {0};
.card_id = 0,
.balance = 0,
.flags = 0,
.trip_time_window = {0, 0, 0, 0, 0, 0},
.top_ups =
{
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
},
.charges =
{
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
{{0, 0, 0, 0, 0, 0}, 0},
},
};
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do { do {
// verify first sector keys // verify sector 0 key A
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
if(data->type != MfClassicType1k) break;
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
if(key != bip_keys_a[0]) { if(key != bip_1k_keys[0].a) {
parsed = false;
break; break;
} }
// verify sector 0 key B
key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6);
if(key != bip_keys_b[0]) { if(key != bip_1k_keys[0].b) {
parsed = false;
break; break;
} }
// Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7 // Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7
parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id); const uint8_t card_id_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_CARD_ID_SECTOR_NUMBER);
const uint8_t* block_start_ptr = &data->block[card_id_start_block_num + 1].data[0];
bip_data.card_id = bit_lib_bytes_to_num_le(block_start_ptr + 4, 4);
// Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1 // Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance); const uint8_t balance_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_BALANCE_SECTOR_NUMBER);
block_start_ptr = &data->block[balance_start_block_num + 1].data[0];
bip_data.balance = bit_lib_bytes_to_num_le(block_start_ptr, 2);
// Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3 // Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3
parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags); bip_data.flags = bit_lib_bytes_to_num_le(block_start_ptr + 2, 2);
// Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7 // Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window); const uint8_t trip_time_window_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER);
const MfClassicBlock* trip_window_block_ptr =
&data->block[trip_time_window_start_block_num + 1];
bip_parse_datetime(trip_window_block_ptr, &bip_data.trip_time_window);
// Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10 // Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10
const uint8_t top_ups_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_LAST_TOP_UPS_SECTOR_NUMBER);
for(size_t i = 0; i < 3; i++) { for(size_t i = 0; i < 3; i++) {
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) { const MfClassicBlock* block = &data->block[top_ups_start_block_num + i];
continue;
} if(is_bip_block_empty(block)) continue;
BipTransaction* top_up = &bip_data.top_ups[i]; BipTransaction* top_up = &bip_data.top_ups[i];
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp); bip_parse_datetime(block, &top_up->datetime);
parse_uint16_txn_amount(
&data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount); top_up->amount = bit_lib_bytes_to_num_le(&block->data[9], 2) >> 2;
} }
// Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11 // Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11
const uint8_t trips_start_block_num =
mf_classic_get_first_block_num_of_sector(BIP_TRIPS_INFO_SECTOR_NUMBER);
for(size_t i = 0; i < 3; i++) { for(size_t i = 0; i < 3; i++) {
if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) { const MfClassicBlock* block = &data->block[trips_start_block_num + i];
continue;
} if(is_bip_block_empty(block)) continue;
BipTransaction* charge = &bip_data.charges[i]; BipTransaction* charge = &bip_data.charges[i];
parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp); bip_parse_datetime(block, &charge->datetime);
parse_uint16_txn_amount(
&data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount); charge->amount = bit_lib_bytes_to_num_le(&block->data[10], 2) >> 2;
} }
// All data is now parsed and stored in bip_data, now print it // All data is now parsed and stored in bip_data, now print it
@ -303,14 +282,14 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
bip_data.balance, bip_data.balance,
bip_data.flags); bip_data.flags);
print_bip_timestamp(&bip_data.trip_time_window, parsed_data); bip_print_datetime(&bip_data.trip_time_window, parsed_data);
// Find newest top-up // Find newest top-up
size_t newest_top_up = 0; size_t newest_top_up = 0;
for(size_t i = 1; i < 3; i++) { for(size_t i = 1; i < 3; i++) {
const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp; const DateTime* newest = &bip_data.top_ups[newest_top_up].datetime;
const BipTimestamp* current = &bip_data.top_ups[i].timestamp; const DateTime* current = &bip_data.top_ups[i].datetime;
if(compare_bip_timestamp(current, newest) > 0) { if(datetime_cmp(current, newest) > 0) {
newest_top_up = i; newest_top_up = i;
} }
} }
@ -320,15 +299,15 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
for(size_t i = 0; i < 3; i++) { for(size_t i = 0; i < 3; i++) {
const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3]; const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3];
furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount); furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount);
print_bip_timestamp(&top_up->timestamp, parsed_data); bip_print_datetime(&top_up->datetime, parsed_data);
} }
// Find newest charge // Find newest charge
size_t newest_charge = 0; size_t newest_charge = 0;
for(size_t i = 1; i < 3; i++) { for(size_t i = 1; i < 3; i++) {
const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp; const DateTime* newest = &bip_data.charges[newest_charge].datetime;
const BipTimestamp* current = &bip_data.charges[i].timestamp; const DateTime* current = &bip_data.charges[i].datetime;
if(compare_bip_timestamp(current, newest) > 0) { if(datetime_cmp(current, newest) > 0) {
newest_charge = i; newest_charge = i;
} }
} }
@ -338,7 +317,7 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) {
for(size_t i = 0; i < 3; i++) { for(size_t i = 0; i < 3; i++) {
const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3]; const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3];
furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount); furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount);
print_bip_timestamp(&charge->timestamp, parsed_data); bip_print_datetime(&charge->datetime, parsed_data);
} }
parsed = true; parsed = true;