metroflip_scene_myki.c 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. #include <flipper_application.h>
  2. #include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
  3. #include <stdio.h>
  4. #include "../metroflip_i.h"
  5. #include <nfc/protocols/mf_desfire/mf_desfire_poller.h>
  6. static const MfDesfireApplicationId myki_app_id = {.data = {0x00, 0x11, 0xf2}};
  7. static const MfDesfireFileId myki_file_id = 0x0f;
  8. static uint8_t myki_calculate_luhn(uint64_t number) {
  9. // https://en.wikipedia.org/wiki/Luhn_algorithm
  10. // Drop existing check digit to form payload
  11. uint64_t payload = number / 10;
  12. int sum = 0;
  13. int position = 0;
  14. while(payload > 0) {
  15. int digit = payload % 10;
  16. if(position % 2 == 0) {
  17. digit *= 2;
  18. }
  19. if(digit > 9) {
  20. digit = (digit / 10) + (digit % 10);
  21. }
  22. sum += digit;
  23. payload /= 10;
  24. position++;
  25. }
  26. return (10 - (sum % 10)) % 10;
  27. }
  28. static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) {
  29. furi_assert(device);
  30. furi_assert(parsed_data);
  31. bool parsed = false;
  32. do {
  33. const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
  34. const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id);
  35. if(app == NULL) break;
  36. typedef struct {
  37. uint32_t top;
  38. uint32_t bottom;
  39. } MykiFile;
  40. const MfDesfireFileSettings* file_settings =
  41. mf_desfire_get_file_settings(app, &myki_file_id);
  42. if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
  43. file_settings->data.size < sizeof(MykiFile))
  44. break;
  45. const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_file_id);
  46. if(file_data == NULL) break;
  47. const MykiFile* myki_file = simple_array_cget_data(file_data->data);
  48. // All myki card numbers are prefixed with "308425"
  49. if(myki_file->top != 308425UL) break;
  50. // Card numbers are always 15 digits in length
  51. if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) break;
  52. uint64_t card_number = myki_file->top * 1000000000ULL + myki_file->bottom * 10UL;
  53. // Stored card number doesn't include check digit
  54. card_number += myki_calculate_luhn(card_number);
  55. furi_string_set(parsed_data, "\e#myki\nNo.: ");
  56. // Stylise card number according to the physical card
  57. char card_string[20];
  58. snprintf(card_string, sizeof(card_string), "%llu", card_number);
  59. // Digit count in each space-separated group
  60. static const uint8_t digit_count[] = {1, 5, 4, 4, 1};
  61. for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) {
  62. for(uint32_t j = 0; j < digit_count[i]; ++j) {
  63. furi_string_push_back(parsed_data, card_string[j + k]);
  64. }
  65. furi_string_push_back(parsed_data, ' ');
  66. }
  67. parsed = true;
  68. } while(false);
  69. return parsed;
  70. }
  71. static NfcCommand metroflip_scene_myki_poller_callback(NfcGenericEvent event, void* context) {
  72. furi_assert(event.protocol == NfcProtocolMfDesfire);
  73. Metroflip* app = context;
  74. NfcCommand command = NfcCommandContinue;
  75. FuriString* parsed_data = furi_string_alloc();
  76. Widget* widget = app->widget;
  77. furi_string_reset(app->text_box_store);
  78. const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
  79. if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
  80. nfc_device_set_data(
  81. app->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(app->poller));
  82. myki_parse(app->nfc_device, parsed_data);
  83. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  84. widget_add_button_element(
  85. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  86. furi_string_free(parsed_data);
  87. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  88. metroflip_app_blink_stop(app);
  89. command = NfcCommandStop;
  90. } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) {
  91. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerSuccess);
  92. command = NfcCommandReset;
  93. }
  94. return command;
  95. }
  96. void metroflip_scene_myki_on_enter(void* context) {
  97. Metroflip* app = context;
  98. dolphin_deed(DolphinDeedNfcRead);
  99. // Setup view
  100. Popup* popup = app->popup;
  101. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  102. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  103. // Start worker
  104. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  105. nfc_scanner_alloc(app->nfc);
  106. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfDesfire);
  107. nfc_poller_start(app->poller, metroflip_scene_myki_poller_callback, app);
  108. metroflip_app_blink_start(app);
  109. }
  110. bool metroflip_scene_myki_on_event(void* context, SceneManagerEvent event) {
  111. Metroflip* app = context;
  112. bool consumed = false;
  113. if(event.type == SceneManagerEventTypeCustom) {
  114. if(event.event == MetroflipCustomEventCardDetected) {
  115. Popup* popup = app->popup;
  116. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  117. consumed = true;
  118. } else if(event.event == MetroflipCustomEventCardLost) {
  119. Popup* popup = app->popup;
  120. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  121. consumed = true;
  122. } else if(event.event == MetroflipCustomEventWrongCard) {
  123. Popup* popup = app->popup;
  124. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  125. consumed = true;
  126. } else if(event.event == MetroflipCustomEventPollerFail) {
  127. Popup* popup = app->popup;
  128. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  129. consumed = true;
  130. }
  131. } else if(event.type == SceneManagerEventTypeBack) {
  132. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  133. consumed = true;
  134. }
  135. return consumed;
  136. }
  137. void metroflip_scene_myki_on_exit(void* context) {
  138. Metroflip* app = context;
  139. widget_reset(app->widget);
  140. if(app->poller) {
  141. nfc_poller_stop(app->poller);
  142. nfc_poller_free(app->poller);
  143. }
  144. }