Просмотр исходного кода

Implemented #137 (#141)

* Implemented #137
*Refactoring
Alexander Kopachov 2 лет назад
Родитель
Сommit
3458f0a373
58 измененных файлов с 1635 добавлено и 1593 удалено
  1. 0 3
      application.fam
  2. 1 1
      cli/cli.c
  3. 40 2
      cli/cli_helpers.c
  4. 45 22
      cli/cli_helpers.h
  5. 86 77
      cli/commands/add/add.c
  6. 6 14
      cli/commands/automation/automation.c
  7. 23 29
      cli/commands/delete/delete.c
  8. 29 17
      cli/commands/details/details.c
  9. 20 9
      cli/commands/list/list.c
  10. 31 25
      cli/commands/move/move.c
  11. 7 14
      cli/commands/notification/notification.c
  12. 17 51
      cli/commands/pin/pin.c
  13. 8 2
      cli/commands/reset/reset.c
  14. 4 1
      cli/commands/reset/reset.h
  15. 4 9
      cli/commands/timezone/timezone.c
  16. 93 95
      cli/commands/update/update.c
  17. 2 2
      cli/common_command_arguments.c
  18. 6 0
      cli/common_command_arguments.h
  19. 0 136
      lib/linked_list/linked_list.c
  20. 0 98
      lib/linked_list/linked_list.h
  21. 1 1
      lib/roll_value/roll_value.c
  22. 3 2
      lib/roll_value/roll_value.h
  23. 258 502
      services/config/config.c
  24. 39 86
      services/config/config.h
  25. 3 0
      services/config/config_file_context.h
  26. 4 1
      services/config/constants.h
  27. 27 12
      services/config/migrations/common_migration.c
  28. 512 0
      services/config/token_info_iterator.c
  29. 42 0
      services/config/token_info_iterator.h
  30. 7 7
      services/crypto/crypto.c
  31. 23 2
      services/crypto/crypto.h
  32. 26 17
      totp_app.c
  33. 3 0
      types/common.c
  34. 1 1
      types/common.h
  35. 0 17
      types/nullable.h
  36. 2 15
      types/plugin_state.h
  37. 18 9
      types/token_info.c
  38. 10 4
      types/token_info.h
  39. 12 8
      ui/scene_director.c
  40. 1 4
      ui/scene_director.h
  41. 43 52
      ui/scenes/add_new_token/totp_scene_add_new_token.c
  42. 1 7
      ui/scenes/add_new_token/totp_scene_add_new_token.h
  43. 4 28
      ui/scenes/app_settings/totp_app_settings.c
  44. 1 7
      ui/scenes/app_settings/totp_app_settings.h
  45. 10 3
      ui/scenes/authenticate/totp_scene_authenticate.c
  46. 49 80
      ui/scenes/generate_token/totp_scene_generate_token.c
  47. 1 7
      ui/scenes/generate_token/totp_scene_generate_token.h
  48. 12 0
      ui/scenes/standby/standby.c
  49. 5 0
      ui/scenes/standby/standby.h
  50. 19 52
      ui/scenes/token_menu/totp_scene_token_menu.c
  51. 1 7
      ui/scenes/token_menu/totp_scene_token_menu.h
  52. 6 1
      ui/totp_scenes_enum.h
  53. 30 0
      workers/bt_type_code/bt_type_code.c
  54. 5 25
      workers/bt_type_code/bt_type_code.h
  55. 16 2
      workers/generate_totp_code/generate_totp_code.c
  56. 2 14
      workers/generate_totp_code/generate_totp_code.h
  57. 13 0
      workers/usb_type_code/usb_type_code.c
  58. 3 13
      workers/usb_type_code/usb_type_code.h

+ 0 - 3
application.fam

@@ -28,9 +28,6 @@ App(
         Lib(
             name="base64",
         ),
-        Lib(
-            name="linked_list"
-        ),
         Lib(
             name="timezone_utils",
         ),

+ 1 - 1
cli/cli.c

@@ -63,7 +63,7 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
     } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_AUTOMATION) == 0) {
         totp_cli_command_automation_handle(plugin_state, args, cli);
     } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) {
-        totp_cli_command_reset_handle(cli, cli_context->event_queue);
+        totp_cli_command_reset_handle(plugin_state, cli, cli_context->event_queue);
     } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_UPDATE) == 0) {
         totp_cli_command_update_handle(plugin_state, args, cli);
     } else if(

+ 40 - 2
cli/cli_helpers.c

@@ -3,6 +3,11 @@
 #include <lib/toolbox/args.h>
 #include "../types/plugin_event.h"
 
+const char* TOTP_CLI_COLOR_ERROR = "91m";
+const char* TOTP_CLI_COLOR_WARNING = "93m";
+const char* TOTP_CLI_COLOR_SUCCESS = "92m";
+const char* TOTP_CLI_COLOR_INFO = "96m";
+
 bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
     if(plugin_state->current_scene == TotpSceneAuthentication) {
         TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n");
@@ -13,10 +18,11 @@ bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
             furi_delay_ms(100);
         }
 
-        TOTP_CLI_DELETE_LAST_LINE();
+        totp_cli_delete_last_line();
 
         if(plugin_state->current_scene == TotpSceneAuthentication || //-V560
            plugin_state->current_scene == TotpSceneNone) { //-V560
+            TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
             return false;
         }
     }
@@ -54,7 +60,7 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
         } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
             size_t out_str_size = furi_string_size(out_str);
             if(out_str_size > 0) {
-                TOTP_CLI_DELETE_LAST_CHAR();
+                totp_cli_delete_last_char();
                 furi_string_left(out_str, out_str_size - 1);
             }
         } else if(c == CliSymbolAsciiCR) {
@@ -83,3 +89,35 @@ void furi_string_secure_free(FuriString* str) {
 
     furi_string_free(str);
 }
+
+void totp_cli_print_invalid_arguments() {
+    TOTP_CLI_PRINTF_ERROR(
+        "Invalid command arguments. use \"help\" command to get list of available commands");
+}
+
+void totp_cli_print_error_updating_config_file() {
+    TOTP_CLI_PRINTF_ERROR("An error has occurred during updating config file\r\n");
+}
+
+void totp_cli_print_error_loading_token_info() {
+    TOTP_CLI_PRINTF_ERROR("An error has occurred during loading token information\r\n");
+}
+
+void totp_cli_print_processing() {
+    TOTP_CLI_PRINTF("Processing, please wait...\r\n");
+}
+
+void totp_cli_delete_last_char() {
+    TOTP_CLI_PRINTF("\b \b");
+    fflush(stdout);
+}
+
+void totp_cli_delete_current_line() {
+    TOTP_CLI_PRINTF("\33[2K\r");
+    fflush(stdout);
+}
+
+void totp_cli_delete_last_line() {
+    TOTP_CLI_PRINTF("\033[A\33[2K\r");
+    fflush(stdout);
+}

+ 45 - 22
cli/cli_helpers.h

@@ -14,6 +14,11 @@
 #define DOCOPT_OPTIONS "[options]"
 #define DOCOPT_DEFAULT(val) "[default: " val "]"
 
+extern const char* TOTP_CLI_COLOR_ERROR;
+extern const char* TOTP_CLI_COLOR_WARNING;
+extern const char* TOTP_CLI_COLOR_SUCCESS;
+extern const char* TOTP_CLI_COLOR_INFO;
+
 #define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__)
 
 #define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \
@@ -22,11 +27,6 @@
     printf("\e[0m");                                 \
     fflush(stdout)
 
-#define TOTP_CLI_COLOR_ERROR "91m"
-#define TOTP_CLI_COLOR_WARNING "93m"
-#define TOTP_CLI_COLOR_SUCCESS "92m"
-#define TOTP_CLI_COLOR_INFO "96m"
-
 #define TOTP_CLI_PRINTF_ERROR(format, ...) \
     TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_ERROR, format, ##__VA_ARGS__)
 #define TOTP_CLI_PRINTF_WARNING(format, ...) \
@@ -36,24 +36,12 @@
 #define TOTP_CLI_PRINTF_INFO(format, ...) \
     TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_INFO, format, ##__VA_ARGS__)
 
-#define TOTP_CLI_DELETE_LAST_LINE()    \
-    TOTP_CLI_PRINTF("\033[A\33[2K\r"); \
-    fflush(stdout)
-
-#define TOTP_CLI_DELETE_CURRENT_LINE() \
-    TOTP_CLI_PRINTF("\33[2K\r");       \
-    fflush(stdout)
-
-#define TOTP_CLI_DELETE_LAST_CHAR() \
-    TOTP_CLI_PRINTF("\b \b");       \
-    fflush(stdout)
-
-#define TOTP_CLI_PRINT_INVALID_ARGUMENTS() \
-    TOTP_CLI_PRINTF_ERROR(                 \
-        "Invalid command arguments. use \"help\" command to get list of available commands")
+#define TOTP_CLI_LOCK_UI(plugin_state)                    \
+    Scene __previous_scene = plugin_state->current_scene; \
+    totp_scene_director_activate_scene(plugin_state, TotpSceneStandby)
 
-#define TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE() \
-    TOTP_CLI_PRINTF_ERROR("An error has occurred during updating config file\r\n")
+#define TOTP_CLI_UNLOCK_UI(plugin_state) \
+    totp_scene_director_activate_scene(plugin_state, __previous_scene)
 
 /**
  * @brief Checks whether user is authenticated and entered correct PIN.
@@ -92,3 +80,38 @@ bool args_read_uint8_and_trim(FuriString* args, uint8_t* value);
  * @param str instance to free
  */
 void furi_string_secure_free(FuriString* str);
+
+/**
+ * @brief Deletes last printed line in console
+ */
+void totp_cli_delete_last_line();
+
+/**
+ * @brief Deletes current printed line in console
+ */
+void totp_cli_delete_current_line();
+
+/**
+ * @brief Deletes last printed char in console
+ */
+void totp_cli_delete_last_char();
+
+/**
+ * @brief Prints error message about invalid command arguments
+ */
+void totp_cli_print_invalid_arguments();
+
+/**
+ * @brief Prints error message about config file update error
+ */
+void totp_cli_print_error_updating_config_file();
+
+/**
+ * @brief Prints error message about config file loading error
+ */
+void totp_cli_print_error_loading_token_info();
+
+/**
+ * @brief Prints message to let user knwo that command is processing now
+ */
+void totp_cli_print_processing();

+ 86 - 77
cli/commands/add/add.c

@@ -1,7 +1,6 @@
 #include "add.h"
 #include <stdlib.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../services/config/config.h"
 #include "../../../services/convert/convert.h"
@@ -9,6 +8,76 @@
 #include "../../../ui/scene_director.h"
 #include "../../common_command_arguments.h"
 
+struct TotpAddContext {
+    FuriString* args;
+    Cli* cli;
+    uint8_t* iv;
+};
+
+enum TotpIteratorUpdateTokenResultsEx {
+    TotpIteratorUpdateTokenResultInvalidSecret = 1,
+    TotpIteratorUpdateTokenResultCancelled = 2,
+    TotpIteratorUpdateTokenResultInvalidArguments = 3
+};
+
+static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* token_info, const void* context) {
+    const struct TotpAddContext* context_t = context;
+
+    // Reading token name
+    if(!args_read_probably_quoted_string_and_trim(context_t->args, token_info->name)) {
+        return TotpIteratorUpdateTokenResultInvalidArguments;
+    }
+
+    FuriString* temp_str = furi_string_alloc();
+
+    // Read optional arguments
+    bool mask_user_input = true;
+    PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
+    while(args_read_string_and_trim(context_t->args, temp_str)) {
+        bool parsed = false;
+        if(!totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
+           !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_plain_token_secret_encoding(
+               temp_str, context_t->args, &parsed, &token_secret_encoding)) {
+            totp_cli_printf_unknown_argument(temp_str);
+        }
+
+        if(!parsed) {
+            furi_string_free(temp_str);
+            return TotpIteratorUpdateTokenResultInvalidArguments;
+        }
+    }
+
+    // Reading token secret
+    furi_string_reset(temp_str);
+    TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
+    if(!totp_cli_read_line(context_t->cli, temp_str, mask_user_input)) {
+        totp_cli_delete_last_line();
+        furi_string_secure_free(temp_str);
+        return TotpIteratorUpdateTokenResultCancelled;
+    }
+
+    totp_cli_delete_last_line();
+
+    bool secret_set = token_info_set_secret(
+        token_info,
+        furi_string_get_cstr(temp_str),
+        furi_string_size(temp_str),
+        token_secret_encoding,
+        context_t->iv);
+
+    furi_string_secure_free(temp_str);
+
+    if (!secret_set) {
+        return TotpIteratorUpdateTokenResultInvalidSecret;
+    }
+
+    return TotpIteratorUpdateTokenResultSuccess;
+}
+
 void totp_cli_command_add_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
                     ", " TOTP_CLI_COMMAND_ADD_ALT2 "     Add new token\r\n");
@@ -75,90 +144,30 @@ void totp_cli_command_add_docopt_options() {
 }
 
 void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
-    FuriString* temp_str = furi_string_alloc();
-    TokenInfo* token_info = token_info_alloc();
-
-    // Reading token name
-    if(!args_read_probably_quoted_string_and_trim(args, temp_str)) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
-        furi_string_free(temp_str);
-        token_info_free(token_info);
+    if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
         return;
     }
 
-    size_t temp_cstr_len = furi_string_size(temp_str);
-    token_info->name = malloc(temp_cstr_len + 1);
-    furi_check(token_info->name != NULL);
-    strlcpy(token_info->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
 
-    // Read optional arguments
-    bool mask_user_input = true;
-    PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
-    while(args_read_string_and_trim(args, temp_str)) {
-        bool parsed = false;
-        if(!totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
-           !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_plain_token_secret_encoding(
-               temp_str, args, &parsed, &token_secret_encoding)) {
-            totp_cli_printf_unknown_argument(temp_str);
-        }
+    TOTP_CLI_LOCK_UI(plugin_state);
 
-        if(!parsed) {
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
-            furi_string_free(temp_str);
-            token_info_free(token_info);
-            return;
-        }
-    }
+    struct TotpAddContext add_context = { .args = args, .cli = cli, .iv = &plugin_state->iv[0] };
+    TotpIteratorUpdateTokenResult add_result = totp_token_info_iterator_add_new_token(iterator_context, &add_token_handler, &add_context);
 
-    // Reading token secret
-    furi_string_reset(temp_str);
-    TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
-    if(!totp_cli_read_line(cli, temp_str, mask_user_input) ||
-       !totp_cli_ensure_authenticated(plugin_state, cli)) {
-        TOTP_CLI_DELETE_LAST_LINE();
+    if(add_result == TotpIteratorUpdateTokenResultSuccess) {
+        TOTP_CLI_PRINTF_SUCCESS(
+                "Token \"%s\" has been successfully added\r\n",
+                furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name));
+    } else if (add_result == TotpIteratorUpdateTokenResultCancelled) {
         TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
-        furi_string_secure_free(temp_str);
-        token_info_free(token_info);
-        return;
-    }
-
-    TOTP_CLI_DELETE_LAST_LINE();
-
-    bool load_generate_token_scene = false;
-    if(plugin_state->current_scene == TotpSceneGenerateToken) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-        load_generate_token_scene = true;
-    }
-
-    bool secret_set = token_info_set_secret(
-        token_info,
-        furi_string_get_cstr(temp_str),
-        furi_string_size(temp_str),
-        token_secret_encoding,
-        plugin_state->iv);
-
-    furi_string_secure_free(temp_str);
-
-    if(secret_set) {
-        TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, token_info, furi_check);
-        plugin_state->tokens_count++;
-
-        if(totp_config_file_save_new_token(token_info) == TotpConfigFileUpdateSuccess) {
-            TOTP_CLI_PRINTF_SUCCESS(
-                "Token \"%s\" has been successfully added\r\n", token_info->name);
-        } else {
-            TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
-        }
-    } else {
-        token_info_free(token_info);
+    } else if (add_result == TotpIteratorUpdateTokenResultInvalidArguments) {
+        totp_cli_print_invalid_arguments();
+    } else if (add_result == TotpIteratorUpdateTokenResultInvalidSecret) {
         TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+    } else if (add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {
+        totp_cli_print_error_updating_config_file();
     }
 
-    if(load_generate_token_scene) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-    }
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 6 - 14
cli/commands/automation/automation.c

@@ -31,7 +31,7 @@ void totp_cli_command_automation_docopt_arguments() {
         "\r\n");
 }
 
-static void totp_cli_command_automation_print_method(AutomationMethod method, char* color) {
+static void totp_cli_command_automation_print_method(AutomationMethod method, const char* color) {
 #ifdef TOTP_BADBT_TYPE_ENABLED
     bool has_previous_method = false;
 #endif
@@ -88,26 +88,20 @@ void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* a
 
     do {
         if(!args_valid) {
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+            totp_cli_print_invalid_arguments();
             break;
         }
 
         if(new_method_provided) {
-            Scene previous_scene = TotpSceneNone;
-            if(plugin_state->current_scene == TotpSceneGenerateToken ||
-               plugin_state->current_scene == TotpSceneAppSettings) {
-                previous_scene = plugin_state->current_scene;
-                totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-            }
+            TOTP_CLI_LOCK_UI(plugin_state);
 
             plugin_state->automation_method = new_method;
-            if(totp_config_file_update_automation_method(new_method) ==
-               TotpConfigFileUpdateSuccess) {
+            if(totp_config_file_update_automation_method(plugin_state)) {
                 TOTP_CLI_PRINTF_SUCCESS("Automation method is set to ");
                 totp_cli_command_automation_print_method(new_method, TOTP_CLI_COLOR_SUCCESS);
                 cli_nl();
             } else {
-                TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+                totp_cli_print_error_updating_config_file();
             }
 
 #ifdef TOTP_BADBT_TYPE_ENABLED
@@ -118,9 +112,7 @@ void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* a
             }
 #endif
 
-            if(previous_scene != TotpSceneNone) {
-                totp_scene_director_activate_scene(plugin_state, previous_scene, NULL);
-            }
+            TOTP_CLI_UNLOCK_UI(plugin_state);
         } else {
             TOTP_CLI_PRINTF_INFO("Current automation method is ");
             totp_cli_command_automation_print_method(

+ 23 - 29
cli/commands/delete/delete.c

@@ -3,7 +3,6 @@
 #include <stdlib.h>
 #include <ctype.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../services/config/config.h"
 #include "../../cli_helpers.h"
 #include "../../../ui/scene_director.h"
@@ -37,10 +36,13 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
         return;
     }
 
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+
     int token_number;
     if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
-       token_number > plugin_state->tokens_count) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+       (size_t)token_number >
+            totp_token_info_iterator_get_total_count(iterator_context)) {
+        totp_cli_print_invalid_arguments();
         return;
     }
 
@@ -51,23 +53,26 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
             confirm_needed = false;
         } else {
             totp_cli_printf_unknown_argument(temp_str);
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+            totp_cli_print_invalid_arguments();
             furi_string_free(temp_str);
             return;
         }
     }
     furi_string_free(temp_str);
 
-    ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+    TOTP_CLI_LOCK_UI(plugin_state);
 
-    TokenInfo* token_info = list_node->data;
+    size_t original_token_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+    totp_token_info_iterator_go_to(iterator_context, token_number - 1);
+    const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
+    const char* token_info_name = furi_string_get_cstr(token_info->name);
 
     bool confirmed = !confirm_needed;
     if(confirm_needed) {
         TOTP_CLI_PRINTF_WARNING("WARNING!\r\n");
         TOTP_CLI_PRINTF_WARNING(
             "TOKEN \"%s\" WILL BE PERMANENTLY DELETED WITHOUT ABILITY TO RECOVER IT.\r\n",
-            token_info->name);
+            token_info_name);
         TOTP_CLI_PRINTF_WARNING("Confirm? [y/n]\r\n");
         fflush(stdout);
         char user_pick;
