metroflip_scene_bip.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. * Parser for bip card (Georgia).
  3. *
  4. * Copyright 2023 Leptoptilos <leptoptilos@icloud.com>
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <flipper_application.h>
  20. #include "../metroflip_i.h"
  21. #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
  22. #include <nfc/protocols/mf_classic/mf_classic.h>
  23. #include <nfc/protocols/mf_classic/mf_classic_poller.h>
  24. #include <dolphin/dolphin.h>
  25. #include <bit_lib.h>
  26. #include <furi_hal.h>
  27. #include <nfc/nfc.h>
  28. #include <nfc/nfc_device.h>
  29. #include <nfc/nfc_listener.h>
  30. #include <locale/locale.h>
  31. #define TAG "Metroflip:Scene:Bip"
  32. #define BIP_CARD_ID_SECTOR_NUMBER (0)
  33. #define BIP_BALANCE_SECTOR_NUMBER (8)
  34. #define BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER (5)
  35. #define BIP_LAST_TOP_UPS_SECTOR_NUMBER (10)
  36. #define BIP_TRIPS_INFO_SECTOR_NUMBER (11)
  37. typedef struct {
  38. DateTime datetime;
  39. uint16_t amount;
  40. } BipTransaction;
  41. static void bip_parse_datetime(const MfClassicBlock* block, DateTime* parsed_data) {
  42. furi_assert(block);
  43. furi_assert(parsed_data);
  44. parsed_data->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f;
  45. parsed_data->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf;
  46. parsed_data->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f);
  47. parsed_data->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f;
  48. parsed_data->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f;
  49. parsed_data->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f;
  50. }
  51. static void bip_print_datetime(const DateTime* datetime, FuriString* str) {
  52. furi_assert(datetime);
  53. furi_assert(str);
  54. LocaleDateFormat date_format = locale_get_date_format();
  55. const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
  56. FuriString* date_str = furi_string_alloc();
  57. locale_format_date(date_str, datetime, date_format, separator);
  58. FuriString* time_str = furi_string_alloc();
  59. locale_format_time(time_str, datetime, locale_get_time_format(), true);
  60. furi_string_cat_printf(
  61. str, "%s %s", furi_string_get_cstr(date_str), furi_string_get_cstr(time_str));
  62. furi_string_free(date_str);
  63. furi_string_free(time_str);
  64. }
  65. static int datetime_cmp(const DateTime* dt_1, const DateTime* dt_2) {
  66. furi_assert(dt_1);
  67. furi_assert(dt_2);
  68. if(dt_1->year != dt_2->year) {
  69. return dt_1->year - dt_2->year;
  70. }
  71. if(dt_1->month != dt_2->month) {
  72. return dt_1->month - dt_2->month;
  73. }
  74. if(dt_1->day != dt_2->day) {
  75. return dt_1->day - dt_2->day;
  76. }
  77. if(dt_1->hour != dt_2->hour) {
  78. return dt_1->hour - dt_2->hour;
  79. }
  80. if(dt_1->minute != dt_2->minute) {
  81. return dt_1->minute - dt_2->minute;
  82. }
  83. if(dt_1->second != dt_2->second) {
  84. return dt_1->second - dt_2->second;
  85. }
  86. return 0;
  87. }
  88. static bool is_bip_block_empty(const MfClassicBlock* block) {
  89. furi_assert(block);
  90. // check if all but last byte are zero (last is checksum)
  91. for(size_t i = 0; i < sizeof(block->data) - 1; i++) {
  92. if(block->data[i] != 0) {
  93. return false;
  94. }
  95. }
  96. return true;
  97. }
  98. static bool
  99. bip_parse(const NfcDevice* device, FuriString* parsed_data, const MfClassicData* data) {
  100. furi_assert(device);
  101. furi_assert(parsed_data);
  102. struct {
  103. uint32_t card_id;
  104. uint16_t balance;
  105. uint16_t flags;
  106. DateTime trip_time_window;
  107. BipTransaction top_ups[3];
  108. BipTransaction charges[3];
  109. } bip_data = {0};
  110. bool parsed = false;
  111. do {
  112. // verify sector 0 key A
  113. MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
  114. if(data->type != MfClassicType1k) break;
  115. uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
  116. if(key != bip_1k_keys[0].a) {
  117. break;
  118. }
  119. // Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7
  120. const uint8_t card_id_start_block_num =
  121. mf_classic_get_first_block_num_of_sector(BIP_CARD_ID_SECTOR_NUMBER);
  122. const uint8_t* block_start_ptr = &data->block[card_id_start_block_num + 1].data[0];
  123. bip_data.card_id = bit_lib_bytes_to_num_le(block_start_ptr + 4, 4);
  124. // Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1
  125. const uint8_t balance_start_block_num =
  126. mf_classic_get_first_block_num_of_sector(BIP_BALANCE_SECTOR_NUMBER);
  127. block_start_ptr = &data->block[balance_start_block_num + 1].data[0];
  128. bip_data.balance = bit_lib_bytes_to_num_le(block_start_ptr, 2);
  129. // Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3
  130. bip_data.flags = bit_lib_bytes_to_num_le(block_start_ptr + 2, 2);
  131. // Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7
  132. const uint8_t trip_time_window_start_block_num =
  133. mf_classic_get_first_block_num_of_sector(BIP_TRIP_TIME_WINDOW_SECTOR_NUMBER);
  134. const MfClassicBlock* trip_window_block_ptr =
  135. &data->block[trip_time_window_start_block_num + 1];
  136. bip_parse_datetime(trip_window_block_ptr, &bip_data.trip_time_window);
  137. // Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10
  138. const uint8_t top_ups_start_block_num =
  139. mf_classic_get_first_block_num_of_sector(BIP_LAST_TOP_UPS_SECTOR_NUMBER);
  140. for(size_t i = 0; i < 3; i++) {
  141. const MfClassicBlock* block = &data->block[top_ups_start_block_num + i];
  142. if(is_bip_block_empty(block)) continue;
  143. BipTransaction* top_up = &bip_data.top_ups[i];
  144. bip_parse_datetime(block, &top_up->datetime);
  145. top_up->amount = bit_lib_bytes_to_num_le(&block->data[9], 2) >> 2;
  146. }
  147. // Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11
  148. const uint8_t trips_start_block_num =
  149. mf_classic_get_first_block_num_of_sector(BIP_TRIPS_INFO_SECTOR_NUMBER);
  150. for(size_t i = 0; i < 3; i++) {
  151. const MfClassicBlock* block = &data->block[trips_start_block_num + i];
  152. if(is_bip_block_empty(block)) continue;
  153. BipTransaction* charge = &bip_data.charges[i];
  154. bip_parse_datetime(block, &charge->datetime);
  155. charge->amount = bit_lib_bytes_to_num_le(&block->data[10], 2) >> 2;
  156. }
  157. // All data is now parsed and stored in bip_data, now print it
  158. // Print basic info
  159. furi_string_printf(
  160. parsed_data,
  161. "\e#Tarjeta Bip!\n"
  162. "Card Number: %lu\n"
  163. "Balance: $%hu (flags %hu)\n"
  164. "Current Trip Window Ends:\n @",
  165. bip_data.card_id,
  166. bip_data.balance,
  167. bip_data.flags);
  168. bip_print_datetime(&bip_data.trip_time_window, parsed_data);
  169. // Find newest top-up
  170. size_t newest_top_up = 0;
  171. for(size_t i = 1; i < 3; i++) {
  172. const DateTime* newest = &bip_data.top_ups[newest_top_up].datetime;
  173. const DateTime* current = &bip_data.top_ups[i].datetime;
  174. if(datetime_cmp(current, newest) > 0) {
  175. newest_top_up = i;
  176. }
  177. }
  178. // Print top-ups, newest first
  179. furi_string_cat_printf(parsed_data, "\n\e#Last Top-ups");
  180. for(size_t i = 0; i < 3; i++) {
  181. const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3];
  182. furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount);
  183. bip_print_datetime(&top_up->datetime, parsed_data);
  184. }
  185. // Find newest charge
  186. size_t newest_charge = 0;
  187. for(size_t i = 1; i < 3; i++) {
  188. const DateTime* newest = &bip_data.charges[newest_charge].datetime;
  189. const DateTime* current = &bip_data.charges[i].datetime;
  190. if(datetime_cmp(current, newest) > 0) {
  191. newest_charge = i;
  192. }
  193. }
  194. // Print charges
  195. furi_string_cat_printf(parsed_data, "\n\e#Last Charges (Trips)");
  196. for(size_t i = 0; i < 3; i++) {
  197. const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3];
  198. furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount);
  199. bip_print_datetime(&charge->datetime, parsed_data);
  200. }
  201. parsed = true;
  202. } while(false);
  203. return parsed;
  204. }
  205. static NfcCommand metroflip_scene_bip_poller_callback(NfcGenericEvent event, void* context) {
  206. furi_assert(context);
  207. furi_assert(event.event_data);
  208. furi_assert(event.protocol == NfcProtocolMfClassic);
  209. NfcCommand command = NfcCommandContinue;
  210. const MfClassicPollerEvent* mfc_event = event.event_data;
  211. Metroflip* app = context;
  212. if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
  213. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
  214. command = NfcCommandContinue;
  215. } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
  216. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardLost);
  217. app->sec_num = 0;
  218. command = NfcCommandStop;
  219. } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
  220. mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
  221. } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
  222. MfClassicKey key = {0};
  223. bit_lib_num_to_bytes_be(bip_1k_keys[app->sec_num].a, COUNT_OF(key.data), key.data);
  224. MfClassicKeyType key_type = MfClassicKeyTypeA;
  225. mfc_event->data->read_sector_request_data.sector_num = app->sec_num;
  226. mfc_event->data->read_sector_request_data.key = key;
  227. mfc_event->data->read_sector_request_data.key_type = key_type;
  228. mfc_event->data->read_sector_request_data.key_provided = true;
  229. if(app->sec_num == 16) {
  230. mfc_event->data->read_sector_request_data.key_provided = false;
  231. app->sec_num = 0;
  232. }
  233. app->sec_num++;
  234. } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
  235. nfc_device_set_data(
  236. app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
  237. const MfClassicData* mfc_data = nfc_device_get_data(app->nfc_device, NfcProtocolMfClassic);
  238. FuriString* parsed_data = furi_string_alloc();
  239. Widget* widget = app->widget;
  240. dolphin_deed(DolphinDeedNfcReadSuccess);
  241. furi_string_reset(app->text_box_store);
  242. if(!bip_parse(app->nfc_device, parsed_data, mfc_data)) {
  243. furi_string_reset(app->text_box_store);
  244. FURI_LOG_I(TAG, "Unknown card type");
  245. furi_string_printf(parsed_data, "\e#Unknown card\n");
  246. }
  247. metroflip_app_blink_stop(app);
  248. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  249. widget_add_button_element(
  250. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  251. furi_string_free(parsed_data);
  252. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  253. command = NfcCommandStop;
  254. } else if(mfc_event->type == MfClassicPollerEventTypeFail) {
  255. FURI_LOG_I(TAG, "fail");
  256. command = NfcCommandStop;
  257. }
  258. return command;
  259. }
  260. void metroflip_scene_bip_on_enter(void* context) {
  261. Metroflip* app = context;
  262. dolphin_deed(DolphinDeedNfcRead);
  263. app->sec_num = 0;
  264. // Setup view
  265. Popup* popup = app->popup;
  266. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  267. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  268. // Start worker
  269. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  270. nfc_scanner_alloc(app->nfc);
  271. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
  272. nfc_poller_start(app->poller, metroflip_scene_bip_poller_callback, app);
  273. metroflip_app_blink_start(app);
  274. }
  275. bool metroflip_scene_bip_on_event(void* context, SceneManagerEvent event) {
  276. Metroflip* app = context;
  277. bool consumed = false;
  278. if(event.type == SceneManagerEventTypeCustom) {
  279. if(event.event == MetroflipCustomEventCardDetected) {
  280. Popup* popup = app->popup;
  281. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  282. consumed = true;
  283. } else if(event.event == MetroflipCustomEventCardLost) {
  284. Popup* popup = app->popup;
  285. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  286. consumed = true;
  287. } else if(event.event == MetroflipCustomEventWrongCard) {
  288. Popup* popup = app->popup;
  289. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  290. consumed = true;
  291. } else if(event.event == MetroflipCustomEventPollerFail) {
  292. Popup* popup = app->popup;
  293. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  294. consumed = true;
  295. }
  296. } else if(event.type == SceneManagerEventTypeBack) {
  297. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  298. consumed = true;
  299. }
  300. return consumed;
  301. }
  302. void metroflip_scene_bip_on_exit(void* context) {
  303. Metroflip* app = context;
  304. widget_reset(app->widget);
  305. if(app->poller) {
  306. nfc_poller_stop(app->poller);
  307. nfc_poller_free(app->poller);
  308. }
  309. // Clear view
  310. popup_reset(app->popup);
  311. metroflip_app_blink_stop(app);
  312. }