Zolotaya Korona Online parser added

This commit is contained in:
Methodius 2024-01-13 16:52:50 +09:00 committed by MX
parent 634e841ce8
commit 9aae348cae
No known key found for this signature in database
GPG Key ID: 7CCC66B7DBDD1C83
4 changed files with 197 additions and 19 deletions

View File

@ -146,6 +146,15 @@ App(
sources=["plugins/supported_cards/zolotaya_korona.c"],
)
App(
appid="zolotaya_korona_online_parser",
apptype=FlipperAppType.PLUGIN,
entry_point="zolotaya_korona_online_plugin_ep",
targets=["f7"],
requires=["nfc"],
sources=["plugins/supported_cards/zolotaya_korona_online.c"],
)
App(
appid="hid_parser",
apptype=FlipperAppType.PLUGIN,

View File

@ -328,6 +328,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
last_trip.minute);
}
furi_string_free(tariff_name);
parsed = true;
} while(false);

View File

@ -34,11 +34,6 @@
#define PURSE_SECTOR_NUM (6)
#define INFO_SECTOR_NUM (15)
typedef struct {
uint64_t a;
uint64_t b;
} MfClassicKeyPair;
// Sector 15 data. Byte [11] contains the mistake. If byte [11] was 0xEF, bytes [1-18] means "ЗАО Золотая Корона"
static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x87, 0xAE,
0xAB, 0xAE, 0xF2, 0xA0, 0xEF, 0x20, 0x8A,
@ -76,19 +71,24 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE;
}
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes) {
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
furi_assert(src);
furi_assert(len_bytes <= 9);
uint64_t res = 0;
uint64_t result = 0;
*is_bcd = true;
for(uint8_t i = 0; i < len_bytes; i++) {
res *= 10;
res += src[i] / 16;
res *= 10;
res += src[i] % 16;
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
result *= 10;
result += src[i] / 16;
result *= 10;
result += src[i] % 16;
}
return res;
return result;
}
static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_data) {
@ -121,12 +121,12 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
// INFO SECTOR
// block 1
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1);
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1, &verified);
// block 2
block_start_ptr = &data->block[start_info_block_number + 2].data[4];
const uint64_t card_number =
bytes2num_bcd(block_start_ptr, 9) * 10 + bytes2num_bcd(block_start_ptr + 9, 1) / 10;
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &verified);
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &verified) / 10;
// TRIP SECTOR
const uint8_t start_trip_block_number =
@ -157,7 +157,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
block_start_ptr = &data->block[start_trip_block_number + 2].data[0];
const char validator_first_letter =
nfc_util_bytes2num_little_endian(block_start_ptr + 1, 1);
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3);
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3, &verified);
const uint32_t last_trip_timestamp =
nfc_util_bytes2num_little_endian(block_start_ptr + 6, 4);
const uint8_t track_number = nfc_util_bytes2num_little_endian(block_start_ptr + 10, 1);
@ -174,15 +174,16 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
block_start_ptr = &data->block[start_purse_block_number].data[0];
// block 0
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
const uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
uint32_t balance_rub = balance / 100;
uint8_t balance_kop = balance % 100;
furi_string_cat_printf(
parsed_data,
"\e#Zolotaya korona\nCard number: %llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
card_number,
"\e#Zolotaya korona\nCard number: %u%015llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
card_number_prefix,
card_number_postfix,
region_number,
balance_rub,
balance_kop,

View File

@ -0,0 +1,166 @@
/*
* Parser for Zolotaya Korona Online card (Russia).
* Tariffs research by DNZ1393
*
* Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "furi_hal_rtc.h"
#include "nfc_supported_card_plugin.h"
#include "protocols/mf_classic/mf_classic.h"
#include <flipper_application/flipper_application.h>
#include <nfc/nfc_device.h>
#include <nfc/helpers/nfc_util.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#define TAG "Zolotaya Korona Online"
#define TRIP_SECTOR_NUM (4)
#define INFO_SECTOR_NUM (15)
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
furi_assert(src);
furi_assert(len_bytes <= 9);
uint64_t result = 0;
*is_bcd = true;
for(uint8_t i = 0; i < len_bytes; i++) {
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
result *= 10;
result += src[i] / 16;
result *= 10;
result += src[i] % 16;
}
return result;
}
bool parse_online_card_tariff(uint16_t tariff_num, FuriString* tariff_name) {
bool tariff_parsed = false;
switch(tariff_num) {
case 0x0100:
furi_string_set_str(tariff_name, "Standart (online)");
tariff_parsed = true;
break;
case 0x0101:
case 0x0121:
furi_string_set_str(tariff_name, "Standart (airtag)");
tariff_parsed = true;
break;
case 0x0401:
furi_string_set_str(tariff_name, "Student (50%% discount)");
tariff_parsed = true;
break;
case 0x0402:
furi_string_set_str(tariff_name, "Student (travel)");
tariff_parsed = true;
break;
case 0x0002:
furi_string_set_str(tariff_name, "School (50%% discount)");
tariff_parsed = true;
break;
case 0x0505:
furi_string_set_str(tariff_name, "Social (large families)");
tariff_parsed = true;
break;
case 0x0528:
furi_string_set_str(tariff_name, "Social (handicapped)");
tariff_parsed = true;
break;
default:
furi_string_set_str(tariff_name, "Unknown");
tariff_parsed = false;
break;
}
return tariff_parsed;
}
static bool zolotaya_korona_online_parse(const NfcDevice* device, FuriString* parsed_data) {
furi_assert(device);
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
bool parsed = false;
do {
// Verify info sector data (card number prefix)
const uint8_t start_trip_block_number =
mf_classic_get_first_block_num_of_sector(TRIP_SECTOR_NUM);
const uint8_t start_info_block_number =
mf_classic_get_first_block_num_of_sector(INFO_SECTOR_NUM);
const uint8_t* block_start_ptr = &data->block[start_info_block_number].data[3];
// Validate card number
bool is_bcd;
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &is_bcd);
if(!is_bcd) break;
if(card_number_prefix != 9643) break;
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &is_bcd) / 10;
if(!is_bcd) break;
// Parse data
FuriString* tariff_name = furi_string_alloc();
block_start_ptr = &data->block[start_info_block_number].data[1];
const uint16_t tariff = nfc_util_bytes2num(block_start_ptr, 2);
parse_online_card_tariff(tariff, tariff_name);
block_start_ptr = &data->block[start_trip_block_number].data[0];
const uint8_t region_number = nfc_util_bytes2num(block_start_ptr, 1);
furi_string_cat_printf(
parsed_data,
"\e#Zolotaya korona\nCard number: %u%015llu\nTariff: %02X.%02X: %s\nRegion: %u\n",
card_number_prefix,
card_number_postfix,
tariff / 256,
tariff % 256,
furi_string_get_cstr(tariff_name),
region_number);
furi_string_free(tariff_name);
parsed = true;
} while(false);
return parsed;
}
/* Actual implementation of app<>plugin interface */
static const NfcSupportedCardsPlugin zolotaya_korona_online_plugin = {
.protocol = NfcProtocolMfClassic,
.verify = NULL,
.read = NULL,
.parse = zolotaya_korona_online_parse,
};
/* Plugin descriptor to comply with basic plugin specification */
static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor = {
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
.entry_point = &zolotaya_korona_online_plugin,
};
/* Plugin entry point - must return a pointer to const descriptor */
const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() {
return &zolotaya_korona_online_plugin_descriptor;
}