@@ -80,32 +85,21 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
     }
 
     if(confirmed) {
-        if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
-            return;
-        }
-
-        bool activate_generate_token_scene = false;
-        if(plugin_state->current_scene != TotpSceneAuthentication) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-            activate_generate_token_scene = true;
-        }
-
-        plugin_state->tokens_list = list_remove(plugin_state->tokens_list, list_node);
-        plugin_state->tokens_count--;
-
-        if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+        totp_cli_print_processing();
+        if(totp_token_info_iterator_remove_current_token_info(iterator_context)) {
+            totp_cli_delete_last_line();
             TOTP_CLI_PRINTF_SUCCESS(
-                "Token \"%s\" has been successfully deleted\r\n", token_info->name);
+                "Token \"%s\" has been successfully deleted\r\n", token_info_name);
+            totp_token_info_iterator_go_to(iterator_context, 0);
         } else {
-            TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
-        }
-
-        token_info_free(token_info);
-
-        if(activate_generate_token_scene) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+            totp_cli_delete_last_line();
+            totp_cli_print_error_updating_config_file();
+            totp_token_info_iterator_go_to(iterator_context, original_token_index);
         }
     } else {
         TOTP_CLI_PRINTF_INFO("User has not confirmed\r\n");
+        totp_token_info_iterator_go_to(iterator_context, original_token_index);
     }
+
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 29 - 17
cli/commands/details/details.c

@@ -1,9 +1,10 @@
 #include "details.h"
 #include <stdlib.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../services/config/constants.h"
+#include "../../../services/config/config.h"
+#include "../../../ui/scene_director.h"
 #include "../../cli_helpers.h"
 #include "../../common_command_arguments.h"
 
@@ -53,26 +54,37 @@ void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args
     }
 
     int token_number;
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
     if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
-       token_number > plugin_state->tokens_count) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+       (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) {
+        totp_cli_print_invalid_arguments();
         return;
     }
 
-    ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+    TOTP_CLI_LOCK_UI(plugin_state);
 
-    TokenInfo* token_info = list_node->data;
+    size_t original_token_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+    if(totp_token_info_iterator_go_to(iterator_context, token_number - 1)) {
+        const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
 
-    TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
-    TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value");
-    TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
-    TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number);
-    TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Name", token_info->name);
-    TOTP_CLI_PRINTF(
-        "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info));
-    TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits);
-    TOTP_CLI_PRINTF(
-        "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " ");
-    print_automation_features(token_info);
-    TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+        TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+        TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value");
+        TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+        TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number);
+        TOTP_CLI_PRINTF(
+            "| %-20s | %-28.28s |\r\n", "Name", furi_string_get_cstr(token_info->name));
+        TOTP_CLI_PRINTF(
+            "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info));
+        TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits);
+        TOTP_CLI_PRINTF(
+            "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " ");
+        print_automation_features(token_info);
+        TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n");
+    } else {
+        totp_cli_print_error_loading_token_info();
+    }
+
+    totp_token_info_iterator_go_to(iterator_context, original_token_index);
+
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 20 - 9
cli/commands/list/list.c

@@ -1,8 +1,9 @@
 #include "list.h"
 #include <stdlib.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../services/config/constants.h"
+#include "../../../services/config/config.h"
+#include "../../../ui/scene_director.h"
 #include "../../cli_helpers.h"
 
 void totp_cli_command_list_docopt_commands() {
@@ -20,25 +21,35 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
         return;
     }
 
-    if(plugin_state->tokens_list == NULL) {
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    size_t total_count = totp_token_info_iterator_get_total_count(iterator_context);
+    if(total_count <= 0) {
         TOTP_CLI_PRINTF("There are no tokens");
         return;
     }
 
+    TOTP_CLI_LOCK_UI(plugin_state);
+
+    size_t original_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
     TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur");
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
-    uint16_t index = 1;
-    TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
-        TokenInfo* token_info = (TokenInfo*)node->data;
+    for(size_t i = 0; i < total_count; i++) {
+        totp_token_info_iterator_go_to(iterator_context, i);
+        const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context);
         TOTP_CLI_PRINTF(
             "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
-            index,
-            token_info->name,
+            i + 1,
+            furi_string_get_cstr(token_info->name),
             token_info_get_algo_as_cstr(token_info),
             token_info->digits,
             token_info->duration);
-        index++;
-    });
+    }
+
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
+
+    totp_token_info_iterator_go_to(iterator_context, original_index);
+
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 31 - 25
cli/commands/move/move.c

@@ -2,7 +2,6 @@
 
 #include <stdlib.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../services/config/config.h"
 #include "../../cli_helpers.h"
@@ -33,42 +32,49 @@ void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, C
         return;
     }
 
-    int token_index;
-    if(!args_read_int_and_trim(args, &token_index) || token_index < 1 ||
-       token_index > plugin_state->tokens_count) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+    int token_number;
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    size_t total_count = totp_token_info_iterator_get_total_count(iterator_context);
+    if(!args_read_int_and_trim(args, &token_number) || token_number < 1 ||
+       (size_t)token_number > total_count) {
+        totp_cli_print_invalid_arguments();
         return;
     }
 
-    int new_token_index = 0;
+    int new_token_number = 0;
 
-    if(!args_read_int_and_trim(args, &new_token_index) || new_token_index < 1 ||
-       new_token_index > plugin_state->tokens_count) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+    if(!args_read_int_and_trim(args, &new_token_number) || new_token_number < 1 ||
+       (size_t)new_token_number > total_count) {
+        totp_cli_print_invalid_arguments();
         return;
     }
 
-    bool activate_generate_token_scene = false;
-    if(plugin_state->current_scene != TotpSceneAuthentication) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-        activate_generate_token_scene = true;
+    if(token_number == new_token_number) {
+        TOTP_CLI_PRINTF_ERROR("New token number matches current token number\r\n");
+        return;
     }
 
-    TokenInfo* token_info = NULL;
-    plugin_state->tokens_list =
-        list_remove_at(plugin_state->tokens_list, token_index - 1, (void**)&token_info);
-    furi_check(token_info != NULL);
-    plugin_state->tokens_list =
-        list_insert_at(plugin_state->tokens_list, new_token_index - 1, token_info);
+    TOTP_CLI_LOCK_UI(plugin_state);
+
+    size_t token_index = token_number - 1;
+    size_t new_token_index = new_token_number - 1;
+
+    size_t original_token_index = totp_token_info_iterator_get_current_token_index(iterator_context);
 
-    if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+    totp_cli_print_processing();
+
+    if(totp_token_info_iterator_go_to(iterator_context, token_index) &&
+       totp_token_info_iterator_move_current_token_info(iterator_context, new_token_index)) {
+        totp_cli_delete_last_line();
         TOTP_CLI_PRINTF_SUCCESS(
-            "Token \"%s\" has been successfully updated\r\n", token_info->name);
+            "Token \"%s\" has been successfully updated\r\n",
+            furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name));
     } else {
-        TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+        totp_cli_delete_last_line();
+        totp_cli_print_error_updating_config_file();
     }
 
-    if(activate_generate_token_scene) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-    }
+    totp_token_info_iterator_go_to(iterator_context, original_token_index);
+
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 7 - 14
cli/commands/notification/notification.c

@@ -28,7 +28,8 @@ void totp_cli_command_notification_docopt_arguments() {
         ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\r\n");
 }
 
-static void totp_cli_command_notification_print_method(NotificationMethod method, char* color) {
+static void
+    totp_cli_command_notification_print_method(NotificationMethod method, const char* color) {
     bool has_previous_method = false;
     if(method & NotificationMethodSound) {
         TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "\"");
@@ -73,31 +74,23 @@ void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString*
 
     do {
         if(!args_valid) {
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+            totp_cli_print_invalid_arguments();
             break;
         }
 
         if(new_method_provided) {
-            Scene previous_scene = TotpSceneNone;
-            if(plugin_state->current_scene == TotpSceneGenerateToken ||
-               plugin_state->current_scene == TotpSceneAppSettings) {
-                previous_scene = plugin_state->current_scene;
-                totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-            }
+            TOTP_CLI_LOCK_UI(plugin_state);
 
             plugin_state->notification_method = new_method;
-            if(totp_config_file_update_notification_method(new_method) ==
-               TotpConfigFileUpdateSuccess) {
+            if(totp_config_file_update_notification_method(plugin_state)) {
                 TOTP_CLI_PRINTF_SUCCESS("Notification method is set to ");
                 totp_cli_command_notification_print_method(new_method, TOTP_CLI_COLOR_SUCCESS);
                 cli_nl();
             } else {
-                TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+                totp_cli_print_error_updating_config_file();
             }
 
-            if(previous_scene != TotpSceneNone) {
-                totp_scene_director_activate_scene(plugin_state, previous_scene, NULL);
-            }
+            TOTP_CLI_UNLOCK_UI(plugin_state);
         } else {
             TOTP_CLI_PRINTF_INFO("Current notification method is ");
             totp_cli_command_notification_print_method(

+ 17 - 51
cli/commands/pin/pin.c

@@ -2,7 +2,6 @@
 
 #include <stdlib.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../types/user_pin_codes.h"
 #include "../../../services/config/config.h"
@@ -65,14 +64,14 @@ static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) {
                 }
             }
         } else if(c == CliSymbolAsciiETX) {
-            TOTP_CLI_DELETE_CURRENT_LINE();
+            totp_cli_delete_current_line();
             TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
             return false;
         } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) {
             if(*pin_length > 0) {
                 *pin_length = *pin_length - 1;
                 pin[*pin_length] = 0;
-                TOTP_CLI_DELETE_LAST_CHAR();
+                totp_cli_delete_last_char();
             }
         } else if(c == CliSymbolAsciiCR) {
             cli_nl();
@@ -80,7 +79,7 @@ static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) {
         }
     }
 
-    TOTP_CLI_DELETE_LAST_LINE();
+    totp_cli_delete_last_line();
     return true;
 }
 
@@ -97,22 +96,22 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
         } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) == 0) {
             do_remove = true;
         } else {
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+            totp_cli_print_invalid_arguments();
         }
     } else {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+        totp_cli_print_invalid_arguments();
     }
 
     if((do_change || do_remove) && totp_cli_ensure_authenticated(plugin_state, cli)) {
-        bool load_generate_token_scene = false;
+        TOTP_CLI_LOCK_UI(plugin_state);
         do {
             uint8_t old_iv[TOTP_IV_SIZE];
             memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE);
             uint8_t new_pin[TOTP_IV_SIZE];
+            memset(&new_pin[0], 0, TOTP_IV_SIZE);
             uint8_t new_pin_length = 0;
             if(do_change) {
-                if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length) ||
-                   !totp_cli_ensure_authenticated(plugin_state, cli)) {
+                if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length)) {
                     memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
                     break;
                 }
@@ -121,7 +120,7 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
                 memset(&new_pin[0], 0, TOTP_IV_SIZE);
             }
 
-            char* backup_path = totp_config_file_backup();
+            char* backup_path = totp_config_file_backup(plugin_state);
             if(backup_path != NULL) {
                 TOTP_CLI_PRINTF_WARNING("Backup conf file %s has been created\r\n", backup_path);
                 TOTP_CLI_PRINTF_WARNING(
@@ -134,61 +133,28 @@ void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cl
                 break;
             }
 
-            if(plugin_state->current_scene == TotpSceneGenerateToken) {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-                load_generate_token_scene = true;
-            }
-
-            TOTP_CLI_PRINTF("Encrypting, please wait...\r\n");
+            TOTP_CLI_PRINTF("Encrypting...\r\n");
 
-            memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
-            memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE);
-            if(plugin_state->crypto_verify_data != NULL) {
-                free(plugin_state->crypto_verify_data);
-                plugin_state->crypto_verify_data = NULL;
-            }
-
-            if(!totp_crypto_seed_iv(
-                   plugin_state, new_pin_length > 0 ? &new_pin[0] : NULL, new_pin_length)) {
-                memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
-                TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
-                break;
-            }
+            bool update_result =
+                totp_config_file_update_encryption(plugin_state, new_pin, new_pin_length);
 
             memset_s(&new_pin[0], TOTP_IV_SIZE, 0, TOTP_IV_SIZE);
 
-            TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
-                TokenInfo* token_info = node->data;
-                size_t plain_token_length;
-                uint8_t* plain_token = totp_crypto_decrypt(
-                    token_info->token, token_info->token_length, &old_iv[0], &plain_token_length);
-                free(token_info->token);
-                token_info->token = totp_crypto_encrypt(
-                    plain_token,
-                    plain_token_length,
-                    &plugin_state->iv[0],
-                    &token_info->token_length);
-                memset_s(plain_token, plain_token_length, 0, plain_token_length);
-                free(plain_token);
-            });
-
-            TOTP_CLI_DELETE_LAST_LINE();
-
-            if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
+            totp_cli_delete_last_line();
+
+            if(update_result) {
                 if(do_change) {
                     TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully changed\r\n");
                 } else if(do_remove) {
                     TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully removed\r\n");
                 }
             } else {
-                TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
+                totp_cli_print_error_updating_config_file();
             }
 
         } while(false);
 
-        if(load_generate_token_scene) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-        }
+        TOTP_CLI_UNLOCK_UI(plugin_state);
     }
 
     furi_string_free(temp_str);

+ 8 - 2
cli/commands/reset/reset.c

@@ -3,6 +3,7 @@
 #include <stdlib.h>
 #include <furi/core/string.h>
 #include "../../cli_helpers.h"
+#include "../../../ui/scene_director.h"
 #include "../../../services/config/config.h"
 
 #define TOTP_CLI_RESET_CONFIRMATION_KEYWORD "YES"
@@ -16,7 +17,11 @@ void totp_cli_command_reset_docopt_usage() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_RESET "\r\n");
 }
 
-void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue) {
+void totp_cli_command_reset_handle(
+    PluginState* plugin_state,
+    Cli* cli,
+    FuriMessageQueue* event_queue) {
+    TOTP_CLI_LOCK_UI(plugin_state);
     TOTP_CLI_PRINTF_WARNING(
         "As a result of reset all the settings and tokens will be permanently lost.\r\n");
     TOTP_CLI_PRINTF_WARNING("Do you really want to reset application?\r\n");
@@ -27,11 +32,12 @@ void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue) {
                         furi_string_cmpi_str(temp_str, TOTP_CLI_RESET_CONFIRMATION_KEYWORD) == 0;
     furi_string_free(temp_str);
     if(is_confirmed) {
-        totp_config_file_reset();
+        totp_config_file_reset(plugin_state);
         TOTP_CLI_PRINTF_SUCCESS("Application has been successfully reset to default.\r\n");
         TOTP_CLI_PRINTF_SUCCESS("Now application will be closed to apply all the changes.\r\n");
         totp_cli_force_close_app(event_queue);
     } else {
         TOTP_CLI_PRINTF_INFO("Action was not confirmed by user\r\n");
+        TOTP_CLI_UNLOCK_UI(plugin_state);
     }
 }

+ 4 - 1
cli/commands/reset/reset.h

@@ -5,6 +5,9 @@
 
 #define TOTP_CLI_COMMAND_RESET "reset"
 
-void totp_cli_command_reset_handle(Cli* cli, FuriMessageQueue* event_queue);
+void totp_cli_command_reset_handle(
+    PluginState* plugin_state,
+    Cli* cli,
+    FuriMessageQueue* event_queue);
 void totp_cli_command_reset_docopt_commands();
 void totp_cli_command_reset_docopt_usage();

+ 4 - 9
cli/commands/timezone/timezone.c

@@ -33,19 +33,14 @@ void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* arg
         char* strtof_endptr;
         float tz = strtof(furi_string_get_cstr(temp_str), &strtof_endptr);
         if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) {
+            TOTP_CLI_LOCK_UI(plugin_state);
             plugin_state->timezone_offset = tz;
-            if(totp_config_file_update_timezone_offset(tz) == TotpConfigFileUpdateSuccess) {
+            if(totp_config_file_update_timezone_offset(plugin_state)) {
                 TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz);
             } else {
-                TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
-            }
-            if(plugin_state->current_scene == TotpSceneGenerateToken) {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-                totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-            } else if(plugin_state->current_scene == TotpSceneAppSettings) {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-                totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
+                totp_cli_print_error_updating_config_file();
             }
+            TOTP_CLI_UNLOCK_UI(plugin_state);
         } else {
             TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n");
         }

+ 93 - 95
cli/commands/update/update.c

@@ -1,7 +1,6 @@
 #include "update.h"
 #include <stdlib.h>
 #include <lib/toolbox/args.h>
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../../../services/config/config.h"
 #include "../../../services/convert/convert.h"
@@ -11,43 +10,25 @@
 
 #define TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX "-s"
 
-void totp_cli_command_update_docopt_commands() {
-    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_UPDATE "           Update existing token\r\n");
-}
+struct TotpUpdateContext {
+    FuriString* args;
+    Cli* cli;
+    uint8_t* iv;
+};
 
-void totp_cli_command_update_docopt_usage() {
-    TOTP_CLI_PRINTF(
-        "  " TOTP_CLI_COMMAND_NAME
-        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL(
-            DOCOPT_OPTION(
-                TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
-                DOCOPT_ARGUMENT(
-                    TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n");
-}
-
-void totp_cli_command_update_docopt_options() {
-    TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
-        TOTP_CLI_COMMAND_ARG_NAME_PREFIX,
-        DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) "      Token name\r\n");
-
-    TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
-        TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) "             Update token secret\r\n");
-}
+enum TotpIteratorUpdateTokenResultsEx {
+    TotpIteratorUpdateTokenResultInvalidSecret = 1,
+    TotpIteratorUpdateTokenResultCancelled = 2,
+    TotpIteratorUpdateTokenResultInvalidArguments = 3
+};
 
 static bool
-    totp_cli_try_read_name(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed) {
+    totp_cli_try_read_name(TokenInfo* token_info, const FuriString* arg, FuriString* args, bool* parsed) {
     if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_NAME_PREFIX) == 0) {
-        if(!args_read_probably_quoted_string_and_trim(args, arg) || furi_string_empty(arg)) {
+        if(!args_read_probably_quoted_string_and_trim(args, token_info->name) ||
+           furi_string_empty(token_info->name)) {
             totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_NAME_PREFIX);
         } else {
-            if(token_info->name != NULL) {
-                free(token_info->name);
-            }
-
-            size_t temp_cstr_len = furi_string_size(arg);
-            token_info->name = malloc(temp_cstr_len + 1);
-            furi_check(token_info->name != NULL);
-            strlcpy(token_info->name, furi_string_get_cstr(arg), temp_cstr_len + 1);
             *parsed = true;
         }
 
@@ -67,103 +48,120 @@ static bool totp_cli_try_read_change_secret_flag(const FuriString* arg, bool* pa
     return false;
 }
 
-void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
-    FuriString* temp_str = furi_string_alloc();
-
-    if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
-        return;
-    }
-
-    int token_number;
-    if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
-       token_number > plugin_state->tokens_count) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
-        return;
-    }
-
-    ListNode* list_item = list_element_at(plugin_state->tokens_list, token_number - 1);
-    TokenInfo* existing_token_info = list_item->data;
-    TokenInfo* token_info = token_info_clone(existing_token_info);
+static TotpIteratorUpdateTokenResult update_token_handler(TokenInfo* token_info, const void* context) {
+    const struct TotpUpdateContext* context_t = context;
 
     // Read optional arguments
+    FuriString* temp_str = furi_string_alloc();
     bool mask_user_input = true;
     bool update_token_secret = false;
     PlainTokenSecretEncoding token_secret_encoding = PLAIN_TOKEN_ENCODING_BASE32;
-    while(args_read_string_and_trim(args, temp_str)) {
+    while(args_read_string_and_trim(context_t->args, temp_str)) {
         bool parsed = false;
-        if(!totp_cli_try_read_name(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_algo(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_digits(token_info, temp_str, args, &parsed) &&
-           !totp_cli_try_read_duration(token_info, temp_str, args, &parsed) &&
+        if(!totp_cli_try_read_name(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) &&
+           !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) &&
            !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) &&
            !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) &&
-           !totp_cli_try_read_automation_features(token_info, temp_str, args, &parsed) &&
+           !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) &&
            !totp_cli_try_read_plain_token_secret_encoding(
-               temp_str, args, &parsed, &token_secret_encoding)) {
+               temp_str, context_t->args, &parsed, &token_secret_encoding)) {
             totp_cli_printf_unknown_argument(temp_str);
         }
 
         if(!parsed) {
-            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
             furi_string_free(temp_str);
-            token_info_free(token_info);
-            return;
+            return TotpIteratorUpdateTokenResultInvalidArguments;
         }
     }
 
