Skip to content

Commit 9aae348

Browse files
Leptopt1losxMasterX
authored andcommitted
Zolotaya Korona Online parser added
1 parent 634e841 commit 9aae348

File tree

4 files changed

+197
-19
lines changed

4 files changed

+197
-19
lines changed

applications/main/nfc/application.fam

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ App(
146146
sources=["plugins/supported_cards/zolotaya_korona.c"],
147147
)
148148

149+
App(
150+
appid="zolotaya_korona_online_parser",
151+
apptype=FlipperAppType.PLUGIN,
152+
entry_point="zolotaya_korona_online_plugin_ep",
153+
targets=["f7"],
154+
requires=["nfc"],
155+
sources=["plugins/supported_cards/zolotaya_korona_online.c"],
156+
)
157+
149158
App(
150159
appid="hid_parser",
151160
apptype=FlipperAppType.PLUGIN,

applications/main/nfc/plugins/supported_cards/kazan.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,8 @@ static bool kazan_parse(const NfcDevice* device, FuriString* parsed_data) {
328328
last_trip.minute);
329329
}
330330

331+
furi_string_free(tariff_name);
332+
331333
parsed = true;
332334
} while(false);
333335

applications/main/nfc/plugins/supported_cards/zolotaya_korona.c

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,6 @@
3434
#define PURSE_SECTOR_NUM (6)
3535
#define INFO_SECTOR_NUM (15)
3636

37-
typedef struct {
38-
uint64_t a;
39-
uint64_t b;
40-
} MfClassicKeyPair;
41-
4237
// Sector 15 data. Byte [11] contains the mistake. If byte [11] was 0xEF, bytes [1-18] means "ЗАО Золотая Корона"
4338
static const uint8_t info_sector_signature[] = {0xE2, 0x87, 0x80, 0x8E, 0x20, 0x87, 0xAE,
4439
0xAB, 0xAE, 0xF2, 0xA0, 0xEF, 0x20, 0x8A,
@@ -76,19 +71,24 @@ void timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) {
7671
datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE;
7772
}
7873

79-
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes) {
74+
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
8075
furi_assert(src);
76+
furi_assert(len_bytes <= 9);
8177

82-
uint64_t res = 0;
78+
uint64_t result = 0;
79+
*is_bcd = true;
8380

8481
for(uint8_t i = 0; i < len_bytes; i++) {
85-
res *= 10;
86-
res += src[i] / 16;
87-
res *= 10;
88-
res += src[i] % 16;
82+
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
83+
84+
result *= 10;
85+
result += src[i] / 16;
86+
87+
result *= 10;
88+
result += src[i] % 16;
8989
}
9090

91-
return res;
91+
return result;
9292
}
9393

9494
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
121121

122122
// INFO SECTOR
123123
// block 1
124-
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1);
124+
const uint8_t region_number = bytes2num_bcd(block_start_ptr + 10, 1, &verified);
125125

126126
// block 2
127127
block_start_ptr = &data->block[start_info_block_number + 2].data[4];
128-
const uint64_t card_number =
129-
bytes2num_bcd(block_start_ptr, 9) * 10 + bytes2num_bcd(block_start_ptr + 9, 1) / 10;
128+
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &verified);
129+
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &verified) / 10;
130130

131131
// TRIP SECTOR
132132
const uint8_t start_trip_block_number =
@@ -157,7 +157,7 @@ static bool zolotaya_korona_parse(const NfcDevice* device, FuriString* parsed_da
157157
block_start_ptr = &data->block[start_trip_block_number + 2].data[0];
158158
const char validator_first_letter =
159159
nfc_util_bytes2num_little_endian(block_start_ptr + 1, 1);
160-
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3);
160+
const uint32_t validator_id = bytes2num_bcd(block_start_ptr + 2, 3, &verified);
161161
const uint32_t last_trip_timestamp =
162162
nfc_util_bytes2num_little_endian(block_start_ptr + 6, 4);
163163
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
174174
block_start_ptr = &data->block[start_purse_block_number].data[0];
175175

176176
// block 0
177-
uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
177+
const uint32_t balance = nfc_util_bytes2num_little_endian(block_start_ptr, 4);
178178

179179
uint32_t balance_rub = balance / 100;
180180
uint8_t balance_kop = balance % 100;
181181

