totp_scene_generate_token.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. #include "totp_scene_generate_token.h"
  2. #include <gui/gui.h>
  3. #include <notification/notification.h>
  4. #include <notification/notification_messages.h>
  5. #include <totp_icons.h>
  6. #include <roll_value.h>
  7. #include "../../../services/fonts/font_provider.h"
  8. #include "../../canvas_extensions.h"
  9. #include "../../../types/token_info.h"
  10. #include "../../../types/common.h"
  11. #include "../../constants.h"
  12. #include "../../../services/config/config.h"
  13. #include "../../scene_director.h"
  14. #include "../../../config/app/config.h"
  15. #include "../../../workers/generate_totp_code/generate_totp_code.h"
  16. #include "../../../workers/usb_type_code/usb_type_code.h"
  17. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  18. #include "../../../workers/bt_type_code/bt_type_code.h"
  19. #endif
  20. #define PROGRESS_BAR_MARGIN (3)
  21. #define PROGRESS_BAR_HEIGHT (4)
  22. #define DIV_CEIL(a, b) (((a) + (b) - 1) / (b))
  23. typedef struct {
  24. uint8_t progress_bar_x;
  25. uint8_t progress_bar_width;
  26. uint8_t code_total_length;
  27. uint8_t code_offset_x;
  28. uint8_t code_offset_y;
  29. uint8_t group_space_width;
  30. } UiPrecalculatedDimensions;
  31. typedef struct {
  32. char last_code[TokenDigitsCountMax + 1];
  33. TotpUsbTypeCodeWorkerContext* usb_type_code_worker_context;
  34. NotificationMessage const* notification_sequence_new_token[8];
  35. NotificationMessage const* notification_sequence_automation[11];
  36. FuriMutex* last_code_update_sync;
  37. TotpGenerateCodeWorkerContext* generate_code_worker_context;
  38. UiPrecalculatedDimensions ui_precalculated_dimensions;
  39. FontInfo* active_font;
  40. NotificationApp* notification_app;
  41. } SceneState;
  42. static const NotificationSequence*
  43. get_notification_sequence_new_token(const PluginState* plugin_state, SceneState* scene_state) {
  44. if(scene_state->notification_sequence_new_token[0] == NULL) {
  45. NotificationMessage const** sequence = &scene_state->notification_sequence_new_token[0];
  46. *(sequence++) = &message_display_backlight_on;
  47. *(sequence++) = &message_green_255;
  48. if(plugin_state->notification_method & NotificationMethodVibro) {
  49. *(sequence++) = &message_vibro_on;
  50. }
  51. if(plugin_state->notification_method & NotificationMethodSound) {
  52. *(sequence++) = &message_note_c5;
  53. }
  54. *(sequence++) = &message_delay_50;
  55. if(plugin_state->notification_method & NotificationMethodVibro) {
  56. *(sequence++) = &message_vibro_off;
  57. }
  58. if(plugin_state->notification_method & NotificationMethodSound) {
  59. *(sequence++) = &message_sound_off;
  60. }
  61. *(sequence++) = NULL;
  62. }
  63. return (NotificationSequence*)scene_state->notification_sequence_new_token;
  64. }
  65. static const NotificationSequence*
  66. get_notification_sequence_automation(const PluginState* plugin_state, SceneState* scene_state) {
  67. if(scene_state->notification_sequence_automation[0] == NULL) {
  68. NotificationMessage const** sequence = &scene_state->notification_sequence_automation[0];
  69. *(sequence++) = &message_blue_255;
  70. if(plugin_state->notification_method & NotificationMethodVibro) {
  71. *(sequence++) = &message_vibro_on;
  72. }
  73. if(plugin_state->notification_method & NotificationMethodSound) {
  74. *(sequence++) = &message_note_d5; //-V525
  75. *(sequence++) = &message_delay_50;
  76. *(sequence++) = &message_note_e4;
  77. *(sequence++) = &message_delay_50;
  78. *(sequence++) = &message_note_f3;
  79. }
  80. *(sequence++) = &message_delay_50;
  81. if(plugin_state->notification_method & NotificationMethodVibro) {
  82. *(sequence++) = &message_vibro_off;
  83. }
  84. if(plugin_state->notification_method & NotificationMethodSound) {
  85. *(sequence++) = &message_sound_off;
  86. }
  87. *(sequence++) = NULL;
  88. }
  89. return (NotificationSequence*)scene_state->notification_sequence_automation;
  90. }
  91. static void update_totp_params(PluginState* const plugin_state, size_t token_index) {
  92. SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
  93. TokenInfoIteratorContext* iterator_context =
  94. totp_config_get_token_iterator_context(plugin_state);
  95. if(totp_token_info_iterator_go_to(iterator_context, token_index)) {
  96. totp_generate_code_worker_notify(
  97. scene_state->generate_code_worker_context, TotpGenerateCodeWorkerEventForceUpdate);
  98. }
  99. }
  100. static void draw_totp_code(Canvas* const canvas, const PluginState* const plugin_state) {
  101. const SceneState* scene_state = plugin_state->current_scene_state;
  102. const TokenInfoIteratorContext* iterator_context =
  103. totp_config_get_token_iterator_context(plugin_state);
  104. uint8_t code_length = totp_token_info_iterator_get_current_token(iterator_context)->digits;
  105. if(plugin_state->split_token_into_groups > 0) {
  106. uint8_t offset_x = scene_state->ui_precalculated_dimensions.code_offset_x;
  107. uint8_t char_width = scene_state->active_font->char_info[0].width;
  108. uint8_t group_length = DIV_CEIL(code_length, plugin_state->split_token_into_groups);
  109. for(uint8_t i = 0; i < code_length; i += group_length) {
  110. canvas_draw_str_ex(
  111. canvas,
  112. offset_x,
  113. scene_state->ui_precalculated_dimensions.code_offset_y,
  114. &scene_state->last_code[i],
  115. MIN(group_length, code_length - i),
  116. scene_state->active_font);
  117. offset_x +=
  118. (group_length * (char_width + scene_state->active_font->space_width) +
  119. scene_state->ui_precalculated_dimensions.group_space_width);
  120. }
  121. } else {
  122. canvas_draw_str_ex(
  123. canvas,
  124. scene_state->ui_precalculated_dimensions.code_offset_x,
  125. scene_state->ui_precalculated_dimensions.code_offset_y,
  126. scene_state->last_code,
  127. code_length,
  128. scene_state->active_font);
  129. }
  130. }
  131. static void on_new_token_code_generated(bool time_left, void* context) {
  132. PluginState* const plugin_state = context;
  133. const TokenInfoIteratorContext* iterator_context =
  134. totp_config_get_token_iterator_context(plugin_state);
  135. if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
  136. return;
  137. }
  138. SceneState* scene_state = plugin_state->current_scene_state;
  139. const TokenInfo* current_token = totp_token_info_iterator_get_current_token(iterator_context);
  140. uint8_t char_width = scene_state->active_font->char_info[0].width;
  141. uint8_t group_spaces =
  142. plugin_state->split_token_into_groups > 0 ? plugin_state->split_token_into_groups - 1 : 0;
  143. uint8_t group_space_width = char_width;
  144. scene_state->ui_precalculated_dimensions.code_total_length =
  145. current_token->digits * (char_width + scene_state->active_font->space_width);
  146. uint8_t rest_available_width =
  147. SCREEN_WIDTH -
  148. MIN(SCREEN_WIDTH, scene_state->ui_precalculated_dimensions.code_total_length);
  149. if(group_space_width * group_spaces > rest_available_width) {
  150. group_space_width = rest_available_width / group_spaces;
  151. }
  152. scene_state->ui_precalculated_dimensions.code_total_length += group_space_width * group_spaces;
  153. scene_state->ui_precalculated_dimensions.group_space_width = group_space_width;
  154. scene_state->ui_precalculated_dimensions.code_offset_x =
  155. (SCREEN_WIDTH -
  156. MIN(SCREEN_WIDTH, scene_state->ui_precalculated_dimensions.code_total_length)) >>
  157. 1;
  158. scene_state->ui_precalculated_dimensions.code_offset_y =
  159. SCREEN_HEIGHT_CENTER - (scene_state->active_font->height >> 1);
  160. if(time_left) {
  161. notification_message(
  162. scene_state->notification_app,
  163. get_notification_sequence_new_token(plugin_state, scene_state));
  164. }
  165. totp_scene_director_force_redraw(plugin_state);
  166. }
  167. static void on_code_lifetime_updated_generated(float code_lifetime_percent, void* context) {
  168. PluginState* const plugin_state = context;
  169. SceneState* scene_state = plugin_state->current_scene_state;
  170. scene_state->ui_precalculated_dimensions.progress_bar_width =
  171. (uint8_t)((float)(SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1)) * code_lifetime_percent);
  172. scene_state->ui_precalculated_dimensions.progress_bar_x =
  173. ((SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1) -
  174. scene_state->ui_precalculated_dimensions.progress_bar_width) >>
  175. 1) +
  176. PROGRESS_BAR_MARGIN;
  177. totp_scene_director_force_redraw(plugin_state);
  178. }
  179. void totp_scene_generate_token_activate(PluginState* plugin_state) {
  180. SceneState* scene_state = malloc(sizeof(SceneState));
  181. furi_check(scene_state != NULL);
  182. plugin_state->current_scene_state = scene_state;
  183. FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset);
  184. scene_state->last_code_update_sync = furi_mutex_alloc(FuriMutexTypeNormal);
  185. if(plugin_state->automation_method & AutomationMethodBadUsb) {
  186. scene_state->usb_type_code_worker_context = totp_usb_type_code_worker_start(
  187. scene_state->last_code,
  188. TokenDigitsCountMax + 1,
  189. scene_state->last_code_update_sync,
  190. plugin_state->automation_kb_layout,
  191. plugin_state->automation_initial_delay);
  192. }
  193. scene_state->active_font = totp_font_info_alloc();
  194. if(!totp_font_provider_get_font(plugin_state->active_font_index, scene_state->active_font)) {
  195. totp_font_provider_get_font(0, scene_state->active_font);
  196. }
  197. scene_state->notification_app = furi_record_open(RECORD_NOTIFICATION);
  198. scene_state->notification_sequence_automation[0] = NULL;
  199. scene_state->notification_sequence_new_token[0] = NULL;
  200. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  201. if(plugin_state->automation_method & AutomationMethodBadBt) {
  202. if(plugin_state->bt_type_code_worker_context == NULL) {
  203. plugin_state->bt_type_code_worker_context = totp_bt_type_code_worker_init(
  204. *((uint16_t*)plugin_state->crypto_settings.crypto_verify_data),
  205. plugin_state->bt_type_code_worker_profile_index);
  206. }
  207. totp_bt_type_code_worker_start(
  208. plugin_state->bt_type_code_worker_context,
  209. scene_state->last_code,
  210. TokenDigitsCountMax + 1,
  211. scene_state->last_code_update_sync,
  212. plugin_state->automation_kb_layout,
  213. plugin_state->automation_initial_delay);
  214. }
  215. #endif
  216. const TokenInfoIteratorContext* iterator_context =
  217. totp_config_get_token_iterator_context(plugin_state);
  218. scene_state->generate_code_worker_context = totp_generate_code_worker_start(
  219. scene_state->last_code,
  220. totp_token_info_iterator_get_current_token(iterator_context),
  221. scene_state->last_code_update_sync,
  222. plugin_state->timezone_offset,
  223. &plugin_state->crypto_settings);
  224. totp_generate_code_worker_set_code_generated_handler(
  225. scene_state->generate_code_worker_context, &on_new_token_code_generated, plugin_state);
  226. totp_generate_code_worker_set_lifetime_changed_handler(
  227. scene_state->generate_code_worker_context,
  228. &on_code_lifetime_updated_generated,
  229. plugin_state);
  230. update_totp_params(
  231. plugin_state, totp_token_info_iterator_get_current_token_index(iterator_context));
  232. }
  233. void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) {
  234. const TokenInfoIteratorContext* iterator_context =
  235. totp_config_get_token_iterator_context(plugin_state);
  236. if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
  237. canvas_draw_str_aligned(
  238. canvas,
  239. SCREEN_WIDTH_CENTER,
  240. SCREEN_HEIGHT_CENTER - 10,
  241. AlignCenter,
  242. AlignCenter,
  243. "Token list is empty");
  244. canvas_draw_str_aligned(
  245. canvas,
  246. SCREEN_WIDTH_CENTER,
  247. SCREEN_HEIGHT_CENTER + 10,
  248. AlignCenter,
  249. AlignCenter,
  250. "Press OK button to open menu");
  251. return;
  252. }
  253. const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
  254. canvas_set_font(canvas, FontPrimary);
  255. const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
  256. const char* token_name_cstr = furi_string_get_cstr(token_info->name);
  257. uint16_t token_name_width = canvas_string_width(canvas, token_name_cstr);
  258. if(SCREEN_WIDTH - token_name_width > 18) {
  259. canvas_draw_str_aligned(
  260. canvas,
  261. SCREEN_WIDTH_CENTER,
  262. SCREEN_HEIGHT_CENTER - 20,
  263. AlignCenter,
  264. AlignCenter,
  265. token_name_cstr);
  266. } else {
  267. canvas_draw_str_aligned(
  268. canvas, 9, SCREEN_HEIGHT_CENTER - 20, AlignLeft, AlignCenter, token_name_cstr);
  269. canvas_set_color(canvas, ColorWhite);
  270. canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
  271. canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
  272. canvas_set_color(canvas, ColorBlack);
  273. }
  274. draw_totp_code(canvas, plugin_state);
  275. if(token_info->type == TokenTypeTOTP) {
  276. canvas_draw_box(
  277. canvas,
  278. scene_state->ui_precalculated_dimensions.progress_bar_x,
  279. SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT,
  280. scene_state->ui_precalculated_dimensions.progress_bar_width,
  281. PROGRESS_BAR_HEIGHT);
  282. } else {
  283. char buffer[21];
  284. snprintf(&buffer[0], sizeof(buffer), "%" PRIu64, token_info->counter);
  285. canvas_set_font(canvas, FontSecondary);
  286. canvas_draw_str_aligned(
  287. canvas, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT - 5, AlignCenter, AlignCenter, buffer);
  288. }
  289. if(totp_token_info_iterator_get_total_count(iterator_context) > 1) {
  290. canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9);
  291. canvas_draw_icon(
  292. canvas, SCREEN_WIDTH - 8, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
  293. }
  294. if(plugin_state->automation_method & AutomationMethodBadUsb) {
  295. canvas_draw_icon(
  296. canvas,
  297. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  298. SCREEN_WIDTH_CENTER -
  299. (plugin_state->automation_method & AutomationMethodBadBt ? 33 : 15),
  300. #else
  301. SCREEN_WIDTH_CENTER - 15,
  302. #endif
  303. SCREEN_HEIGHT_CENTER + 12,
  304. &I_hid_usb_31x9);
  305. }
  306. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  307. if(plugin_state->automation_method & AutomationMethodBadBt &&
  308. plugin_state->bt_type_code_worker_context != NULL &&
  309. totp_bt_type_code_worker_is_advertising(plugin_state->bt_type_code_worker_context)) {
  310. canvas_draw_icon(
  311. canvas,
  312. SCREEN_WIDTH_CENTER +
  313. (plugin_state->automation_method & AutomationMethodBadUsb ? 2 : -15),
  314. SCREEN_HEIGHT_CENTER + 12,
  315. &I_hid_ble_31x9);
  316. }
  317. #endif
  318. }
  319. bool totp_scene_generate_token_handle_event(
  320. const PluginEvent* const event,
  321. PluginState* plugin_state) {
  322. if(event->type != EventTypeKey) {
  323. return true;
  324. }
  325. if(event->input.type == InputTypePress && event->input.key == InputKeyBack) {
  326. return false;
  327. }
  328. SceneState* scene_state;
  329. if(event->input.type == InputTypeLong) {
  330. if(event->input.key == InputKeyDown &&
  331. plugin_state->automation_method & AutomationMethodBadUsb) {
  332. scene_state = (SceneState*)plugin_state->current_scene_state;
  333. const TokenInfoIteratorContext* iterator_context =
  334. totp_config_get_token_iterator_context(plugin_state);
  335. totp_usb_type_code_worker_notify(
  336. scene_state->usb_type_code_worker_context,
  337. TotpUsbTypeCodeWorkerEventType,
  338. totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
  339. notification_message(
  340. scene_state->notification_app,
  341. get_notification_sequence_automation(plugin_state, scene_state));
  342. return true;
  343. }
  344. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  345. else if(
  346. event->input.key == InputKeyUp &&
  347. plugin_state->automation_method & AutomationMethodBadBt) {
  348. scene_state = (SceneState*)plugin_state->current_scene_state;
  349. const TokenInfoIteratorContext* iterator_context =
  350. totp_config_get_token_iterator_context(plugin_state);
  351. totp_bt_type_code_worker_notify(
  352. plugin_state->bt_type_code_worker_context,
  353. TotpBtTypeCodeWorkerEventType,
  354. totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
  355. notification_message(
  356. scene_state->notification_app,
  357. get_notification_sequence_automation(plugin_state, scene_state));
  358. return true;
  359. }
  360. #endif
  361. else if(event->input.key == InputKeyOk) {
  362. TokenInfoIteratorContext* iterator_context =
  363. totp_config_get_token_iterator_context(plugin_state);
  364. const TokenInfo* token_info =
  365. totp_token_info_iterator_get_current_token(iterator_context);
  366. if(token_info->type == TokenTypeHOTP) {
  367. scene_state = (SceneState*)plugin_state->current_scene_state;
  368. totp_token_info_iterator_current_token_inc_counter(iterator_context);
  369. totp_generate_code_worker_notify(
  370. scene_state->generate_code_worker_context,
  371. TotpGenerateCodeWorkerEventForceUpdate);
  372. notification_message(
  373. scene_state->notification_app,
  374. get_notification_sequence_new_token(plugin_state, scene_state));
  375. }
  376. }
  377. } else if(event->input.type == InputTypePress || event->input.type == InputTypeRepeat) {
  378. switch(event->input.key) {
  379. case InputKeyUp:
  380. break;
  381. case InputKeyDown:
  382. break;
  383. case InputKeyRight: {
  384. const TokenInfoIteratorContext* iterator_context =
  385. totp_config_get_token_iterator_context(plugin_state);
  386. size_t current_token_index =
  387. totp_token_info_iterator_get_current_token_index(iterator_context);
  388. totp_roll_value_size_t(
  389. &current_token_index,
  390. 1,
  391. 0,
  392. totp_token_info_iterator_get_total_count(iterator_context) - 1,
  393. RollOverflowBehaviorRoll);
  394. update_totp_params(plugin_state, current_token_index);
  395. break;
  396. }
  397. case InputKeyLeft: {
  398. const TokenInfoIteratorContext* iterator_context =
  399. totp_config_get_token_iterator_context(plugin_state);
  400. size_t current_token_index =
  401. totp_token_info_iterator_get_current_token_index(iterator_context);
  402. totp_roll_value_size_t(
  403. &current_token_index,
  404. -1,
  405. 0,
  406. totp_token_info_iterator_get_total_count(iterator_context) - 1,
  407. RollOverflowBehaviorRoll);
  408. update_totp_params(plugin_state, current_token_index);
  409. break;
  410. }
  411. case InputKeyOk:
  412. break;
  413. case InputKeyBack:
  414. break;
  415. default:
  416. break;
  417. }
  418. } else if(event->input.type == InputTypeShort && event->input.key == InputKeyOk) {
  419. totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
  420. }
  421. return true;
  422. }
  423. void totp_scene_generate_token_deactivate(PluginState* plugin_state) {
  424. if(plugin_state->current_scene_state == NULL) return;
  425. SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
  426. totp_generate_code_worker_stop(scene_state->generate_code_worker_context);
  427. furi_record_close(RECORD_NOTIFICATION);
  428. if(plugin_state->automation_method & AutomationMethodBadUsb) {
  429. totp_usb_type_code_worker_stop(scene_state->usb_type_code_worker_context);
  430. }
  431. #ifdef TOTP_BADBT_AUTOMATION_ENABLED
  432. if(plugin_state->automation_method & AutomationMethodBadBt) {
  433. totp_bt_type_code_worker_stop(plugin_state->bt_type_code_worker_context);
  434. }
  435. #endif
  436. furi_mutex_free(scene_state->last_code_update_sync);
  437. totp_font_info_free(scene_state->active_font);
  438. free(scene_state);
  439. plugin_state->current_scene_state = NULL;
  440. }