-    bool token_secret_read = false;
     if(update_token_secret) {
         // Reading token secret
         furi_string_reset(temp_str);
         TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]\r\n");
-        token_secret_read = totp_cli_read_line(cli, temp_str, mask_user_input);
-        TOTP_CLI_DELETE_LAST_LINE();
-        if(!token_secret_read) {
-            TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
+        bool token_secret_read = totp_cli_read_line(context_t->cli, temp_str, mask_user_input);
+        totp_cli_delete_last_line();
+        if (!token_secret_read) {
+            furi_string_secure_free(temp_str);
+            return TotpIteratorUpdateTokenResultCancelled;
         }
-    }
-
-    bool load_generate_token_scene = false;
-    if(plugin_state->current_scene == TotpSceneGenerateToken) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneNone, NULL);
-        load_generate_token_scene = true;
-    }
 
-    bool token_secret_set = false;
-    if(update_token_secret && token_secret_read) {
-        if(token_info->token != NULL) {
-            free(token_info->token);
-        }
-        token_secret_set = token_info_set_secret(
+        if (!token_info_set_secret(
             token_info,
             furi_string_get_cstr(temp_str),
             furi_string_size(temp_str),
             token_secret_encoding,
-            plugin_state->iv);
-        if(!token_secret_set) {
-            TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+            context_t->iv)) {
+            furi_string_secure_free(temp_str);
+            return TotpIteratorUpdateTokenResultInvalidSecret;
         }
     }
 
     furi_string_secure_free(temp_str);
 
-    if(!update_token_secret || (token_secret_read && token_secret_set)) {
-        list_item->data = token_info;
+    return TotpIteratorUpdateTokenResultSuccess;
+}
 
-        if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
-            TOTP_CLI_PRINTF_SUCCESS(
-                "Token \"%s\" has been successfully updated\r\n", token_info->name);
-            token_info_free(existing_token_info);
-        } else {
-            TOTP_CLI_PRINT_ERROR_UPDATING_CONFIG_FILE();
-            list_item->data = existing_token_info;
-            token_info_free(token_info);
-        }
-    } else {
-        token_info_free(token_info);
+void totp_cli_command_update_docopt_commands() {
+    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_UPDATE "           Update existing token\r\n");
+}
+
+void totp_cli_command_update_docopt_usage() {
+    TOTP_CLI_PRINTF(
+        "  " TOTP_CLI_COMMAND_NAME
+        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL(
+            DOCOPT_OPTION(
+                TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
+                DOCOPT_ARGUMENT(
+                    TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n");
+}
+
+void totp_cli_command_update_docopt_options() {
+    TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
+        TOTP_CLI_COMMAND_ARG_NAME_PREFIX,
+        DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) "      Token name\r\n");
+
+    TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
+        TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) "             Update token secret\r\n");
+}
+
+void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+    if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
+        return;
+    }
+
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+
+    int token_number;
+    if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
+       (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) {
+        totp_cli_print_invalid_arguments();
+        return;
     }
 
-    if(load_generate_token_scene) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+    TOTP_CLI_LOCK_UI(plugin_state);
+
+    size_t previous_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+    totp_token_info_iterator_go_to(iterator_context, token_number - 1);
+
+    struct TotpUpdateContext update_context = { .args = args, .cli = cli, .iv = &plugin_state->iv[0] };
+    TotpIteratorUpdateTokenResult update_result = totp_token_info_iterator_update_current_token(iterator_context, &update_token_handler, &update_context);
+        
+    if(update_result == TotpIteratorUpdateTokenResultSuccess) {
+        TOTP_CLI_PRINTF_SUCCESS(
+            "Token \"%s\" has been successfully updated\r\n",
+            furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name));
+    } else if (update_result == TotpIteratorUpdateTokenResultInvalidArguments) {
+        totp_cli_print_invalid_arguments();
+    } else if (update_result == TotpIteratorUpdateTokenResultCancelled) {
+        TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n");
+    } else if (update_result == TotpIteratorUpdateTokenResultInvalidSecret) {
+        TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+    } else if (update_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {        
+        totp_cli_print_error_updating_config_file();
     }
+    
+    totp_token_info_iterator_go_to(iterator_context, previous_index);
+    TOTP_CLI_UNLOCK_UI(plugin_state);
 }

+ 2 - 2
cli/common_command_arguments.c

@@ -1,11 +1,11 @@
 #include "common_command_arguments.h"
 #include <lib/toolbox/args.h>
 
-inline void totp_cli_printf_missed_argument_value(char* arg) {
+void totp_cli_printf_missed_argument_value(char* arg) {
     TOTP_CLI_PRINTF_ERROR("Missed or incorrect value for argument \"%s\"\r\n", arg);
 }
 
-inline void totp_cli_printf_unknown_argument(const FuriString* arg) {
+void totp_cli_printf_unknown_argument(const FuriString* arg) {
     TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(arg));
 }
 

+ 6 - 0
cli/common_command_arguments.h

@@ -19,23 +19,29 @@
 #define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding"
 
 void totp_cli_printf_unknown_argument(const FuriString* arg);
+
 void totp_cli_printf_missed_argument_value(char* arg);
+
 bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed);
+
 bool totp_cli_try_read_digits(
     TokenInfo* token_info,
     const FuriString* arg,
     FuriString* args,
     bool* parsed);
+
 bool totp_cli_try_read_duration(
     TokenInfo* token_info,
     const FuriString* arg,
     FuriString* args,
     bool* parsed);
+
 bool totp_cli_try_read_automation_features(
     TokenInfo* token_info,
     FuriString* arg,
     FuriString* args,
     bool* parsed);
+
 bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag);
 
 bool totp_cli_try_read_plain_token_secret_encoding(

+ 0 - 136
lib/linked_list/linked_list.c

@@ -1,136 +0,0 @@
-#include "linked_list.h"
-
-ListNode* list_init_head(void* data) {
-    ListNode* new = malloc(sizeof(ListNode));
-    if(new == NULL) return NULL;
-    new->data = data;
-    new->next = NULL;
-    return new;
-}
-
-ListNode* list_add(ListNode* head, void* data) {
-    ListNode* new = malloc(sizeof(ListNode));
-    if(new == NULL) return NULL;
-    new->data = data;
-    new->next = NULL;
-
-    if(head == NULL)
-        head = new;
-    else {
-        ListNode* it;
-
-        for(it = head; it->next != NULL; it = it->next)
-            ;
-
-        it->next = new;
-    }
-
-    return head;
-}
-
-ListNode* list_find(ListNode* head, const void* data) {
-    ListNode* it = NULL;
-
-    for(it = head; it != NULL; it = it->next)
-        if(it->data == data) break;
-
-    return it;
-}
-
-ListNode* list_element_at(ListNode* head, uint16_t index) {
-    ListNode* it;
-    uint16_t i;
-    for(it = head, i = 0; it != NULL && i < index; it = it->next, i++)
-        ;
-    return it;
-}
-
-ListNode* list_remove(ListNode* head, ListNode* ep) {
-    if(head == NULL) {
-        return NULL;
-    }
-
-    if(head == ep) {
-        ListNode* new_head = head->next;
-        free(head);
-        return new_head;
-    }
-
-    ListNode* it;
-
-    for(it = head; it->next != ep; it = it->next)
-        ;
-
-    it->next = ep->next;
-    free(ep);
-
-    return head;
-}
-
-ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data) {
-    if(head == NULL) {
-        return NULL;
-    }
-
-    ListNode* it;
-    ListNode* prev = NULL;
-
-    uint16_t i;
-
-    for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
-        ;
-
-    if(it == NULL) return head;
-
-    ListNode* new_head = head;
-    if(prev == NULL) {
-        new_head = it->next;
-    } else {
-        prev->next = it->next;
-    }
-
-    if(removed_node_data != NULL) {
-        *removed_node_data = it->data;
-    }
-
-    free(it);
-
-    return new_head;
-}
-
-ListNode* list_insert_at(ListNode* head, uint16_t index, void* data) {
-    if(index == 0 || head == NULL) {
-        ListNode* new_head = list_init_head(data);
-        if(new_head != NULL) {
-            new_head->next = head;
-        }
-        return new_head;
-    }
-
-    ListNode* it;
-    ListNode* prev = NULL;
-
-    uint16_t i;
-
-    for(it = head, i = 0; it != NULL && i < index; prev = it, it = it->next, i++)
-        ;
-
-    ListNode* new = malloc(sizeof(ListNode));
-    if(new == NULL) return NULL;
-    new->data = data;
-    new->next = it;
-    prev->next = new;
-
-    return head;
-}
-
-void list_free(ListNode* head) {
-    ListNode* it = head;
-    ListNode* tmp;
-
-    while(it != NULL) {
-        tmp = it;
-        it = it->next;
-        free(tmp);
-    }
-}

+ 0 - 98
lib/linked_list/linked_list.h

@@ -1,98 +0,0 @@
-#pragma once
-
-#include <stdlib.h>
-#include <inttypes.h>
-
-/**
- * @brief Single linked list node
- */
-typedef struct ListNode {
-    /**
-     * @brief Pointer to the data assigned to the current list node
-     */
-    void* data;
-
-    /**
-     * @brief Pointer to the next list node
-     */
-    struct ListNode* next;
-} ListNode;
-
-/**
- * @brief Initializes a new list node head
- * @param data data to be assigned to the head list node
- * @return Head list node
- */
-ListNode* list_init_head(void* data);
-
-/**
- * @brief Adds new list node to the end of the list
- * @param head head list node
- * @param data data to be assigned to the newly added list node
- * @return Head list node
- */
-ListNode* list_add(
-    ListNode* head,
-    void* data); /* adds element with specified data to the end of the list and returns new head node. */
-
-/**
- * @brief Searches list node with the given assigned \p data in the list
- * @param head head list node
- * @param data data to be searched
- * @return List node containing \p data if there is such a node in the list; \c NULL otherwise
- */
-ListNode* list_find(ListNode* head, const void* data);
-
-/**
- * @brief Searches list node with the given \p index in the list
- * @param head head list node
- * @param index desired list node index
- * @return List node with the given \p index in the list if there is such a list node; \c NULL otherwise
- */
-ListNode* list_element_at(ListNode* head, uint16_t index);
-
-/**
- * @brief Removes list node from the list
- * @param head head list node
- * @param ep list node to be removed
- * @return Head list node
- */
-ListNode* list_remove(ListNode* head, ListNode* ep);
-
-/**
- * @brief Removes list node with the given \p index in the list from the list
- * @param head head list node
- * @param index index of the node to be removed
- * @param[out] removed_node_data data which was assigned to the removed list node
- * @return Head list node
- */
-ListNode* list_remove_at(ListNode* head, uint16_t index, void** removed_node_data);
-
-/**
- * @brief Inserts new list node at the given index
- * @param head head list node
- * @param index index in the list where the new list node should be inserted
- * @param data data to be assgned to the new list node
- * @return Head list node
- */
-ListNode* list_insert_at(ListNode* head, uint16_t index, void* data);
-
-/**
- * @brief Disposes all the list nodes in the list
- * @param head head list node
- */
-void list_free(ListNode* head);
-
-#define TOTP_LIST_INIT_OR_ADD(head, item, assert) \
-    if(head == NULL) {                            \
-        head = list_init_head(item);              \
-        assert(head != NULL);                     \
-    } else {                                      \
-        assert(list_add(head, item) != NULL);     \
-    }
-
-#define TOTP_LIST_FOREACH(head, node, action) \
-    ListNode* node = head;                    \
-    while(node != NULL) {                     \
-        action node = node->next;             \
-    }

+ 1 - 1
lib/roll_value/roll_value.c

@@ -25,4 +25,4 @@ TOTP_ROLL_VALUE_FN(int8_t, int8_t)
 
 TOTP_ROLL_VALUE_FN(uint8_t, int8_t)
 
-TOTP_ROLL_VALUE_FN(uint16_t, int16_t);
+TOTP_ROLL_VALUE_FN(size_t, int16_t);

+ 3 - 2
lib/roll_value/roll_value.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <stddef.h>
 
 typedef uint8_t TotpRollValueOverflowBehavior;
 
@@ -47,7 +48,7 @@ TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t);
 TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
 
 /**
- * @brief Rolls \c uint16_t \p value using \p min and \p max as an value constraints with \p step step.
+ * @brief Rolls \c size_t \p value using \p min and \p max as an value constraints with \p step step.
  *        When value reaches constraint value \p overflow_behavior defines what to do next.
  * @param[in,out] value value to roll
  * @param step step to be used to change value
@@ -55,4 +56,4 @@ TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t);
  * @param max maximum possible value
  * @param overflow_behavior defines what to do when value reaches constraint value
  */
-TOTP_ROLL_VALUE_FN_HEADER(uint16_t, int16_t);
+TOTP_ROLL_VALUE_FN_HEADER(size_t, int16_t);

+ 258 - 502
services/config/config.c

@@ -1,19 +1,41 @@
 #include "config.h"
 #include <stdlib.h>
 #include <string.h>
-#include <linked_list.h>
+#include <flipper_format/flipper_format.h>
+#include <furi_hal_rtc.h>
+#include <flipper_format/flipper_format_i.h>
+#include <flipper_format/flipper_format_stream.h>
+#include <memset_s.h>
 #include "../../types/common.h"
 #include "../../types/token_info.h"
 #include "../../features_config.h"
+#include "../crypto/crypto.h"
 #include "migrations/common_migration.h"
 
-#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
 #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
-#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_PATH ".backup"
+#define CONFIG_FILE_BACKUP_DIR CONFIG_FILE_DIRECTORY_PATH "/backups"
+#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_BACKUP_DIR "/totp.conf"
 #define CONFIG_FILE_TEMP_PATH CONFIG_FILE_PATH ".tmp"
 #define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
 #define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
 
+struct ConfigFileContext {
+    /**
+     * @brief Config file reference
+     */
+    FlipperFormat* config_file;
+
+    /**
+     * @brief Storage reference
+     */
+    Storage* storage;
+
+    /**
+     * @brief Token list iterator context 
+     */
+    TokenInfoIteratorContext* token_info_iterator_context;
+};
+
 /**
  * @brief Opens storage record
  * @return Storage record
@@ -45,16 +67,31 @@ static void totp_close_config_file(FlipperFormat* file) {
  * @return backup path if backup successfully taken; \c NULL otherwise
  */
 static char* totp_config_file_backup_i(Storage* storage) {
-    uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 5;
+    if(!storage_dir_exists(storage, CONFIG_FILE_BACKUP_DIR) &&
+        !storage_simply_mkdir(storage, CONFIG_FILE_BACKUP_DIR)) {
+        return NULL;
+    }
+
+    FuriHalRtcDateTime current_datetime;
+    furi_hal_rtc_get_datetime(&current_datetime);
+
+    uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 14;
     char* backup_path = malloc(backup_path_size);
     furi_check(backup_path != NULL);
     memcpy(backup_path, CONFIG_FILE_BACKUP_BASE_PATH, sizeof(CONFIG_FILE_BACKUP_BASE_PATH));
     uint16_t i = 1;
     bool backup_file_exists;
-    while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999) {
-        snprintf(backup_path, backup_path_size, CONFIG_FILE_BACKUP_BASE_PATH ".%" PRIu16, i);
+    do {
+        snprintf(
+            backup_path,
+            backup_path_size,
+            CONFIG_FILE_BACKUP_BASE_PATH ".%4" PRIu16 "%02" PRIu8 "%02" PRIu8 "-%" PRIu16,
+            current_datetime.year,
+            current_datetime.month,
+            current_datetime.day,
+            i);
         i++;
-    }
+    } while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999);
 
     if(backup_file_exists ||
        storage_common_copy(storage, CONFIG_FILE_PATH, backup_path) != FSE_OK) {
@@ -73,7 +110,7 @@ static char* totp_config_file_backup_i(Storage* storage) {
  * @param[out] file opened config file
  * @return Config file open result
  */
-static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperFormat** file) {
+static bool totp_open_config_file(Storage* storage, FlipperFormat** file) {
     FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
 
     if(storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK) {
@@ -81,7 +118,7 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
         if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) {
             FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH);
             totp_close_config_file(fff_data_file);
-            return TotpConfigFileOpenError;
+            return false;
         }
     } else if(storage_common_stat(storage, CONFIG_FILE_PATH_PREVIOUS, NULL) == FSE_OK) {
         FURI_LOG_D(LOGGING_TAG, "Old config file %s found", CONFIG_FILE_PATH_PREVIOUS);
@@ -93,13 +130,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
             if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
                 FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
                 totp_close_config_file(fff_data_file);
-                return TotpConfigFileOpenError;
+                return false;
             }
         }
         if(storage_common_rename(storage, CONFIG_FILE_PATH_PREVIOUS, CONFIG_FILE_PATH) != FSE_OK) {
             FURI_LOG_E(LOGGING_TAG, "Error moving config to %s", CONFIG_FILE_PATH);
             totp_close_config_file(fff_data_file);
-            return TotpConfigFileOpenError;
+            return false;
         }
         FURI_LOG_I(LOGGING_TAG, "Applied config file path migration");
         return totp_open_config_file(storage, file);
@@ -112,421 +149,155 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
                 CONFIG_FILE_DIRECTORY_PATH);
             if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
                 FURI_LOG_E(LOGGING_TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
-                return TotpConfigFileOpenError;
+                return false;
             }
         }
 
         if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) {
             totp_close_config_file(fff_data_file);
             FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH);
-            return TotpConfigFileOpenError;
+            return false;
         }
 
         flipper_format_write_header_cstr(
             fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION);
-        float tmp_tz = 0;
-        flipper_format_write_comment_cstr(fff_data_file, " ");
+
         flipper_format_write_comment_cstr(
             fff_data_file,
-            "Timezone offset in hours. Important note: do not put '+' sign for positive values");
+            "Config file format specification can be found here: https://github.com/akopachov/flipper-zero_authenticator/blob/master/docs/conf-file_description.md");
+
+        float tmp_tz = 0;
         flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1);
 
         uint32_t tmp_uint32 = NotificationMethodSound | NotificationMethodVibro;
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "How to notify user when new token is generated or badusb mode is activated (possible values: 0 - do not notify, 1 - sound, 2 - vibro, 3 sound and vibro)");
         flipper_format_write_uint32(
             fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1);
 
         tmp_uint32 = AutomationMethodBadUsb;
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "Automation method (0 - None, 1 - BadUSB, 2 - BadBT, 3 - BadUSB and BadBT)");
         flipper_format_write_uint32(
             fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1);
 
