smartrider.c 16 KB

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