totp_scene_generate_token.c 20 KB

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