-        FuriString* temp_str = furi_string_alloc();
-
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-        flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE BEGIN ===");
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-        flipper_format_write_comment_cstr(
-            fff_data_file, "# Token name which will be visible in the UI.");
-        furi_string_printf(temp_str, "%s: Sample token name", TOTP_CONFIG_KEY_TOKEN_NAME);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "# Plain token secret without spaces, dashes and etc, just pure alpha-numeric characters. Important note: plain token will be encrypted and replaced by TOTP app");
-        furi_string_printf(temp_str, "%s: plaintokensecret", TOTP_CONFIG_KEY_TOKEN_SECRET);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        furi_string_printf(
-            temp_str,
-            " # Token hashing algorithm to use during code generation. Supported options are %s, %s, %s, and %s. If you are not use which one to use - use %s",
-            TOTP_TOKEN_ALGO_SHA1_NAME,
-            TOTP_TOKEN_ALGO_SHA256_NAME,
-            TOTP_TOKEN_ALGO_SHA512_NAME,
-            TOTP_TOKEN_ALGO_STEAM_NAME,
-            TOTP_TOKEN_ALGO_SHA1_NAME);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        furi_string_printf(
-            temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "# How many digits there should be in generated code. Available options are 5, 6 and 8. Majority websites requires 6 digits code, however some rare websites wants to get 8 digits code. If you are not sure which one to use - use 6");
-        furi_string_printf(temp_str, "%s: 6", TOTP_CONFIG_KEY_TOKEN_DIGITS);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "# Token lifetime duration in seconds. Should be between 15 and 255. Majority websites requires 30, however some rare websites may require custom lifetime. If you are not sure which one to use - use 30");
-        furi_string_printf(temp_str, "%s: 30", TOTP_CONFIG_KEY_TOKEN_DURATION);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        flipper_format_write_comment_cstr(
-            fff_data_file,
-            "# Token input automation features (0 - None, 1 - press \"Enter\" key at the end of automation)");
-        furi_string_printf(temp_str, "%s: 0", TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES);
-        flipper_format_write_comment(fff_data_file, temp_str);
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        flipper_format_write_comment_cstr(fff_data_file, "=== TOKEN SAMPLE END ===");
-        flipper_format_write_comment_cstr(fff_data_file, " ");
-
-        furi_string_free(temp_str);
         if(!flipper_format_rewind(fff_data_file)) {
             totp_close_config_file(fff_data_file);
             FURI_LOG_E(LOGGING_TAG, "Rewind error");
-            return TotpConfigFileOpenError;
+            return false;
         }
     }
 
     *file = fff_data_file;
-    return TotpConfigFileOpenSuccess;
+    return true;
 }
 
