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