itso.c 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /* itso.c - Parser for ITSO cards (United Kingdom). */
  2. #include "../../metroflip_i.h"
  3. #include <flipper_application.h>
  4. #include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
  5. #include <lib/nfc/protocols/mf_desfire/mf_desfire_poller.h>
  6. #include <lib/toolbox/strint.h>
  7. #include "../../api/metroflip/metroflip_api.h"
  8. #include "../../metroflip_plugins.h"
  9. #include <applications/services/locale/locale.h>
  10. #include <datetime.h>
  11. #define TAG "Metroflip:Scene:ITSO"
  12. static const MfDesfireApplicationId itso_app_id = {.data = {0x16, 0x02, 0xa0}};
  13. static const MfDesfireFileId itso_file_id = 0x0f;
  14. int64_t swap_int64(int64_t val) {
  15. val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
  16. val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
  17. return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
  18. }
  19. uint64_t swap_uint64(uint64_t val) {
  20. val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL);
  21. val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL);
  22. return (val << 32) | (val >> 32);
  23. }
  24. bool itso_parse(const MfDesfireData* data, FuriString* parsed_data) {
  25. furi_assert(parsed_data);
  26. bool parsed = false;
  27. do {
  28. const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id);
  29. if(app == NULL) break;
  30. typedef struct {
  31. uint64_t part1;
  32. uint64_t part2;
  33. uint64_t part3;
  34. uint64_t part4;
  35. } ItsoFile;
  36. const MfDesfireFileSettings* file_settings =
  37. mf_desfire_get_file_settings(app, &itso_file_id);
  38. if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
  39. file_settings->data.size < sizeof(ItsoFile))
  40. break;
  41. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_file_id);
  42. if(file_data == NULL) break;
  43. const ItsoFile* itso_file = simple_array_cget_data(file_data->data);
  44. uint64_t x1 = swap_uint64(itso_file->part1);
  45. uint64_t x2 = swap_uint64(itso_file->part2);
  46. char cardBuff[32];
  47. char dateBuff[18];
  48. snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2);
  49. snprintf(dateBuff, sizeof(dateBuff), "%llx", x2);
  50. char* cardp = cardBuff + 4;
  51. cardp[18] = '\0';
  52. // All itso card numbers are prefixed with "633597"
  53. if(strncmp(cardp, "633597", 6) != 0) break;
  54. char* datep = dateBuff + 12;
  55. dateBuff[17] = '\0';
  56. // DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997
  57. uint32_t dateStamp;
  58. if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) {
  59. return false;
  60. }
  61. uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U;
  62. furi_string_set(parsed_data, "\e#ITSO Card\n");
  63. // Digit count in each space-separated group
  64. static const uint8_t digit_count[] = {6, 4, 4, 4};
  65. for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) {
  66. for(uint32_t j = 0; j < digit_count[i]; ++j) {
  67. furi_string_push_back(parsed_data, cardp[j + k]);
  68. }
  69. furi_string_push_back(parsed_data, ' ');
  70. }
  71. DateTime timestamp = {0};
  72. datetime_timestamp_to_datetime(unixTimestamp, &timestamp);
  73. FuriString* timestamp_str = furi_string_alloc();
  74. locale_format_date(timestamp_str, &timestamp, locale_get_date_format(), "-");
  75. furi_string_cat(parsed_data, "\nExpiry: ");
  76. furi_string_cat(parsed_data, timestamp_str);
  77. furi_string_free(timestamp_str);
  78. parsed = true;
  79. } while(false);
  80. return parsed;
  81. }
  82. static NfcCommand itso_poller_callback(NfcGenericEvent event, void* context) {
  83. furi_assert(event.protocol == NfcProtocolMfDesfire);
  84. Metroflip* app = context;
  85. NfcCommand command = NfcCommandContinue;
  86. FuriString* parsed_data = furi_string_alloc();
  87. Widget* widget = app->widget;
  88. furi_string_reset(app->text_box_store);
  89. const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
  90. if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
  91. nfc_device_set_data(
  92. app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
  93. const MfDesfireData* data = nfc_device_get_data(app->nfc_device, NfcProtocolMfDesfire);
  94. if(!itso_parse(data, parsed_data)) {
  95. furi_string_reset(app->text_box_store);
  96. FURI_LOG_I(TAG, "Unknown card type");
  97. furi_string_printf(parsed_data, "\e#Unknown card\n");
  98. }
  99. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  100. widget_add_button_element(
  101. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  102. widget_add_button_element(
  103. widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
  104. furi_string_free(parsed_data);
  105. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  106. metroflip_app_blink_stop(app);
  107. command = NfcCommandStop;
  108. } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
  109. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerSuccess);
  110. command = NfcCommandContinue;
  111. }
  112. return command;
  113. }
  114. static void itso_on_enter(Metroflip* app) {
  115. dolphin_deed(DolphinDeedNfcRead);
  116. if(app->data_loaded) {
  117. Storage* storage = furi_record_open(RECORD_STORAGE);
  118. FlipperFormat* ff = flipper_format_file_alloc(storage);
  119. if(flipper_format_file_open_existing(ff, app->file_path)) {
  120. MfDesfireData* data = mf_desfire_alloc();
  121. mf_desfire_load(data, ff, 2);
  122. FuriString* parsed_data = furi_string_alloc();
  123. Widget* widget = app->widget;
  124. furi_string_reset(app->text_box_store);
  125. if(!itso_parse(data, parsed_data)) {
  126. furi_string_reset(app->text_box_store);
  127. FURI_LOG_I(TAG, "Unknown card type");
  128. furi_string_printf(parsed_data, "\e#Unknown card\n");
  129. }
  130. widget_add_text_scroll_element(
  131. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  132. widget_add_button_element(
  133. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  134. widget_add_button_element(
  135. widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
  136. mf_desfire_free(data);
  137. furi_string_free(parsed_data);
  138. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  139. }
  140. flipper_format_free(ff);
  141. } else {
  142. // Setup view
  143. Popup* popup = app->popup;
  144. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  145. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  146. // Start worker
  147. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  148. nfc_scanner_alloc(app->nfc);
  149. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
  150. nfc_poller_start(app->poller, itso_poller_callback, app);
  151. metroflip_app_blink_start(app);
  152. }
  153. }
  154. static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
  155. bool consumed = false;
  156. if(event.type == SceneManagerEventTypeCustom) {
  157. if(event.event == MetroflipCustomEventCardDetected) {
  158. Popup* popup = app->popup;
  159. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  160. consumed = true;
  161. } else if(event.event == MetroflipCustomEventCardLost) {
  162. Popup* popup = app->popup;
  163. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  164. consumed = true;
  165. } else if(event.event == MetroflipCustomEventWrongCard) {
  166. Popup* popup = app->popup;
  167. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  168. consumed = true;
  169. } else if(event.event == MetroflipCustomEventPollerFail) {
  170. Popup* popup = app->popup;
  171. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  172. consumed = true;
  173. }
  174. } else if(event.type == SceneManagerEventTypeBack) {
  175. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  176. scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
  177. consumed = true;
  178. }
  179. return consumed;
  180. }
  181. static void itso_on_exit(Metroflip* app) {
  182. widget_reset(app->widget);
  183. metroflip_app_blink_stop(app);
  184. if(app->poller && !app->data_loaded) {
  185. nfc_poller_stop(app->poller);
  186. nfc_poller_free(app->poller);
  187. }
  188. }
  189. /* Actual implementation of app<>plugin interface */
  190. static const MetroflipPlugin itso_plugin = {
  191. .card_name = "ITSO",
  192. .plugin_on_enter = itso_on_enter,
  193. .plugin_on_event = itso_on_event,
  194. .plugin_on_exit = itso_on_exit,
  195. };
  196. /* Plugin descriptor to comply with basic plugin specification */
  197. static const FlipperAppPluginDescriptor itso_plugin_descriptor = {
  198. .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
  199. .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
  200. .entry_point = &itso_plugin,
  201. };
  202. /* Plugin entry point - must return a pointer to const descriptor */
  203. const FlipperAppPluginDescriptor* itso_plugin_ep(void) {
  204. return &itso_plugin_descriptor;
  205. }