182182
furi_string_cat_printf(
183183
parsed_data,
184-
"\e#Zolotaya korona\nCard number: %llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
185-
card_number,
184+
"\e#Zolotaya korona\nCard number: %u%015llu\nRegion: %u\nBalance: %lu.%02u RUR\nPrev. balance: %lu.%02u RUR",
185+
card_number_prefix,
186+
card_number_postfix,
186187
region_number,
187188
balance_rub,
188189
balance_kop,
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Parser for Zolotaya Korona Online card (Russia).
3+
* Tariffs research by DNZ1393
4+
*
5+
* Copyright 2023 Leptoptilos <[email protected]>
6+
*
7+
* This program is free software: you can redistribute it and/or modify it
8+
* under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful, but
13+
* WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
#include "furi_hal_rtc.h"
21+
#include "nfc_supported_card_plugin.h"
22+
23+
#include "protocols/mf_classic/mf_classic.h"
24+
#include <flipper_application/flipper_application.h>
25+
26+
#include <nfc/nfc_device.h>
27+
#include <nfc/helpers/nfc_util.h>
28+
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
29+
30+
#define TAG "Zolotaya Korona Online"
31+
32+
#define TRIP_SECTOR_NUM (4)
33+
#define INFO_SECTOR_NUM (15)
34+
35+
uint64_t bytes2num_bcd(const uint8_t* src, uint8_t len_bytes, bool* is_bcd) {
36+
furi_assert(src);
37+
furi_assert(len_bytes <= 9);
38+
39+
uint64_t result = 0;
40+
*is_bcd = true;
41+
42+
for(uint8_t i = 0; i < len_bytes; i++) {
43+
if(((src[i] / 16) > 9) || ((src[i] % 16) > 9)) *is_bcd = false;
44+
45+
result *= 10;
46+
result += src[i] / 16;
47+
48+
result *= 10;
49+
result += src[i] % 16;
50+
}
51+
52+
return result;
53+
}
54+
55+
bool parse_online_card_tariff(uint16_t tariff_num, FuriString* tariff_name) {
56+
bool tariff_parsed = false;
57+
58+
switch(tariff_num) {
59+
case 0x0100:
60+
furi_string_set_str(tariff_name, "Standart (online)");
61+
tariff_parsed = true;
62+
break;
63+
case 0x0101:
64+
case 0x0121:
65+
furi_string_set_str(tariff_name, "Standart (airtag)");
66+
tariff_parsed = true;
67+
break;
68+
case 0x0401:
69+
furi_string_set_str(tariff_name, "Student (50%% discount)");
70+
tariff_parsed = true;
71+
break;
72+
case 0x0402:
73+
furi_string_set_str(tariff_name, "Student (travel)");
74+
tariff_parsed = true;
75+
break;
76+
case 0x0002:
77+
furi_string_set_str(tariff_name, "School (50%% discount)");
78+
tariff_parsed = true;
79+
break;
80+
case 0x0505:
81+
furi_string_set_str(tariff_name, "Social (large families)");
82+
tariff_parsed = true;
83+
break;
84+
case 0x0528:
85+
furi_string_set_str(tariff_name, "Social (handicapped)");
86+
tariff_parsed = true;
87+
break;
88+
default:
89+
furi_string_set_str(tariff_name, "Unknown");
90+
tariff_parsed = false;
91+
break;
92+
}
93+
94+
return tariff_parsed;
95+
}
96+
97+
static bool zolotaya_korona_online_parse(const NfcDevice* device, FuriString* parsed_data) {
98+
furi_assert(device);
99+
100+
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
101+
102+
bool parsed = false;
103+
104+
do {
105+
// Verify info sector data (card number prefix)
106+
const uint8_t start_trip_block_number =
107+
mf_classic_get_first_block_num_of_sector(TRIP_SECTOR_NUM);
108+
const uint8_t start_info_block_number =
109+
mf_classic_get_first_block_num_of_sector(INFO_SECTOR_NUM);
110+
const uint8_t* block_start_ptr = &data->block[start_info_block_number].data[3];
111+
112+
// Validate card number
113+
bool is_bcd;
114+
const uint16_t card_number_prefix = bytes2num_bcd(block_start_ptr, 2, &is_bcd);
115+
if(!is_bcd) break;
116+
if(card_number_prefix != 9643) break;
117+
const uint64_t card_number_postfix = bytes2num_bcd(block_start_ptr + 2, 8, &is_bcd) / 10;
118+
if(!is_bcd) break;
119+
120+
// Parse data
121+
FuriString* tariff_name = furi_string_alloc();
122+
123+
block_start_ptr = &data->block[start_info_block_number].data[1];
124+
const uint16_t tariff = nfc_util_bytes2num(block_start_ptr, 2);
125+
parse_online_card_tariff(tariff, tariff_name);
126+
127+
block_start_ptr = &data->block[start_trip_block_number].data[0];
128+
const uint8_t region_number = nfc_util_bytes2num(block_start_ptr, 1);
129+
130+
furi_string_cat_printf(
131+
parsed_data,
132+
"\e#Zolotaya korona\nCard number: %u%015llu\nTariff: %02X.%02X: %s\nRegion: %u\n",
133+
card_number_prefix,
134+
card_number_postfix,
135+
tariff / 256,
136+
tariff % 256,
137+
furi_string_get_cstr(tariff_name),
138+
region_number);
139+
140+
furi_string_free(tariff_name);
141+
142+
parsed = true;
143+
} while(false);
144+
145+
return parsed;
146+
}
147+
148+
/* Actual implementation of app<>plugin interface */
149+
static const NfcSupportedCardsPlugin zolotaya_korona_online_plugin = {
150+
.protocol = NfcProtocolMfClassic,
151+
.verify = NULL,
152+
.read = NULL,
153+
.parse = zolotaya_korona_online_parse,
154+
};
155+
156+
/* Plugin descriptor to comply with basic plugin specification */
157+
static const FlipperAppPluginDescriptor zolotaya_korona_online_plugin_descriptor = {
158+
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
159+
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
160+
.entry_point = &zolotaya_korona_online_plugin,
161+
};
162+
163+
/* Plugin entry point - must return a pointer to const descriptor */
164+
const FlipperAppPluginDescriptor* zolotaya_korona_online_plugin_ep() {
165+
return &zolotaya_korona_online_plugin_descriptor;
166+
}

0 commit comments

Comments
 (0)