metroflip_scene_ravkav.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #include "../metroflip_i.h"
  2. #include <datetime.h>
  3. #include <dolphin/dolphin.h>
  4. #include <locale/locale.h>
  5. #include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
  6. #define Metroflip_POLLER_MAX_BUFFER_SIZE 1024
  7. #define TAG "Metroflip:Scene:RavKav"
  8. #define epoch_ravkav 852073200
  9. uint8_t apdu_success[] = {0x90, 0x00};
  10. // balance
  11. uint8_t select_balance_file[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x2A, 0x00};
  12. uint8_t read_balance[] = {0x94, 0xb2, 0x01, 0x04, 0x1D};
  13. // balance
  14. uint8_t select_events_aid[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x10, 0x00};
  15. uint8_t read_event[] = {0x00, 0xb2, 0x01, 0x04, 0x1D};
  16. void locale_format_datetime_cat(FuriString* out, const DateTime* dt) {
  17. // helper to print datetimes
  18. FuriString* s = furi_string_alloc();
  19. LocaleDateFormat date_format = locale_get_date_format();
  20. const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
  21. locale_format_date(s, dt, date_format, separator);
  22. furi_string_cat(out, s);
  23. locale_format_time(s, dt, locale_get_time_format(), false);
  24. furi_string_cat_printf(out, " ");
  25. furi_string_cat(out, s);
  26. furi_string_free(s);
  27. }
  28. void byte_to_binary(uint8_t byte, char* bits) {
  29. for (int i = 7; i >= 0; i--) {
  30. bits[7 - i] = (byte & (1 << i)) ? '1' : '0';
  31. }
  32. bits[8] = '\0';
  33. }
  34. int binary_to_decimal(const char binary[]) {
  35. int decimal = 0;
  36. int length = strlen(binary);
  37. for (int i = 0; i < length; i++) {
  38. decimal = decimal * 2 + (binary[i] - '0');
  39. }
  40. return decimal;
  41. }
  42. void metroflip_charliecard_ravkav_widget_callback(GuiButtonType result, InputType type, void* context) {
  43. Metroflip* app = context;
  44. UNUSED(result);
  45. if(type == InputTypeShort) {
  46. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  47. }
  48. }
  49. static NfcCommand metroflip_scene_ravkav_poller_callback(NfcGenericEvent event, void* context) {
  50. furi_assert(event.protocol == NfcProtocolIso14443_4b);
  51. NfcCommand next_command = NfcCommandContinue;
  52. MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
  53. Metroflip* app = context;
  54. FuriString* parsed_data = furi_string_alloc();
  55. Widget* widget = app->widget;
  56. furi_string_reset(app->text_box_store);
  57. const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
  58. Iso14443_4bPoller* iso14443_4b_poller = event.instance;
  59. BitBuffer* tx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  60. BitBuffer* rx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  61. if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
  62. if(stage == MetroflipPollerEventTypeStart) {
  63. nfc_device_set_data(
  64. app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
  65. Iso14443_4bError error;
  66. size_t response_length = 0;
  67. do {
  68. // Select file of balance
  69. bit_buffer_append_bytes(
  70. tx_buffer, select_balance_file, sizeof(select_balance_file));
  71. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  72. if(error != Iso14443_4bErrorNone) {
  73. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  74. stage = MetroflipPollerEventTypeFail;
  75. view_dispatcher_send_custom_event(
  76. app->view_dispatcher, MetroflipCustomEventPollerFail);
  77. break;
  78. }
  79. // Check the response after selecting file
  80. response_length = bit_buffer_get_size_bytes(rx_buffer);
  81. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  82. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  83. FURI_LOG_I(
  84. TAG,
  85. "Select file failed: %02x%02x",
  86. bit_buffer_get_byte(rx_buffer, response_length - 2),
  87. bit_buffer_get_byte(rx_buffer, response_length - 1));
  88. stage = MetroflipPollerEventTypeFail;
  89. view_dispatcher_send_custom_event(
  90. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  91. break;
  92. }
  93. // Now send the read command
  94. bit_buffer_reset(tx_buffer);
  95. bit_buffer_append_bytes(tx_buffer, read_balance, sizeof(read_balance));
  96. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  97. if(error != Iso14443_4bErrorNone) {
  98. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  99. stage = MetroflipPollerEventTypeFail;
  100. view_dispatcher_send_custom_event(
  101. app->view_dispatcher, MetroflipCustomEventPollerFail);
  102. break;
  103. }
  104. // Check the response after reading the file
  105. response_length = bit_buffer_get_size_bytes(rx_buffer);
  106. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  107. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  108. FURI_LOG_I(
  109. TAG,
  110. "Read file failed: %02x%02x",
  111. bit_buffer_get_byte(rx_buffer, response_length - 2),
  112. bit_buffer_get_byte(rx_buffer, response_length - 1));
  113. stage = MetroflipPollerEventTypeFail;
  114. view_dispatcher_send_custom_event(
  115. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  116. break;
  117. }
  118. // Process the response data
  119. if(response_length < 3) {
  120. FURI_LOG_I(TAG, "Response too short: %d bytes", response_length);
  121. stage = MetroflipPollerEventTypeFail;
  122. view_dispatcher_send_custom_event(
  123. app->view_dispatcher, MetroflipCustomEventPollerFail);
  124. break;
  125. }
  126. uint32_t value = 0;
  127. for(uint8_t i = 0; i < 3; i++) {
  128. value = (value << 8) | bit_buffer_get_byte(rx_buffer, i);
  129. }
  130. float result = value / 100.0f;
  131. FURI_LOG_I(TAG, "Value: %.2f ILS", (double)result);
  132. furi_string_printf(parsed_data, "\e#Rav-Kav:\n");
  133. furi_string_cat_printf(parsed_data, "Card Type: Anonymous\n");
  134. furi_string_cat_printf(parsed_data, "Balance: %.2f ILS\n", (double)result);
  135. // Select app for events
  136. bit_buffer_reset(tx_buffer);
  137. bit_buffer_append_bytes(
  138. tx_buffer, select_events_aid, sizeof(select_events_aid));
  139. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  140. if(error != Iso14443_4bErrorNone) {
  141. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  142. stage = MetroflipPollerEventTypeFail;
  143. view_dispatcher_send_custom_event(
  144. app->view_dispatcher, MetroflipCustomEventPollerFail);
  145. break;
  146. }
  147. // Check the response after selecting app
  148. response_length = bit_buffer_get_size_bytes(rx_buffer);
  149. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  150. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  151. FURI_LOG_I(
  152. TAG,
  153. "Select events app failed: %02x%02x",
  154. bit_buffer_get_byte(rx_buffer, response_length - 2),
  155. bit_buffer_get_byte(rx_buffer, response_length - 1));
  156. stage = MetroflipPollerEventTypeFail;
  157. view_dispatcher_send_custom_event(
  158. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  159. break;
  160. }
  161. // Now send the read command
  162. for(size_t i=1; i<7; i++) {
  163. read_event[2] = i;
  164. bit_buffer_reset(tx_buffer);
  165. bit_buffer_append_bytes(tx_buffer, read_event, sizeof(read_event));
  166. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  167. if(error != Iso14443_4bErrorNone) {
  168. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  169. stage = MetroflipPollerEventTypeFail;
  170. view_dispatcher_send_custom_event(
  171. app->view_dispatcher, MetroflipCustomEventPollerFail);
  172. break;
  173. }
  174. // Check the response after reading the file
  175. response_length = bit_buffer_get_size_bytes(rx_buffer);
  176. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  177. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  178. FURI_LOG_I(
  179. TAG,
  180. "Read file failed: %02x%02x",
  181. bit_buffer_get_byte(rx_buffer, response_length - 2),
  182. bit_buffer_get_byte(rx_buffer, response_length - 1));
  183. stage = MetroflipPollerEventTypeFail;
  184. view_dispatcher_send_custom_event(
  185. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  186. break;
  187. }
  188. char bit_representation[response_length * 8 + 1]; // Total bits in the response (each byte = 8 bits)
  189. bit_representation[0] = '\0'; // Initialize the string to empty
  190. for (size_t i = 0; i < response_length; i++) {
  191. char bits[9]; // Temporary string for each byte (8 bits + null terminator)
  192. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  193. byte_to_binary(byte, bits);
  194. strcat(bit_representation, bits); // Append binary string to the result
  195. }
  196. int start = 23, end = 52;
  197. char bit_slice[end - start + 2];
  198. strncpy(bit_slice, bit_representation + start, end - start + 1);
  199. bit_slice[end - start + 1] = '\0';
  200. int decimal_value = binary_to_decimal(bit_slice);
  201. uint64_t result_timestamp = decimal_value + epoch_ravkav + (3600 *3);
  202. DateTime dt = {0};
  203. datetime_timestamp_to_datetime(result_timestamp, &dt);
  204. furi_string_cat_printf(parsed_data, "\nEvent 0%d:\n", i);
  205. locale_format_datetime_cat(parsed_data, &dt);
  206. furi_string_cat_printf(parsed_data, "\n\n");
  207. }
  208. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  209. widget_add_button_element(
  210. widget, GuiButtonTypeRight, "Exit", metroflip_charliecard_ravkav_widget_callback, app);
  211. furi_string_free(parsed_data);
  212. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  213. metroflip_app_blink_stop(app);
  214. stage = MetroflipPollerEventTypeSuccess;
  215. next_command = NfcCommandStop;
  216. } while(false);
  217. if(stage != MetroflipPollerEventTypeSuccess) {
  218. next_command = NfcCommandStop;
  219. }
  220. }
  221. }
  222. bit_buffer_free(tx_buffer);
  223. bit_buffer_free(rx_buffer);
  224. return next_command;
  225. }
  226. void metroflip_scene_ravkav_on_enter(void* context) {
  227. Metroflip* app = context;
  228. dolphin_deed(DolphinDeedNfcRead);
  229. // Setup view
  230. Popup* popup = app->popup;
  231. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  232. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  233. // Start worker
  234. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  235. nfc_scanner_alloc(app->nfc);
  236. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolIso14443_4b);
  237. nfc_poller_start(app->poller, metroflip_scene_ravkav_poller_callback, app);
  238. metroflip_app_blink_start(app);
  239. }
  240. bool metroflip_scene_ravkav_on_event(void* context, SceneManagerEvent event) {
  241. Metroflip* app = context;
  242. bool consumed = false;
  243. if(event.type == SceneManagerEventTypeCustom) {
  244. if(event.event == MetroflipPollerEventTypeCardDetect) {
  245. Popup* popup = app->popup;
  246. popup_set_header(popup, "Scanning..", 68, 30, AlignLeft, AlignTop);
  247. consumed = true;
  248. } else if(event.event == MetroflipCustomEventPollerFileNotFound) {
  249. Popup* popup = app->popup;
  250. popup_set_header(popup, "No\nRecord\nFile", 68, 30, AlignLeft, AlignTop);
  251. consumed = true;
  252. }
  253. } else if(event.type == SceneManagerEventTypeBack) {
  254. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  255. consumed = true;
  256. }
  257. return consumed;
  258. }
  259. void metroflip_scene_ravkav_on_exit(void* context) {
  260. Metroflip* app = context;
  261. if(app->poller) {
  262. nfc_poller_stop(app->poller);
  263. nfc_poller_free(app->poller);
  264. }
  265. metroflip_app_blink_stop(app);
  266. widget_reset(app->widget);
  267. // Clear view
  268. popup_reset(app->popup);
  269. }