metroflip_scene_smartrider.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. #include "../metroflip_i.h"
  2. #include <bit_lib.h>
  3. #include <flipper_application.h>
  4. #include <furi.h>
  5. #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
  6. #include <nfc/protocols/mf_classic/mf_classic.h>
  7. #include <nfc/protocols/mf_classic/mf_classic_poller.h>
  8. #include <string.h>
  9. #include <dolphin/dolphin.h>
  10. #include <furi_hal.h>
  11. #include <nfc/nfc.h>
  12. #include <nfc/nfc_device.h>
  13. #include <nfc/nfc_listener.h>
  14. #include <storage/storage.h>
  15. #define MAX_TRIPS 10
  16. #define TAG "Metroflip:Scene:Smartrider"
  17. #define MAX_BLOCKS 64
  18. #define MAX_DATE_ITERATIONS 366
  19. uint8_t sector_num = 0;
  20. static const uint8_t STANDARD_KEYS[3][6] = {
  21. {0x20, 0x31, 0xD1, 0xE5, 0x7A, 0x3B},
  22. {0x4C, 0xA6, 0x02, 0x9F, 0x94, 0x73},
  23. {0x19, 0x19, 0x53, 0x98, 0xE3, 0x2F}};
  24. void uid_to_string(const uint8_t* uid, size_t uid_len, char* uid_str, size_t max_len) {
  25. size_t pos = 0;
  26. for(size_t i = 0; i < uid_len && pos + 2 < max_len; ++i) {
  27. pos += snprintf(&uid_str[pos], max_len - pos, "%02X", uid[i]);
  28. }
  29. uid_str[pos] = '\0'; // Null-terminate the string
  30. }
  31. typedef struct {
  32. uint32_t timestamp;
  33. uint16_t cost;
  34. uint16_t transaction_number;
  35. uint16_t journey_number;
  36. char route[5];
  37. uint8_t tap_on : 1;
  38. uint8_t block;
  39. } __attribute__((packed)) TripData;
  40. typedef struct {
  41. uint32_t balance;
  42. uint16_t issued_days;
  43. uint16_t expiry_days;
  44. uint16_t purchase_cost;
  45. uint16_t auto_load_threshold;
  46. uint16_t auto_load_value;
  47. char card_serial_number[11];
  48. uint8_t token;
  49. TripData trips[MAX_TRIPS];
  50. uint8_t trip_count;
  51. } __attribute__((packed)) SmartRiderData;
  52. static const char* const CONCESSION_TYPES[] = {
  53. "Pre-issue",
  54. "Standard Fare",
  55. "Student",
  56. NULL,
  57. "Tertiary",
  58. NULL,
  59. "Seniors",
  60. "Health Care",
  61. NULL,
  62. NULL,
  63. NULL,
  64. NULL,
  65. NULL,
  66. NULL,
  67. "PTA Staff",
  68. "Pensioner",
  69. "Free Travel"};
  70. static inline const char* get_concession_type(uint8_t token) {
  71. return (token <= 0x10) ? CONCESSION_TYPES[token] : "Unknown";
  72. }
  73. static inline bool
  74. parse_trip_data(const MfClassicBlock* block_data, TripData* trip, uint8_t block_number) {
  75. trip->timestamp = bit_lib_bytes_to_num_le(block_data->data + 3, 4);
  76. trip->tap_on = (block_data->data[7] & 0x10) == 0x10;
  77. memcpy(trip->route, block_data->data + 8, 4);
  78. trip->route[4] = '\0';
  79. trip->cost = bit_lib_bytes_to_num_le(block_data->data + 13, 2);
  80. trip->transaction_number = bit_lib_bytes_to_num_le(block_data->data, 2);
  81. trip->journey_number = bit_lib_bytes_to_num_le(block_data->data + 2, 2);
  82. trip->block = block_number;
  83. return true;
  84. }
  85. static bool is_leap_year(uint16_t year) {
  86. return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
  87. }
  88. static void calculate_date(uint32_t timestamp, char* date_str, size_t date_str_size) {
  89. uint32_t seconds_since_2000 = timestamp;
  90. uint32_t days_since_2000 = seconds_since_2000 / 86400;
  91. uint16_t year = 2000;
  92. uint8_t month = 1;
  93. uint16_t day = 1;
  94. static const uint16_t days_in_month[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  95. while(days_since_2000 >= (is_leap_year(year) ? 366 : 365)) {
  96. days_since_2000 -= (is_leap_year(year) ? 366 : 365);
  97. year++;
  98. }
  99. for(month = 0; month < 12; month++) {
  100. uint16_t dim = days_in_month[month];
  101. if(month == 1 && is_leap_year(year)) {
  102. dim++;
  103. }
  104. if(days_since_2000 < dim) {
  105. break;
  106. }
  107. days_since_2000 -= dim;
  108. }
  109. day = days_since_2000 + 1;
  110. month++; // Adjust month to 1-based
  111. if(date_str_size > 0) {
  112. size_t written = 0;
  113. written += snprintf(date_str + written, date_str_size - written, "%02u", day);
  114. if(written < date_str_size - 1) {
  115. written += snprintf(date_str + written, date_str_size - written, "/");
  116. }
  117. if(written < date_str_size - 1) {
  118. written += snprintf(date_str + written, date_str_size - written, "%02u", month);
  119. }
  120. if(written < date_str_size - 1) {
  121. written += snprintf(date_str + written, date_str_size - written, "/");
  122. }
  123. if(written < date_str_size - 1) {
  124. snprintf(date_str + written, date_str_size - written, "%02u", year % 100);
  125. }
  126. } else {
  127. // If the buffer size is 0, do nothing
  128. }
  129. }
  130. static bool smartrider_parse(const NfcDevice* device, FuriString* parsed_data) {
  131. furi_assert(device);
  132. furi_assert(parsed_data);
  133. const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
  134. SmartRiderData sr_data = {0};
  135. if(data->type != MfClassicType1k) {
  136. FURI_LOG_E(TAG, "Invalid card type");
  137. return false;
  138. }
  139. const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0);
  140. if(!sec_tr || memcmp(sec_tr->key_a.data, STANDARD_KEYS[0], 6) != 0) {
  141. FURI_LOG_E(TAG, "Key verification failed for sector 0");
  142. return false;
  143. }
  144. static const uint8_t required_blocks[] = {14, 4, 5, 1, 52, 50, 0};
  145. for(size_t i = 0; i < COUNT_OF(required_blocks); i++) {
  146. if(required_blocks[i] >= MAX_BLOCKS ||
  147. !mf_classic_is_block_read(data, required_blocks[i])) {
  148. FURI_LOG_E(TAG, "Required block %d is not read or out of range", required_blocks[i]);
  149. return false;
  150. }
  151. }
  152. sr_data.balance = bit_lib_bytes_to_num_le(data->block[14].data + 7, 2);
  153. sr_data.issued_days = bit_lib_bytes_to_num_le(data->block[4].data + 16, 2);
  154. sr_data.expiry_days = bit_lib_bytes_to_num_le(data->block[4].data + 18, 2);
  155. sr_data.auto_load_threshold = bit_lib_bytes_to_num_le(data->block[4].data + 20, 2);
  156. sr_data.auto_load_value = bit_lib_bytes_to_num_le(data->block[4].data + 22, 2);
  157. sr_data.token = data->block[5].data[8];
  158. sr_data.purchase_cost = bit_lib_bytes_to_num_le(data->block[0].data + 14, 2);
  159. snprintf(
  160. sr_data.card_serial_number,
  161. sizeof(sr_data.card_serial_number),
  162. "%02X%02X%02X%02X%02X",
  163. data->block[1].data[6],
  164. data->block[1].data[7],
  165. data->block[1].data[8],
  166. data->block[1].data[9],
  167. data->block[1].data[10]);
  168. for(uint8_t block_number = 40; block_number <= 52 && sr_data.trip_count < MAX_TRIPS;
  169. block_number++) {
  170. if((block_number != 43 && block_number != 47 && block_number != 51) &&
  171. mf_classic_is_block_read(data, block_number) &&
  172. parse_trip_data(
  173. &data->block[block_number], &sr_data.trips[sr_data.trip_count], block_number)) {
  174. sr_data.trip_count++;
  175. }
  176. }
  177. // Sort trips by timestamp (descending order)
  178. for(uint8_t i = 0; i < sr_data.trip_count - 1; i++) {
  179. for(uint8_t j = 0; j < sr_data.trip_count - i - 1; j++) {
  180. if(sr_data.trips[j].timestamp < sr_data.trips[j + 1].timestamp) {
  181. TripData temp = sr_data.trips[j];
  182. sr_data.trips[j] = sr_data.trips[j + 1];
  183. sr_data.trips[j + 1] = temp;
  184. }
  185. }
  186. }
  187. furi_string_printf(
  188. parsed_data,
  189. "\e#SmartRider\nBalance: $%lu.%02lu\nConcession: %s\nSerial: %s%s\n"
  190. "Total Cost: $%u.%02u\nAuto-Load: $%u.%02u/$%u.%02u\n\e#Tag On/Off History\n",
  191. sr_data.balance / 100,
  192. sr_data.balance % 100,
  193. get_concession_type(sr_data.token),
  194. memcmp(sr_data.card_serial_number, "00", 2) == 0 ? "SR0" : "",
  195. memcmp(sr_data.card_serial_number, "00", 2) == 0 ? sr_data.card_serial_number + 2 :
  196. sr_data.card_serial_number,
  197. sr_data.purchase_cost / 100,
  198. sr_data.purchase_cost % 100,
  199. sr_data.auto_load_threshold / 100,
  200. sr_data.auto_load_threshold % 100,
  201. sr_data.auto_load_value / 100,
  202. sr_data.auto_load_value % 100);
  203. for(uint8_t i = 0; i < sr_data.trip_count; i++) {
  204. char date_str[9];
  205. calculate_date(sr_data.trips[i].timestamp, date_str, sizeof(date_str));
  206. uint32_t cost = sr_data.trips[i].cost;
  207. if(cost > 0) {
  208. furi_string_cat_printf(
  209. parsed_data,
  210. "%s %c $%lu.%02lu %s\n",
  211. date_str,
  212. sr_data.trips[i].tap_on ? '+' : '-',
  213. cost / 100,
  214. cost % 100,
  215. sr_data.trips[i].route);
  216. } else {
  217. furi_string_cat_printf(
  218. parsed_data,
  219. "%s %c %s\n",
  220. date_str,
  221. sr_data.trips[i].tap_on ? '+' : '-',
  222. sr_data.trips[i].route);
  223. }
  224. }
  225. return true;
  226. }
  227. // made with love by jay candel <3
  228. void handle_keyfile_case(
  229. Metroflip* app,
  230. const char* message_title,
  231. const char* log_message,
  232. FuriString* parsed_data) {
  233. FURI_LOG_I(TAG, log_message);
  234. dolphin_deed(DolphinDeedNfcReadSuccess);
  235. furi_string_reset(parsed_data);
  236. furi_string_printf(
  237. parsed_data,
  238. "\e#%s\n\n"
  239. "To read a SmartRider, \nyou need to read \nit in NFC "
  240. "app on \nthe flipper, and it\nneeds to show \n32/32 keys and\n"
  241. "16/16 sectors read\n"
  242. "Here is a guide to \nfollow to read \nMIFARE Classic:\n"
  243. "https://flipper.wiki/mifareclassic/\n"
  244. "Once completed, Scan again",
  245. message_title);
  246. widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  247. widget_add_button_element(
  248. app->widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  249. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  250. metroflip_app_blink_stop(app);
  251. }
  252. static NfcCommand
  253. metroflip_scene_smartrider_poller_callback(NfcGenericEvent event, void* context) {
  254. furi_assert(context);
  255. furi_assert(event.event_data);
  256. furi_assert(event.protocol == NfcProtocolMfClassic);
  257. NfcCommand command = NfcCommandContinue;
  258. const MfClassicPollerEvent* mfc_event = event.event_data;
  259. Metroflip* app = context;
  260. FuriString* parsed_data = furi_string_alloc();
  261. Widget* widget = app->widget;
  262. if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
  263. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
  264. command = NfcCommandContinue;
  265. } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
  266. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardLost);
  267. command = NfcCommandStop;
  268. } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
  269. mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
  270. nfc_device_set_data(
  271. app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
  272. size_t uid_len = 0;
  273. const uint8_t* uid = nfc_device_get_uid(app->nfc_device, &uid_len);
  274. /*-----------------All of this is to store a keyfile in a permanent way for the user to always access------------*/
  275. /*-----------------Open cache file (if exists)------------*/
  276. char uid_str[uid_len * 2 + 1];
  277. uid_to_string(uid, uid_len, uid_str, sizeof(uid_str));
  278. KeyfileManager manage = manage_keyfiles(uid_str);
  279. switch(manage) {
  280. case MISSING_KEYFILE:
  281. handle_keyfile_case(app, "No keys found", "Missing keyfile", parsed_data);
  282. command = NfcCommandStop;
  283. break;
  284. case INCOMPLETE_KEYFILE:
  285. handle_keyfile_case(app, "Incomplete keyfile", "incomplete keyfile", parsed_data);
  286. command = NfcCommandStop;
  287. break;
  288. case SUCCESSFUL:
  289. mf_classic_key_cache_load(app->mfc_key_cache, uid, uid_len);
  290. FURI_LOG_I(TAG, "success");
  291. break;
  292. }
  293. } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
  294. FURI_LOG_I(TAG, "sec_num: %d", sector_num);
  295. MfClassicKey key = {};
  296. MfClassicKeyType key_type = MfClassicKeyTypeA;
  297. if(mf_classic_key_cache_get_next_key(app->mfc_key_cache, &sector_num, &key, &key_type)) {
  298. mfc_event->data->read_sector_request_data.sector_num = sector_num;
  299. mfc_event->data->read_sector_request_data.key = key;
  300. mfc_event->data->read_sector_request_data.key_type = key_type;
  301. mfc_event->data->read_sector_request_data.key_provided = true;
  302. } else {
  303. mfc_event->data->read_sector_request_data.key_provided = false;
  304. }
  305. } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
  306. nfc_device_set_data(
  307. app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
  308. dolphin_deed(DolphinDeedNfcReadSuccess);
  309. furi_string_reset(app->text_box_store);
  310. if(!smartrider_parse(app->nfc_device, parsed_data)) {
  311. FURI_LOG_I(TAG, "Unknown card type");
  312. furi_string_printf(parsed_data, "\e#Unknown card\n");
  313. }
  314. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  315. widget_add_button_element(
  316. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  317. furi_string_free(parsed_data);
  318. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  319. metroflip_app_blink_stop(app);
  320. UNUSED(smartrider_parse);
  321. command = NfcCommandStop;
  322. } else if(mfc_event->type == MfClassicPollerEventTypeFail) {
  323. FURI_LOG_I(TAG, "fail");
  324. command = NfcCommandStop;
  325. }
  326. return command;
  327. }
  328. void metroflip_scene_smartrider_on_enter(void* context) {
  329. Metroflip* app = context;
  330. dolphin_deed(DolphinDeedNfcRead);
  331. mf_classic_key_cache_reset(app->mfc_key_cache);
  332. // Setup view
  333. Popup* popup = app->popup;
  334. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  335. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  336. // Start worker
  337. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  338. nfc_scanner_alloc(app->nfc);
  339. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
  340. nfc_poller_start(app->poller, metroflip_scene_smartrider_poller_callback, app);
  341. metroflip_app_blink_start(app);
  342. }
  343. bool metroflip_scene_smartrider_on_event(void* context, SceneManagerEvent event) {
  344. Metroflip* app = context;
  345. bool consumed = false;
  346. if(event.type == SceneManagerEventTypeCustom) {
  347. if(event.event == MetroflipCustomEventCardDetected) {
  348. Popup* popup = app->popup;
  349. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  350. consumed = true;
  351. } else if(event.event == MetroflipCustomEventCardLost) {
  352. Popup* popup = app->popup;
  353. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  354. consumed = true;
  355. } else if(event.event == MetroflipCustomEventWrongCard) {
  356. Popup* popup = app->popup;
  357. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  358. consumed = true;
  359. } else if(event.event == MetroflipCustomEventPollerFail) {
  360. Popup* popup = app->popup;
  361. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  362. consumed = true;
  363. } else if(event.event == MetroflipCustomEventPollerSuccess) {
  364. scene_manager_next_scene(app->scene_manager, MetroflipSceneReadSuccess);
  365. consumed = true;
  366. }
  367. } else if(event.type == SceneManagerEventTypeBack) {
  368. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  369. consumed = true;
  370. }
  371. return consumed;
  372. }
  373. void metroflip_scene_smartrider_on_exit(void* context) {
  374. Metroflip* app = context;
  375. widget_reset(app->widget);
  376. if(app->poller) {
  377. nfc_poller_stop(app->poller);
  378. nfc_poller_free(app->poller);
  379. }
  380. // Clear view
  381. popup_reset(app->popup);
  382. metroflip_app_blink_stop(app);
  383. }