-static TotpConfigFileUpdateResult
-    totp_config_file_save_new_token_i(FlipperFormat* file, const TokenInfo* token_info) {
-    TotpConfigFileUpdateResult update_result;
-    do {
-        if(!flipper_format_seek_to_end(file)) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
-
-        if(!flipper_format_write_string_cstr(file, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+char* totp_config_file_backup(const PluginState* plugin_state) {
+    if(plugin_state->config_file_context == NULL) return NULL;
 
-        bool token_is_valid = token_info->token != NULL && token_info->token_length > 0;
-        if(!token_is_valid &&
-           !flipper_format_write_comment_cstr(file, "!!! WARNING BEGIN: INVALID TOKEN !!!")) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+    totp_close_config_file(plugin_state->config_file_context->config_file);
 
-        if(!flipper_format_write_hex(
-               file, TOTP_CONFIG_KEY_TOKEN_SECRET, token_info->token, token_info->token_length)) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+    char* result = totp_config_file_backup_i(plugin_state->config_file_context->storage);
 
-        if(!token_is_valid && !flipper_format_write_comment_cstr(file, "!!! WARNING END !!!")) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+    totp_open_config_file(
+        plugin_state->config_file_context->storage,
+        &plugin_state->config_file_context->config_file);
 
-        if(!flipper_format_write_string_cstr(
-               file, TOTP_CONFIG_KEY_TOKEN_ALGO, token_info_get_algo_as_cstr(token_info))) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+    totp_token_info_iterator_attach_to_config_file(
+        plugin_state->config_file_context->token_info_iterator_context,
+        plugin_state->config_file_context->config_file);
 
-        uint32_t tmp_uint32 = token_info->digits;
-        if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+    return result;
+}
 
-        tmp_uint32 = token_info->duration;
-        if(!flipper_format_write_uint32(file, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
-            update_result = TotpConfigFileUpdateError;
-            break;
-        }
+bool totp_config_file_update_timezone_offset(const PluginState* plugin_state) {
+    FlipperFormat* file = plugin_state->config_file_context->config_file;
+    flipper_format_rewind(file);
+    bool update_result = false;
 
-        tmp_uint32 = token_info->automation_features;
-        if(!flipper_format_write_uint32(
-               file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
-            update_result = TotpConfigFileUpdateError;
+    do {
+        if(!flipper_format_insert_or_update_float(
+               file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
             break;
         }
 
-        update_result = TotpConfigFileUpdateSuccess;
+        update_result = true;
     } while(false);
 
     return update_result;
 }
 
-char* totp_config_file_backup() {
-    Storage* storage = totp_open_storage();
-    char* result = totp_config_file_backup_i(storage);
-    totp_close_storage();
-    return result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info) {
-    Storage* cfg_storage = totp_open_storage();
-    FlipperFormat* file;
-    TotpConfigFileUpdateResult update_result;
-
-    if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
-        do {
-            if(totp_config_file_save_new_token_i(file, token_info) !=
-               TotpConfigFileUpdateSuccess) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
-
-        totp_close_config_file(file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
-    }
-
-    totp_close_storage();
-    return update_result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset) {
-    Storage* cfg_storage = totp_open_storage();
-    FlipperFormat* file;
-    TotpConfigFileUpdateResult update_result;
-
-    if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
-        do {
-            if(!flipper_format_insert_or_update_float(
-                   file, TOTP_CONFIG_KEY_TIMEZONE, &new_timezone_offset, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
-
-        totp_close_config_file(file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
-    }
-
-    totp_close_storage();
-    return update_result;
-}
-
-TotpConfigFileUpdateResult
-    totp_config_file_update_notification_method(NotificationMethod new_notification_method) {
-    Storage* cfg_storage = totp_open_storage();
-    FlipperFormat* file;
-    TotpConfigFileUpdateResult update_result;
-
-    if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
-        do {
-            uint32_t tmp_uint32 = new_notification_method;
-            if(!flipper_format_insert_or_update_uint32(
-                   file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
+bool totp_config_file_update_notification_method(const PluginState* plugin_state) {
+    FlipperFormat* file = plugin_state->config_file_context->config_file;
+    flipper_format_rewind(file);
+    bool update_result = false;
 
-        totp_close_config_file(file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
-    }
-
-    totp_close_storage();
-    return update_result;
-}
-
-TotpConfigFileUpdateResult
-    totp_config_file_update_automation_method(AutomationMethod new_automation_method) {
-    Storage* cfg_storage = totp_open_storage();
-    FlipperFormat* file;
-    TotpConfigFileUpdateResult update_result;
-
-    if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
-        do {
-            uint32_t tmp_uint32 = new_automation_method;
-            if(!flipper_format_insert_or_update_uint32(
-                   file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
-
-        totp_close_config_file(file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
-    }
-
-    totp_close_storage();
-    return update_result;
-}
-
-TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state) {
-    Storage* cfg_storage = totp_open_storage();
-    FlipperFormat* file;
-    TotpConfigFileUpdateResult update_result;
-    if(totp_open_config_file(cfg_storage, &file) == TotpConfigFileOpenSuccess) {
-        do {
-            if(!flipper_format_insert_or_update_float(
-                   file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-            uint32_t tmp_uint32 = plugin_state->notification_method;
-            if(!flipper_format_insert_or_update_uint32(
-                   file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            tmp_uint32 = plugin_state->automation_method;
-            if(!flipper_format_insert_or_update_uint32(
-                   file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
+    do {
+        uint32_t tmp_uint32 = plugin_state->notification_method;
+        if(!flipper_format_insert_or_update_uint32(
+               file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
+            break;
+        }
 
-        totp_close_config_file(file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
-    }
+        update_result = true;
+    } while(false);
 
-    totp_close_storage();
     return update_result;
 }
 
-TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state) {
-    Storage* storage = totp_open_storage();
-    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
-    TotpConfigFileUpdateResult result = TotpConfigFileUpdateSuccess;
+bool totp_config_file_update_automation_method(const PluginState* plugin_state) {
+    FlipperFormat* file = plugin_state->config_file_context->config_file;
+    flipper_format_rewind(file);
+    bool update_result = false;
 
     do {
-        if(!flipper_format_file_open_always(fff_data_file, CONFIG_FILE_TEMP_PATH)) {
-            result = TotpConfigFileUpdateError;
-            break;
-        }
-
-        if(!flipper_format_write_header_cstr(
-               fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION)) {
-            result = TotpConfigFileUpdateError;
+        uint32_t tmp_uint32 = plugin_state->automation_method;
+        if(!flipper_format_insert_or_update_uint32(
+               file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
             break;
         }
 
-        if(!flipper_format_write_hex(
-               fff_data_file, TOTP_CONFIG_KEY_BASE_IV, &plugin_state->base_iv[0], TOTP_IV_SIZE)) {
-            result = TotpConfigFileUpdateError;
-            break;
-        }
-
-        if(!flipper_format_write_hex(
-               fff_data_file,
-               TOTP_CONFIG_KEY_CRYPTO_VERIFY,
-               plugin_state->crypto_verify_data,
-               plugin_state->crypto_verify_data_length)) {
-            result = TotpConfigFileUpdateError;
-            break;
-        }
+        update_result = true;
+    } while(false);
 
-        if(!flipper_format_write_float(
-               fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
-            result = TotpConfigFileUpdateError;
-            break;
-        }
+    return update_result;
+}
 
-        if(!flipper_format_write_bool(
-               fff_data_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
-            result = TotpConfigFileUpdateError;
+bool totp_config_file_update_user_settings(const PluginState* plugin_state) {
+    FlipperFormat* file = plugin_state->config_file_context->config_file;
+    flipper_format_rewind(file);
+    bool update_result = false;
+    do {
+        if(!flipper_format_insert_or_update_float(
+               file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) {
             break;
         }
         uint32_t tmp_uint32 = plugin_state->notification_method;
-        if(!flipper_format_write_uint32(
-               fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
-            result = TotpConfigFileUpdateError;
+        if(!flipper_format_insert_or_update_uint32(
+               file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) {
             break;
         }
 
         tmp_uint32 = plugin_state->automation_method;
-        if(!flipper_format_write_uint32(
-               fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
-            result = TotpConfigFileUpdateError;
+        if(!flipper_format_insert_or_update_uint32(
+               file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) {
             break;
         }
 
-        bool tokens_written = true;
-        TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
-            const TokenInfo* token_info = node->data;
-            tokens_written = tokens_written &&
-                             totp_config_file_save_new_token_i(fff_data_file, token_info) ==
-                                 TotpConfigFileUpdateSuccess;
-        });
-
-        if(!tokens_written) {
-            result = TotpConfigFileUpdateError;
-            break;
-        }
+        update_result = true;
     } while(false);
 
-    totp_close_config_file(fff_data_file);
-
-    if(result == TotpConfigFileUpdateSuccess) {
-        if(storage_file_exists(storage, CONFIG_FILE_ORIG_PATH)) {
-            storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH);
-        }
-
-        if(storage_common_rename(storage, CONFIG_FILE_PATH, CONFIG_FILE_ORIG_PATH) != FSE_OK) {
-            result = TotpConfigFileUpdateError;
-        } else if(storage_common_rename(storage, CONFIG_FILE_TEMP_PATH, CONFIG_FILE_PATH) != FSE_OK) {
-            result = TotpConfigFileUpdateError;
-        } else if(!storage_simply_remove(storage, CONFIG_FILE_ORIG_PATH)) {
-            result = TotpConfigFileUpdateError;
-        }
-    }
-
-    totp_close_storage();
-    return result;
+    return update_result;
 }
 
-TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state) {
+bool totp_config_file_load(PluginState* const plugin_state) {
     Storage* storage = totp_open_storage();
     FlipperFormat* fff_data_file;
-
-    TotpConfigFileOpenResult result;
-    if((result = totp_open_config_file(storage, &fff_data_file)) != TotpConfigFileOpenSuccess) {
+    if(!totp_open_config_file(storage, &fff_data_file)) {
         totp_close_storage();
-        return result;
+        return false;
     }
 
+    flipper_format_rewind(fff_data_file);
+
+    bool result = false;
+
     plugin_state->timezone_offset = 0;
 
     FuriString* temp_str = furi_string_alloc();
@@ -535,7 +306,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
         uint32_t file_version;
         if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) {
             FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
-            result = TotpConfigFileOpenError;
             break;
         }
 
@@ -551,8 +321,7 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
             char* backup_path = totp_config_file_backup_i(storage);
 
             if(backup_path != NULL) {
-                if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
-                    result = TotpConfigFileOpenError;
+                if(totp_open_config_file(storage, &fff_data_file) != true) {
                     break;
                 }
 
@@ -560,7 +329,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
                 if(!flipper_format_file_open_existing(fff_backup_data_file, backup_path)) {
                     flipper_format_file_close(fff_backup_data_file);
                     flipper_format_free(fff_backup_data_file);
-                    result = TotpConfigFileOpenError;
                     break;
                 }
 
@@ -575,7 +343,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
                         LOGGING_TAG,
                         "An error occurred during migration to version %" PRId16,
                         CONFIG_FILE_ACTUAL_VERSION);
-                    result = TotpConfigFileOpenError;
                     break;
                 }
 
@@ -588,7 +355,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
                     LOGGING_TAG,
                     "An error occurred during taking backup of %s before migration",
                     CONFIG_FILE_PATH);
-                result = TotpConfigFileOpenError;
                 break;
             }
         }
@@ -599,7 +365,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
         }
 
         if(!flipper_format_rewind(fff_data_file)) {
-            result = TotpConfigFileOpenError;
             break;
         }
 
@@ -626,7 +391,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
         }
 
         if(!flipper_format_rewind(fff_data_file)) {
-            result = TotpConfigFileOpenError;
             break;
         }
 
@@ -637,7 +401,6 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
         }
 
         if(!flipper_format_rewind(fff_data_file)) {
-            result = TotpConfigFileOpenError;
             break;
         }
 
@@ -664,186 +427,179 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
         }
 
         plugin_state->automation_method = tmp_uint32;
+
+        plugin_state->config_file_context = malloc(sizeof(ConfigFileContext));
+        furi_check(plugin_state->config_file_context != NULL);
+        plugin_state->config_file_context->storage = storage;
+        plugin_state->config_file_context->config_file = fff_data_file;
+        plugin_state->config_file_context->token_info_iterator_context =
+            totp_token_info_iterator_alloc(
+                storage,
+                plugin_state->config_file_context->config_file, 
+                plugin_state->iv);
+        result = true;
     } while(false);
 
     furi_string_free(temp_str);
-    totp_close_config_file(fff_data_file);
-    totp_close_storage();
     return result;
 }
 
-TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state) {
-    Storage* storage = totp_open_storage();
-    FlipperFormat* fff_data_file;
-    if(totp_open_config_file(storage, &fff_data_file) != TotpConfigFileOpenSuccess) {
-        totp_close_storage();
-        return TokenLoadingResultError;
-    }
-
-    FuriString* temp_str = furi_string_alloc();
-    uint32_t temp_data32;
-
-    if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
-        FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header");
-        totp_close_storage();
-        furi_string_free(temp_str);
-        return TokenLoadingResultError;
-    }
+bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state) {
+    FlipperFormat* config_file = plugin_state->config_file_context->config_file;
+    flipper_format_rewind(config_file);
+    bool update_result = false;
+    do {
+        if(!flipper_format_insert_or_update_hex(
+               config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) {
+            break;
+        }
 
-    TokenLoadingResult result = TokenLoadingResultSuccess;
-    uint16_t index = 0;
-    bool has_any_plain_secret = false;
+        if(!flipper_format_insert_or_update_hex(
+               config_file,
+               TOTP_CONFIG_KEY_CRYPTO_VERIFY,
+               plugin_state->crypto_verify_data,
+               plugin_state->crypto_verify_data_length)) {
+            break;
+        }
 
-    while(true) {
-        if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
+        if(!flipper_format_insert_or_update_bool(
+               config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
             break;
         }
 
-        TokenInfo* tokenInfo = token_info_alloc();
+        update_result = true;
+    } while(false);
 
-        size_t temp_cstr_len = furi_string_size(temp_str);
-        tokenInfo->name = malloc(temp_cstr_len + 1);
-        furi_check(tokenInfo->name != NULL);
-        strlcpy(tokenInfo->name, furi_string_get_cstr(temp_str), temp_cstr_len + 1);
+    return update_result;
+}
 
-        uint32_t secret_bytes_count;
-        if(!flipper_format_get_value_count(
-               fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
-            secret_bytes_count = 0;
-        }
+void totp_config_file_close(PluginState* const plugin_state) {
+    if(plugin_state->config_file_context == NULL) return;
+    totp_token_info_iterator_free(plugin_state->config_file_context->token_info_iterator_context);
+    totp_close_config_file(plugin_state->config_file_context->config_file);
+    free(plugin_state->config_file_context);
+    plugin_state->config_file_context = NULL;
+    totp_close_storage();
+}
 
-        if(secret_bytes_count == 1) { // Plain secret key
-            if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
-                if(token_info_set_secret(
-                       tokenInfo,
-                       furi_string_get_cstr(temp_str),
-                       furi_string_size(temp_str),
-                       PLAIN_TOKEN_ENCODING_BASE32,
-                       &plugin_state->iv[0])) {
-                    FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has plain secret", tokenInfo->name);
-                } else {
-                    tokenInfo->token = NULL;
-                    tokenInfo->token_length = 0;
-                    FURI_LOG_W(LOGGING_TAG, "Token \"%s\" has invalid secret", tokenInfo->name);
-                    result = TokenLoadingResultWarning;
-                }
-            } else {
-                tokenInfo->token = NULL;
-                tokenInfo->token_length = 0;
-                result = TokenLoadingResultWarning;
-            }
+void totp_config_file_reset(PluginState* const plugin_state) {
+    totp_config_file_close(plugin_state);
+    Storage* storage = totp_open_storage();
+    storage_simply_remove(storage, CONFIG_FILE_PATH);
+    totp_close_storage();
+}
 
-            has_any_plain_secret = true;
-        } else { // encrypted
-            tokenInfo->token_length = secret_bytes_count;
-            if(secret_bytes_count > 0) {
-                tokenInfo->token = malloc(tokenInfo->token_length);
-                furi_check(tokenInfo->token != NULL);
-                if(!flipper_format_read_hex(
-                       fff_data_file,
-                       TOTP_CONFIG_KEY_TOKEN_SECRET,
-                       tokenInfo->token,
-                       tokenInfo->token_length)) {
-                    free(tokenInfo->token);
-                    tokenInfo->token = NULL;
-                    tokenInfo->token_length = 0;
-                    result = TokenLoadingResultWarning;
-                }
-            } else {
-                tokenInfo->token = NULL;
-                result = TokenLoadingResultWarning;
-            }
-        }
+bool totp_config_file_update_encryption(
+    PluginState* plugin_state,
+    const uint8_t* new_pin,
+    uint8_t new_pin_length) {
+    FlipperFormat* config_file =
+        plugin_state->config_file_context->config_file;
+    Stream* stream = flipper_format_get_raw_stream(config_file);
+    size_t original_offset = stream_tell(stream);
+    if(!stream_rewind(stream)) {
+        return false;
+    }
 
-        if(!flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str) ||
-           !token_info_set_algo_from_str(tokenInfo, temp_str)) {
-            tokenInfo->algo = SHA1;
-        }
+    uint8_t old_iv[TOTP_IV_SIZE];
+    memcpy(&old_iv[0], &plugin_state->iv[0], TOTP_IV_SIZE);
 
-        if(!flipper_format_read_uint32(
-               fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
-           !token_info_set_digits_from_int(tokenInfo, temp_data32)) {
-            tokenInfo->digits = TOTP_6_DIGITS;
-        }
+    memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
+    memset(&plugin_state->base_iv[0], 0, TOTP_IV_SIZE);
+    if(plugin_state->crypto_verify_data != NULL) {
+        free(plugin_state->crypto_verify_data);
+        plugin_state->crypto_verify_data = NULL;
+    }
 
-        if(!flipper_format_read_uint32(
-               fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
-           !token_info_set_duration_from_int(tokenInfo, temp_data32)) {
-            tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
+    CryptoSeedIVResult seed_result =
+        totp_crypto_seed_iv(plugin_state, new_pin_length > 0 ? new_pin : NULL, new_pin_length);
+    if(seed_result & CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS &&
+       seed_result & CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA) {
+        if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+            return false;
         }
+    } else if(seed_result == CRYPTO_SEED_IV_RESULT_FAILED) {
+        return false;
+    }
 
-        if(flipper_format_read_uint32(
-               fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
-            tokenInfo->automation_features = temp_data32;
-        } else {
-            tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+    char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_SECRET) + 1];
+    bool result = true;
+
+    while(true) {
+        if(!stream_seek_to_char(stream, '\n', StreamDirectionForward)) {
+            break;
         }
 
-        FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
+        size_t buffer_read_size;
+        if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) {
+            break;
+        }
 
-        TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
+        if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) {
+            result = false;
+            break;
+        }
 
-        index++;
-    }
+        if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_SECRET ":", sizeof(buffer)) == 0) {
+            uint32_t secret_bytes_count;
+            if(!flipper_format_get_value_count(
+                   config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
+                secret_bytes_count = 0;
+            }
 
-    plugin_state->tokens_count = index;
-    plugin_state->token_list_loaded = true;
+            if(secret_bytes_count > 1) {
+                size_t secret_token_start = stream_tell(stream) + 1;
+                uint8_t* encrypted_token = malloc(secret_bytes_count);
+                furi_check(encrypted_token != NULL);
 
-    FURI_LOG_D(LOGGING_TAG, "Found %" PRIu16 " tokens", index);
+                if(!flipper_format_read_hex(
+                       config_file,
+                       TOTP_CONFIG_KEY_TOKEN_SECRET,
+                       encrypted_token,
+                       secret_bytes_count)) {
+                    result = false;
+                    free(encrypted_token);
+                    break;
+                }
 
-    furi_string_free(temp_str);
-    totp_close_config_file(fff_data_file);
-    totp_close_storage();
+                size_t plain_token_length;
+                uint8_t* plain_token = totp_crypto_decrypt(
+                    encrypted_token, secret_bytes_count, &old_iv[0], &plain_token_length);
 
-    if(has_any_plain_secret) {
-        totp_full_save_config_file(plugin_state);
-    }
+                free(encrypted_token);
+                size_t encrypted_token_length;
+                encrypted_token = totp_crypto_encrypt(
+                    plain_token, plain_token_length, &plugin_state->iv[0], &encrypted_token_length);
 
-    return result;
-}
+                memset_s(plain_token, plain_token_length, 0, plain_token_length);
+                free(plain_token);
 
-TotpConfigFileUpdateResult
-    totp_config_file_update_crypto_signatures(const PluginState* plugin_state) {
-    Storage* storage = totp_open_storage();
-    FlipperFormat* config_file;
-    TotpConfigFileUpdateResult update_result;
-    if(totp_open_config_file(storage, &config_file) == TotpConfigFileOpenSuccess) {
-        do {
-            if(!flipper_format_insert_or_update_hex(
-                   config_file, TOTP_CONFIG_KEY_BASE_IV, plugin_state->base_iv, TOTP_IV_SIZE)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
+                if(!stream_seek(stream, secret_token_start, StreamOffsetFromStart)) {
+                    result = false;
+                    free(encrypted_token);
+                    break;
+                }
 
-            if(!flipper_format_insert_or_update_hex(
-                   config_file,
-                   TOTP_CONFIG_KEY_CRYPTO_VERIFY,
-                   plugin_state->crypto_verify_data,
-                   plugin_state->crypto_verify_data_length)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
-            }
+                if(!flipper_format_write_hex(
+                       config_file,
+                       TOTP_CONFIG_KEY_TOKEN_SECRET,
+                       encrypted_token,
+                       encrypted_token_length)) {
+                    free(encrypted_token);
+                    result = false;
+                    break;
+                }
 
-            if(!flipper_format_insert_or_update_bool(
-                   config_file, TOTP_CONFIG_KEY_PINSET, &plugin_state->pin_set, 1)) {
-                update_result = TotpConfigFileUpdateError;
-                break;
+                free(encrypted_token);
             }
-
-            update_result = TotpConfigFileUpdateSuccess;
-        } while(false);
-
-        totp_close_config_file(config_file);
-    } else {
-        update_result = TotpConfigFileUpdateError;
+        }
     }
 
-    totp_close_storage();
-    return update_result;
+    stream_seek(stream, original_offset, StreamOffsetFromStart);
+
+    return result;
 }
 
-void totp_config_file_reset() {
-    Storage* storage = totp_open_storage();
-    storage_simply_remove(storage, CONFIG_FILE_PATH);
-    totp_close_storage();
+TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state) {
+    return plugin_state->config_file_context->token_info_iterator_context;
 }

+ 39 - 86
services/config/config.h

@@ -1,137 +1,90 @@
 #pragma once
 
-#include <flipper_format/flipper_format.h>
 #include "../../types/plugin_state.h"
 #include "../../types/token_info.h"
+#include "config_file_context.h"
 #include "constants.h"
+#include "token_info_iterator.h"
 
-typedef uint8_t TokenLoadingResult;
 typedef uint8_t TotpConfigFileOpenResult;
 typedef uint8_t TotpConfigFileUpdateResult;
 
-/**
- * @brief Token loading results
- */
-enum TokenLoadingResults {
-    /**
-     * @brief All the tokens loaded successfully 
-     */
-    TokenLoadingResultSuccess,
-
-    /**
-     * @brief All the tokens loaded, but there are some warnings
-     */
-    TokenLoadingResultWarning,
-
-    /**
-     * @brief Tokens not loaded because of error(s) 
-     */
-    TokenLoadingResultError
-};
-
-/**
- * @brief Config file opening result
- */
-enum TotpConfigFileOpenResults {
-    /**
-     * @brief Config file opened successfully
-     */
-    TotpConfigFileOpenSuccess = 0,
-
-    /**
-     * @brief An error has occurred during opening config file
-     */
-    TotpConfigFileOpenError = 1
-};
-
-/**
- * @brief Config file updating result
- */
-enum TotpConfigFileUpdateResults {
-    /**
-     * @brief Config file updated successfully
-     */
-    TotpConfigFileUpdateSuccess,
-
-    /**
-     * @brief An error has occurred during updating config file
-     */
-    TotpConfigFileUpdateError
-};
-
 /**
  * @brief Tries to take a config file backup
+ * @param plugin_state application state
  * @return backup path if backup successfully taken; \c NULL otherwise
  */
-char* totp_config_file_backup();
+char* totp_config_file_backup(const PluginState* plugin_state);
 
 /**
- * @brief Saves all the settings and tokens to an application config file
+ * @brief Loads basic information from an application config file into application state without loading all the tokens
  * @param plugin_state application state
- * @return Config file update result
+ * @return Config file open result
  */
-TotpConfigFileUpdateResult totp_full_save_config_file(const PluginState* const plugin_state);
+bool totp_config_file_load(PluginState* const plugin_state);
 
 /**
- * @brief Loads basic information from an application config file into application state without loading all the tokens
+ * @brief Updates timezone offset in an application config file
  * @param plugin_state application state
- * @return Config file open result
+ * @return Config file update result
  */
-TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_state);
+bool totp_config_file_update_timezone_offset(const PluginState* plugin_state);
 
 /**
- * @brief Loads tokens from an application config file into application state
+ * @brief Updates notification method in an application config file
  * @param plugin_state application state
- * @return Results of the loading
+ * @return Config file update result
  */
-TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state);
+bool totp_config_file_update_notification_method(const PluginState* plugin_state);
 
 /**
- * @brief Add new token to the end of the application config file
- * @param token_info token information to be saved
+ * @brief Updates automation method in an application config file
+ * @param plugin_state application state
  * @return Config file update result
  */
-TotpConfigFileUpdateResult totp_config_file_save_new_token(const TokenInfo* token_info);
+bool totp_config_file_update_automation_method(const PluginState* plugin_state);
 
 /**
- * @brief Updates timezone offset in an application config file
- * @param new_timezone_offset new timezone offset to be set
+ * @brief Updates application user settings
+ * @param plugin_state application state
  * @return Config file update result
  */
-TotpConfigFileUpdateResult totp_config_file_update_timezone_offset(float new_timezone_offset);
+bool totp_config_file_update_user_settings(const PluginState* plugin_state);
 
 /**
- * @brief Updates notification method in an application config file
- * @param new_notification_method new notification method to be set
+ * @brief Updates crypto signatures information
+ * @param plugin_state application state
  * @return Config file update result
  */
-TotpConfigFileUpdateResult
-    totp_config_file_update_notification_method(NotificationMethod new_notification_method);
+bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
 
 /**
- * @brief Updates automation method in an application config file
- * @param new_automation_method new automation method to be set
- * @return Config file update result
+ * @brief Reset all the settings to default
+ * @param plugin_state application state
  */
-TotpConfigFileUpdateResult
-    totp_config_file_update_automation_method(AutomationMethod new_automation_method);
+void totp_config_file_reset(PluginState* const plugin_state);
 
 /**
- * @brief Updates application user settings
+ * @brief Closes config file and releases all the resources
  * @param plugin_state application state
- * @return Config file update result
  */
-TotpConfigFileUpdateResult totp_config_file_update_user_settings(const PluginState* plugin_state);
+void totp_config_file_close(PluginState* const plugin_state);
 
 /**
- * @brief Updates crypto signatures information
+ * @brief Updates config file encryption by re-encrypting it using new user's PIN and new randomly generated IV
  * @param plugin_state application state
- * @return Config file update result
+ * @param new_pin new user's PIN
+ * @param new_pin_length new user's PIN length
+ * @return \c true if config file encryption successfully updated; \c false otherwise
  */
-TotpConfigFileUpdateResult
-    totp_config_file_update_crypto_signatures(const PluginState* plugin_state);
+bool totp_config_file_update_encryption(
+    PluginState* plugin_state,
+    const uint8_t* new_pin,
+    uint8_t new_pin_length);
 
 /**
- * @brief Reset all the settings to default
+ * @brief Gets token info iterator context
+ * @param plugin_state application state
+ * @return token info iterator context
  */
-void totp_config_file_reset();
+TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state);

+ 3 - 0
services/config/config_file_context.h

@@ -0,0 +1,3 @@
+#pragma once
+
+typedef struct ConfigFileContext ConfigFileContext;

+ 4 - 1
services/config/constants.h

@@ -1,7 +1,10 @@
 #pragma once
 
+#include <storage/storage.h>
+
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
 #define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
-#define CONFIG_FILE_ACTUAL_VERSION (4)
+#define CONFIG_FILE_ACTUAL_VERSION (5)
 
 #define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
 #define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"

+ 27 - 12
services/config/migrations/common_migration.c

@@ -1,6 +1,7 @@
 #include "common_migration.h"
 #include "../constants.h"
 #include "../../../types/token_info.h"
+#include <flipper_format/flipper_format_i.h>
 
 bool totp_config_migrate_to_latest(
     FlipperFormat* fff_data_file,
@@ -57,18 +58,12 @@ bool totp_config_migrate_to_latest(
 
         flipper_format_rewind(fff_backup_data_file);
 
-        FuriString* comment_str = furi_string_alloc();
-
         while(true) {
             if(!flipper_format_read_string(
                    fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
                 break;
             }
 
-            furi_string_printf(
-                comment_str, "=== BEGIN \"%s\" ===", furi_string_get_cstr(temp_str));
-            flipper_format_write_comment(fff_data_file, comment_str);
-            furi_string_printf(comment_str, "=== END \"%s\" ===", furi_string_get_cstr(temp_str));
             flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
 
             flipper_format_read_string(
@@ -78,14 +73,31 @@ bool totp_config_migrate_to_latest(
             if(current_version > 1) {
                 flipper_format_read_string(
                     fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
-                flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+
+                if(current_version < 5) {
+                    uint32_t algo_as_uint32t = SHA1;
+                    if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_SHA256_NAME) == 0) {
+                        algo_as_uint32t = SHA256;
+                    } else if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_SHA512_NAME) == 0) {
+                        algo_as_uint32t = SHA512;
+                    } else if(furi_string_cmpi_str(temp_str, TOTP_TOKEN_ALGO_STEAM_NAME) == 0) {
+                        algo_as_uint32t = STEAM;
+                    }
+
+                    flipper_format_write_uint32(
+                        fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &algo_as_uint32t, 1);
+                } else {
+                    flipper_format_write_string(
+                        fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str);
+                }
 
                 flipper_format_read_string(
                     fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
                 flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str);
             } else {
-                flipper_format_write_string_cstr(
-                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_TOKEN_ALGO_SHA1_NAME);
+                const uint32_t default_algo = SHA1;
+                flipper_format_write_uint32(
+                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &default_algo, 1);
                 const uint32_t default_digits = TOTP_6_DIGITS;
                 flipper_format_write_uint32(
                     fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
@@ -115,11 +127,14 @@ bool totp_config_migrate_to_latest(
                     &default_automation_features,
                     1);
             }
-
-            flipper_format_write_comment(fff_data_file, comment_str);
         }
 
-        furi_string_free(comment_str);
+        Stream* stream = flipper_format_get_raw_stream(fff_data_file);
+        size_t current_pos = stream_tell(stream);
+        size_t total_size = stream_size(stream);
+        if (current_pos < total_size) {
+            stream_delete(stream, total_size - current_pos);
+        }
 
         result = true;
     } while(false);

+ 512 - 0
services/config/token_info_iterator.c

@@ -0,0 +1,512 @@
+#include "token_info_iterator.h"
+
+#include <flipper_format/flipper_format_i.h>
+#include <flipper_format/flipper_format_stream.h>
+#include <toolbox/stream/file_stream.h>
+#include "../../types/common.h"
+
+#define CONFIG_FILE_PART_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf.part"
+#define STREAM_COPY_BUFFER_SIZE 128
+
+struct TokenInfoIteratorContext {
+    size_t total_count;
+    size_t current_index;
+    size_t last_seek_offset;
+    size_t last_seek_index;
+    TokenInfo* current_token;
+    FlipperFormat* config_file;
+    uint8_t* iv;
+    Storage* storage;
+};
+
+static bool
+    flipper_format_seek_to_siblinig_token_start(Stream* stream, StreamDirection direction) {
+    char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_NAME) + 1];
+    bool found = false;
+    while(!found) {
+        if(!stream_seek_to_char(stream, '\n', direction)) {
+            break;
+        }
+
+        size_t buffer_read_size;
+        if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) {
+            break;
+        }
+
+        if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) {
+            break;
+        }
+
+        if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_NAME ":", sizeof(buffer)) == 0) {
+            found = true;
+        }
+    }
+
+    return found;
+}
+
+static bool seek_to_token(size_t token_index, TokenInfoIteratorContext* context) {
+    furi_check(context != NULL && context->config_file != NULL);
+    if(token_index >= context->total_count) {
+        return false;
+    }
+
+    Stream* stream = flipper_format_get_raw_stream(context->config_file);
+    long token_index_diff = (long)token_index - (long)context->last_seek_index;
+    size_t token_index_diff_weight = (size_t)labs(token_index_diff);
+    StreamDirection direction = token_index_diff >= 0 ? StreamDirectionForward :
+                                                        StreamDirectionBackward;
+    if(token_index_diff_weight > token_index || context->last_seek_offset == 0) {
+        context->last_seek_offset = 0;
+        context->last_seek_index = 0;
+        token_index_diff = token_index + 1;
+        direction = StreamDirectionForward;
+    } else if(token_index_diff_weight > (context->total_count - token_index - 1)) {
+        context->last_seek_offset = stream_size(stream);
+        context->last_seek_index = context->total_count - 1;
+        token_index_diff = -(long)(context->total_count - token_index);
+        direction = StreamDirectionBackward;
+    }
+
+    stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart);
+
+    if(token_index_diff != 0) {
+        long i = 0;
+        long i_inc = token_index_diff >= 0 ? 1 : -1;
+        do {
+            if(!flipper_format_seek_to_siblinig_token_start(stream, direction)) {
+                break;
+            }
+
+            i += i_inc;
+        } while((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff));
+
+        if((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff)) {
+            context->last_seek_offset = 0;
+            FURI_LOG_D(LOGGING_TAG, "Was not able to move");
+            return false;
+        }
+
+        context->last_seek_offset = stream_tell(stream);
+        context->last_seek_index = token_index;
+    } else {
+        if (!stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool stream_insert_stream(Stream* dst, Stream* src) {
+    uint8_t buffer[STREAM_COPY_BUFFER_SIZE];
+    size_t buffer_read_size;
+    while((buffer_read_size = stream_read(src, buffer, sizeof(buffer))) != 0) {
+        if(!stream_insert(dst, buffer, buffer_read_size)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool totp_token_info_iterator_save_current_token_info_changes(TokenInfoIteratorContext* context) {
+    bool is_new_token = context->current_index >= context->total_count;
+    Stream* stream = flipper_format_get_raw_stream(context->config_file);
+    if(is_new_token) {
+        if(!flipper_format_seek_to_end(context->config_file)) {
+            return false;
+        }
+    } else {
+        if(!seek_to_token(context->current_index, context)) {
+            return false;
+        }
+    }
+
+    size_t offset_start = stream_tell(stream);
+
+    size_t offset_end;
+    if(is_new_token) {
+        offset_end = offset_start;
+    } else if(context->current_index + 1 >= context->total_count) {
+        offset_end = stream_size(stream);
+    } else if(seek_to_token(context->current_index + 1, context)) {
+        offset_end = stream_tell(stream);
+    } else {
+        return false;
+    }
+
+    FlipperFormat* temp_ff = flipper_format_file_alloc(context->storage);
+    if (!flipper_format_file_open_always(temp_ff, CONFIG_FILE_PART_FILE_PATH)) {
+        flipper_format_free(temp_ff);
+        return false;
+    }
+
+    TokenInfo* token_info = context->current_token;
+    bool result = false;
+
+    do {
+        if(!flipper_format_write_string(temp_ff, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) {
+            break;
+        }
+
+        if(!flipper_format_write_hex(
+               temp_ff,
+               TOTP_CONFIG_KEY_TOKEN_SECRET,
+               token_info->token,
+               token_info->token_length)) {
+            break;
+        }
+
+        uint32_t tmp_uint32 = token_info->algo;
+        if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_ALGO, &tmp_uint32, 1)) {
+            break;
+        }
+
+        tmp_uint32 = token_info->digits;
+        if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) {
+            break;
+        }
+
+        tmp_uint32 = token_info->duration;
+        if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) {
+            break;
+        }
+
+        tmp_uint32 = token_info->automation_features;
+        if(!flipper_format_write_uint32(
+               temp_ff, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
+            break;
+        }
+
+        Stream* temp_stream = flipper_format_get_raw_stream(temp_ff);
+
+        if(!stream_rewind(temp_stream)) {
+            break;
+        }
+
+        if(!stream_seek(stream, offset_start, StreamOffsetFromStart)) {
+            break;
+        }
+
+        if(offset_end != offset_start && !stream_delete(stream, offset_end - offset_start)) {
+            break;
+        }
+
+        if(!is_new_token && !stream_write_char(stream, '\n')) {
+            break;
+        }
+
+        if(!stream_insert_stream(stream, temp_stream)) {
+            break;
+        }
+
+        if(is_new_token) {
+            context->total_count++;
+        }
+
+        result = true;
+    } while(false);
+
+    flipper_format_free(temp_ff);
+    storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
+
+    stream_seek(stream, offset_start, StreamOffsetFromStart);
+    context->last_seek_offset = offset_start;
+    context->last_seek_index = context->current_index;
+
+    return result;
+}
+
+TokenInfoIteratorContext* totp_token_info_iterator_alloc(Storage* storage, FlipperFormat* config_file, uint8_t* iv) {
+    Stream* stream = flipper_format_get_raw_stream(config_file);
+    stream_rewind(stream);
+    size_t tokens_count = 0;
+    while(true) {
+        if(!flipper_format_seek_to_siblinig_token_start(stream, StreamDirectionForward)) {
+            break;
+        }
+
+        tokens_count++;
+    }
+
+    TokenInfoIteratorContext* context = malloc(sizeof(TokenInfoIteratorContext));
+    furi_check(context != NULL);
+
+    context->total_count = tokens_count;
+    context->current_token = token_info_alloc();
+    context->config_file = config_file;
+    context->iv = iv;
+    context->storage = storage;
+    return context;
+}
+
+void totp_token_info_iterator_free(TokenInfoIteratorContext* context) {
+    if(context == NULL) return;
+    token_info_free(context->current_token);
+    free(context);
+}
+
+bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context) {
+    if(!seek_to_token(context->current_index, context)) {
+        return false;
+    }
+
+    Stream* stream = flipper_format_get_raw_stream(context->config_file);
+    size_t begin_offset = stream_tell(stream);
+    size_t end_offset;
+    if(context->current_index >= context->total_count - 1) {
+        end_offset = stream_size(stream);
+    } else if(seek_to_token(context->current_index + 1, context)) {
+        end_offset = stream_tell(stream);
+    } else {
+        return false;
+    }
+
+    if(!stream_seek(stream, begin_offset, StreamOffsetFromStart) ||
+       !stream_delete(stream, end_offset - begin_offset)) {
+        return false;
+    }
+
+    context->total_count--;
+    if(context->current_index >= context->total_count) {
+        context->current_index = context->total_count - 1;
+    }
+
+    return true;
+}
+
+bool totp_token_info_iterator_move_current_token_info(
+    TokenInfoIteratorContext* context,
+    size_t new_index) {
+    if(context->current_index == new_index) return true;
+
+    if(!seek_to_token(context->current_index, context)) {
+        return false;
+    }
+
+    Stream* stream = flipper_format_get_raw_stream(context->config_file);
+    size_t begin_offset = stream_tell(stream);
+    size_t end_offset;
+    if(context->current_index >= context->total_count - 1) {
+        end_offset = stream_size(stream);
+    } else if(seek_to_token(context->current_index + 1, context)) {
+        end_offset = stream_tell(stream);
+    } else {
+        return false;
+    }
+
+    
+    Stream* temp_stream = file_stream_alloc(context->storage);
+    if (!file_stream_open(temp_stream, CONFIG_FILE_PART_FILE_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) {
+        stream_free(temp_stream);
+        return false;
+    }
+
+    size_t moving_size = end_offset - begin_offset;
+
+    bool result = false;
+    do {
+        if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
+            break;
+        }
+
+        if(stream_copy(stream, temp_stream, moving_size) < moving_size) {
+            break;
+        }
+
+        if(!stream_rewind(temp_stream)) {
+            break;
+        }
+
+        if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) {
+            break;
+        }
+
+        if(!stream_delete(stream, moving_size)) {
+            break;
+        }
+
+        context->last_seek_offset = 0;
+        context->last_seek_index = 0;
+        if(new_index >= context->total_count - 1) {
+            if(!stream_seek(stream, stream_size(stream), StreamOffsetFromStart)) {
+                break;
+            }
+        } else if(!seek_to_token(new_index, context)) {
+            break;
+        }
+
+        result = stream_insert_stream(stream, temp_stream);
+    } while(false);
+
+    stream_free(temp_stream);
+    storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH);
+
+    context->last_seek_offset = 0;
+    context->last_seek_index = 0;
+
+    return result;
+}
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token(TokenInfoIteratorContext* context, TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, const void* update_context) {
+    TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
+    if (result == TotpIteratorUpdateTokenResultSuccess) {
+        if (!totp_token_info_iterator_save_current_token_info_changes(context)) {
+            result = TotpIteratorUpdateTokenResultFileUpdateFailed;
+        } 
+
+        return result;
+    }
+
+    totp_token_info_iterator_go_to(context, context->current_index);
+    return result;
+}
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token(TokenInfoIteratorContext* context, TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, const void* update_context) {
+    size_t previous_index = context->current_index;
+    context->current_index = context->total_count;
+    token_info_set_defaults(context->current_token);
+    TotpIteratorUpdateTokenResult result = update(context->current_token, update_context);
+    if (result == TotpIteratorUpdateTokenResultSuccess &&
+        !totp_token_info_iterator_save_current_token_info_changes(context)) {
+        result = TotpIteratorUpdateTokenResultFileUpdateFailed;
+    }
+
+    if (result != TotpIteratorUpdateTokenResultSuccess) {
+        totp_token_info_iterator_go_to(context, previous_index);
+    }
+
+    return result;
+}
+
+bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index) {
+    furi_check(context != NULL);
+    context->current_index = token_index;
+    if(!seek_to_token(context->current_index, context)) {
+        return false;
+    }
+
+    Stream* stream = flipper_format_get_raw_stream(context->config_file);
+    size_t original_offset = stream_tell(stream);
+
+    if(!flipper_format_read_string(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_NAME, context->current_token->name)) {
+        stream_seek(stream, original_offset, StreamOffsetFromStart);
+        return false;
+    }
+
+    uint32_t secret_bytes_count;
+    if(!flipper_format_get_value_count(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) {
+        secret_bytes_count = 0;
+    }
+    TokenInfo* tokenInfo = context->current_token;
+    bool token_update_needed = false;
+    if(tokenInfo->token != NULL) {
+        free(tokenInfo->token);
+        tokenInfo->token_length = 0;
+    }
+
+    if(secret_bytes_count == 1) { // Plain secret key
+        FuriString* temp_str = furi_string_alloc();
+
+        if(flipper_format_read_string(
+               context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) {
+            if(token_info_set_secret(
+                   tokenInfo,
+                   furi_string_get_cstr(temp_str),
+                   furi_string_size(temp_str),
+                   PLAIN_TOKEN_ENCODING_BASE32,
+                   context->iv)) {
+                FURI_LOG_W(
+                    LOGGING_TAG,
+                    "Token \"%s\" has plain secret",
+                    furi_string_get_cstr(tokenInfo->name));
+                token_update_needed = true;
+            } else {
+                tokenInfo->token = NULL;
+                tokenInfo->token_length = 0;
+                FURI_LOG_W(
+                    LOGGING_TAG,
+                    "Token \"%s\" has invalid secret",
+                    furi_string_get_cstr(tokenInfo->name));
+            }
+        } else {
+            tokenInfo->token = NULL;
+            tokenInfo->token_length = 0;
+        }
+
+        furi_string_free(temp_str);
+    } else { // encrypted
+        tokenInfo->token_length = secret_bytes_count;
+        if(secret_bytes_count > 0) {
+            tokenInfo->token = malloc(tokenInfo->token_length);
+            furi_check(tokenInfo->token != NULL);
+            if(!flipper_format_read_hex(
+                   context->config_file,
+                   TOTP_CONFIG_KEY_TOKEN_SECRET,
+                   tokenInfo->token,
+                   tokenInfo->token_length)) {
+                free(tokenInfo->token);
+                tokenInfo->token = NULL;
+                tokenInfo->token_length = 0;
+            }
+        } else {
+            tokenInfo->token = NULL;
+        }
+    }
+
+    uint32_t temp_data32;
+    if(flipper_format_read_uint32(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &temp_data32, 1) &&
+       temp_data32 <= STEAM) {
+        tokenInfo->algo = (TokenHashAlgo)temp_data32;
+    } else {
+        tokenInfo->algo = SHA1;
+    }
+
+    if(!flipper_format_read_uint32(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) ||
+       !token_info_set_digits_from_int(tokenInfo, temp_data32)) {
+        tokenInfo->digits = TOTP_6_DIGITS;
+    }
+
+    if(!flipper_format_read_uint32(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) ||
+       !token_info_set_duration_from_int(tokenInfo, temp_data32)) {
+        tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
+    }
+
+    if(flipper_format_read_uint32(
+           context->config_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) {
+        tokenInfo->automation_features = temp_data32;
+    } else {
+        tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+    }
+
+    stream_seek(stream, original_offset, StreamOffsetFromStart);
+
+    if(token_update_needed &&
+        !totp_token_info_iterator_save_current_token_info_changes(context)) {
+        return false;
+    }
+
+    return true;
+}
+
+const TokenInfo* totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context) {
+    return context->current_token;
+}
+
+size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context) {
+    return context->current_index;
+}
+
+size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context) {
+    return context->total_count;
+}
+
+void totp_token_info_iterator_attach_to_config_file(TokenInfoIteratorContext* context, FlipperFormat* config_file) {
+    context->config_file = config_file;
+}

+ 42 - 0
services/config/token_info_iterator.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "../../types/token_info.h"
+#include <flipper_format/flipper_format.h>
+#include "constants.h"
+
+typedef int TotpIteratorUpdateTokenResult;
+
+enum TotpIteratorUpdateTokenResults {
+    TotpIteratorUpdateTokenResultSuccess = 0,
+    TotpIteratorUpdateTokenResultFileUpdateFailed = -1
+};
+
+typedef TotpIteratorUpdateTokenResult (*TOTP_ITERATOR_UPDATE_TOKEN_ACTION)(
+    TokenInfo* const token_info,
+    const void* context);
+
+typedef struct TokenInfoIteratorContext TokenInfoIteratorContext;
+
+TokenInfoIteratorContext* totp_token_info_iterator_alloc(Storage* storage, FlipperFormat* config_file, uint8_t* iv);
+
+bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index);
+
+bool totp_token_info_iterator_move_current_token_info(
+    TokenInfoIteratorContext* context,
+    size_t new_index);
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token(TokenInfoIteratorContext* context, TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, const void* update_context);
+
+TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token(TokenInfoIteratorContext* context, TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, const void* update_context);
+
+bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context);
+
+void totp_token_info_iterator_free(TokenInfoIteratorContext* context);
+
+const TokenInfo* totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context);
+
+size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context);
+
+size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context);
+
+void totp_token_info_iterator_attach_to_config_file(TokenInfoIteratorContext* context, FlipperFormat* config_file);

+ 7 - 7
services/crypto/crypto.c

@@ -2,7 +2,6 @@
 #include <furi_hal_crypto.h>
 #include <furi_hal_random.h>
 #include <furi_hal_version.h>
-#include "../config/config.h"
 #include "../../types/common.h"
 #include "memset_s.h"
 
@@ -62,9 +61,11 @@ uint8_t* totp_crypto_decrypt(
     return decrypted_data;
 }
 
-bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) {
+CryptoSeedIVResult
+    totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length) {
+    CryptoSeedIVResult result;
     if(plugin_state->crypto_verify_data == NULL) {
-        FURI_LOG_D(LOGGING_TAG, "Generating new IV");
+        FURI_LOG_I(LOGGING_TAG, "Generating new IV");
         furi_hal_random_fill_buf(&plugin_state->base_iv[0], TOTP_IV_SIZE);
     }
 
@@ -95,9 +96,9 @@ bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t
         }
     }
 
-    bool result = true;
+    result = CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS;
     if(plugin_state->crypto_verify_data == NULL) {
-        FURI_LOG_D(LOGGING_TAG, "Generating crypto verify data");
+        FURI_LOG_I(LOGGING_TAG, "Generating crypto verify data");
         plugin_state->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH);
         furi_check(plugin_state->crypto_verify_data != NULL);
         plugin_state->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH;
@@ -110,8 +111,7 @@ bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t
 
         plugin_state->pin_set = pin != NULL && pin_length > 0;
 
-        result = totp_config_file_update_crypto_signatures(plugin_state) ==
-                 TotpConfigFileUpdateSuccess;
+        result |= CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA;
     }
 
     return result;

+ 23 - 2
services/crypto/crypto.h

@@ -2,6 +2,26 @@
 
 #include "../../types/plugin_state.h"
 
+typedef uint8_t CryptoSeedIVResult;
+
+enum CryptoSeedIVResults {
+
+    /**
+     * @brief IV seeding operation failed
+     */
+    CRYPTO_SEED_IV_RESULT_FAILED = 0b00,
+
+    /**
+     * @brief IV seeding operation succeeded
+     */
+    CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS = 0b01,
+
+    /**
+     * @brief As a part of IV seeding operation new crypto verify data has been generated
+     */
+    CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA = 0b10
+};
+
 /**
  * @brief Encrypts plain data using built-in certificate and given initialization vector (IV)
  * @param plain_data plain data to be encrypted
@@ -35,9 +55,10 @@ uint8_t* totp_crypto_decrypt(
  * @param plugin_state application state
  * @param pin user's PIN
  * @param pin_length user's PIN length
- * @return \c true on success; \c false otherwise
+ * @return Results of seeding IV
  */
-bool totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length);
+CryptoSeedIVResult
+    totp_crypto_seed_iv(PluginState* plugin_state, const uint8_t* pin, uint8_t pin_length);
 
 /**
  * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption

+ 26 - 17
totp_app.c

@@ -51,23 +51,39 @@ static bool totp_activate_initial_scene(PluginState* const plugin_state) {
             dialog_message_show(plugin_state->dialogs_app, message);
         dialog_message_free(message);
         if(dialog_result == DialogMessageButtonRight) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+            totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
         } else {
-            if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
+            CryptoSeedIVResult seed_result = totp_crypto_seed_iv(plugin_state, NULL, 0);
+            if(seed_result & CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS &&
+               seed_result & CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA) {
+                if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+                    totp_dialogs_config_loading_error(plugin_state);
+                    return false;
+                }
+            } else if(seed_result == CRYPTO_SEED_IV_RESULT_FAILED) {
                 totp_dialogs_config_loading_error(plugin_state);
                 return false;
             }
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
         }
     } else if(plugin_state->pin_set) {
-        totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+        totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
     } else {
-        if(!totp_crypto_seed_iv(plugin_state, NULL, 0)) {
+        CryptoSeedIVResult seed_result = totp_crypto_seed_iv(plugin_state, NULL, 0);
+        if(seed_result & CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS &&
+           seed_result & CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA) {
+            if(!totp_config_file_update_crypto_signatures(plugin_state)) {
+                totp_dialogs_config_loading_error(plugin_state);
+                return false;
+            }
+        } else if(seed_result == CRYPTO_SEED_IV_RESULT_FAILED) {
             totp_dialogs_config_loading_error(plugin_state);
             return false;
         }
+
         if(totp_crypto_verify_key(plugin_state)) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
         } else {
             FURI_LOG_E(
                 LOGGING_TAG,
@@ -96,7 +112,7 @@ static bool totp_plugin_state_init(PluginState* const plugin_state) {
     plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS);
     memset(&plugin_state->iv[0], 0, TOTP_IV_SIZE);
 
-    if(totp_config_file_load_base(plugin_state) != TotpConfigFileOpenSuccess) {
+    if(!totp_config_file_load(plugin_state)) {
         totp_dialogs_config_loading_error(plugin_state);
         return false;
     }
@@ -119,15 +135,7 @@ static void totp_plugin_state_free(PluginState* plugin_state) {
     furi_record_close(RECORD_NOTIFICATION);
     furi_record_close(RECORD_DIALOGS);
 
-    ListNode* node = plugin_state->tokens_list;
-    ListNode* tmp;
-    while(node != NULL) {
-        tmp = node->next;
-        TokenInfo* tokenInfo = node->data;
-        token_info_free(tokenInfo);
-        free(node);
-        node = tmp;
-    }
+    totp_config_file_close(plugin_state);
 
     if(plugin_state->crypto_verify_data != NULL) {
         free(plugin_state->crypto_verify_data);
@@ -193,8 +201,9 @@ int32_t totp_app() {
                 }
             } else if(
                 plugin_state->pin_set && plugin_state->current_scene != TotpSceneAuthentication &&
+                plugin_state->current_scene != TotpSceneStandby &&
                 furi_get_tick() - last_user_interaction_time > IDLE_TIMEOUT) {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication, NULL);
+                totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication);
             }
 
             view_port_update(view_port);

+ 3 - 0
types/common.c

@@ -0,0 +1,3 @@
+#include "common.h"
+
+const char* LOGGING_TAG = "TOTP APP";

+ 1 - 1
types/common.h

@@ -1,3 +1,3 @@
 #pragma once
 
-#define LOGGING_TAG "TOTP APP"
+extern const char* LOGGING_TAG;

+ 0 - 17
types/nullable.h

@@ -1,17 +0,0 @@
-#pragma once
-
-#include <stdint.h>
-#include <stdbool.h>
-
-#define TOTP_NULLABLE_STRUCT(value_type)       \
-    typedef struct TotpNullable_##value_type { \
-        bool is_null;                          \
-        value_type value;                      \
-    } TotpNullable_##value_type
-
-#define TOTP_NULLABLE_NULL(s) s.is_null = true
-#define TOTP_NULLABLE_VALUE(s, v) \
-    s.is_null = false;            \
-    s.value = v
-
-TOTP_NULLABLE_STRUCT(uint16_t);

+ 2 - 15
types/plugin_state.h

@@ -4,8 +4,8 @@
 #include <gui/gui.h>
 #include <dialogs/dialogs.h>
 #include "../features_config.h"
-#include <linked_list.h>
 #include "../ui/totp_scenes_enum.h"
+#include "../services/config/config_file_context.h"
 #include "notification_method.h"
 #include "automation_method.h"
 #ifdef TOTP_BADBT_TYPE_ENABLED
@@ -48,20 +48,7 @@ typedef struct {
      */
     float timezone_offset;
 
-    /**
-     * @brief Token list head node 
-     */
-    ListNode* tokens_list;
-
-    /**
-     * @brief Whether token list is loaded or not 
-     */
-    bool token_list_loaded;
-
-    /**
-     * @brief Tokens list length 
-     */
-    uint16_t tokens_count;
+    ConfigFileContext* config_file_context;
 
     /**
      * @brief Encrypted well-known string data

+ 18 - 9
types/token_info.c

@@ -8,17 +8,15 @@
 TokenInfo* token_info_alloc() {
     TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
     furi_check(tokenInfo != NULL);
-    tokenInfo->algo = SHA1;
-    tokenInfo->digits = TOTP_6_DIGITS;
-    tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
-    tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+    tokenInfo->name = furi_string_alloc();
+    token_info_set_defaults(tokenInfo);
     return tokenInfo;
 }
 
 void token_info_free(TokenInfo* token_info) {
     if(token_info == NULL) return;
-    free(token_info->name);
     free(token_info->token);
+    furi_string_free(token_info->name);
     free(token_info);
 }
 
@@ -52,6 +50,10 @@ bool token_info_set_secret(
 
     bool result;
     if(plain_secret_length > 0) {
+        if(token_info->token != NULL) {
+            free(token_info->token);
+        }
+
         token_info->token =
             totp_crypto_encrypt(plain_secret, plain_secret_length, iv, &token_info->token_length);
         result = true;
@@ -164,10 +166,17 @@ TokenInfo* token_info_clone(const TokenInfo* src) {
     furi_check(clone->token != NULL);
     memcpy(clone->token, src->token, src->token_length);
 
-    int name_length = strnlen(src->name, TOTP_TOKEN_MAX_LENGTH);
-    clone->name = malloc(name_length + 1);
-    furi_check(clone->name != NULL);
-    strlcpy(clone->name, src->name, name_length + 1);
+    clone->name = furi_string_alloc();
+    furi_string_set(clone->name, src->name);
 
     return clone;
+}
+
+void token_info_set_defaults(TokenInfo* token_info) {
+    furi_check(token_info != NULL);
+    token_info->algo = SHA1;
+    token_info->digits = TOTP_6_DIGITS;
+    token_info->duration = TOTP_TOKEN_DURATION_DEFAULT;
+    token_info->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+    furi_string_reset(token_info->name);
 }

+ 10 - 4
types/token_info.h

@@ -20,6 +20,8 @@
 #define TOTP_TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab"
 #define TOTP_TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME "slower"
 
+#define TOTP_TOKEN_DIGITS_MAX_COUNT (8)
+
 typedef uint8_t TokenHashAlgo;
 typedef uint8_t TokenDigitsCount;
 typedef uint8_t TokenAutomationFeature;
@@ -111,8 +113,6 @@ enum PlainTokenSecretEncodings {
     PLAIN_TOKEN_ENCODING_BASE64 = 1
 };
 
-#define TOTP_TOKEN_DIGITS_MAX_COUNT (8)
-
 /**
  * @brief TOTP token information
  */
@@ -130,7 +130,7 @@ typedef struct {
     /**
      * @brief User-friendly token name 
      */
-    char* name;
+    FuriString* name;
 
     /**
      * @brief Hashing algorithm
@@ -225,4 +225,10 @@ bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const Fur
  * @param src instance to clone
  * @return cloned instance
  */
-TokenInfo* token_info_clone(const TokenInfo* src);
+TokenInfo* token_info_clone(const TokenInfo* src);
+
+/**
+ * @brief Sets default values to all the properties of \c token_info
+ * @param token_info instance to set defaults to
+ */
+void token_info_set_defaults(TokenInfo* token_info);

+ 12 - 8
ui/scene_director.c

@@ -5,29 +5,28 @@
 #include "scenes/add_new_token/totp_scene_add_new_token.h"
 #include "scenes/token_menu/totp_scene_token_menu.h"
 #include "scenes/app_settings/totp_app_settings.h"
+#include "scenes/standby/standby.h"
 
-void totp_scene_director_activate_scene(
-    PluginState* const plugin_state,
-    Scene scene,
-    const void* context) {
+void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene) {
     totp_scene_director_deactivate_active_scene(plugin_state);
     switch(scene) {
     case TotpSceneGenerateToken:
-        totp_scene_generate_token_activate(plugin_state, context);
+        totp_scene_generate_token_activate(plugin_state);
         break;
     case TotpSceneAuthentication:
         totp_scene_authenticate_activate(plugin_state);
         break;
     case TotpSceneAddNewToken:
-        totp_scene_add_new_token_activate(plugin_state, context);
+        totp_scene_add_new_token_activate(plugin_state);
         break;
     case TotpSceneTokenMenu:
-        totp_scene_token_menu_activate(plugin_state, context);
+        totp_scene_token_menu_activate(plugin_state);
         break;
     case TotpSceneAppSettings:
-        totp_scene_app_settings_activate(plugin_state, context);
+        totp_scene_app_settings_activate(plugin_state);
         break;
     case TotpSceneNone:
+    case TotpSceneStandby:
         break;
     default:
         break;
@@ -56,6 +55,7 @@ void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state
         totp_scene_app_settings_deactivate(plugin_state);
         break;
     case TotpSceneNone:
+    case TotpSceneStandby:
         break;
     default:
         break;
@@ -81,6 +81,9 @@ void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_
         break;
     case TotpSceneNone:
         break;
+    case TotpSceneStandby:
+        totp_scene_standby_render(canvas);
+        break;
     default:
         break;
     }
@@ -105,6 +108,7 @@ bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* con
         processing = totp_scene_app_settings_handle_event(event, plugin_state);
         break;
     case TotpSceneNone:
+    case TotpSceneStandby:
         break;
     default:
         break;

+ 1 - 4
ui/scene_director.h

@@ -11,10 +11,7 @@
  * @param scene scene to be activated
  * @param context scene context to be passed to the scene activation method
  */
-void totp_scene_director_activate_scene(
-    PluginState* const plugin_state,
-    Scene scene,
-    const void* context);
+void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene);
 
 /**
  * @brief Deactivate current scene

+ 43 - 52
ui/scenes/add_new_token/totp_scene_add_new_token.c

@@ -4,12 +4,10 @@
 #include "../../scene_director.h"
 #include "totp_input_text.h"
 #include "../../../types/token_info.h"
-#include <linked_list.h>
 #include "../../../services/config/config.h"
 #include "../../ui_controls.h"
 #include "../../common_dialogs.h"
 #include <roll_value.h>
-#include "../../../types/nullable.h"
 #include "../generate_token/totp_scene_generate_token.h"
 
 char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"};
@@ -36,7 +34,6 @@ typedef struct {
     InputTextSceneContext* token_secret_input_context;
     InputTextSceneState* input_state;
     uint32_t input_started_at;
-    TotpNullable_uint16_t current_token_index;
     int16_t screen_y_offset;
     TokenHashAlgo algo;
     uint8_t digits_count_index;
@@ -44,6 +41,15 @@ typedef struct {
     FuriString* duration_text;
 } SceneState;
 
+struct TotpAddContext {
+    SceneState* scene_state;
+    uint8_t* iv;
+};
+
+enum TotpIteratorUpdateTokenResultsEx {
+    TotpIteratorUpdateTokenResultInvalidSecret = 1
+};
+
 static void on_token_name_user_comitted(InputTextSceneCallbackResult* result) {
     SceneState* scene_state = result->callback_data;
     free(scene_state->token_name);
@@ -66,9 +72,26 @@ static void update_duration_text(SceneState* scene_state) {
     furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration);
 }
 
-void totp_scene_add_new_token_activate(
-    PluginState* plugin_state,
-    const TokenAddEditSceneContext* context) {
+static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* tokenInfo, const void* context) {
+    const struct TotpAddContext* context_t = context;
+    if (!token_info_set_secret(
+        tokenInfo,
+        context_t->scene_state->token_secret,
+        context_t->scene_state->token_secret_length,
+        PLAIN_TOKEN_ENCODING_BASE32,
+        context_t->iv)) {
+        return TotpIteratorUpdateTokenResultInvalidSecret;
+    }
+
+    furi_string_set_strn(tokenInfo->name, context_t->scene_state->token_name, context_t->scene_state->token_name_length + 1);
+    tokenInfo->algo = context_t->scene_state->algo;
+    tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[context_t->scene_state->digits_count_index];
+    tokenInfo->duration = context_t->scene_state->duration;
+
+    return TotpIteratorUpdateTokenResultSuccess;
+}
+
+void totp_scene_add_new_token_activate(PluginState* plugin_state) {
     SceneState* scene_state = malloc(sizeof(SceneState));
     furi_check(scene_state != NULL);
     plugin_state->current_scene_state = scene_state;
@@ -97,12 +120,6 @@ void totp_scene_add_new_token_activate(
     scene_state->duration = TOTP_TOKEN_DURATION_DEFAULT;
     scene_state->duration_text = furi_string_alloc();
     update_duration_text(scene_state);
-
-    if(context == NULL) {
-        TOTP_NULLABLE_NULL(scene_state->current_token_index);
-    } else {
-        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
-    }
 }
 
 void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state) {
@@ -260,38 +277,16 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
         case TokenDurationSelect:
             break;
         case ConfirmButton: {
-            TokenInfo* tokenInfo = token_info_alloc();
-            bool token_secret_set = token_info_set_secret(
-                tokenInfo,
-                scene_state->token_secret,
-                scene_state->token_secret_length,
-                PLAIN_TOKEN_ENCODING_BASE32,
-                &plugin_state->iv[0]);
-
-            if(token_secret_set) {
-                tokenInfo->name = malloc(scene_state->token_name_length + 1);
-                furi_check(tokenInfo->name != NULL);
-                strlcpy(
-                    tokenInfo->name, scene_state->token_name, scene_state->token_name_length + 1);
-                tokenInfo->algo = scene_state->algo;
-                tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[scene_state->digits_count_index];
-                tokenInfo->duration = scene_state->duration;
-
-                TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
-                plugin_state->tokens_count++;
-
-                if(totp_config_file_save_new_token(tokenInfo) != TotpConfigFileUpdateSuccess) {
-                    token_info_free(tokenInfo);
-                    totp_dialogs_config_updating_error(plugin_state);
-                    return false;
-                }
-
-                GenerateTokenSceneContext generate_scene_context = {
-                    .current_token_index = plugin_state->tokens_count - 1};
-                totp_scene_director_activate_scene(
-                    plugin_state, TotpSceneGenerateToken, &generate_scene_context);
-            } else {
-                token_info_free(tokenInfo);
+            struct TotpAddContext add_context = { .iv = plugin_state->iv, .scene_state = scene_state };
+            TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+            TotpIteratorUpdateTokenResult add_result = totp_token_info_iterator_add_new_token(
+                iterator_context,
+                &add_token_handler,
+                &add_context);
+
+            if (add_result == TotpIteratorUpdateTokenResultSuccess) {
+                totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
+            } else if (add_result == TotpIteratorUpdateTokenResultInvalidSecret) {
                 DialogMessage* message = dialog_message_alloc();
                 dialog_message_set_buttons(message, "Back", NULL, NULL);
                 dialog_message_set_text(
@@ -305,7 +300,10 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
                 dialog_message_free(message);
                 scene_state->selected_control = TokenSecretTextBox;
                 update_screen_y_offset(scene_state);
+            } else if (add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) {
+                totp_dialogs_config_updating_error(plugin_state);
             }
+
             break;
         }
         default:
@@ -313,14 +311,7 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
         }
         break;
     case InputKeyBack:
-        if(!scene_state->current_token_index.is_null) {
-            GenerateTokenSceneContext generate_scene_context = {
-                .current_token_index = scene_state->current_token_index.value};
-            totp_scene_director_activate_scene(
-                plugin_state, TotpSceneGenerateToken, &generate_scene_context);
-        } else {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-        }
+        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
         break;
     default:
         break;

+ 1 - 7
ui/scenes/add_new_token/totp_scene_add_new_token.h

@@ -4,13 +4,7 @@
 #include "../../../types/plugin_state.h"
 #include "../../../types/plugin_event.h"
 
-typedef struct {
-    uint16_t current_token_index;
-} TokenAddEditSceneContext;
-
-void totp_scene_add_new_token_activate(
-    PluginState* plugin_state,
-    const TokenAddEditSceneContext* context);
+void totp_scene_add_new_token_activate(PluginState* plugin_state);
 void totp_scene_add_new_token_render(Canvas* const canvas, PluginState* plugin_state);
 bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState* plugin_state);
 void totp_scene_add_new_token_deactivate(PluginState* plugin_state);

+ 4 - 28
ui/scenes/app_settings/totp_app_settings.c

@@ -9,7 +9,6 @@
 #include "../../../services/config/config.h"
 #include "../../../services/convert/convert.h"
 #include <roll_value.h>
-#include "../../../types/nullable.h"
 #include "../../../features_config.h"
 #ifdef TOTP_BADBT_TYPE_ENABLED
 #include "../../../workers/bt_type_code/bt_type_code.h"
@@ -40,21 +39,13 @@ typedef struct {
     bool badbt_enabled;
 #endif
     uint8_t y_offset;
-    TotpNullable_uint16_t current_token_index;
     Control selected_control;
 } SceneState;
 
-void totp_scene_app_settings_activate(
-    PluginState* plugin_state,
-    const AppSettingsSceneContext* context) {
+void totp_scene_app_settings_activate(PluginState* plugin_state) {
     SceneState* scene_state = malloc(sizeof(SceneState));
     furi_check(scene_state != NULL);
     plugin_state->current_scene_state = scene_state;
-    if(context != NULL) {
-        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
-    } else {
-        TOTP_NULLABLE_NULL(scene_state->current_token_index);
-    }
 
     float off_int;
     float off_dec = modff(plugin_state->timezone_offset, &off_int);
@@ -281,8 +272,7 @@ bool totp_scene_app_settings_handle_event(
                                                                             AutomationMethodNone;
 #endif
 
-            if(totp_config_file_update_user_settings(plugin_state) !=
-               TotpConfigFileUpdateSuccess) {
+            if(!totp_config_file_update_user_settings(plugin_state)) {
                 totp_dialogs_config_updating_error(plugin_state);
                 return false;
             }
@@ -294,25 +284,11 @@ bool totp_scene_app_settings_handle_event(
             }
 #endif
 
-            if(!scene_state->current_token_index.is_null) {
-                TokenMenuSceneContext generate_scene_context = {
-                    .current_token_index = scene_state->current_token_index.value};
-                totp_scene_director_activate_scene(
-                    plugin_state, TotpSceneTokenMenu, &generate_scene_context);
-            } else {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-            }
+            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
         }
         break;
     case InputKeyBack: {
-        if(!scene_state->current_token_index.is_null) {
-            TokenMenuSceneContext generate_scene_context = {
-                .current_token_index = scene_state->current_token_index.value};
-            totp_scene_director_activate_scene(
-                plugin_state, TotpSceneTokenMenu, &generate_scene_context);
-        } else {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-        }
+        totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
         break;
     }
     default:

+ 1 - 7
ui/scenes/app_settings/totp_app_settings.h

@@ -4,13 +4,7 @@
 #include "../../../types/plugin_state.h"
 #include "../../../types/plugin_event.h"
 
-typedef struct {
-    uint16_t current_token_index;
-} AppSettingsSceneContext;
-
-void totp_scene_app_settings_activate(
-    PluginState* plugin_state,
-    const AppSettingsSceneContext* context);
+void totp_scene_app_settings_activate(PluginState* plugin_state);
 void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state);
 bool totp_scene_app_settings_handle_event(
     const PluginEvent* const event,

+ 10 - 3
ui/scenes/authenticate/totp_scene_authenticate.c

@@ -114,12 +114,18 @@ bool totp_scene_authenticate_handle_event(
             scene_state->code_length++;
         }
         break;
-    case InputKeyOk:
-        totp_crypto_seed_iv(plugin_state, &scene_state->code_input[0], scene_state->code_length);
+    case InputKeyOk: {
+        CryptoSeedIVResult seed_result = totp_crypto_seed_iv(
+            plugin_state, &scene_state->code_input[0], scene_state->code_length);
+
+        if(seed_result & CRYPTO_SEED_IV_RESULT_FLAG_SUCCESS &&
+           seed_result & CRYPTO_SEED_IV_RESULT_FLAG_NEW_CRYPTO_VERIFY_DATA) {
+            totp_config_file_update_crypto_signatures(plugin_state);
+        }
 
         if(totp_crypto_verify_key(plugin_state)) {
             FURI_LOG_D(LOGGING_TAG, "PIN is valid");
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
         } else {
             FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid");
             memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH);
@@ -140,6 +146,7 @@ bool totp_scene_authenticate_handle_event(
             dialog_message_free(message);
         }
         break;
+    }
     case InputKeyBack:
         if(scene_state->code_length > 0) {
             scene_state->code_input[scene_state->code_length - 1] = 0;

+ 49 - 80
ui/scenes/generate_token/totp_scene_generate_token.c

@@ -31,9 +31,7 @@ typedef struct {
 } UiPrecalculatedDimensions;
 
 typedef struct {
-    uint16_t current_token_index;
     char last_code[TOTP_TOKEN_DIGITS_MAX_COUNT + 1];
-    TokenInfo* current_token;
     TotpUsbTypeCodeWorkerContext* usb_type_code_worker_context;
     NotificationMessage const** notification_sequence_new_token;
     NotificationMessage const** notification_sequence_automation;
@@ -128,19 +126,20 @@ static const NotificationSequence*
     return (NotificationSequence*)scene_state->notification_sequence_automation;
 }
 
-static void update_totp_params(PluginState* const plugin_state) {
+static void update_totp_params(PluginState* const plugin_state, size_t token_index) {
     SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-
-    if(scene_state->current_token_index < plugin_state->tokens_count) {
-        scene_state->current_token =
-            list_element_at(plugin_state->tokens_list, scene_state->current_token_index)->data;
+    TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    if(totp_token_info_iterator_go_to(iterator_context, token_index)) {
         totp_generate_code_worker_notify(
             scene_state->generate_code_worker_context, TotpGenerateCodeWorkerEventForceUpdate);
     }
 }
 
-static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_state) {
-    uint8_t code_length = scene_state->current_token->digits;
+static void draw_totp_code(Canvas* const canvas, const PluginState* const plugin_state) {
+    const SceneState* scene_state = plugin_state->current_scene_state;
+    const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    uint8_t code_length =
+        totp_token_info_iterator_get_current_token(iterator_context)->digits;
     uint8_t offset_x = scene_state->ui_precalculated_dimensions.code_offset_x;
     uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width;
     uint8_t offset_x_inc = scene_state->ui_precalculated_dimensions.code_offset_x_inc;
@@ -164,9 +163,12 @@ static void draw_totp_code(Canvas* const canvas, const SceneState* const scene_s
 static void on_new_token_code_generated(bool time_left, void* context) {
     const PluginState* plugin_state = context;
     SceneState* scene_state = plugin_state->current_scene_state;
+    const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    const TokenInfo* current_token = totp_token_info_iterator_get_current_token(iterator_context);
+
     uint8_t char_width = modeNine_15ptFontInfo.charInfo[0].width;
     scene_state->ui_precalculated_dimensions.code_total_length =
-        scene_state->current_token->digits * (char_width + modeNine_15ptFontInfo.spacePixels);
+        current_token->digits * (char_width + modeNine_15ptFontInfo.spacePixels);
     scene_state->ui_precalculated_dimensions.code_offset_x =
         (SCREEN_WIDTH - scene_state->ui_precalculated_dimensions.code_total_length) >> 1;
     scene_state->ui_precalculated_dimensions.code_offset_x_inc =
@@ -192,43 +194,9 @@ static void on_code_lifetime_updated_generated(float code_lifetime_percent, void
         PROGRESS_BAR_MARGIN;
 }
 
-void totp_scene_generate_token_activate(
-    PluginState* plugin_state,
-    const GenerateTokenSceneContext* context) {
-    if(!plugin_state->token_list_loaded) {
-        TokenLoadingResult token_load_result = totp_config_file_load_tokens(plugin_state);
-        if(token_load_result != TokenLoadingResultSuccess) {
-            DialogMessage* message = dialog_message_alloc();
-            dialog_message_set_buttons(message, NULL, "Okay", NULL);
-            if(token_load_result == TokenLoadingResultWarning) {
-                dialog_message_set_text(
-                    message,
-                    "Unable to load some tokens\nPlease review conf file",
-                    SCREEN_WIDTH_CENTER,
-                    SCREEN_HEIGHT_CENTER,
-                    AlignCenter,
-                    AlignCenter);
-            } else if(token_load_result == TokenLoadingResultError) {
-                dialog_message_set_text(
-                    message,
-                    "Unable to load tokens\nPlease review conf file",
-                    SCREEN_WIDTH_CENTER,
-                    SCREEN_HEIGHT_CENTER,
-                    AlignCenter,
-                    AlignCenter);
-            }
-
-            dialog_message_show(plugin_state->dialogs_app, message);
-            dialog_message_free(message);
-        }
-    }
+void totp_scene_generate_token_activate(PluginState* plugin_state) {
     SceneState* scene_state = malloc(sizeof(SceneState));
     furi_check(scene_state != NULL);
-    if(context == NULL || context->current_token_index > plugin_state->tokens_count) {
-        scene_state->current_token_index = 0;
-    } else {
-        scene_state->current_token_index = context->current_token_index;
-    }
 
     plugin_state->current_scene_state = scene_state;
     FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset);
@@ -254,10 +222,10 @@ void totp_scene_generate_token_activate(
             scene_state->last_code_update_sync);
     }
 #endif
-
+    const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
     scene_state->generate_code_worker_context = totp_generate_code_worker_start(
         scene_state->last_code,
-        &scene_state->current_token,
+        totp_token_info_iterator_get_current_token(iterator_context),
         scene_state->last_code_update_sync,
         plugin_state->timezone_offset,
         plugin_state->iv);
@@ -270,11 +238,12 @@ void totp_scene_generate_token_activate(
         &on_code_lifetime_updated_generated,
         scene_state);
 
-    update_totp_params(plugin_state);
+    update_totp_params(plugin_state, totp_token_info_iterator_get_current_token_index(iterator_context));
 }
 
 void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) {
-    if(plugin_state->tokens_count == 0) {
+    const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
         canvas_draw_str_aligned(
             canvas,
             SCREEN_WIDTH_CENTER,
@@ -295,7 +264,9 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
     const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
 
     canvas_set_font(canvas, FontPrimary);
-    uint16_t token_name_width = canvas_string_width(canvas, scene_state->current_token->name);
+    const char* token_name_cstr = furi_string_get_cstr(
+        totp_token_info_iterator_get_current_token(iterator_context)->name);
+    uint16_t token_name_width = canvas_string_width(canvas, token_name_cstr);
     if(SCREEN_WIDTH - token_name_width > 18) {
         canvas_draw_str_aligned(
             canvas,
@@ -303,22 +274,17 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
             SCREEN_HEIGHT_CENTER - 20,
             AlignCenter,
             AlignCenter,
-            scene_state->current_token->name);
+            token_name_cstr);
     } else {
         canvas_draw_str_aligned(
-            canvas,
-            9,
-            SCREEN_HEIGHT_CENTER - 20,
-            AlignLeft,
-            AlignCenter,
-            scene_state->current_token->name);
+            canvas, 9, SCREEN_HEIGHT_CENTER - 20, AlignLeft, AlignCenter, token_name_cstr);
         canvas_set_color(canvas, ColorWhite);
         canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9);
         canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9);
         canvas_set_color(canvas, ColorBlack);
     }
 
-    draw_totp_code(canvas, scene_state);
+    draw_totp_code(canvas, plugin_state);
 
     canvas_draw_box(
         canvas,
@@ -326,8 +292,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
         SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT,
         scene_state->ui_precalculated_dimensions.progress_bar_width,
         PROGRESS_BAR_HEIGHT);
-
-    if(plugin_state->tokens_count > 1) {
+    if(totp_token_info_iterator_get_total_count(iterator_context) > 1) {
         canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9);
         canvas_draw_icon(
             canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
@@ -351,7 +316,7 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
 #ifdef TOTP_BADBT_TYPE_ENABLED
     if(plugin_state->automation_method & AutomationMethodBadBt &&
        plugin_state->bt_type_code_worker_context != NULL &&
-       plugin_state->bt_type_code_worker_context->is_advertising) {
+       totp_bt_type_code_worker_is_advertising(plugin_state->bt_type_code_worker_context)) {
         canvas_draw_icon(
             canvas,
             SCREEN_WIDTH_CENTER +
@@ -379,10 +344,11 @@ bool totp_scene_generate_token_handle_event(
         if(event->input.key == InputKeyDown &&
            plugin_state->automation_method & AutomationMethodBadUsb) {
             scene_state = (SceneState*)plugin_state->current_scene_state;
+            const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
             totp_usb_type_code_worker_notify(
                 scene_state->usb_type_code_worker_context,
                 TotpUsbTypeCodeWorkerEventType,
-                scene_state->current_token->automation_features);
+                totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
             notification_message(
                 plugin_state->notification_app,
                 get_notification_sequence_automation(plugin_state, scene_state));
@@ -393,10 +359,11 @@ bool totp_scene_generate_token_handle_event(
             event->input.key == InputKeyUp &&
             plugin_state->automation_method & AutomationMethodBadBt) {
             scene_state = (SceneState*)plugin_state->current_scene_state;
+            const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
             totp_bt_type_code_worker_notify(
                 plugin_state->bt_type_code_worker_context,
                 TotpBtTypeCodeWorkerEventType,
-                scene_state->current_token->automation_features);
+                totp_token_info_iterator_get_current_token(iterator_context)->automation_features);
             notification_message(
                 plugin_state->notification_app,
                 get_notification_sequence_automation(plugin_state, scene_state));
@@ -409,37 +376,39 @@ bool totp_scene_generate_token_handle_event(
         return true;
     }
 
-    scene_state = (SceneState*)plugin_state->current_scene_state;
     switch(event->input.key) {
     case InputKeyUp:
         break;
     case InputKeyDown:
         break;
-    case InputKeyRight:
-        totp_roll_value_uint16_t(
-            &scene_state->current_token_index,
+    case InputKeyRight: {
+        const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+        size_t current_token_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+        totp_roll_value_size_t(
+            &current_token_index,
             1,
             0,
-            plugin_state->tokens_count - 1,
+            totp_token_info_iterator_get_total_count(iterator_context) - 1,
             RollOverflowBehaviorRoll);
-        update_totp_params(plugin_state);
+
+        update_totp_params(plugin_state, current_token_index);
         break;
-    case InputKeyLeft:
-        totp_roll_value_uint16_t(
-            &scene_state->current_token_index,
+    }
+    case InputKeyLeft: {
+        const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+        size_t current_token_index = totp_token_info_iterator_get_current_token_index(iterator_context);
+        totp_roll_value_size_t(
+            &current_token_index,
             -1,
             0,
-            plugin_state->tokens_count - 1,
+            totp_token_info_iterator_get_total_count(iterator_context) - 1,
             RollOverflowBehaviorRoll);
-        update_totp_params(plugin_state);
+
+        update_totp_params(plugin_state, current_token_index);
         break;
+    }
     case InputKeyOk:
-        if(plugin_state->tokens_count == 0) {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, NULL);
-        } else {
-            TokenMenuSceneContext ctx = {.current_token_index = scene_state->current_token_index};
-            totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu, &ctx);
-        }
+        totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu);
         break;
     case InputKeyBack:
         break;

+ 1 - 7
ui/scenes/generate_token/totp_scene_generate_token.h

@@ -4,13 +4,7 @@
 #include "../../../types/plugin_state.h"
 #include "../../../types/plugin_event.h"
 
-typedef struct {
-    uint16_t current_token_index;
-} GenerateTokenSceneContext;
-
-void totp_scene_generate_token_activate(
-    PluginState* plugin_state,
-    const GenerateTokenSceneContext* context);
+void totp_scene_generate_token_activate(PluginState* plugin_state);
 void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state);
 bool totp_scene_generate_token_handle_event(
     const PluginEvent* const event,

+ 12 - 0
ui/scenes/standby/standby.c

@@ -0,0 +1,12 @@
+#include "standby.h"
+#include <totp_icons.h>
+#include "../../constants.h"
+
+void totp_scene_standby_render(Canvas* const canvas) {
+    canvas_draw_icon(canvas, SCREEN_WIDTH - 56, SCREEN_HEIGHT - 48, &I_DolphinCommon_56x48);
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignTop, "CLI command");
+
+    canvas_draw_str_aligned(canvas, 5, 24, AlignLeft, AlignTop, "is running now");
+}

+ 5 - 0
ui/scenes/standby/standby.h

@@ -0,0 +1,5 @@
+#pragma once
+
+#include <gui/gui.h>
+
+void totp_scene_standby_render(Canvas* const canvas);

+ 19 - 52
ui/scenes/token_menu/totp_scene_token_menu.c

@@ -6,12 +6,10 @@
 #include "../../constants.h"
 #include "../../scene_director.h"
 #include "../../../services/config/config.h"
-#include <linked_list.h>
 #include "../../../types/token_info.h"
 #include "../generate_token/totp_scene_generate_token.h"
 #include "../add_new_token/totp_scene_add_new_token.h"
 #include "../app_settings/totp_app_settings.h"
-#include "../../../types/nullable.h"
 #include <roll_value.h>
 
 #define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3)
@@ -21,25 +19,18 @@ typedef enum { AddNewToken, DeleteToken, AppSettings } Control;
 
 typedef struct {
     Control selected_control;
-    TotpNullable_uint16_t current_token_index;
 } SceneState;
 
-void totp_scene_token_menu_activate(
-    PluginState* plugin_state,
-    const TokenMenuSceneContext* context) {
+void totp_scene_token_menu_activate(PluginState* plugin_state) {
     SceneState* scene_state = malloc(sizeof(SceneState));
     furi_check(scene_state != NULL);
     plugin_state->current_scene_state = scene_state;
-    if(context != NULL) {
-        TOTP_NULLABLE_VALUE(scene_state->current_token_index, context->current_token_index);
-    } else {
-        TOTP_NULLABLE_NULL(scene_state->current_token_index);
-    }
 }
 
 void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) {
     const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state;
-    if(scene_state->current_token_index.is_null) {
+    const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
+    if(totp_token_info_iterator_get_total_count(iterator_context) == 0) {
         ui_control_button_render(
             canvas,
             SCREEN_WIDTH_CENTER - 36,
@@ -95,22 +86,26 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
     }
 
     switch(event->input.key) {
-    case InputKeyUp:
+    case InputKeyUp: {
+        const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
         totp_roll_value_uint8_t(
             &scene_state->selected_control, -1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
         if(scene_state->selected_control == DeleteToken &&
-           scene_state->current_token_index.is_null) {
+            totp_token_info_iterator_get_total_count(iterator_context) == 0) {
             scene_state->selected_control--;
         }
         break;
-    case InputKeyDown:
+    }
+    case InputKeyDown: {
+        const TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
         totp_roll_value_uint8_t(
             &scene_state->selected_control, 1, AddNewToken, AppSettings, RollOverflowBehaviorRoll);
         if(scene_state->selected_control == DeleteToken &&
-           scene_state->current_token_index.is_null) {
+           totp_token_info_iterator_get_total_count(iterator_context) == 0) {
             scene_state->selected_control++;
         }
         break;
+    }
     case InputKeyRight:
         break;
     case InputKeyLeft:
@@ -118,14 +113,7 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
     case InputKeyOk:
         switch(scene_state->selected_control) {
         case AddNewToken: {
-            if(scene_state->current_token_index.is_null) {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken, NULL);
-            } else {
-                TokenAddEditSceneContext add_new_token_scene_context = {
-                    .current_token_index = scene_state->current_token_index.value};
-                totp_scene_director_activate_scene(
-                    plugin_state, TotpSceneAddNewToken, &add_new_token_scene_context);
-            }
+            totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken);
             break;
         }
         case DeleteToken: {
@@ -142,34 +130,20 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
             DialogMessageButton dialog_result =
                 dialog_message_show(plugin_state->dialogs_app, message);
             dialog_message_free(message);
+            TokenInfoIteratorContext* iterator_context = totp_config_get_token_iterator_context(plugin_state);
             if(dialog_result == DialogMessageButtonRight &&
-               !scene_state->current_token_index.is_null) {
-                TokenInfo* tokenInfo = NULL;
-                plugin_state->tokens_list = list_remove_at(
-                    plugin_state->tokens_list,
-                    scene_state->current_token_index.value,
-                    (void**)&tokenInfo);
-                plugin_state->tokens_count--;
-                furi_check(tokenInfo != NULL);
-                token_info_free(tokenInfo);
-
-                if(totp_full_save_config_file(plugin_state) != TotpConfigFileUpdateSuccess) {
+               totp_token_info_iterator_get_total_count(iterator_context) > 0) {
+                if(!totp_token_info_iterator_remove_current_token_info(iterator_context)) {
                     totp_dialogs_config_updating_error(plugin_state);
                     return false;
                 }
-                totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+
+                totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
             }
             break;
         }
         case AppSettings: {
-            if(!scene_state->current_token_index.is_null) {
-                AppSettingsSceneContext app_settings_context = {
-                    .current_token_index = scene_state->current_token_index.value};
-                totp_scene_director_activate_scene(
-                    plugin_state, TotpSceneAppSettings, &app_settings_context);
-            } else {
-                totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings, NULL);
-            }
+            totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings);
             break;
         }
         default:
@@ -177,14 +151,7 @@ bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginSt
         }
         break;
     case InputKeyBack: {
-        if(!scene_state->current_token_index.is_null) {
-            GenerateTokenSceneContext generate_scene_context = {
-                .current_token_index = scene_state->current_token_index.value};
-            totp_scene_director_activate_scene(
-                plugin_state, TotpSceneGenerateToken, &generate_scene_context);
-        } else {
-            totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
-        }
+        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken);
         break;
     }
     default:

+ 1 - 7
ui/scenes/token_menu/totp_scene_token_menu.h

@@ -4,13 +4,7 @@
 #include "../../../types/plugin_state.h"
 #include "../../../types/plugin_event.h"
 
-typedef struct {
-    uint16_t current_token_index;
-} TokenMenuSceneContext;
-
-void totp_scene_token_menu_activate(
-    PluginState* plugin_state,
-    const TokenMenuSceneContext* context);
+void totp_scene_token_menu_activate(PluginState* plugin_state);
 void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state);
 bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state);
 void totp_scene_token_menu_deactivate(PluginState* plugin_state);

+ 6 - 1
ui/totp_scenes_enum.h

@@ -34,5 +34,10 @@ enum Scenes {
     /**
      * @brief Scene where user can change application settings 
      */
-    TotpSceneAppSettings
+    TotpSceneAppSettings,
+
+    /**
+     * @brief Scene which informs user that CLI command is running
+     */
+    TotpSceneStandby
 };

+ 30 - 0
workers/bt_type_code/bt_type_code.c

@@ -2,13 +2,39 @@
 #include <furi_hal_bt_hid.h>
 #include <furi_hal_version.h>
 #include <bt/bt_service/bt_i.h>
+#include <furi/core/thread.h>
+#include <furi/core/mutex.h>
+#include <furi/core/string.h>
+#include <furi/core/kernel.h>
+#include <bt/bt_service/bt.h>
 #include <storage/storage.h>
 #include "../../types/common.h"
 #include "../../types/token_info.h"
 #include "../type-code-common.h"
+#include "../../features_config.h"
+
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
+#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
+#endif
 
 #define HID_BT_KEYS_STORAGE_PATH EXT_PATH("authenticator/.bt_hid.keys")
 
+struct TotpBtTypeCodeWorkerContext {
+    char* code_buffer;
+    uint8_t code_buffer_size;
+    uint8_t flags;
+    FuriThread* thread;
+    FuriMutex* code_buffer_sync;
+    Bt* bt;
+    bool is_advertising;
+    bool is_connected;
+#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
+    char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
+    uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
+#endif
+};
+
 static inline bool totp_type_code_worker_stop_requested() {
     return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop;
 }
@@ -197,4 +223,8 @@ void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) {
     context->bt = NULL;
 
     free(context);
+}
+
+bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context) {
+    return context->is_advertising;
 }

+ 5 - 25
workers/bt_type_code/bt_type_code.h

@@ -1,34 +1,12 @@
 #pragma once
 
-#include <stdlib.h>
-#include <furi/core/thread.h>
+#include <stdint.h>
+#include <stdbool.h>
 #include <furi/core/mutex.h>
-#include <furi/core/string.h>
-#include <furi/core/kernel.h>
-#include <bt/bt_service/bt.h>
-#include "../../features_config.h"
-
-#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
-#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
-#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
-#endif
 
 typedef uint8_t TotpBtTypeCodeWorkerEvent;
 
-typedef struct {
-    char* code_buffer;
-    uint8_t code_buffer_size;
-    uint8_t flags;
-    FuriThread* thread;
-    FuriMutex* code_buffer_sync;
-    Bt* bt;
-    bool is_advertising;
-    bool is_connected;
-#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME
-    char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN];
-    uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN];
-#endif
-} TotpBtTypeCodeWorkerContext;
+typedef struct TotpBtTypeCodeWorkerContext TotpBtTypeCodeWorkerContext;
 
 enum TotpBtTypeCodeWorkerEvents {
     TotpBtTypeCodeWorkerEventReserved = 0b00,
@@ -48,3 +26,5 @@ void totp_bt_type_code_worker_notify(
     TotpBtTypeCodeWorkerContext* context,
     TotpBtTypeCodeWorkerEvent event,
     uint8_t flags);
+
+bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context);

+ 16 - 2
workers/generate_totp_code/generate_totp_code.c

@@ -1,4 +1,5 @@
 #include "generate_totp_code.h"
+#include <furi/core/thread.h>
 #include "../../services/crypto/crypto.h"
 #include "../../services/totp/totp.h"
 #include "../../services/convert/convert.h"
@@ -7,6 +8,19 @@
 
 #define ONE_SEC_MS (1000)
 
+struct TotpGenerateCodeWorkerContext {
+    char* code_buffer;
+    FuriThread* thread;
+    FuriMutex* code_buffer_sync;
+    const TokenInfo* token_info;
+    float timezone_offset;
+    uint8_t* iv;
+    TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler;
+    void* on_new_code_generated_handler_context;
+    TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler;
+    void* on_code_lifetime_changed_handler_context;
+};
+
 static const char* STEAM_ALGO_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY";
 
 static void
@@ -93,7 +107,7 @@ static int32_t totp_generate_worker_callback(void* context) {
 
         if(flags & TotpGenerateCodeWorkerEventStop) break;
 
-        const TokenInfo* token_info = *(t_context->token_info);
+        const TokenInfo* token_info = t_context->token_info;
         if(token_info == NULL) {
             continue;
         }
@@ -127,7 +141,7 @@ static int32_t totp_generate_worker_callback(void* context) {
 
 TotpGenerateCodeWorkerContext* totp_generate_code_worker_start(
     char* code_buffer,
-    TokenInfo** token_info,
+    const TokenInfo* token_info,
     FuriMutex* code_buffer_sync,
     float timezone_offset,
     uint8_t* iv) {

+ 2 - 14
workers/generate_totp_code/generate_totp_code.h

@@ -1,7 +1,6 @@
 #pragma once
 
 #include <stdlib.h>
-#include <furi/core/thread.h>
 #include <furi/core/mutex.h>
 #include "../../types/token_info.h"
 
@@ -10,18 +9,7 @@ typedef uint8_t TotpGenerateCodeWorkerEvent;
 typedef void (*TOTP_NEW_CODE_GENERATED_HANDLER)(bool time_left, void* context);
 typedef void (*TOTP_CODE_LIFETIME_CHANGED_HANDLER)(float code_lifetime_percent, void* context);
 
-typedef struct {
-    char* code_buffer;
-    FuriThread* thread;
-    FuriMutex* code_buffer_sync;
-    TokenInfo** token_info;
-    float timezone_offset;
-    uint8_t* iv;
-    TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler;
-    void* on_new_code_generated_handler_context;
-    TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler;
-    void* on_code_lifetime_changed_handler_context;
-} TotpGenerateCodeWorkerContext;
+typedef struct TotpGenerateCodeWorkerContext TotpGenerateCodeWorkerContext;
 
 enum TotGenerateCodeWorkerEvents {
     TotpGenerateCodeWorkerEventReserved = 0b00,
@@ -31,7 +19,7 @@ enum TotGenerateCodeWorkerEvents {
 
 TotpGenerateCodeWorkerContext* totp_generate_code_worker_start(
     char* code_buffer,
-    TokenInfo** token_info,
+    const TokenInfo* token_info,
     FuriMutex* code_buffer_sync,
     float timezone_offset,
     uint8_t* iv);

+ 13 - 0
workers/usb_type_code/usb_type_code.c

@@ -1,9 +1,22 @@
 #include "usb_type_code.h"
+#include <furi_hal_usb.h>
 #include <furi_hal_usb_hid.h>
+#include <furi/core/thread.h>
+#include <furi/core/kernel.h>
+#include <furi/core/check.h>
 #include "../../services/convert/convert.h"
 #include "../../types/token_info.h"
 #include "../type-code-common.h"
 
+struct TotpUsbTypeCodeWorkerContext {
+    char* code_buffer;
+    uint8_t code_buffer_size;
+    uint8_t flags;
+    FuriThread* thread;
+    FuriMutex* code_buffer_sync;
+    FuriHalUsbInterface* usb_mode_prev;
+};
+
 static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) {
     if(context->usb_mode_prev != NULL) {
         furi_hal_usb_set_config(context->usb_mode_prev, NULL);

+ 3 - 13
workers/usb_type_code/usb_type_code.h

@@ -1,22 +1,12 @@
 #pragma once
 
-#include <stdlib.h>
-#include <furi/core/thread.h>
+#include <stdint.h>
+#include <stdbool.h>
 #include <furi/core/mutex.h>
-#include <furi/core/kernel.h>
-#include <furi/core/check.h>
-#include <furi_hal_usb.h>
 
 typedef uint8_t TotpUsbTypeCodeWorkerEvent;
 
-typedef struct {
-    char* code_buffer;
-    uint8_t code_buffer_size;
-    uint8_t flags;
-    FuriThread* thread;
-    FuriMutex* code_buffer_sync;
-    FuriHalUsbInterface* usb_mode_prev;
-} TotpUsbTypeCodeWorkerContext;
+typedef struct TotpUsbTypeCodeWorkerContext TotpUsbTypeCodeWorkerContext;
 
 enum TotpUsbTypeCodeWorkerEvents {
     TotpUsbTypeCodeWorkerEventReserved = 0b00,