metroflip_scene_ravkav.c 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. // read file
  11. uint8_t read_file[] = {0x94, 0xb2, 0x01, 0x04, 0x1D};
  12. // balance app
  13. uint8_t select_counters_app[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x2A, 0x00};
  14. // events app
  15. uint8_t select_events_app[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x10, 0x00};
  16. // environments app
  17. uint8_t select_environment_app[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x01, 0x00};
  18. void locale_format_datetime_cat(FuriString* out, const DateTime* dt) {
  19. // helper to print datetimes
  20. FuriString* s = furi_string_alloc();
  21. LocaleDateFormat date_format = locale_get_date_format();
  22. const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
  23. locale_format_date(s, dt, date_format, separator);
  24. furi_string_cat(out, s);
  25. locale_format_time(s, dt, locale_get_time_format(), false);
  26. furi_string_cat_printf(out, " ");
  27. furi_string_cat(out, s);
  28. furi_string_free(s);
  29. }
  30. void byte_to_binary(uint8_t byte, char* bits) {
  31. for(int i = 7; i >= 0; i--) {
  32. bits[7 - i] = (byte & (1 << i)) ? '1' : '0';
  33. }
  34. bits[8] = '\0';
  35. }
  36. int binary_to_decimal(const char binary[]) {
  37. int decimal = 0;
  38. int length = strlen(binary);
  39. for(int i = 0; i < length; i++) {
  40. decimal = decimal * 2 + (binary[i] - '0');
  41. }
  42. return decimal;
  43. }
  44. void metroflip_charliecard_ravkav_widget_callback(
  45. GuiButtonType result,
  46. InputType type,
  47. void* context) {
  48. Metroflip* app = context;
  49. UNUSED(result);
  50. if(type == InputTypeShort) {
  51. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  52. }
  53. }
  54. static NfcCommand metroflip_scene_ravkav_poller_callback(NfcGenericEvent event, void* context) {
  55. furi_assert(event.protocol == NfcProtocolIso14443_4b);
  56. NfcCommand next_command = NfcCommandContinue;
  57. MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
  58. Metroflip* app = context;
  59. FuriString* parsed_data = furi_string_alloc();
  60. Widget* widget = app->widget;
  61. furi_string_reset(app->text_box_store);
  62. const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
  63. Iso14443_4bPoller* iso14443_4b_poller = event.instance;
  64. BitBuffer* tx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  65. BitBuffer* rx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  66. if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
  67. if(stage == MetroflipPollerEventTypeStart) {
  68. nfc_device_set_data(
  69. app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
  70. Iso14443_4bError error;
  71. size_t response_length = 0;
  72. do {
  73. // Select file of balance
  74. bit_buffer_append_bytes(
  75. tx_buffer, select_counters_app, sizeof(select_counters_app));
  76. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  77. if(error != Iso14443_4bErrorNone) {
  78. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  79. stage = MetroflipPollerEventTypeFail;
  80. view_dispatcher_send_custom_event(
  81. app->view_dispatcher, MetroflipCustomEventPollerFail);
  82. break;
  83. }
  84. // Check the response after selecting file
  85. response_length = bit_buffer_get_size_bytes(rx_buffer);
  86. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  87. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  88. FURI_LOG_I(
  89. TAG,
  90. "Select file failed: %02x%02x",
  91. bit_buffer_get_byte(rx_buffer, response_length - 2),
  92. bit_buffer_get_byte(rx_buffer, response_length - 1));
  93. stage = MetroflipPollerEventTypeFail;
  94. view_dispatcher_send_custom_event(
  95. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  96. break;
  97. }
  98. // Now send the read command
  99. bit_buffer_reset(tx_buffer);
  100. bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
  101. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  102. if(error != Iso14443_4bErrorNone) {
  103. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  104. stage = MetroflipPollerEventTypeFail;
  105. view_dispatcher_send_custom_event(
  106. app->view_dispatcher, MetroflipCustomEventPollerFail);
  107. break;
  108. }
  109. // Check the response after reading the file
  110. response_length = bit_buffer_get_size_bytes(rx_buffer);
  111. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  112. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  113. FURI_LOG_I(
  114. TAG,
  115. "Read file failed: %02x%02x",
  116. bit_buffer_get_byte(rx_buffer, response_length - 2),
  117. bit_buffer_get_byte(rx_buffer, response_length - 1));
  118. stage = MetroflipPollerEventTypeFail;
  119. view_dispatcher_send_custom_event(
  120. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  121. break;
  122. }
  123. // Process the response data
  124. if(response_length < 3) {
  125. FURI_LOG_I(TAG, "Response too short: %d bytes", response_length);
  126. stage = MetroflipPollerEventTypeFail;
  127. view_dispatcher_send_custom_event(
  128. app->view_dispatcher, MetroflipCustomEventPollerFail);
  129. break;
  130. }
  131. uint32_t value = 0;
  132. for(uint8_t i = 0; i < 3; i++) {
  133. value = (value << 8) | bit_buffer_get_byte(rx_buffer, i);
  134. }
  135. float result = value / 100.0f;
  136. FURI_LOG_I(TAG, "Value: %.2f ILS", (double)result);
  137. furi_string_printf(parsed_data, "\e#Rav-Kav:\n");
  138. if(result != 0.0f) {
  139. furi_string_cat_printf(parsed_data, "Balance: %.2f ILS\n", (double)result);
  140. } else {
  141. furi_string_cat_printf(parsed_data, "Not a stored value Rav-Kav\n");
  142. }
  143. // Select app for profile
  144. bit_buffer_reset(tx_buffer);
  145. bit_buffer_append_bytes(
  146. tx_buffer, select_environment_app, sizeof(select_environment_app));
  147. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  148. if(error != Iso14443_4bErrorNone) {
  149. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  150. stage = MetroflipPollerEventTypeFail;
  151. view_dispatcher_send_custom_event(
  152. app->view_dispatcher, MetroflipCustomEventPollerFail);
  153. break;
  154. }
  155. // Check the response after selecting app
  156. response_length = bit_buffer_get_size_bytes(rx_buffer);
  157. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  158. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  159. FURI_LOG_I(
  160. TAG,
  161. "Select profile app failed: %02x%02x",
  162. bit_buffer_get_byte(rx_buffer, response_length - 2),
  163. bit_buffer_get_byte(rx_buffer, response_length - 1));
  164. stage = MetroflipPollerEventTypeFail;
  165. view_dispatcher_send_custom_event(
  166. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  167. break;
  168. }
  169. bit_buffer_reset(tx_buffer);
  170. bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
  171. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  172. if(error != Iso14443_4bErrorNone) {
  173. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  174. stage = MetroflipPollerEventTypeFail;
  175. view_dispatcher_send_custom_event(
  176. app->view_dispatcher, MetroflipCustomEventPollerFail);
  177. break;
  178. }
  179. // Check the response after reading the file
  180. response_length = bit_buffer_get_size_bytes(rx_buffer);
  181. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  182. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  183. FURI_LOG_I(
  184. TAG,
  185. "Read file failed: %02x%02x",
  186. bit_buffer_get_byte(rx_buffer, response_length - 2),
  187. bit_buffer_get_byte(rx_buffer, response_length - 1));
  188. stage = MetroflipPollerEventTypeFail;
  189. view_dispatcher_send_custom_event(
  190. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  191. break;
  192. }
  193. char bit_representation
  194. [response_length * 8 + 1]; // Total bits in the response (each byte = 8 bits)
  195. bit_representation[0] = '\0'; // Initialize the string to empty
  196. for(size_t i = 0; i < response_length; i++) {
  197. char bits[9]; // Temporary string for each byte (8 bits + null terminator)
  198. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  199. byte_to_binary(byte, bits);
  200. strcat(bit_representation, bits); // Append binary string to the result
  201. }
  202. int start = 54, end = 83;
  203. char bit_slice[end - start + 1];
  204. strncpy(bit_slice, bit_representation + start, end - start + 1);
  205. bit_slice[end - start + 1] = '\0';
  206. int decimal_value = binary_to_decimal(bit_slice);
  207. uint64_t result_timestamp = decimal_value + epoch_ravkav + (3600 * 3);
  208. DateTime dt = {0};
  209. datetime_timestamp_to_datetime(result_timestamp, &dt);
  210. furi_string_cat_printf(parsed_data, "\nActivation date:\n");
  211. locale_format_datetime_cat(parsed_data, &dt);
  212. furi_string_cat_printf(parsed_data, "\n\n");
  213. // Select app for events
  214. bit_buffer_reset(tx_buffer);
  215. bit_buffer_append_bytes(tx_buffer, select_events_app, sizeof(select_events_app));
  216. error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  217. if(error != Iso14443_4bErrorNone) {
  218. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  219. stage = MetroflipPollerEventTypeFail;
  220. view_dispatcher_send_custom_event(
  221. app->view_dispatcher, MetroflipCustomEventPollerFail);
  222. break;
  223. }
  224. // Check the response after selecting app
  225. response_length = bit_buffer_get_size_bytes(rx_buffer);
  226. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  227. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  228. FURI_LOG_I(
  229. TAG,
  230. "Select events app failed: %02x%02x",
  231. bit_buffer_get_byte(rx_buffer, response_length - 2),
  232. bit_buffer_get_byte(rx_buffer, response_length - 1));
  233. stage = MetroflipPollerEventTypeFail;
  234. view_dispatcher_send_custom_event(
  235. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  236. break;
  237. }
  238. // Now send the read command
  239. for(size_t i = 1; i < 7; i++) {
  240. read_file[2] = i;
  241. bit_buffer_reset(tx_buffer);
  242. bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
  243. error =
  244. iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  245. if(error != Iso14443_4bErrorNone) {
  246. FURI_LOG_I(
  247. TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  248. stage = MetroflipPollerEventTypeFail;
  249. view_dispatcher_send_custom_event(
  250. app->view_dispatcher, MetroflipCustomEventPollerFail);
  251. break;
  252. }
  253. // Check the response after reading the file
  254. response_length = bit_buffer_get_size_bytes(rx_buffer);
  255. if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
  256. bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
  257. FURI_LOG_I(
  258. TAG,
  259. "Read file failed: %02x%02x",
  260. bit_buffer_get_byte(rx_buffer, response_length - 2),
  261. bit_buffer_get_byte(rx_buffer, response_length - 1));
  262. stage = MetroflipPollerEventTypeFail;
  263. view_dispatcher_send_custom_event(
  264. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  265. break;
  266. }
  267. char bit_representation
  268. [response_length * 8 + 1]; // Total bits in the response (each byte = 8 bits)
  269. bit_representation[0] = '\0'; // Initialize the string to empty
  270. for(size_t i = 0; i < response_length; i++) {
  271. char bits[9]; // Temporary string for each byte (8 bits + null terminator)
  272. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  273. byte_to_binary(byte, bits);
  274. strcat(bit_representation, bits); // Append binary string to the result
  275. }
  276. int start = 23, end = 52;
  277. char bit_slice[end - start + 2];
  278. strncpy(bit_slice, bit_representation + start, end - start + 1);
  279. bit_slice[end - start + 1] = '\0';
  280. int decimal_value = binary_to_decimal(bit_slice);
  281. uint64_t result_timestamp = decimal_value + epoch_ravkav + (3600 * 3);
  282. DateTime dt = {0};
  283. datetime_timestamp_to_datetime(result_timestamp, &dt);
  284. furi_string_cat_printf(parsed_data, "\nEvent 0%d:\n", i);
  285. locale_format_datetime_cat(parsed_data, &dt);
  286. furi_string_cat_printf(parsed_data, "\n\n");
  287. }
  288. widget_add_text_scroll_element(
  289. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  290. widget_add_button_element(
  291. widget,
  292. GuiButtonTypeRight,
  293. "Exit",
  294. metroflip_charliecard_ravkav_widget_callback,
  295. app);
  296. furi_string_free(parsed_data);
  297. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  298. metroflip_app_blink_stop(app);
  299. stage = MetroflipPollerEventTypeSuccess;
  300. next_command = NfcCommandStop;
  301. } while(false);
  302. if(stage != MetroflipPollerEventTypeSuccess) {
  303. next_command = NfcCommandStop;
  304. }
  305. }
  306. }
  307. bit_buffer_free(tx_buffer);
  308. bit_buffer_free(rx_buffer);
  309. return next_command;
  310. }
  311. void metroflip_scene_ravkav_on_enter(void* context) {
  312. Metroflip* app = context;
  313. dolphin_deed(DolphinDeedNfcRead);
  314. // Setup view
  315. Popup* popup = app->popup;
  316. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  317. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  318. // Start worker
  319. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  320. nfc_scanner_alloc(app->nfc);
  321. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolIso14443_4b);
  322. nfc_poller_start(app->poller, metroflip_scene_ravkav_poller_callback, app);
  323. metroflip_app_blink_start(app);
  324. }
  325. bool metroflip_scene_ravkav_on_event(void* context, SceneManagerEvent event) {
  326. Metroflip* app = context;
  327. bool consumed = false;
  328. if(event.type == SceneManagerEventTypeCustom) {
  329. if(event.event == MetroflipPollerEventTypeCardDetect) {
  330. Popup* popup = app->popup;
  331. popup_set_header(popup, "Scanning..", 68, 30, AlignLeft, AlignTop);
  332. consumed = true;
  333. } else if(event.event == MetroflipCustomEventPollerFileNotFound) {
  334. Popup* popup = app->popup;
  335. popup_set_header(popup, "Read Error,\n wrong card", 68, 30, AlignLeft, AlignTop);
  336. consumed = true;
  337. } else if(event.event == MetroflipCustomEventPollerFail) {
  338. Popup* popup = app->popup;
  339. popup_set_header(popup, "Error, try\n again", 68, 30, AlignLeft, AlignTop);
  340. consumed = true;
  341. }
  342. } else if(event.type == SceneManagerEventTypeBack) {
  343. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  344. consumed = true;
  345. }
  346. return consumed;
  347. }
  348. void metroflip_scene_ravkav_on_exit(void* context) {
  349. Metroflip* app = context;
  350. if(app->poller) {
  351. nfc_poller_stop(app->poller);
  352. nfc_poller_free(app->poller);
  353. }
  354. metroflip_app_blink_stop(app);
  355. widget_reset(app->widget);
  356. // Clear view
  357. popup_reset(app->popup);
  358. }