metroflip_scene_ravkav.c 20 KB

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