metroflip_scene_navigo.c 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574
  1. #include "../metroflip_i.h"
  2. #include <datetime.h>
  3. #include <dolphin/dolphin.h>
  4. #include <notification/notification_messages.h>
  5. #include <locale/locale.h>
  6. #include "navigo.h"
  7. #include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
  8. #define TAG "Metroflip:Scene:Navigo"
  9. int select_new_app(
  10. int new_app_directory,
  11. int new_app,
  12. BitBuffer* tx_buffer,
  13. BitBuffer* rx_buffer,
  14. Iso14443_4bPoller* iso14443_4b_poller,
  15. Metroflip* app,
  16. MetroflipPollerEventType* stage) {
  17. select_app[5] = new_app_directory;
  18. select_app[6] = new_app;
  19. bit_buffer_reset(tx_buffer);
  20. bit_buffer_append_bytes(tx_buffer, select_app, sizeof(select_app));
  21. FURI_LOG_D(
  22. TAG,
  23. "SEND %02x %02x %02x %02x %02x %02x %02x %02x",
  24. select_app[0],
  25. select_app[1],
  26. select_app[2],
  27. select_app[3],
  28. select_app[4],
  29. select_app[5],
  30. select_app[6],
  31. select_app[7]);
  32. int error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  33. if(error != Iso14443_4bErrorNone) {
  34. FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
  35. *stage = MetroflipPollerEventTypeFail;
  36. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerFail);
  37. return error;
  38. }
  39. return 0;
  40. }
  41. int read_new_file(
  42. int new_file,
  43. BitBuffer* tx_buffer,
  44. BitBuffer* rx_buffer,
  45. Iso14443_4bPoller* iso14443_4b_poller,
  46. Metroflip* app,
  47. MetroflipPollerEventType* stage) {
  48. read_file[2] = new_file;
  49. bit_buffer_reset(tx_buffer);
  50. bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
  51. FURI_LOG_D(
  52. TAG,
  53. "SEND %02x %02x %02x %02x %02x",
  54. read_file[0],
  55. read_file[1],
  56. read_file[2],
  57. read_file[3],
  58. read_file[4]);
  59. Iso14443_4bError error =
  60. iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
  61. if(error != Iso14443_4bErrorNone) {
  62. FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
  63. *stage = MetroflipPollerEventTypeFail;
  64. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventPollerFail);
  65. return error;
  66. }
  67. return 0;
  68. }
  69. int check_response(
  70. BitBuffer* rx_buffer,
  71. Metroflip* app,
  72. MetroflipPollerEventType* stage,
  73. size_t* response_length) {
  74. *response_length = bit_buffer_get_size_bytes(rx_buffer);
  75. if(bit_buffer_get_byte(rx_buffer, *response_length - 2) != apdu_success[0] ||
  76. bit_buffer_get_byte(rx_buffer, *response_length - 1) != apdu_success[1]) {
  77. int error_code_1 = bit_buffer_get_byte(rx_buffer, *response_length - 2);
  78. int error_code_2 = bit_buffer_get_byte(rx_buffer, *response_length - 1);
  79. FURI_LOG_E(TAG, "Select profile app/file failed: %02x%02x", error_code_1, error_code_2);
  80. if(error_code_1 == 0x6a && error_code_2 == 0x82) {
  81. FURI_LOG_E(TAG, "Wrong parameter(s) P1-P2 - File not found");
  82. } else if(error_code_1 == 0x69 && error_code_2 == 0x82) {
  83. FURI_LOG_E(TAG, "Command not allowed - Security status not satisfied");
  84. }
  85. *stage = MetroflipPollerEventTypeFail;
  86. view_dispatcher_send_custom_event(
  87. app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
  88. return 1;
  89. }
  90. return 0;
  91. }
  92. const char* get_country(int country_num) {
  93. switch(country_num) {
  94. case 250:
  95. return "France";
  96. case 124:
  97. return "Canada";
  98. default: {
  99. char* country = malloc(4 * sizeof(char));
  100. snprintf(country, 4, "%d", country_num);
  101. return country;
  102. }
  103. }
  104. }
  105. const char* get_network(int country_num, int network_num) {
  106. switch(country_num) {
  107. case 250:
  108. switch(network_num) {
  109. case 901:
  110. return "IDFM";
  111. default:
  112. return "Unknown";
  113. }
  114. case 124:
  115. switch(network_num) {
  116. case 1:
  117. return "STM";
  118. default:
  119. return "Unknown";
  120. }
  121. default:
  122. return "Unknown";
  123. }
  124. }
  125. const char* get_transport_type(int type) {
  126. switch(type) {
  127. case BUS_URBAIN:
  128. return "Bus Urbain";
  129. case BUS_INTERURBAIN:
  130. return "Bus Interurbain";
  131. case METRO:
  132. return "Metro";
  133. case TRAM:
  134. return "Tram";
  135. case TRAIN:
  136. return "Train";
  137. case PARKING:
  138. return "Parking";
  139. default:
  140. return "Unknown";
  141. }
  142. }
  143. const char* get_service_provider(int provider) {
  144. switch(provider) {
  145. case 2:
  146. return "SNCF";
  147. case 3:
  148. return "RATP";
  149. case 4:
  150. return "IDF Mobilites";
  151. case 10:
  152. return "IDF Mobilites";
  153. case 115:
  154. return "CSO (VEOLIA)";
  155. case 116:
  156. return "R'Bus (VEOLIA)";
  157. case 156:
  158. return "Phebus";
  159. case 175:
  160. return "RATP (Veolia Transport Nanterre)";
  161. default:
  162. return "Unknown";
  163. }
  164. }
  165. const char* get_transition_type(int transition) {
  166. switch(transition) {
  167. case 1:
  168. return "Entry";
  169. case 2:
  170. return "Exit";
  171. case 4:
  172. return "Controle volant (a bord)";
  173. case 5:
  174. return "Test validation";
  175. case 6:
  176. return "Interchange - Entry";
  177. case 7:
  178. return "Interchange - Exit";
  179. case 9:
  180. return "Validation cancelled";
  181. case 10:
  182. return "Entry";
  183. case 11:
  184. return "Exit";
  185. case 13:
  186. return "Distribution";
  187. case 15:
  188. return "Invalidation";
  189. default: {
  190. char* transition_str = malloc(6 * sizeof(char));
  191. snprintf(transition_str, 6, "%d", transition);
  192. return transition_str;
  193. }
  194. }
  195. }
  196. const char* get_navigo_type(int type) {
  197. switch(type) {
  198. case NAVIGO_EASY:
  199. return "Navigo Easy";
  200. case NAVIGO_DECOUVERTE:
  201. return "Navigo Decouverte";
  202. case NAVIGO_STANDARD:
  203. return "Navigo Standard";
  204. case NAVIGO_INTEGRAL:
  205. return "Navigo Integral";
  206. case IMAGINE_R:
  207. return "Imagine R";
  208. default:
  209. return "Navigo";
  210. }
  211. }
  212. const char* get_tariff(int tariff) {
  213. switch(tariff) {
  214. case 0x0000:
  215. return "Navigo Mois";
  216. case 0x0001:
  217. return "Navigo Semaine";
  218. case 0x0002:
  219. return "Navigo Annuel";
  220. case 0x0003:
  221. return "Navigo Jour";
  222. case 0x0004:
  223. return "Imagine R Junior";
  224. case 0x0005:
  225. return "Imagine R Etudiant";
  226. case 0x000D:
  227. return "Navigo Jeunes Week-end";
  228. case 0x0015:
  229. return "Paris-Visite"; // Theoric
  230. case 0x1000:
  231. return "Navigo Liberte+";
  232. case 0x4000:
  233. return "Navigo Mois 75%%";
  234. case 0x4001:
  235. return "Navigo Semaine 75%%";
  236. case 0x4015:
  237. return "Paris-Visite (Enfant)"; // Theoric
  238. case 0x5000:
  239. return "Tickets T+";
  240. case 0x5004:
  241. return "Tickets OrlyBus"; // Theoric
  242. case 0x5005:
  243. return "Tickets RoissyBus"; // Theoric
  244. case 0x5006:
  245. return "Bus-Tram"; // Theoric
  246. case 0x5008:
  247. return "Metro-Train-RER"; // Theoric
  248. case 0x500b:
  249. return "Paris <> Aeroports"; // Theoric
  250. case 0x5010:
  251. return "Tickets T+ (Reduit)"; // Theoric
  252. case 0x5016:
  253. return "Bus-Tram (Reduit)"; // Theoric
  254. case 0x5018:
  255. return "Metro-Train-RER (Reduit)"; // Theoric
  256. case 0x501b:
  257. return "Paris <> Aeroports (Reduit)"; // Theoric
  258. case 0x8003:
  259. return "Navigo Solidarite Gratuit";
  260. default: {
  261. char* tariff_str = malloc(6 * sizeof(char));
  262. snprintf(tariff_str, 6, "%d", tariff);
  263. return tariff_str;
  264. }
  265. }
  266. }
  267. bool is_ticket_count_available(int tariff) {
  268. return tariff >= 0x5000 && tariff <= 0x501b;
  269. }
  270. const char* get_pay_method(int pay_method) {
  271. switch(pay_method) {
  272. case 0x30:
  273. return "Apple Pay";
  274. case 0x80:
  275. return "Debit PME";
  276. case 0x90:
  277. return "Cash";
  278. case 0xA0:
  279. return "Mobility Check";
  280. case 0xB3:
  281. return "Payment Card";
  282. case 0xA4:
  283. return "Check";
  284. case 0xA5:
  285. return "Vacation Check";
  286. case 0xB7:
  287. return "Telepayment";
  288. case 0xD0:
  289. return "Remote Payment";
  290. case 0xD7:
  291. return "Voucher, Prepayment, Exchange Voucher, Travel Voucher";
  292. case 0xD9:
  293. return "Discount Voucher";
  294. default:
  295. return "Unknown";
  296. }
  297. }
  298. const char* get_zones(int* zones) {
  299. if(zones[0] && zones[4]) {
  300. return "All Zones (1-5)";
  301. } else if(zones[0] && zones[3]) {
  302. return "Zones 1-4";
  303. } else if(zones[0] && zones[2]) {
  304. return "Zones 1-3";
  305. } else if(zones[0] && zones[1]) {
  306. return "Zones 1-2";
  307. } else if(zones[0]) {
  308. return "Zone 1";
  309. } else if(zones[1] && zones[4]) {
  310. return "Zones 2-5";
  311. } else if(zones[1] && zones[3]) {
  312. return "Zones 2-4";
  313. } else if(zones[1] && zones[2]) {
  314. return "Zones 2-3";
  315. } else if(zones[1]) {
  316. return "Zone 2";
  317. } else if(zones[2] && zones[4]) {
  318. return "Zones 3-5";
  319. } else if(zones[2] && zones[3]) {
  320. return "Zones 3-4";
  321. } else if(zones[2]) {
  322. return "Zone 3";
  323. } else if(zones[3] && zones[4]) {
  324. return "Zones 4-5";
  325. } else if(zones[3]) {
  326. return "Zone 4";
  327. } else if(zones[4]) {
  328. return "Zone 5";
  329. } else {
  330. return "Unknown";
  331. }
  332. }
  333. const char* get_intercode_version(int version) {
  334. // version is a 6 bits int
  335. // if the first 3 bits are 000, it's a 1.x version
  336. // if the first 3 bits are 001, it's a 2.x version
  337. // else, it's unknown
  338. int major = (version >> 3) & 0x07;
  339. if(major == 0) {
  340. return "Intercode I";
  341. } else if(major == 1) {
  342. return "Intercode II";
  343. }
  344. return "Unknown";
  345. }
  346. int get_intercode_subversion(int version) {
  347. // subversion is a 3 bits int
  348. return version & 0x07;
  349. }
  350. const char* get_metro_station(int station_group_id, int station_id) {
  351. // Use NAVIGO_H constants
  352. if(station_group_id < 32 && station_id < 16) {
  353. return METRO_STATION_LIST[station_group_id][station_id];
  354. }
  355. // cast station_group_id-station_id to a string
  356. char* station = malloc(12 * sizeof(char));
  357. if(!station) {
  358. return "Unknown";
  359. }
  360. snprintf(station, 10, "%d-%d", station_group_id, station_id);
  361. return station;
  362. }
  363. const char* get_train_line(int station_group_id) {
  364. if(station_group_id < 77) {
  365. return TRAIN_LINES_LIST[station_group_id];
  366. }
  367. return "Unknown";
  368. }
  369. const char* get_train_station(int station_group_id, int station_id) {
  370. if(station_group_id < 77 && station_id < 19) {
  371. return TRAIN_STATION_LIST[station_group_id][station_id];
  372. }
  373. // cast station_group_id-station_id to a string
  374. char* station = malloc(12 * sizeof(char));
  375. if(!station) {
  376. return "Unknown";
  377. }
  378. snprintf(station, 10, "%d-%d", station_group_id, station_id);
  379. return station;
  380. }
  381. const char* get_tram_line(int route_number) {
  382. switch(route_number) {
  383. case 16:
  384. return "T6";
  385. default: {
  386. char* line = malloc(3 * sizeof(char));
  387. if(!line) {
  388. return "Unknown";
  389. }
  390. snprintf(line, 3, "T%d", route_number);
  391. return line;
  392. }
  393. }
  394. }
  395. void show_event_info(
  396. NavigoCardEvent* event,
  397. NavigoCardContract* contracts,
  398. FuriString* parsed_data) {
  399. if(event->used_contract == 0) {
  400. furi_string_cat_printf(parsed_data, "No event data\n");
  401. return;
  402. }
  403. if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN ||
  404. event->transport_type == METRO || event->transport_type == TRAM) {
  405. if(event->route_number_available) {
  406. if(event->transport_type == METRO && event->route_number == 103) {
  407. furi_string_cat_printf(
  408. parsed_data,
  409. "%s 3 bis\n%s\n",
  410. get_transport_type(event->transport_type),
  411. get_transition_type(event->transition));
  412. } else if(event->transport_type == TRAM) {
  413. furi_string_cat_printf(
  414. parsed_data,
  415. "%s %s\n%s\n",
  416. get_transport_type(event->transport_type),
  417. get_tram_line(event->route_number),
  418. get_transition_type(event->transition));
  419. } else {
  420. furi_string_cat_printf(
  421. parsed_data,
  422. "%s %d\n%s\n",
  423. get_transport_type(event->transport_type),
  424. event->route_number,
  425. get_transition_type(event->transition));
  426. }
  427. } else {
  428. furi_string_cat_printf(
  429. parsed_data,
  430. "%s\n%s\n",
  431. get_transport_type(event->transport_type),
  432. get_transition_type(event->transition));
  433. }
  434. furi_string_cat_printf(
  435. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  436. if(event->transport_type == METRO) {
  437. furi_string_cat_printf(
  438. parsed_data,
  439. "Station: %s\nSector: %s\n",
  440. get_metro_station(event->station_group_id, event->station_id),
  441. get_metro_station(event->station_group_id, 0));
  442. } else {
  443. furi_string_cat_printf(
  444. parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
  445. }
  446. if(event->location_gate_available) {
  447. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  448. }
  449. if(event->device_available) {
  450. if(event->transport_type == BUS_URBAIN || event->transport_type == BUS_INTERURBAIN) {
  451. const char* side = event->side == 0 ? "right" : "left";
  452. furi_string_cat_printf(parsed_data, "Door: %d\nSide: %s\n", event->door, side);
  453. } else {
  454. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  455. }
  456. }
  457. if(event->mission_available) {
  458. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  459. }
  460. if(event->vehicle_id_available) {
  461. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  462. }
  463. if(event->used_contract_available) {
  464. furi_string_cat_printf(
  465. parsed_data,
  466. "Contract: %d - %s\n",
  467. event->used_contract,
  468. get_tariff(contracts[event->used_contract - 1].tariff));
  469. }
  470. locale_format_datetime_cat(parsed_data, &event->date, true);
  471. furi_string_cat_printf(parsed_data, "\n");
  472. } else if(event->transport_type == TRAIN) {
  473. if(event->route_number_available) {
  474. furi_string_cat_printf(
  475. parsed_data,
  476. "RER %c\n%s\n",
  477. (65 + event->route_number - 17),
  478. get_transition_type(event->transition));
  479. } else {
  480. furi_string_cat_printf(
  481. parsed_data,
  482. "%s %s\n%s\n",
  483. get_transport_type(event->transport_type),
  484. get_train_line(event->station_group_id),
  485. get_transition_type(event->transition));
  486. }
  487. furi_string_cat_printf(
  488. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  489. furi_string_cat_printf(
  490. parsed_data,
  491. "Station: %s\n",
  492. get_train_station(event->station_group_id, event->station_id));
  493. /* if(event->route_number_available) {
  494. furi_string_cat_printf(parsed_data, "Route: %d\n", event->route_number);
  495. } */
  496. if(event->location_gate_available) {
  497. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  498. }
  499. if(event->device_available) {
  500. if(event->service_provider == 2) {
  501. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
  502. } else {
  503. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  504. }
  505. }
  506. if(event->mission_available) {
  507. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  508. }
  509. if(event->vehicle_id_available) {
  510. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  511. }
  512. if(event->used_contract_available) {
  513. furi_string_cat_printf(
  514. parsed_data,
  515. "Contract: %d - %s\n",
  516. event->used_contract,
  517. get_tariff(contracts[event->used_contract - 1].tariff));
  518. }
  519. locale_format_datetime_cat(parsed_data, &event->date, true);
  520. furi_string_cat_printf(parsed_data, "\n");
  521. } else {
  522. furi_string_cat_printf(
  523. parsed_data,
  524. "%s - %s\n",
  525. get_transport_type(event->transport_type),
  526. get_transition_type(event->transition));
  527. furi_string_cat_printf(
  528. parsed_data, "Transporter: %s\n", get_service_provider(event->service_provider));
  529. furi_string_cat_printf(
  530. parsed_data, "Station ID: %d-%d\n", event->station_group_id, event->station_id);
  531. if(event->location_gate_available) {
  532. furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
  533. }
  534. if(event->device_available) {
  535. furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
  536. }
  537. if(event->mission_available) {
  538. furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
  539. }
  540. if(event->vehicle_id_available) {
  541. furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
  542. }
  543. if(event->used_contract_available) {
  544. furi_string_cat_printf(
  545. parsed_data,
  546. "Contract: %d - %s\n",
  547. event->used_contract,
  548. get_tariff(contracts[event->used_contract - 1].tariff));
  549. }
  550. locale_format_datetime_cat(parsed_data, &event->date, true);
  551. furi_string_cat_printf(parsed_data, "\n");
  552. }
  553. }
  554. void show_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {
  555. furi_string_cat_printf(parsed_data, "Type: %s\n", get_tariff(contract->tariff));
  556. if(is_ticket_count_available(contract->tariff)) {
  557. furi_string_cat_printf(parsed_data, "Remaining Tickets: %d\n", contract->counter.count);
  558. }
  559. if(contract->serial_number_available) {
  560. furi_string_cat_printf(parsed_data, "TCN Number: %d\n", contract->serial_number);
  561. }
  562. if(contract->pay_method_available) {
  563. furi_string_cat_printf(
  564. parsed_data, "Payment Method: %s\n", get_pay_method(contract->pay_method));
  565. }
  566. if(contract->price_amount_available) {
  567. furi_string_cat_printf(parsed_data, "Amount: %.2f EUR\n", contract->price_amount);
  568. }
  569. if(contract->end_date_available) {
  570. furi_string_cat_printf(parsed_data, "Valid\nfrom: ");
  571. locale_format_datetime_cat(parsed_data, &contract->start_date, false);
  572. furi_string_cat_printf(parsed_data, "\nto: ");
  573. locale_format_datetime_cat(parsed_data, &contract->end_date, false);
  574. furi_string_cat_printf(parsed_data, "\n");
  575. } else {
  576. furi_string_cat_printf(parsed_data, "Valid from\n");
  577. locale_format_datetime_cat(parsed_data, &contract->start_date, false);
  578. furi_string_cat_printf(parsed_data, "\n");
  579. }
  580. if(contract->zones_available) {
  581. furi_string_cat_printf(parsed_data, "%s\n", get_zones(contract->zones));
  582. }
  583. furi_string_cat_printf(parsed_data, "Sold on: ");
  584. locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
  585. furi_string_cat_printf(parsed_data, "\n");
  586. furi_string_cat_printf(
  587. parsed_data, "Sales Agent: %s\n", get_service_provider(contract->sale_agent));
  588. furi_string_cat_printf(parsed_data, "Sales Terminal: %d\n", contract->sale_device);
  589. if(contract->status == 1) {
  590. furi_string_cat_printf(parsed_data, "Status: OK\n");
  591. } else {
  592. furi_string_cat_printf(parsed_data, "Status: Unknown (%d)\n", contract->status);
  593. }
  594. furi_string_cat_printf(parsed_data, "Authenticity Code: %d\n", contract->authenticator);
  595. }
  596. void show_environment_info(NavigoCardEnv* environment, FuriString* parsed_data) {
  597. furi_string_cat_printf(
  598. parsed_data,
  599. "App Version: %s - v%d\n",
  600. get_intercode_version(environment->app_version),
  601. get_intercode_subversion(environment->app_version));
  602. furi_string_cat_printf(parsed_data, "Country: %s\n", get_country(environment->country_num));
  603. furi_string_cat_printf(
  604. parsed_data,
  605. "Network: %s\n",
  606. get_network(environment->country_num, environment->network_num));
  607. furi_string_cat_printf(parsed_data, "End of validity:\n");
  608. locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
  609. furi_string_cat_printf(parsed_data, "\n");
  610. }
  611. void update_page_info(void* context, FuriString* parsed_data) {
  612. Metroflip* app = context;
  613. NavigoContext* ctx = app->navigo_context;
  614. if(ctx->page_id == 0) {
  615. furi_string_cat_printf(
  616. parsed_data,
  617. "\e#%s %u:\n",
  618. get_navigo_type(ctx->card->holder.card_status),
  619. ctx->card->card_number);
  620. furi_string_cat_printf(parsed_data, "\e#Contract 1:\n");
  621. show_contract_info(&ctx->card->contracts[0], parsed_data);
  622. } else if(ctx->page_id == 1) {
  623. furi_string_cat_printf(
  624. parsed_data,
  625. "\e#%s %u:\n",
  626. get_navigo_type(ctx->card->holder.card_status),
  627. ctx->card->card_number);
  628. furi_string_cat_printf(parsed_data, "\e#Contract 2:\n");
  629. show_contract_info(&ctx->card->contracts[1], parsed_data);
  630. } else if(ctx->page_id == 2) {
  631. furi_string_cat_printf(
  632. parsed_data,
  633. "\e#%s %u:\n",
  634. get_navigo_type(ctx->card->holder.card_status),
  635. ctx->card->card_number);
  636. furi_string_cat_printf(parsed_data, "\e#Contract 3:\n");
  637. show_contract_info(&ctx->card->contracts[2], parsed_data);
  638. } else if(ctx->page_id == 3) {
  639. furi_string_cat_printf(
  640. parsed_data,
  641. "\e#%s %u:\n",
  642. get_navigo_type(ctx->card->holder.card_status),
  643. ctx->card->card_number);
  644. furi_string_cat_printf(parsed_data, "\e#Contract 4:\n");
  645. show_contract_info(&ctx->card->contracts[3], parsed_data);
  646. } else if(ctx->page_id == 4) {
  647. furi_string_cat_printf(parsed_data, "\e#Environment:\n");
  648. show_environment_info(&ctx->card->environment, parsed_data);
  649. } else if(ctx->page_id == 5) {
  650. furi_string_cat_printf(parsed_data, "\e#Event 1:\n");
  651. show_event_info(&ctx->card->events[0], ctx->card->contracts, parsed_data);
  652. } else if(ctx->page_id == 6) {
  653. furi_string_cat_printf(parsed_data, "\e#Event 2:\n");
  654. show_event_info(&ctx->card->events[1], ctx->card->contracts, parsed_data);
  655. } else if(ctx->page_id == 7) {
  656. furi_string_cat_printf(parsed_data, "\e#Event 3:\n");
  657. show_event_info(&ctx->card->events[2], ctx->card->contracts, parsed_data);
  658. }
  659. }
  660. void update_widget_elements(void* context) {
  661. Metroflip* app = context;
  662. NavigoContext* ctx = app->navigo_context;
  663. Widget* widget = app->widget;
  664. if(ctx->page_id < 5) {
  665. widget_add_button_element(
  666. widget, GuiButtonTypeRight, "Next", metroflip_next_button_widget_callback, context);
  667. } else {
  668. widget_add_button_element(
  669. widget, GuiButtonTypeRight, "Exit", metroflip_next_button_widget_callback, context);
  670. }
  671. if(ctx->page_id > 0) {
  672. widget_add_button_element(
  673. widget, GuiButtonTypeLeft, "Back", metroflip_back_button_widget_callback, context);
  674. }
  675. }
  676. void metroflip_back_button_widget_callback(GuiButtonType result, InputType type, void* context) {
  677. Metroflip* app = context;
  678. NavigoContext* ctx = app->navigo_context;
  679. UNUSED(result);
  680. Widget* widget = app->widget;
  681. if(type == InputTypePress) {
  682. widget_reset(widget);
  683. FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id - 1);
  684. if(ctx->page_id > 0) {
  685. if(ctx->page_id == 4 && ctx->card->contracts[3].present == 0) {
  686. ctx->page_id -= 1;
  687. }
  688. if(ctx->page_id == 3 && ctx->card->contracts[2].present == 0) {
  689. ctx->page_id -= 1;
  690. }
  691. if(ctx->page_id == 2 && ctx->card->contracts[1].present == 0) {
  692. ctx->page_id -= 1;
  693. }
  694. ctx->page_id -= 1;
  695. }
  696. FuriString* parsed_data = furi_string_alloc();
  697. // Ensure no nested mutexes
  698. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  699. update_page_info(app, parsed_data);
  700. furi_mutex_release(ctx->mutex);
  701. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  702. // widget_add_icon_element(widget, 0, 0, &I_RFIDDolphinReceive_97x61);
  703. // Ensure no nested mutexes
  704. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  705. update_widget_elements(app);
  706. furi_mutex_release(ctx->mutex);
  707. furi_string_free(parsed_data);
  708. }
  709. }
  710. void metroflip_next_button_widget_callback(GuiButtonType result, InputType type, void* context) {
  711. Metroflip* app = context;
  712. NavigoContext* ctx = app->navigo_context;
  713. UNUSED(result);
  714. Widget* widget = app->widget;
  715. if(type == InputTypePress) {
  716. widget_reset(widget);
  717. FURI_LOG_I(TAG, "Page ID: %d -> %d", ctx->page_id, ctx->page_id + 1);
  718. if(ctx->page_id < 7) {
  719. if(ctx->page_id == 0 && ctx->card->contracts[1].present == 0) {
  720. ctx->page_id += 1;
  721. }
  722. if(ctx->page_id == 1 && ctx->card->contracts[2].present == 0) {
  723. ctx->page_id += 1;
  724. }
  725. if(ctx->page_id == 2 && ctx->card->contracts[3].present == 0) {
  726. ctx->page_id += 1;
  727. }
  728. ctx->page_id += 1;
  729. } else {
  730. ctx->page_id = 0;
  731. scene_manager_search_and_switch_to_previous_scene(
  732. app->scene_manager, MetroflipSceneStart);
  733. return;
  734. }
  735. FuriString* parsed_data = furi_string_alloc();
  736. // Ensure no nested mutexes
  737. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  738. update_page_info(app, parsed_data);
  739. furi_mutex_release(ctx->mutex);
  740. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  741. // Ensure no nested mutexes
  742. furi_mutex_acquire(ctx->mutex, FuriWaitForever);
  743. update_widget_elements(app);
  744. furi_mutex_release(ctx->mutex);
  745. furi_string_free(parsed_data);
  746. }
  747. }
  748. void delay(int milliseconds) {
  749. furi_thread_flags_wait(0, FuriFlagWaitAny, milliseconds);
  750. }
  751. static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event, void* context) {
  752. furi_assert(event.protocol == NfcProtocolIso14443_4b);
  753. NfcCommand next_command = NfcCommandContinue;
  754. MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
  755. Metroflip* app = context;
  756. FuriString* parsed_data = furi_string_alloc();
  757. Widget* widget = app->widget;
  758. furi_string_reset(app->text_box_store);
  759. const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
  760. Iso14443_4bPoller* iso14443_4b_poller = event.instance;
  761. BitBuffer* tx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  762. BitBuffer* rx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
  763. if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
  764. if(stage == MetroflipPollerEventTypeStart) {
  765. // Start Flipper vibration
  766. NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
  767. notification_message(notification, &sequence_set_vibro_on);
  768. delay(50);
  769. notification_message(notification, &sequence_reset_vibro);
  770. nfc_device_set_data(
  771. app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
  772. Iso14443_4bError error;
  773. size_t response_length = 0;
  774. do {
  775. // Initialize the card data
  776. NavigoCardData* card = malloc(sizeof(NavigoCardData));
  777. // Select app ICC
  778. error = select_new_app(
  779. 0x00, 0x02, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  780. if(error != 0) {
  781. break;
  782. }
  783. // Check the response after selecting app
  784. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  785. break;
  786. }
  787. // Now send the read command for ICC
  788. error = read_new_file(0x01, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  789. if(error != 0) {
  790. break;
  791. }
  792. // Check the response after reading the file
  793. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  794. break;
  795. }
  796. char icc_bit_representation[response_length * 8 + 1];
  797. icc_bit_representation[0] = '\0';
  798. for(size_t i = 0; i < response_length; i++) {
  799. char bits[9];
  800. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  801. byte_to_binary(byte, bits);
  802. strlcat(icc_bit_representation, bits, sizeof(icc_bit_representation));
  803. }
  804. icc_bit_representation[response_length * 8] = '\0';
  805. int start = 128, end = 159;
  806. card->card_number = bit_slice_to_dec(icc_bit_representation, start, end);
  807. // Select app for ticketing
  808. error = select_new_app(
  809. 0x20, 0x00, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  810. if(error != 0) {
  811. FURI_LOG_E(TAG, "Failed to select app for ticketing");
  812. break;
  813. }
  814. // Check the response after selecting app
  815. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  816. FURI_LOG_E(TAG, "Failed to check response after selecting app for ticketing");
  817. break;
  818. }
  819. // Select app for contracts
  820. error = select_new_app(
  821. 0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  822. if(error != 0) {
  823. FURI_LOG_E(TAG, "Failed to select app for contracts");
  824. break;
  825. }
  826. // Check the response after selecting app
  827. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  828. FURI_LOG_E(TAG, "Failed to check response after selecting app for contracts");
  829. break;
  830. }
  831. // Prepare calypso structure
  832. CalypsoApp* NavigoContractStructure = get_navigo_contract_structure();
  833. if(!NavigoContractStructure) {
  834. FURI_LOG_E(TAG, "Failed to load Navigo Contract structure");
  835. break;
  836. }
  837. // Now send the read command for contracts
  838. for(size_t i = 1; i < 5; i++) {
  839. error =
  840. read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  841. if(error != 0) {
  842. FURI_LOG_E(TAG, "Failed to read contract %d", i);
  843. break;
  844. }
  845. // Check the response after reading the file
  846. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  847. FURI_LOG_E(TAG, "Failed to check response after reading contract %d", i);
  848. break;
  849. }
  850. char bit_representation[response_length * 8 + 1];
  851. bit_representation[0] = '\0';
  852. for(size_t i = 0; i < response_length; i++) {
  853. char bits[9];
  854. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  855. byte_to_binary(byte, bits);
  856. strlcat(bit_representation, bits, sizeof(bit_representation));
  857. }
  858. bit_representation[response_length * 8] = '\0';
  859. /* int count = 0;
  860. int start = 0, end = NavigoContractStructure->elements[0].bitmap->size;
  861. char bit_slice[end - start + 1];
  862. strncpy(bit_slice, bit_representation + start, end - start);
  863. bit_slice[end - start] = '\0';
  864. int* positions = get_bit_positions(bit_slice, &count);
  865. FURI_LOG_I(TAG, "Contract %d bit positions: %d", i, count);
  866. // print positions
  867. for(int i = 0; i < count; i++) {
  868. char* key =
  869. (NavigoContractStructure->elements[0]
  870. .bitmap->elements[positions[i]]
  871. .type == CALYPSO_ELEMENT_TYPE_FINAL ?
  872. NavigoContractStructure->elements[0]
  873. .bitmap->elements[positions[i]]
  874. .final->key :
  875. NavigoContractStructure->elements[0]
  876. .bitmap->elements[positions[i]]
  877. .bitmap->key);
  878. int offset = get_calypso_node_offset(
  879. bit_representation, key, NavigoContractStructure);
  880. FURI_LOG_I(
  881. TAG, "Position: %d, Key: %s, Offset: %d", positions[i], key, offset);
  882. } */
  883. if(bit_slice_to_dec(
  884. bit_representation,
  885. 0,
  886. NavigoContractStructure->elements[0].bitmap->size - 1) == 0) {
  887. break;
  888. }
  889. card->contracts[i - 1].present = 1;
  890. // 2. ContractTariff
  891. const char* contract_key = "ContractTariff";
  892. if(is_calypso_node_present(
  893. bit_representation, contract_key, NavigoContractStructure)) {
  894. int positionOffset = get_calypso_node_offset(
  895. bit_representation, contract_key, NavigoContractStructure);
  896. int start = positionOffset,
  897. end = positionOffset +
  898. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  899. card->contracts[i - 1].tariff =
  900. bit_slice_to_dec(bit_representation, start, end);
  901. }
  902. // 3. ContractSerialNumber
  903. contract_key = "ContractSerialNumber";
  904. if(is_calypso_node_present(
  905. bit_representation, contract_key, NavigoContractStructure)) {
  906. int positionOffset = get_calypso_node_offset(
  907. bit_representation, contract_key, NavigoContractStructure);
  908. int start = positionOffset,
  909. end = positionOffset +
  910. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  911. card->contracts[i - 1].serial_number =
  912. bit_slice_to_dec(bit_representation, start, end);
  913. card->contracts[i - 1].serial_number_available = true;
  914. }
  915. // 8. ContractPayMethod
  916. contract_key = "ContractPayMethod";
  917. if(is_calypso_node_present(
  918. bit_representation, contract_key, NavigoContractStructure)) {
  919. int positionOffset = get_calypso_node_offset(
  920. bit_representation, contract_key, NavigoContractStructure);
  921. int start = positionOffset,
  922. end = positionOffset +
  923. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  924. card->contracts[i - 1].pay_method =
  925. bit_slice_to_dec(bit_representation, start, end);
  926. card->contracts[i - 1].pay_method_available = true;
  927. }
  928. // 10. ContractPriceAmount
  929. contract_key = "ContractPriceAmount";
  930. if(is_calypso_node_present(
  931. bit_representation, contract_key, NavigoContractStructure)) {
  932. int positionOffset = get_calypso_node_offset(
  933. bit_representation, contract_key, NavigoContractStructure);
  934. int start = positionOffset,
  935. end = positionOffset +
  936. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  937. card->contracts[i - 1].price_amount =
  938. bit_slice_to_dec(bit_representation, start, end) / 100.0;
  939. card->contracts[i - 1].price_amount_available = true;
  940. }
  941. // 13.0. ContractValidityStartDate
  942. contract_key = "ContractValidityStartDate";
  943. if(is_calypso_node_present(
  944. bit_representation, contract_key, NavigoContractStructure)) {
  945. int positionOffset = get_calypso_node_offset(
  946. bit_representation, contract_key, NavigoContractStructure);
  947. int start = positionOffset,
  948. end = positionOffset +
  949. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  950. float decimal_value =
  951. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  952. uint64_t start_validity_timestamp = (decimal_value + (float)epoch) + 3600;
  953. datetime_timestamp_to_datetime(
  954. start_validity_timestamp, &card->contracts[i - 1].start_date);
  955. }
  956. // 13.2. ContractValidityEndDate
  957. contract_key = "ContractValidityEndDate";
  958. if(is_calypso_node_present(
  959. bit_representation, contract_key, NavigoContractStructure)) {
  960. int positionOffset = get_calypso_node_offset(
  961. bit_representation, contract_key, NavigoContractStructure);
  962. int start = positionOffset,
  963. end = positionOffset +
  964. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  965. float decimal_value =
  966. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  967. uint64_t end_validity_timestamp = (decimal_value + (float)epoch) + 3600;
  968. datetime_timestamp_to_datetime(
  969. end_validity_timestamp, &card->contracts[i - 1].end_date);
  970. card->contracts[i - 1].end_date_available = true;
  971. }
  972. // 13.6. ContractValidityZones
  973. contract_key = "ContractValidityZones";
  974. if(is_calypso_node_present(
  975. bit_representation, contract_key, NavigoContractStructure)) {
  976. int start = get_calypso_node_offset(
  977. bit_representation, contract_key, NavigoContractStructure);
  978. // binary form is 00011111 for zones 5, 4, 3, 2, 1
  979. for(int j = 0; j < 5; j++) {
  980. card->contracts[i - 1].zones[j] =
  981. bit_slice_to_dec(bit_representation, start + 3 + j, start + 3 + j);
  982. }
  983. card->contracts[i - 1].zones_available = true;
  984. }
  985. // 13.7. ContractValidityJourneys -- pas sûr de le mettre lui
  986. // 15.0. ContractValiditySaleDate
  987. contract_key = "ContractValiditySaleDate";
  988. if(is_calypso_node_present(
  989. bit_representation, contract_key, NavigoContractStructure)) {
  990. int positionOffset = get_calypso_node_offset(
  991. bit_representation, contract_key, NavigoContractStructure);
  992. int start = positionOffset,
  993. end = positionOffset +
  994. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  995. float decimal_value =
  996. bit_slice_to_dec(bit_representation, start, end) * 24 * 3600;
  997. uint64_t sale_timestamp = (decimal_value + (float)epoch) + 3600;
  998. datetime_timestamp_to_datetime(
  999. sale_timestamp, &card->contracts[i - 1].sale_date);
  1000. }
  1001. // 15.2. ContractValiditySaleAgent - FIX NEEDED
  1002. contract_key = "ContractValiditySaleAgent";
  1003. /* if(is_calypso_node_present(
  1004. bit_representation, contract_key, NavigoContractStructure)) { */
  1005. int positionOffset = get_calypso_node_offset(
  1006. bit_representation, contract_key, NavigoContractStructure);
  1007. int start = positionOffset,
  1008. end = positionOffset +
  1009. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  1010. card->contracts[i - 1].sale_agent =
  1011. bit_slice_to_dec(bit_representation, start, end);
  1012. // }
  1013. // 15.3. ContractValiditySaleDevice
  1014. contract_key = "ContractValiditySaleDevice";
  1015. if(is_calypso_node_present(
  1016. bit_representation, contract_key, NavigoContractStructure)) {
  1017. int positionOffset = get_calypso_node_offset(
  1018. bit_representation, contract_key, NavigoContractStructure);
  1019. int start = positionOffset,
  1020. end = positionOffset +
  1021. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  1022. card->contracts[i - 1].sale_device =
  1023. bit_slice_to_dec(bit_representation, start, end);
  1024. }
  1025. // 16. ContractStatus -- 0x1 ou 0xff
  1026. contract_key = "ContractStatus";
  1027. if(is_calypso_node_present(
  1028. bit_representation, contract_key, NavigoContractStructure)) {
  1029. int positionOffset = get_calypso_node_offset(
  1030. bit_representation, contract_key, NavigoContractStructure);
  1031. int start = positionOffset,
  1032. end = positionOffset +
  1033. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  1034. card->contracts[i - 1].status =
  1035. bit_slice_to_dec(bit_representation, start, end);
  1036. }
  1037. // 18. ContractAuthenticator
  1038. contract_key = "ContractAuthenticator";
  1039. if(is_calypso_node_present(
  1040. bit_representation, contract_key, NavigoContractStructure)) {
  1041. int positionOffset = get_calypso_node_offset(
  1042. bit_representation, contract_key, NavigoContractStructure);
  1043. int start = positionOffset,
  1044. end = positionOffset +
  1045. get_calypso_node_size(contract_key, NavigoContractStructure) - 1;
  1046. card->contracts[i - 1].authenticator =
  1047. bit_slice_to_dec(bit_representation, start, end);
  1048. }
  1049. }
  1050. // Free the calypso structure
  1051. free_calypso_structure(NavigoContractStructure);
  1052. // Select app for environment
  1053. error = select_new_app(
  1054. 0x20, 0x1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1055. if(error != 0) {
  1056. break;
  1057. }
  1058. // Check the response after selecting app
  1059. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1060. break;
  1061. }
  1062. // read file 1
  1063. error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1064. if(error != 0) {
  1065. break;
  1066. }
  1067. // Check the response after reading the file
  1068. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1069. break;
  1070. }
  1071. char environment_bit_representation[response_length * 8 + 1];
  1072. environment_bit_representation[0] = '\0';
  1073. for(size_t i = 0; i < response_length; i++) {
  1074. char bits[9];
  1075. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  1076. byte_to_binary(byte, bits);
  1077. strlcat(
  1078. environment_bit_representation,
  1079. bits,
  1080. sizeof(environment_bit_representation));
  1081. }
  1082. // FURI_LOG_I(
  1083. // TAG, "Environment bit_representation: %s", environment_bit_representation);
  1084. start = 0;
  1085. end = 5;
  1086. card->environment.app_version =
  1087. bit_slice_to_dec(environment_bit_representation, start, end);
  1088. start = 13;
  1089. end = 16;
  1090. card->environment.country_num =
  1091. bit_slice_to_dec(environment_bit_representation, start, end) * 100 +
  1092. bit_slice_to_dec(environment_bit_representation, start + 4, end + 4) * 10 +
  1093. bit_slice_to_dec(environment_bit_representation, start + 8, end + 8);
  1094. start = 25;
  1095. end = 28;
  1096. card->environment.network_num =
  1097. bit_slice_to_dec(environment_bit_representation, start, end) * 100 +
  1098. bit_slice_to_dec(environment_bit_representation, start + 4, end + 4) * 10 +
  1099. bit_slice_to_dec(environment_bit_representation, start + 8, end + 8);
  1100. start = 45;
  1101. end = 58;
  1102. float decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
  1103. uint64_t end_validity_timestamp =
  1104. (decimal_value * 24 * 3600) + (float)epoch + 3600;
  1105. datetime_timestamp_to_datetime(end_validity_timestamp, &card->environment.end_dt);
  1106. start = 95;
  1107. end = 98;
  1108. card->holder.card_status =
  1109. bit_slice_to_dec(environment_bit_representation, start, end);
  1110. start = 99;
  1111. end = 104;
  1112. card->holder.commercial_id =
  1113. bit_slice_to_dec(environment_bit_representation, start, end);
  1114. // Select app for counters (remaining tickets on Navigo Easy)
  1115. error = select_new_app(
  1116. 0x20, 0x69, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1117. if(error != 0) {
  1118. break;
  1119. }
  1120. // Check the response after selecting app
  1121. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1122. break;
  1123. }
  1124. // read file 1
  1125. error = read_new_file(1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1126. if(error != 0) {
  1127. break;
  1128. }
  1129. // Check the response after reading the file
  1130. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1131. break;
  1132. }
  1133. char counter_bit_representation[response_length * 8 + 1];
  1134. counter_bit_representation[0] = '\0';
  1135. for(size_t i = 0; i < response_length; i++) {
  1136. char bits[9];
  1137. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  1138. byte_to_binary(byte, bits);
  1139. strlcat(counter_bit_representation, bits, sizeof(counter_bit_representation));
  1140. }
  1141. // FURI_LOG_I(TAG, "Counter bit_representation: %s", counter_bit_representation);
  1142. // Ticket counts (contracts 1-4)
  1143. for(int i = 0; i < 4; i++) {
  1144. start = 0;
  1145. end = 5;
  1146. card->contracts[i].counter.count =
  1147. bit_slice_to_dec(counter_bit_representation, 24 * i + start, 24 * i + end);
  1148. start = 6;
  1149. end = 23;
  1150. card->contracts[i].counter.relative_first_stamp_15mn =
  1151. bit_slice_to_dec(counter_bit_representation, 24 * i + start, 24 * i + end);
  1152. }
  1153. // Select app for events
  1154. error = select_new_app(
  1155. 0x20, 0x10, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1156. if(error != 0) {
  1157. break;
  1158. }
  1159. // Check the response after selecting app
  1160. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1161. break;
  1162. }
  1163. // Load the calypso structure for events
  1164. CalypsoApp* NavigoEventStructure = get_navigo_event_structure();
  1165. if(!NavigoEventStructure) {
  1166. FURI_LOG_E(TAG, "Failed to load Navigo Event structure");
  1167. break;
  1168. }
  1169. // furi_string_cat_printf(parsed_data, "\e#Events :\n");
  1170. // Now send the read command for events
  1171. for(size_t i = 1; i < 4; i++) {
  1172. error =
  1173. read_new_file(i, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
  1174. if(error != 0) {
  1175. break;
  1176. }
  1177. // Check the response after reading the file
  1178. if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
  1179. break;
  1180. }
  1181. char event_bit_representation[response_length * 8 + 1];
  1182. event_bit_representation[0] = '\0';
  1183. for(size_t i = 0; i < response_length; i++) {
  1184. char bits[9];
  1185. uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
  1186. byte_to_binary(byte, bits);
  1187. strlcat(event_bit_representation, bits, sizeof(event_bit_representation));
  1188. }
  1189. // furi_string_cat_printf(parsed_data, "Event 0%d :\n", i);
  1190. /* int count = 0;
  1191. int start = 25, end = 52;
  1192. char bit_slice[end - start + 2];
  1193. strncpy(bit_slice, event_bit_representation + start, end - start + 1);
  1194. bit_slice[end - start + 1] = '\0';
  1195. int* positions = get_bit_positions(bit_slice, &count);
  1196. FURI_LOG_I(TAG, "Positions: ");
  1197. for(int i = 0; i < count; i++) {
  1198. FURI_LOG_I(TAG, "%d ", positions[i]);
  1199. } */
  1200. // 2. EventCode
  1201. const char* event_key = "EventCode";
  1202. if(is_calypso_node_present(
  1203. event_bit_representation, event_key, NavigoEventStructure)) {
  1204. int positionOffset = get_calypso_node_offset(
  1205. event_bit_representation, event_key, NavigoEventStructure);
  1206. int start = positionOffset,
  1207. end = positionOffset +
  1208. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1209. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1210. card->events[i - 1].transport_type = decimal_value >> 4;
  1211. card->events[i - 1].transition = decimal_value & 15;
  1212. }
  1213. // 4. EventServiceProvider
  1214. event_key = "EventServiceProvider";
  1215. if(is_calypso_node_present(
  1216. event_bit_representation, event_key, NavigoEventStructure)) {
  1217. int positionOffset = get_calypso_node_offset(
  1218. event_bit_representation, event_key, NavigoEventStructure);
  1219. start = positionOffset,
  1220. end = positionOffset +
  1221. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1222. card->events[i - 1].service_provider =
  1223. bit_slice_to_dec(event_bit_representation, start, end);
  1224. }
  1225. // 8. EventLocationId
  1226. event_key = "EventLocationId";
  1227. if(is_calypso_node_present(
  1228. event_bit_representation, event_key, NavigoEventStructure)) {
  1229. int positionOffset = get_calypso_node_offset(
  1230. event_bit_representation, event_key, NavigoEventStructure);
  1231. start = positionOffset,
  1232. end = positionOffset +
  1233. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1234. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1235. card->events[i - 1].station_group_id = decimal_value >> 9;
  1236. card->events[i - 1].station_id = (decimal_value >> 4) & 31;
  1237. }
  1238. // 9. EventLocationGate
  1239. event_key = "EventLocationGate";
  1240. if(is_calypso_node_present(
  1241. event_bit_representation, event_key, NavigoEventStructure)) {
  1242. int positionOffset = get_calypso_node_offset(
  1243. event_bit_representation, event_key, NavigoEventStructure);
  1244. start = positionOffset,
  1245. end = positionOffset +
  1246. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1247. card->events[i - 1].location_gate =
  1248. bit_slice_to_dec(event_bit_representation, start, end);
  1249. card->events[i - 1].location_gate_available = true;
  1250. }
  1251. // 10. EventDevice
  1252. event_key = "EventDevice";
  1253. if(is_calypso_node_present(
  1254. event_bit_representation, event_key, NavigoEventStructure)) {
  1255. int positionOffset = get_calypso_node_offset(
  1256. event_bit_representation, event_key, NavigoEventStructure);
  1257. start = positionOffset,
  1258. end = positionOffset +
  1259. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1260. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1261. card->events[i - 1].device = decimal_value;
  1262. int bus_device = decimal_value >> 8;
  1263. card->events[i - 1].door = bus_device / 2 + 1;
  1264. card->events[i - 1].side = bus_device % 2;
  1265. card->events[i - 1].device_available = true;
  1266. }
  1267. // 11. EventRouteNumber
  1268. event_key = "EventRouteNumber";
  1269. if(is_calypso_node_present(
  1270. event_bit_representation, event_key, NavigoEventStructure)) {
  1271. int positionOffset = get_calypso_node_offset(
  1272. event_bit_representation, event_key, NavigoEventStructure);
  1273. start = positionOffset,
  1274. end = positionOffset +
  1275. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1276. card->events[i - 1].route_number =
  1277. bit_slice_to_dec(event_bit_representation, start, end);
  1278. card->events[i - 1].route_number_available = true;
  1279. }
  1280. // 13. EventJourneyRun
  1281. event_key = "EventJourneyRun";
  1282. if(is_calypso_node_present(
  1283. event_bit_representation, event_key, NavigoEventStructure)) {
  1284. int positionOffset = get_calypso_node_offset(
  1285. event_bit_representation, event_key, NavigoEventStructure);
  1286. start = positionOffset,
  1287. end = positionOffset +
  1288. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1289. card->events[i - 1].mission =
  1290. bit_slice_to_dec(event_bit_representation, start, end);
  1291. card->events[i - 1].mission_available = true;
  1292. }
  1293. // 14. EventVehicleId
  1294. event_key = "EventVehicleId";
  1295. if(is_calypso_node_present(
  1296. event_bit_representation, event_key, NavigoEventStructure)) {
  1297. int positionOffset = get_calypso_node_offset(
  1298. event_bit_representation, event_key, NavigoEventStructure);
  1299. start = positionOffset,
  1300. end = positionOffset +
  1301. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1302. card->events[i - 1].vehicle_id =
  1303. bit_slice_to_dec(event_bit_representation, start, end);
  1304. card->events[i - 1].vehicle_id_available = true;
  1305. }
  1306. // 25. EventContractPointer
  1307. event_key = "EventContractPointer";
  1308. if(is_calypso_node_present(
  1309. event_bit_representation, event_key, NavigoEventStructure)) {
  1310. int positionOffset = get_calypso_node_offset(
  1311. event_bit_representation, event_key, NavigoEventStructure);
  1312. start = positionOffset,
  1313. end = positionOffset +
  1314. get_calypso_node_size(event_key, NavigoEventStructure) - 1;
  1315. card->events[i - 1].used_contract =
  1316. bit_slice_to_dec(event_bit_representation, start, end);
  1317. card->events[i - 1].used_contract_available = true;
  1318. }
  1319. // EventDateStamp
  1320. event_key = "EventDateStamp";
  1321. int positionOffset = get_calypso_node_offset(
  1322. event_bit_representation, event_key, NavigoEventStructure);
  1323. start = positionOffset,
  1324. end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
  1325. 1;
  1326. int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1327. uint64_t date_timestamp = (decimal_value * 24 * 3600) + epoch + 3600;
  1328. datetime_timestamp_to_datetime(date_timestamp, &card->events[i - 1].date);
  1329. // EventTimeStamp
  1330. event_key = "EventTimeStamp";
  1331. positionOffset = get_calypso_node_offset(
  1332. event_bit_representation, event_key, NavigoEventStructure);
  1333. start = positionOffset,
  1334. end = positionOffset + get_calypso_node_size(event_key, NavigoEventStructure) -
  1335. 1;
  1336. decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
  1337. card->events[i - 1].date.hour = (decimal_value * 60) / 3600;
  1338. card->events[i - 1].date.minute = ((decimal_value * 60) % 3600) / 60;
  1339. card->events[i - 1].date.second = ((decimal_value * 60) % 3600) % 60;
  1340. }
  1341. // Free the calypso structure
  1342. free_calypso_structure(NavigoEventStructure);
  1343. UNUSED(TRANSITION_LIST);
  1344. UNUSED(TRANSPORT_LIST);
  1345. UNUSED(SERVICE_PROVIDERS);
  1346. widget_add_text_scroll_element(
  1347. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  1348. NavigoContext* context = malloc(sizeof(NavigoContext));
  1349. context->card = card;
  1350. context->page_id = 0;
  1351. context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  1352. app->navigo_context = context;
  1353. // Ensure no nested mutexes
  1354. furi_mutex_acquire(context->mutex, FuriWaitForever);
  1355. update_page_info(app, parsed_data);
  1356. furi_mutex_release(context->mutex);
  1357. widget_add_text_scroll_element(
  1358. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  1359. // Ensure no nested mutexes
  1360. furi_mutex_acquire(context->mutex, FuriWaitForever);
  1361. update_widget_elements(app);
  1362. furi_mutex_release(context->mutex);
  1363. furi_string_free(parsed_data);
  1364. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  1365. metroflip_app_blink_stop(app);
  1366. stage = MetroflipPollerEventTypeSuccess;
  1367. next_command = NfcCommandStop;
  1368. } while(false);
  1369. if(stage != MetroflipPollerEventTypeSuccess) {
  1370. next_command = NfcCommandStop;
  1371. }
  1372. }
  1373. }
  1374. bit_buffer_free(tx_buffer);
  1375. bit_buffer_free(rx_buffer);
  1376. return next_command;
  1377. }
  1378. void metroflip_scene_navigo_on_enter(void* context) {
  1379. Metroflip* app = context;
  1380. dolphin_deed(DolphinDeedNfcRead);
  1381. // Setup view
  1382. Popup* popup = app->popup;
  1383. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  1384. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  1385. // Start worker
  1386. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  1387. nfc_scanner_alloc(app->nfc);
  1388. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolIso14443_4b);
  1389. nfc_poller_start(app->poller, metroflip_scene_navigo_poller_callback, app);
  1390. metroflip_app_blink_start(app);
  1391. }
  1392. bool metroflip_scene_navigo_on_event(void* context, SceneManagerEvent event) {
  1393. Metroflip* app = context;
  1394. bool consumed = false;
  1395. if(event.type == SceneManagerEventTypeCustom) {
  1396. if(event.event == MetroflipPollerEventTypeCardDetect) {
  1397. Popup* popup = app->popup;
  1398. popup_set_header(popup, "Scanning..", 68, 30, AlignLeft, AlignTop);
  1399. consumed = true;
  1400. } else if(event.event == MetroflipCustomEventPollerFileNotFound) {
  1401. Popup* popup = app->popup;
  1402. popup_set_header(popup, "Read Error,\n wrong card", 68, 30, AlignLeft, AlignTop);
  1403. consumed = true;
  1404. } else if(event.event == MetroflipCustomEventPollerFail) {
  1405. Popup* popup = app->popup;
  1406. popup_set_header(popup, "Error, try\n again", 68, 30, AlignLeft, AlignTop);
  1407. consumed = true;
  1408. }
  1409. } else if(event.type == SceneManagerEventTypeBack) {
  1410. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  1411. consumed = true;
  1412. }
  1413. return consumed;
  1414. }
  1415. void metroflip_scene_navigo_on_exit(void* context) {
  1416. Metroflip* app = context;
  1417. if(app->poller) {
  1418. nfc_poller_stop(app->poller);
  1419. nfc_poller_free(app->poller);
  1420. }
  1421. metroflip_app_blink_stop(app);
  1422. widget_reset(app->widget);
  1423. // Clear view
  1424. popup_reset(app->popup);
  1425. if(app->navigo_context) {
  1426. NavigoContext* ctx = app->navigo_context;
  1427. free(ctx->card);
  1428. furi_mutex_free(ctx->mutex);
  1429. free(ctx);
  1430. app->navigo_context = NULL;
  1431. }
  1432. }