Alexander Kopachov 2 лет назад
Родитель
Сommit
fd6052585b
36 измененных файлов с 914 добавлено и 420 удалено
  1. 8 0
      cli/cli.c
  2. 20 1
      cli/cli_helpers.c
  3. 14 0
      cli/cli_helpers.h
  4. 33 107
      cli/commands/add/add.c
  5. 2 2
      cli/commands/automation/automation.c
  6. 11 11
      cli/commands/delete/delete.c
  7. 61 0
      cli/commands/details/details.c
  8. 11 0
      cli/commands/details/details.h
  9. 7 0
      cli/commands/help/help.c
  10. 2 18
      cli/commands/list/list.c
  11. 9 56
      cli/commands/move/move.c
  12. 2 2
      cli/commands/notification/notification.c
  13. 162 0
      cli/commands/update/update.c
  14. 11 0
      cli/commands/update/update.h
  15. 112 0
      cli/common_command_arguments.c
  16. 37 0
      cli/common_command_arguments.h
  17. 1 1
      features_config.h
  18. BIN
      images/hid_ble_10x7.png
  19. BIN
      images/hid_ble_31x9.png
  20. BIN
      images/hid_usb_31x9.png
  21. 42 57
      services/config/config.c
  22. 2 5
      services/config/constants.h
  23. 129 0
      services/config/migrations/common_migration.c
  24. 2 2
      services/config/migrations/common_migration.h
  25. 0 47
      services/config/migrations/config_migration_v1_to_v2.c
  26. 0 7
      services/config/migrations/config_migration_v1_to_v2.h
  27. 0 70
      services/config/migrations/config_migration_v2_to_v3.c
  28. 66 1
      types/token_info.c
  29. 61 0
      types/token_info.h
  30. 33 6
      ui/scenes/generate_token/totp_scene_generate_token.c
  31. 41 13
      workers/bt_type_code/bt_type_code.c
  32. 7 4
      workers/bt_type_code/bt_type_code.h
  33. 3 1
      workers/constants.c
  34. 2 1
      workers/constants.h
  35. 17 4
      workers/usb_type_code/usb_type_code.c
  36. 6 4
      workers/usb_type_code/usb_type_code.h

+ 8 - 0
cli/cli.c

@@ -5,6 +5,7 @@
 #include "cli_helpers.h"
 #include "cli_helpers.h"
 #include "commands/list/list.h"
 #include "commands/list/list.h"
 #include "commands/add/add.h"
 #include "commands/add/add.h"
+#include "commands/update/update.h"
 #include "commands/delete/delete.h"
 #include "commands/delete/delete.h"
 #include "commands/timezone/timezone.h"
 #include "commands/timezone/timezone.h"
 #include "commands/help/help.h"
 #include "commands/help/help.h"
@@ -13,6 +14,7 @@
 #include "commands/notification/notification.h"
 #include "commands/notification/notification.h"
 #include "commands/reset/reset.h"
 #include "commands/reset/reset.h"
 #include "commands/automation/automation.h"
 #include "commands/automation/automation.h"
+#include "commands/details/details.h"
 
 
 static void totp_cli_print_unknown_command(const FuriString* unknown_command) {
 static void totp_cli_print_unknown_command(const FuriString* unknown_command) {
     TOTP_CLI_PRINTF_ERROR(
     TOTP_CLI_PRINTF_ERROR(
@@ -62,6 +64,12 @@ static void totp_cli_handler(Cli* cli, FuriString* args, void* context) {
         totp_cli_command_automation_handle(plugin_state, args, cli);
         totp_cli_command_automation_handle(plugin_state, args, cli);
     } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) {
     } 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(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(
+        furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DETAILS) == 0 ||
+        furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DETAILS_ALT) == 0) {
+        totp_cli_command_details_handle(plugin_state, args, cli);
     } else {
     } else {
         totp_cli_print_unknown_command(cmd);
         totp_cli_print_unknown_command(cmd);
     }
     }

+ 20 - 1
cli/cli_helpers.c

@@ -1,5 +1,6 @@
 #include "cli_helpers.h"
 #include "cli_helpers.h"
 #include <cli/cli.h>
 #include <cli/cli.h>
+#include <lib/toolbox/args.h>
 #include "../types/plugin_event.h"
 #include "../types/plugin_event.h"
 
 
 bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
 bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) {
@@ -59,4 +60,22 @@ bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) {
     }
     }
 
 
     return true;
     return true;
-}
+}
+
+bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
+    int int_value;
+    if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
+        return false;
+    }
+
+    *value = (uint8_t)int_value;
+    return true;
+}
+
+void furi_string_secure_free(FuriString* str) {
+    for(long i = furi_string_size(str) - 1; i >= 0; i--) {
+        furi_string_set_char(str, i, '\0');
+    }
+
+    furi_string_free(str);
+}

+ 14 - 0
cli/cli_helpers.h

@@ -89,3 +89,17 @@ void totp_cli_force_close_app(FuriMessageQueue* event_queue);
  * @return \c true if line successfully read and confirmed; \c false otherwise
  * @return \c true if line successfully read and confirmed; \c false otherwise
  */
  */
 bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input);
 bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input);
+
+/**
+ * @brief Extracts \c uint8_t value and trims arguments string
+ * @param args arguments string
+ * @param[out] value parsed value
+ * @return \c true if value successfully read and parsed as \c uint8_t ; \c false otherwise
+ */
+bool args_read_uint8_and_trim(FuriString* args, uint8_t* value);
+
+/**
+ * @brief Free \c FuriString instance in a secure manner by clearing it first
+ * @param str instance to free
+ */
+void furi_string_secure_free(FuriString* str);

+ 33 - 107
cli/commands/add/add.c

@@ -7,44 +7,7 @@
 #include "../../../services/convert/convert.h"
 #include "../../../services/convert/convert.h"
 #include "../../cli_helpers.h"
 #include "../../cli_helpers.h"
 #include "../../../ui/scene_director.h"
 #include "../../../ui/scene_director.h"
-
-#define TOTP_CLI_COMMAND_ADD_ARG_NAME "name"
-#define TOTP_CLI_COMMAND_ADD_ARG_ALGO "algo"
-#define TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "-a"
-#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS "digits"
-#define TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX "-d"
-#define TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX "-u"
-#define TOTP_CLI_COMMAND_ADD_ARG_DURATION "duration"
-#define TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX "-l"
-
-static bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
-    if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
-        token_info->algo = SHA1;
-        return true;
-    }
-
-    if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) {
-        token_info->algo = SHA256;
-        return true;
-    }
-
-    if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) {
-        token_info->algo = SHA512;
-        return true;
-    }
-
-    return false;
-}
-
-static bool args_read_uint8_and_trim(FuriString* args, uint8_t* value) {
-    int int_value;
-    if(!args_read_int_and_trim(args, &int_value) || int_value < 0 || int_value > UINT8_MAX) {
-        return false;
-    }
-
-    *value = (uint8_t)int_value;
-    return true;
-}
+#include "../../common_command_arguments.h"
 
 
 void totp_cli_command_add_docopt_commands() {
 void totp_cli_command_add_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT
@@ -54,42 +17,46 @@ void totp_cli_command_add_docopt_commands() {
 void totp_cli_command_add_docopt_usage() {
 void totp_cli_command_add_docopt_usage() {
     TOTP_CLI_PRINTF(
     TOTP_CLI_PRINTF(
         "  " TOTP_CLI_COMMAND_NAME
         "  " TOTP_CLI_COMMAND_NAME
-        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_NAME) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO))) " " DOCOPT_OPTIONAL(
+        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_ADD " | " TOTP_CLI_COMMAND_ADD_ALT " | " TOTP_CLI_COMMAND_ADD_ALT2) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME) " " 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_DIGITS_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(
             DOCOPT_OPTION(
             DOCOPT_OPTION(
-                TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
+                TOTP_CLI_COMMAND_ARG_DURATION_PREFIX,
                 DOCOPT_ARGUMENT(
                 DOCOPT_ARGUMENT(
-                    TOTP_CLI_COMMAND_ADD_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX)) "\r\n");
+                    TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_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_add_docopt_arguments() {
 void totp_cli_command_add_docopt_arguments() {
-    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ADD_ARG_NAME "          Token name\r\n");
+    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ARG_NAME "          Token name\r\n");
 }
 }
 
 
 void totp_cli_command_add_docopt_options() {
 void totp_cli_command_add_docopt_options() {
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
-        TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX,
-        DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ADD_ARG_ALGO)) "      Token hashing algorithm.\r\n");
-    TOTP_CLI_PRINTF(
-        "                 Could be one of: sha1, sha256, sha512 " DOCOPT_DEFAULT("sha1") "\r\n");
-    cli_nl();
+        TOTP_CLI_COMMAND_ARG_ALGO_PREFIX,
+        DOCOPT_ARGUMENT(
+            TOTP_CLI_COMMAND_ARG_ALGO)) "      Token hashing algorithm. Must be one of: " TOTP_TOKEN_ALGO_SHA1_NAME
+                                        ", " TOTP_TOKEN_ALGO_SHA256_NAME
+                                        ", " TOTP_TOKEN_ALGO_SHA512_NAME
+                                        " " DOCOPT_DEFAULT(TOTP_TOKEN_ALGO_SHA1_NAME) "\r\n");
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
-        TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX,
+        TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX,
         DOCOPT_ARGUMENT(
         DOCOPT_ARGUMENT(
-            TOTP_CLI_COMMAND_ADD_ARG_DIGITS)) "    Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
+            TOTP_CLI_COMMAND_ARG_DIGITS)) "    Number of digits to generate, one of: 6, 8 " DOCOPT_DEFAULT("6") "\r\n");
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
-        TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX,
+        TOTP_CLI_COMMAND_ARG_DURATION_PREFIX,
         DOCOPT_ARGUMENT(
         DOCOPT_ARGUMENT(
-            TOTP_CLI_COMMAND_ADD_ARG_DURATION)) "  Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
+            TOTP_CLI_COMMAND_ARG_DURATION)) "  Token lifetime duration in seconds, between: 15 and 255 " DOCOPT_DEFAULT("30") "\r\n");
     TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
     TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
-        TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) "             Show console user input as-is without masking\r\n");
-}
-
-static void furi_string_secure_free(FuriString* str) {
-    for(long i = furi_string_size(str) - 1; i >= 0; i--) {
-        furi_string_set_char(str, i, '\0');
-    }
-
-    furi_string_free(str);
+        TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX) "             Show console user input as-is without masking\r\n");
+    TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
+        TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX,
+        DOCOPT_ARGUMENT(
+            TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)) "   Token automation features to be enabled. Must be one of: " TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME
+                                                      ", " TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME
+                                                      " " DOCOPT_DEFAULT(
+                                                          TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME) "\r\n");
+    TOTP_CLI_PRINTF("                 # " TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME
+                    " - No features\r\n");
+    TOTP_CLI_PRINTF("                 # " TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME
+                    " - Type <Enter> key at the end of token input automation\r\n");
 }
 }
 
 
 void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
 void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
@@ -113,53 +80,12 @@ void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cl
     bool mask_user_input = true;
     bool mask_user_input = true;
     while(args_read_string_and_trim(args, temp_str)) {
     while(args_read_string_and_trim(args, temp_str)) {
         bool parsed = false;
         bool parsed = false;
-        if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX) == 0) {
-            if(!args_read_string_and_trim(args, temp_str)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "Missed value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX "\"\r\n");
-            } else if(!token_info_set_algo_from_str(token_info, temp_str)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_ALGO_PREFIX
-                    "\"\r\n",
-                    furi_string_get_cstr(temp_str));
-            } else {
-                parsed = true;
-            }
-        } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX) == 0) {
-            uint8_t digit_value;
-            if(!args_read_uint8_and_trim(args, &digit_value)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
-                    "\"\r\n");
-            } else if(!token_info_set_digits_from_int(token_info, digit_value)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "\"%" PRIu8
-                    "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DIGITS_PREFIX
-                    "\"\r\n",
-                    digit_value);
-            } else {
-                parsed = true;
-            }
-        } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX) == 0) {
-            uint8_t duration_value;
-            if(!args_read_uint8_and_trim(args, &duration_value)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "Missed or incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
-                    "\"\r\n");
-            } else if(!token_info_set_duration_from_int(token_info, duration_value)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "\"%" PRIu8
-                    "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ADD_ARG_DURATION_PREFIX
-                    "\"\r\n",
-                    duration_value);
-            } else {
-                parsed = true;
-            }
-        } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_ADD_ARG_UNSECURE_PREFIX) == 0) {
-            mask_user_input = false;
-            parsed = true;
-        } else {
-            TOTP_CLI_PRINTF_ERROR("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
+        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_printf_unknown_argument(temp_str);
         }
         }
 
 
         if(!parsed) {
         if(!parsed) {

+ 2 - 2
cli/commands/automation/automation.c

@@ -23,12 +23,12 @@ void totp_cli_command_automation_docopt_usage() {
 void totp_cli_command_automation_docopt_arguments() {
 void totp_cli_command_automation_docopt_arguments() {
     TOTP_CLI_PRINTF(
     TOTP_CLI_PRINTF(
         "  " TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD
         "  " TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD
-        "    Automation method to be set. Must be one of [" TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE
+        "    Automation method to be set. Must be one of: " TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE
         ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB
         ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB
 #ifdef TOTP_BADBT_TYPE_ENABLED
 #ifdef TOTP_BADBT_TYPE_ENABLED
         ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT
         ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT
 #endif
 #endif
-        "]\r\n");
+        "\r\n");
 }
 }
 
 
 static void totp_cli_command_automation_print_method(AutomationMethod method, char* color) {
 static void totp_cli_command_automation_print_method(AutomationMethod method, char* color) {

+ 11 - 11
cli/commands/delete/delete.c

@@ -7,9 +7,9 @@
 #include "../../../services/config/config.h"
 #include "../../../services/config/config.h"
 #include "../../cli_helpers.h"
 #include "../../cli_helpers.h"
 #include "../../../ui/scene_director.h"
 #include "../../../ui/scene_director.h"
+#include "../../common_command_arguments.h"
 
 
-#define TOTP_CLI_COMMAND_DELETE_ARG_INDEX "index"
-#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX "-f"
+#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX "-f"
 
 
 void totp_cli_command_delete_docopt_commands() {
 void totp_cli_command_delete_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT
@@ -20,19 +20,23 @@ void totp_cli_command_delete_docopt_usage() {
     TOTP_CLI_PRINTF(
     TOTP_CLI_PRINTF(
         "  " TOTP_CLI_COMMAND_NAME
         "  " TOTP_CLI_COMMAND_NAME
         " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT(
         " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT(
-            TOTP_CLI_COMMAND_DELETE_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX)) "\r\n");
+            TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX)) "\r\n");
 }
 }
 
 
 void totp_cli_command_delete_docopt_arguments() {
 void totp_cli_command_delete_docopt_arguments() {
-    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_DELETE_ARG_INDEX "         Token index in the list\r\n");
+    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_ARG_INDEX "         Token index in the list\r\n");
 }
 }
 
 
 void totp_cli_command_delete_docopt_options() {
 void totp_cli_command_delete_docopt_options() {
     TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
     TOTP_CLI_PRINTF("  " DOCOPT_SWITCH(
-        TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) "             Force command to do not ask user for interactive confirmation\r\n");
+        TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) "             Force command to do not ask user for interactive confirmation\r\n");
 }
 }
 
 
 void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
 void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+    if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
+        return;
+    }
+
     int token_number;
     int token_number;
     if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
     if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 ||
        token_number > plugin_state->tokens_count) {
        token_number > plugin_state->tokens_count) {
@@ -43,10 +47,10 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
     FuriString* temp_str = furi_string_alloc();
     FuriString* temp_str = furi_string_alloc();
     bool confirm_needed = true;
     bool confirm_needed = true;
     if(args_read_string_and_trim(args, temp_str)) {
     if(args_read_string_and_trim(args, temp_str)) {
-        if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_SUFFIX) == 0) {
+        if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) == 0) {
             confirm_needed = false;
             confirm_needed = false;
         } else {
         } else {
-            TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(temp_str));
+            totp_cli_printf_unknown_argument(temp_str);
             TOTP_CLI_PRINT_INVALID_ARGUMENTS();
             TOTP_CLI_PRINT_INVALID_ARGUMENTS();
             furi_string_free(temp_str);
             furi_string_free(temp_str);
             return;
             return;
@@ -54,10 +58,6 @@ void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args,
     }
     }
     furi_string_free(temp_str);
     furi_string_free(temp_str);
 
 
-    if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
-        return;
-    }
-
     ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
     ListNode* list_node = list_element_at(plugin_state->tokens_list, token_number - 1);
 
 
     TokenInfo* token_info = list_node->data;
     TokenInfo* token_info = list_node->data;

+ 61 - 0
cli/commands/details/details.c

@@ -0,0 +1,61 @@
+#include "details.h"
+#include <stdlib.h>
+#include <lib/toolbox/args.h>
+#include "../../../lib/list/list.h"
+#include "../../../types/token_info.h"
+#include "../../../services/config/constants.h"
+#include "../../cli_helpers.h"
+#include "../../common_command_arguments.h"
+
+static void print_automation_features(const TokenInfo* token_info) {
+    if(token_info->automation_features == TOKEN_AUTOMATION_FEATURE_NONE) {
+        TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Automation features", "None");
+        return;
+    }
+
+    if(token_info->automation_features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+        TOTP_CLI_PRINTF(
+            "| %-20s | %-28.28s |\r\n", "Automation features", "Type <Enter> key at the end");
+    }
+}
+
+void totp_cli_command_details_docopt_commands() {
+    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_DETAILS ", " TOTP_CLI_COMMAND_DETAILS_ALT
+                    "      Displays token details\r\n");
+}
+
+void totp_cli_command_details_docopt_usage() {
+    TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED(
+        TOTP_CLI_COMMAND_DETAILS
+        " | " TOTP_CLI_COMMAND_DETAILS_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) "\r\n");
+}
+
+void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
+    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_node = list_element_at(plugin_state->tokens_list, token_number - 1);
+
+    TokenInfo* token_info = list_node->data;
+
+    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");
+}

+ 11 - 0
cli/commands/details/details.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#include <cli/cli.h>
+#include "../../../types/plugin_state.h"
+
+#define TOTP_CLI_COMMAND_DETAILS "lsattr"
+#define TOTP_CLI_COMMAND_DETAILS_ALT "cat"
+
+void totp_cli_command_details_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
+void totp_cli_command_details_docopt_commands();
+void totp_cli_command_details_docopt_usage();

+ 7 - 0
cli/commands/help/help.c

@@ -1,6 +1,7 @@
 #include "help.h"
 #include "help.h"
 #include "../../cli_helpers.h"
 #include "../../cli_helpers.h"
 #include "../add/add.h"
 #include "../add/add.h"
+#include "../update/update.h"
 #include "../delete/delete.h"
 #include "../delete/delete.h"
 #include "../list/list.h"
 #include "../list/list.h"
 #include "../timezone/timezone.h"
 #include "../timezone/timezone.h"
@@ -9,6 +10,7 @@
 #include "../notification/notification.h"
 #include "../notification/notification.h"
 #include "../reset/reset.h"
 #include "../reset/reset.h"
 #include "../automation/automation.h"
 #include "../automation/automation.h"
+#include "../details/details.h"
 
 
 void totp_cli_command_help_docopt_commands() {
 void totp_cli_command_help_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT
@@ -25,7 +27,9 @@ void totp_cli_command_help_handle() {
     TOTP_CLI_PRINTF("Usage:\r\n");
     TOTP_CLI_PRINTF("Usage:\r\n");
     totp_cli_command_help_docopt_usage();
     totp_cli_command_help_docopt_usage();
     totp_cli_command_list_docopt_usage();
     totp_cli_command_list_docopt_usage();
+    totp_cli_command_details_docopt_usage();
     totp_cli_command_add_docopt_usage();
     totp_cli_command_add_docopt_usage();
+    totp_cli_command_update_docopt_usage();
     totp_cli_command_delete_docopt_usage();
     totp_cli_command_delete_docopt_usage();
     totp_cli_command_timezone_docopt_usage();
     totp_cli_command_timezone_docopt_usage();
     totp_cli_command_move_docopt_usage();
     totp_cli_command_move_docopt_usage();
@@ -37,7 +41,9 @@ void totp_cli_command_help_handle() {
     TOTP_CLI_PRINTF("Commands:\r\n");
     TOTP_CLI_PRINTF("Commands:\r\n");
     totp_cli_command_help_docopt_commands();
     totp_cli_command_help_docopt_commands();
     totp_cli_command_list_docopt_commands();
     totp_cli_command_list_docopt_commands();
+    totp_cli_command_details_docopt_commands();
     totp_cli_command_add_docopt_commands();
     totp_cli_command_add_docopt_commands();
+    totp_cli_command_update_docopt_commands();
     totp_cli_command_delete_docopt_commands();
     totp_cli_command_delete_docopt_commands();
     totp_cli_command_timezone_docopt_commands();
     totp_cli_command_timezone_docopt_commands();
     totp_cli_command_move_docopt_commands();
     totp_cli_command_move_docopt_commands();
@@ -55,6 +61,7 @@ void totp_cli_command_help_handle() {
     cli_nl();
     cli_nl();
     TOTP_CLI_PRINTF("Options:\r\n");
     TOTP_CLI_PRINTF("Options:\r\n");
     totp_cli_command_add_docopt_options();
     totp_cli_command_add_docopt_options();
+    totp_cli_command_update_docopt_options();
     totp_cli_command_delete_docopt_options();
     totp_cli_command_delete_docopt_options();
     totp_cli_command_move_docopt_options();
     totp_cli_command_move_docopt_options();
 }
 }

+ 2 - 18
cli/commands/list/list.c

@@ -5,21 +5,6 @@
 #include "../../../services/config/constants.h"
 #include "../../../services/config/constants.h"
 #include "../../cli_helpers.h"
 #include "../../cli_helpers.h"
 
 
-static char* get_algo_as_cstr(TokenHashAlgo algo) {
-    switch(algo) {
-    case SHA1:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME;
-    case SHA256:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME;
-    case SHA512:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME;
-    default:
-        break;
-    }
-
-    return "UNKNOWN";
-}
-
 void totp_cli_command_list_docopt_commands() {
 void totp_cli_command_list_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT
                     "         List all available tokens\r\n");
                     "         List all available tokens\r\n");
@@ -41,8 +26,7 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
     }
     }
 
 
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
-    TOTP_CLI_PRINTF(
-        "| %-*s | %-*s | %-*s | %-s | %-s |\r\n", 3, "#", 25, "Name", 6, "Algo", "Ln", "Dur");
+    TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur");
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
     TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n");
     uint16_t index = 1;
     uint16_t index = 1;
     TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
     TOTP_LIST_FOREACH(plugin_state->tokens_list, node, {
@@ -51,7 +35,7 @@ void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) {
             "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
             "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n",
             index,
             index,
             token_info->name,
             token_info->name,
-            get_algo_as_cstr(token_info->algo),
+            token_info_get_algo_as_cstr(token_info),
             token_info->digits,
             token_info->digits,
             token_info->duration);
             token_info->duration);
         index++;
         index++;

+ 9 - 56
cli/commands/move/move.c

@@ -7,85 +7,50 @@
 #include "../../../services/config/config.h"
 #include "../../../services/config/config.h"
 #include "../../cli_helpers.h"
 #include "../../cli_helpers.h"
 #include "../../../ui/scene_director.h"
 #include "../../../ui/scene_director.h"
-
-#define TOTP_CLI_COMMAND_MOVE_ARG_INDEX "index"
-
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME "name"
-#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX "-n"
+#include "../../common_command_arguments.h"
 
 
 #define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "index"
 #define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "index"
 #define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX "-i"
 #define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX "-i"
 
 
 void totp_cli_command_move_docopt_commands() {
 void totp_cli_command_move_docopt_commands() {
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT
     TOTP_CLI_PRINTF("  " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT
-                    "         Move\\rename token\r\n");
+                    "         Move token\r\n");
 }
 }
 
 
 void totp_cli_command_move_docopt_usage() {
 void totp_cli_command_move_docopt_usage() {
     TOTP_CLI_PRINTF(
     TOTP_CLI_PRINTF(
         "  " TOTP_CLI_COMMAND_NAME
         "  " TOTP_CLI_COMMAND_NAME
-        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_INDEX) " " DOCOPT_OPTIONAL(
+        " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(
             DOCOPT_OPTION(
             DOCOPT_OPTION(
-                TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
-                DOCOPT_ARGUMENT(
-                    TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX))) "\r\n");
+                TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX,
+                DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX))) "\r\n");
 }
 }
 
 
 void totp_cli_command_move_docopt_options() {
 void totp_cli_command_move_docopt_options() {
-    TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
-        TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX,
-        DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME)) "      New token name\r\n");
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
     TOTP_CLI_PRINTF("  " DOCOPT_OPTION(
         TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX,
         TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX,
         DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX)) "     New token index\r\n");
         DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX)) "     New token index\r\n");
 }
 }
 
 
 void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
 void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) {
-    int token_index;
-    if(!args_read_int_and_trim(args, &token_index)) {
-        TOTP_CLI_PRINT_INVALID_ARGUMENTS();
-        return;
-    }
-
     if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
     if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
         return;
         return;
     }
     }
 
 
-    if(token_index < 1 || token_index > plugin_state->tokens_count) {
+    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();
         TOTP_CLI_PRINT_INVALID_ARGUMENTS();
         return;
         return;
     }
     }
 
 
     FuriString* temp_str = furi_string_alloc();
     FuriString* temp_str = furi_string_alloc();
 
 
-    char* new_token_name = NULL;
     int new_token_index = 0;
     int new_token_index = 0;
 
 
     while(args_read_string_and_trim(args, temp_str)) {
     while(args_read_string_and_trim(args, temp_str)) {
         bool parsed = false;
         bool parsed = false;
-        if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX) == 0) {
-            if(!args_read_string_and_trim(args, temp_str)) {
-                TOTP_CLI_PRINTF_ERROR(
-                    "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_NAME_PREFIX
-                    "\"\r\n");
-            } else {
-                if(new_token_name != NULL) {
-                    free(new_token_name);
-                }
-
-                new_token_name = malloc(furi_string_size(temp_str) + 1);
-                if(new_token_name == NULL) {
-                    furi_string_free(temp_str);
-                    return;
-                }
-
-                strlcpy(
-                    new_token_name,
-                    furi_string_get_cstr(temp_str),
-                    furi_string_size(temp_str) + 1);
-                parsed = true;
-            }
-        } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX) == 0) {
+        if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX) == 0) {
             if(!args_read_int_and_trim(args, &new_token_index)) {
             if(!args_read_int_and_trim(args, &new_token_index)) {
                 TOTP_CLI_PRINTF_ERROR(
                 TOTP_CLI_PRINTF_ERROR(
                     "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
                     "Missed value for argument \"" TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX_PREFIX
@@ -106,18 +71,12 @@ void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, C
         if(!parsed) {
         if(!parsed) {
             TOTP_CLI_PRINT_INVALID_ARGUMENTS();
             TOTP_CLI_PRINT_INVALID_ARGUMENTS();
             furi_string_free(temp_str);
             furi_string_free(temp_str);
-            if(new_token_name != NULL) {
-                free(new_token_name);
-            }
             return;
             return;
         }
         }
     }
     }
 
 
     if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
     if(!totp_cli_ensure_authenticated(plugin_state, cli)) {
         furi_string_free(temp_str);
         furi_string_free(temp_str);
-        if(new_token_name != NULL) {
-            free(new_token_name);
-        }
         return;
         return;
     }
     }
 
 
@@ -140,12 +99,6 @@ void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, C
         token_info = list_element_at(plugin_state->tokens_list, token_index - 1)->data;
         token_info = list_element_at(plugin_state->tokens_list, token_index - 1)->data;
     }
     }
 
 
-    if(new_token_name != NULL) {
-        free(token_info->name);
-        token_info->name = new_token_name;
-        token_updated = true;
-    }
-
     if(token_updated) {
     if(token_updated) {
         if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
         if(totp_full_save_config_file(plugin_state) == TotpConfigFileUpdateSuccess) {
             TOTP_CLI_PRINTF_SUCCESS(
             TOTP_CLI_PRINTF_SUCCESS(

+ 2 - 2
cli/commands/notification/notification.c

@@ -23,9 +23,9 @@ void totp_cli_command_notification_docopt_usage() {
 void totp_cli_command_notification_docopt_arguments() {
 void totp_cli_command_notification_docopt_arguments() {
     TOTP_CLI_PRINTF(
     TOTP_CLI_PRINTF(
         "  " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD
         "  " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD
-        "  Notification method to be set. Must be one of [" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE
+        "  Notification method to be set. Must be one of: " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE
         ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND
         ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND
-        ", " TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "]\r\n");
+        ", " 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, char* color) {

+ 162 - 0
cli/commands/update/update.c

@@ -0,0 +1,162 @@
+#include "update.h"
+#include <stdlib.h>
+#include <lib/toolbox/args.h>
+#include "../../../lib/list/list.h"
+#include "../../../types/token_info.h"
+#include "../../../services/config/config.h"
+#include "../../../services/convert/convert.h"
+#include "../../cli_helpers.h"
+#include "../../../ui/scene_director.h"
+#include "../../common_command_arguments.h"
+
+#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");
+}
+
+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_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_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");
+}
+
+static bool
+    totp_cli_try_read_name(TokenInfo* token_info, 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)) {
+            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;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+static bool totp_cli_try_read_change_secret_flag(const FuriString* arg, bool* parsed, bool* flag) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) == 0) {
+        *flag = true;
+        *parsed = true;
+        return true;
+    }
+
+    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);
+
+    // Read optional arguments
+    bool mask_user_input = true;
+    bool update_token_secret = false;
+    while(args_read_string_and_trim(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) &&
+           !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_printf_unknown_argument(temp_str);
+        }
+
+        if(!parsed) {
+            TOTP_CLI_PRINT_INVALID_ARGUMENTS();
+            furi_string_free(temp_str);
+            token_info_free(token_info);
+            return;
+        }
+    }
+
+    if(update_token_secret) {
+        // 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();
+            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();
+
+        if(token_info->token != NULL) {
+            free(token_info->token);
+        }
+
+        if(!token_info_set_secret(
+               token_info,
+               furi_string_get_cstr(temp_str),
+               furi_string_size(temp_str),
+               plugin_state->iv)) {
+            TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n");
+            furi_string_secure_free(temp_str);
+            token_info_free(token_info);
+            return;
+        }
+    }
+
+    furi_string_secure_free(temp_str);
+
+    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;
+    }
+
+    list_item->data = token_info;
+
+    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);
+    }
+
+    if(load_generate_token_scene) {
+        totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
+    }
+}

+ 11 - 0
cli/commands/update/update.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#include <cli/cli.h>
+#include "../../../types/plugin_state.h"
+
+#define TOTP_CLI_COMMAND_UPDATE "update"
+
+void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli);
+void totp_cli_command_update_docopt_commands();
+void totp_cli_command_update_docopt_usage();
+void totp_cli_command_update_docopt_options();

+ 112 - 0
cli/common_command_arguments.c

@@ -0,0 +1,112 @@
+#include "common_command_arguments.h"
+#include <lib/toolbox/args.h>
+
+inline 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) {
+    TOTP_CLI_PRINTF("Unknown argument \"%s\"\r\n", furi_string_get_cstr(arg));
+}
+
+bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_ALGO_PREFIX) == 0) {
+        if(!args_read_string_and_trim(args, arg)) {
+            totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX);
+        } else if(!token_info_set_algo_from_str(token_info, arg)) {
+            TOTP_CLI_PRINTF_ERROR(
+                "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_ALGO_PREFIX
+                "\"\r\n",
+                furi_string_get_cstr(arg));
+        } else {
+            *parsed = true;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+bool totp_cli_try_read_digits(
+    TokenInfo* token_info,
+    const FuriString* arg,
+    FuriString* args,
+    bool* parsed) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX) == 0) {
+        uint8_t digit_value;
+        if(!args_read_uint8_and_trim(args, &digit_value)) {
+            totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX);
+        } else if(!token_info_set_digits_from_int(token_info, digit_value)) {
+            TOTP_CLI_PRINTF_ERROR(
+                "\"%" PRIu8
+                "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX
+                "\"\r\n",
+                digit_value);
+        } else {
+            *parsed = true;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+bool totp_cli_try_read_duration(
+    TokenInfo* token_info,
+    const FuriString* arg,
+    FuriString* args,
+    bool* parsed) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_DURATION_PREFIX) == 0) {
+        uint8_t duration_value;
+        if(!args_read_uint8_and_trim(args, &duration_value)) {
+            totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX);
+        } else if(!token_info_set_duration_from_int(token_info, duration_value)) {
+            TOTP_CLI_PRINTF_ERROR(
+                "\"%" PRIu8
+                "\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_DURATION_PREFIX
+                "\"\r\n",
+                duration_value);
+        } else {
+            *parsed = true;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+bool totp_cli_try_read_automation_features(
+    TokenInfo* token_info,
+    FuriString* arg,
+    FuriString* args,
+    bool* parsed) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX) == 0) {
+        if(!args_read_string_and_trim(args, arg)) {
+            totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX);
+        } else if(!token_info_set_automation_feature_from_str(token_info, arg)) {
+            TOTP_CLI_PRINTF_ERROR(
+                "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX
+                "\"\r\n",
+                furi_string_get_cstr(arg));
+        } else {
+            *parsed = true;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag) {
+    if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX) == 0) {
+        *unsecure_flag = false;
+        *parsed = true;
+        return true;
+    }
+
+    return false;
+}

+ 37 - 0
cli/common_command_arguments.h

@@ -0,0 +1,37 @@
+#pragma once
+#include <stdlib.h>
+#include "../types/token_info.h"
+#include "cli_helpers.h"
+
+#define TOTP_CLI_COMMAND_ARG_NAME "name"
+#define TOTP_CLI_COMMAND_ARG_NAME_PREFIX "-n"
+#define TOTP_CLI_COMMAND_ARG_ALGO "algo"
+#define TOTP_CLI_COMMAND_ARG_ALGO_PREFIX "-a"
+#define TOTP_CLI_COMMAND_ARG_DIGITS "digits"
+#define TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX "-d"
+#define TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX "-u"
+#define TOTP_CLI_COMMAND_ARG_DURATION "duration"
+#define TOTP_CLI_COMMAND_ARG_DURATION_PREFIX "-l"
+#define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX "-b"
+#define TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE "feature"
+#define TOTP_CLI_COMMAND_ARG_INDEX "index"
+
+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);

+ 1 - 1
features_config.h

@@ -1,2 +1,2 @@
 #define TOTP_BADBT_TYPE_ENABLED
 #define TOTP_BADBT_TYPE_ENABLED
-#define TOTP_BADBT_TYPE_ICON_ENABLED
+#define TOTP_AUTOMATION_ICONS_ENABLED

BIN
images/hid_ble_10x7.png


BIN
images/hid_ble_31x9.png


BIN
images/hid_usb_31x9.png


+ 42 - 57
services/config/config.c

@@ -5,8 +5,7 @@
 #include "../../types/common.h"
 #include "../../types/common.h"
 #include "../../types/token_info.h"
 #include "../../types/token_info.h"
 #include "../../features_config.h"
 #include "../../features_config.h"
-#include "migrations/config_migration_v1_to_v2.h"
-#include "migrations/config_migration_v2_to_v3.h"
+#include "migrations/common_migration.h"
 
 
 #define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
 #define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("authenticator")
 #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
 #define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf"
@@ -15,31 +14,6 @@
 #define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
 #define CONFIG_FILE_ORIG_PATH CONFIG_FILE_PATH ".orig"
 #define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
 #define CONFIG_FILE_PATH_PREVIOUS EXT_PATH("apps/Misc") "/totp.conf"
 
 
-static char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
-    switch(token_info->algo) {
-    case SHA1:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME;
-    case SHA256:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME;
-    case SHA512:
-        return TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME;
-    default:
-        break;
-    }
-
-    return NULL;
-}
-
-static void token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
-    if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME) == 0) {
-        token_info->algo = SHA1;
-    } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME) == 0) {
-        token_info->algo = SHA256;
-    } else if(furi_string_cmpi_str(str, TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME) == 0) {
-        token_info->algo = SHA512;
-    }
-}
-
 /**
 /**
  * @brief Opens storage record
  * @brief Opens storage record
  * @return Storage record
  * @return Storage record
@@ -166,13 +140,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
         furi_string_printf(
         furi_string_printf(
             temp_str,
             temp_str,
             " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s",
             " # Token hashing algorithm to use during code generation. Supported options are %s, %s and %s. If you are not use which one to use - use %s",
-            TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME,
-            TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME,
-            TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME,
-            TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
+            TOTP_TOKEN_ALGO_SHA1_NAME,
+            TOTP_TOKEN_ALGO_SHA256_NAME,
+            TOTP_TOKEN_ALGO_SHA512_NAME,
+            TOTP_TOKEN_ALGO_SHA1_NAME);
         flipper_format_write_comment(fff_data_file, temp_str);
         flipper_format_write_comment(fff_data_file, temp_str);
         furi_string_printf(
         furi_string_printf(
-            temp_str, "%s: %s", TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
+            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(fff_data_file, temp_str);
         flipper_format_write_comment_cstr(fff_data_file, " ");
         flipper_format_write_comment_cstr(fff_data_file, " ");
 
 
@@ -190,6 +164,13 @@ static TotpConfigFileOpenResult totp_open_config_file(Storage* storage, FlipperF
         flipper_format_write_comment(fff_data_file, temp_str);
         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, " ");
 
 
+        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, "=== TOKEN SAMPLE END ===");
         flipper_format_write_comment_cstr(fff_data_file, " ");
         flipper_format_write_comment_cstr(fff_data_file, " ");
 
 
@@ -255,6 +236,13 @@ static TotpConfigFileUpdateResult
             break;
             break;
         }
         }
 
 
+        tmp_uint32 = token_info->automation_features;
+        if(!flipper_format_write_uint32(
+               file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) {
+            update_result = TotpConfigFileUpdateError;
+            break;
+        }
+
         update_result = TotpConfigFileUpdateSuccess;
         update_result = TotpConfigFileUpdateSuccess;
     } while(false);
     } while(false);
 
 
@@ -544,28 +532,19 @@ TotpConfigFileOpenResult totp_config_file_load_base(PluginState* const plugin_st
                     break;
                     break;
                 }
                 }
 
 
-                if(file_version == 1) {
-                    if(totp_config_migrate_v1_to_v2(fff_data_file, fff_backup_data_file)) {
-                        FURI_LOG_I(LOGGING_TAG, "Applied migration from v1 to v2");
-                        file_version = 2;
-                    } else {
-                        FURI_LOG_W(
-                            LOGGING_TAG, "An error occurred during migration from v1 to v2");
-                        result = TotpConfigFileOpenError;
-                        break;
-                    }
-                }
-
-                if(file_version == 2) {
-                    if(totp_config_migrate_v2_to_v3(fff_data_file, fff_backup_data_file)) {
-                        FURI_LOG_I(LOGGING_TAG, "Applied migration from v2 to v3");
-                        file_version = 3;
-                    } else {
-                        FURI_LOG_W(
-                            LOGGING_TAG, "An error occurred during migration from v2 to v3");
-                        result = TotpConfigFileOpenError;
-                        break;
-                    }
+                if(totp_config_migrate_to_latest(fff_data_file, fff_backup_data_file)) {
+                    FURI_LOG_I(
+                        LOGGING_TAG,
+                        "Applied migration to version %" PRId16,
+                        CONFIG_FILE_ACTUAL_VERSION);
+                    file_version = CONFIG_FILE_ACTUAL_VERSION;
+                } else {
+                    FURI_LOG_W(
+                        LOGGING_TAG,
+                        "An error occurred during migration to version %" PRId16,
+                        CONFIG_FILE_ACTUAL_VERSION);
+                    result = TotpConfigFileOpenError;
+                    break;
                 }
                 }
 
 
                 flipper_format_file_close(fff_backup_data_file);
                 flipper_format_file_close(fff_backup_data_file);
@@ -743,9 +722,8 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
             }
             }
         }
         }
 
 
-        if(flipper_format_read_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str)) {
-            token_info_set_algo_from_str(tokenInfo, temp_str);
-        } else {
+        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;
             tokenInfo->algo = SHA1;
         }
         }
 
 
@@ -761,6 +739,13 @@ TokenLoadingResult totp_config_file_load_tokens(PluginState* const plugin_state)
             tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
             tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
         }
         }
 
 
+        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;
+        }
+
         FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
         FURI_LOG_D(LOGGING_TAG, "Found token \"%s\"", tokenInfo->name);
 
 
         TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);
         TOTP_LIST_INIT_OR_ADD(plugin_state->tokens_list, tokenInfo, furi_check);

+ 2 - 5
services/config/constants.h

@@ -1,7 +1,7 @@
 #pragma once
 #pragma once
 
 
 #define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
 #define CONFIG_FILE_HEADER "Flipper TOTP plugin config file"
-#define CONFIG_FILE_ACTUAL_VERSION 3
+#define CONFIG_FILE_ACTUAL_VERSION 4
 
 
 #define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
 #define TOTP_CONFIG_KEY_TIMEZONE "Timezone"
 #define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
 #define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName"
@@ -9,12 +9,9 @@
 #define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
 #define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo"
 #define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
 #define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits"
 #define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration"
 #define TOTP_CONFIG_KEY_TOKEN_DURATION "TokenDuration"
+#define TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES "TokenAutomationFeatures"
 #define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
 #define TOTP_CONFIG_KEY_CRYPTO_VERIFY "Crypto"
 #define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
 #define TOTP_CONFIG_KEY_BASE_IV "BaseIV"
 #define TOTP_CONFIG_KEY_PINSET "PinIsSet"
 #define TOTP_CONFIG_KEY_PINSET "PinIsSet"
 #define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod"
 #define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod"
 #define TOTP_CONFIG_KEY_AUTOMATION_METHOD "AutomationMethod"
 #define TOTP_CONFIG_KEY_AUTOMATION_METHOD "AutomationMethod"
-
-#define TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME "sha1"
-#define TOTP_CONFIG_TOKEN_ALGO_SHA256_NAME "sha256"
-#define TOTP_CONFIG_TOKEN_ALGO_SHA512_NAME "sha512"

+ 129 - 0
services/config/migrations/common_migration.c

@@ -0,0 +1,129 @@
+#include "common_migration.h"
+#include "../constants.h"
+#include "../../../types/token_info.h"
+
+bool totp_config_migrate_to_latest(
+    FlipperFormat* fff_data_file,
+    FlipperFormat* fff_backup_data_file) {
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t current_version = 0;
+    bool result = false;
+    do {
+        flipper_format_write_header_cstr(
+            fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION);
+
+        if(!flipper_format_read_header(fff_backup_data_file, temp_str, &current_version)) {
+            break;
+        }
+
+        if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
+            flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
+        }
+
+        flipper_format_rewind(fff_backup_data_file);
+
+        if(flipper_format_read_string(
+               fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
+            flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
+        }
+
+        flipper_format_rewind(fff_backup_data_file);
+
+        if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
+            flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
+        }
+
+        flipper_format_rewind(fff_backup_data_file);
+
+        if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
+            flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
+        }
+
+        flipper_format_rewind(fff_backup_data_file);
+
+        if(flipper_format_read_string(
+               fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
+            flipper_format_write_string(
+                fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
+        }
+
+        flipper_format_rewind(fff_backup_data_file);
+
+        if(flipper_format_read_string(
+               fff_backup_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, temp_str)) {
+            flipper_format_write_string(
+                fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, temp_str);
+        }
+
+        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(
+                fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
+            flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
+
+            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);
+
+                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_digits = TOTP_6_DIGITS;
+                flipper_format_write_uint32(
+                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
+            }
+
+            if(current_version > 2) {
+                flipper_format_read_string(
+                    fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, temp_str);
+                flipper_format_write_string(
+                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, temp_str);
+            } else {
+                const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
+                flipper_format_write_uint32(
+                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
+            }
+
+            if(current_version > 3) {
+                flipper_format_read_string(
+                    fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, temp_str);
+                flipper_format_write_string(
+                    fff_data_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, temp_str);
+            } else {
+                const uint32_t default_automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+                flipper_format_write_uint32(
+                    fff_data_file,
+                    TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES,
+                    &default_automation_features,
+                    1);
+            }
+
+            flipper_format_write_comment(fff_data_file, comment_str);
+        }
+
+        furi_string_free(comment_str);
+
+        result = true;
+    } while(false);
+
+    furi_string_free(temp_str);
+    return result;
+}

+ 2 - 2
services/config/migrations/config_migration_v2_to_v3.h → services/config/migrations/common_migration.h

@@ -2,6 +2,6 @@
 
 
 #include <flipper_format/flipper_format.h>
 #include <flipper_format/flipper_format.h>
 
 
-bool totp_config_migrate_v2_to_v3(
+bool totp_config_migrate_to_latest(
     FlipperFormat* fff_data_file,
     FlipperFormat* fff_data_file,
-    FlipperFormat* fff_backup_data_file);
+    FlipperFormat* fff_backup_data_file);

+ 0 - 47
services/config/migrations/config_migration_v1_to_v2.c

@@ -1,47 +0,0 @@
-#include "config_migration_v1_to_v2.h"
-#include <flipper_format/flipper_format.h>
-#include "../constants.h"
-#include "../../../types/token_info.h"
-
-#define NEW_VERSION 2
-
-bool totp_config_migrate_v1_to_v2(
-    FlipperFormat* fff_data_file,
-    FlipperFormat* fff_backup_data_file) {
-    flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
-
-    FuriString* temp_str = furi_string_alloc();
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
-    }
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
-    }
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
-    }
-
-    while(true) {
-        if(!flipper_format_read_string(
-               fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
-            break;
-        }
-
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
-
-        flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-
-        flipper_format_write_string_cstr(
-            fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, TOTP_CONFIG_TOKEN_ALGO_SHA1_NAME);
-        const uint32_t default_digits = TOTP_6_DIGITS;
-        flipper_format_write_uint32(
-            fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &default_digits, 1);
-    }
-
-    furi_string_free(temp_str);
-    return true;
-}

+ 0 - 7
services/config/migrations/config_migration_v1_to_v2.h

@@ -1,7 +0,0 @@
-#pragma once
-
-#include <flipper_format/flipper_format.h>
-
-bool totp_config_migrate_v1_to_v2(
-    FlipperFormat* fff_data_file,
-    FlipperFormat* fff_backup_data_file);

+ 0 - 70
services/config/migrations/config_migration_v2_to_v3.c

@@ -1,70 +0,0 @@
-#include "config_migration_v2_to_v3.h"
-#include <flipper_format/flipper_format.h>
-#include "../constants.h"
-#include "../../../types/token_info.h"
-
-#define NEW_VERSION 3
-
-bool totp_config_migrate_v2_to_v3(
-    FlipperFormat* fff_data_file,
-    FlipperFormat* fff_backup_data_file) {
-    flipper_format_write_header_cstr(fff_data_file, CONFIG_FILE_HEADER, NEW_VERSION);
-
-    FuriString* temp_str = furi_string_alloc();
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_BASE_IV, temp_str);
-    }
-
-    flipper_format_rewind(fff_backup_data_file);
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, temp_str);
-    }
-
-    flipper_format_rewind(fff_backup_data_file);
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, temp_str);
-    }
-
-    flipper_format_rewind(fff_backup_data_file);
-
-    if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_PINSET, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_PINSET, temp_str);
-    }
-
-    flipper_format_rewind(fff_backup_data_file);
-
-    if(flipper_format_read_string(
-           fff_backup_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str)) {
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, temp_str);
-    }
-
-    flipper_format_rewind(fff_backup_data_file);
-
-    while(true) {
-        if(!flipper_format_read_string(
-               fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str)) {
-            break;
-        }
-
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_NAME, temp_str);
-
-        flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-        flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str);
-
-        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);
-
-        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);
-
-        const uint32_t default_duration = TOTP_TOKEN_DURATION_DEFAULT;
-        flipper_format_write_uint32(
-            fff_data_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &default_duration, 1);
-    }
-
-    furi_string_free(temp_str);
-    return true;
-}

+ 66 - 1
types/token_info.c

@@ -1,4 +1,3 @@
-#include <furi/furi.h>
 #include <furi_hal.h>
 #include <furi_hal.h>
 #include "token_info.h"
 #include "token_info.h"
 #include "stdlib.h"
 #include "stdlib.h"
@@ -6,6 +5,7 @@
 #include "../lib/base32/base32.h"
 #include "../lib/base32/base32.h"
 #include "../services/crypto/crypto.h"
 #include "../services/crypto/crypto.h"
 #include "../lib/polyfills/memset_s.h"
 #include "../lib/polyfills/memset_s.h"
+#include "../lib/polyfills/strnlen.h"
 
 
 TokenInfo* token_info_alloc() {
 TokenInfo* token_info_alloc() {
     TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
     TokenInfo* tokenInfo = malloc(sizeof(TokenInfo));
@@ -13,6 +13,7 @@ TokenInfo* token_info_alloc() {
     tokenInfo->algo = SHA1;
     tokenInfo->algo = SHA1;
     tokenInfo->digits = TOTP_6_DIGITS;
     tokenInfo->digits = TOTP_6_DIGITS;
     tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
     tokenInfo->duration = TOTP_TOKEN_DURATION_DEFAULT;
+    tokenInfo->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
     return tokenInfo;
     return tokenInfo;
 }
 }
 
 
@@ -70,4 +71,68 @@ bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration) {
     }
     }
 
 
     return false;
     return false;
+}
+
+bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) {
+    if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA1_NAME) == 0) {
+        token_info->algo = SHA1;
+        return true;
+    }
+
+    if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA256_NAME) == 0) {
+        token_info->algo = SHA256;
+        return true;
+    }
+
+    if(furi_string_cmpi_str(str, TOTP_TOKEN_ALGO_SHA512_NAME) == 0) {
+        token_info->algo = SHA512;
+        return true;
+    }
+
+    return false;
+}
+
+char* token_info_get_algo_as_cstr(const TokenInfo* token_info) {
+    switch(token_info->algo) {
+    case SHA1:
+        return TOTP_TOKEN_ALGO_SHA1_NAME;
+    case SHA256:
+        return TOTP_TOKEN_ALGO_SHA256_NAME;
+    case SHA512:
+        return TOTP_TOKEN_ALGO_SHA512_NAME;
+    default:
+        break;
+    }
+
+    return NULL;
+}
+
+bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str) {
+    if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME) == 0) {
+        token_info->automation_features |= TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END;
+        return true;
+    }
+
+    if(furi_string_cmpi_str(str, TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME) == 0) {
+        token_info->automation_features = TOKEN_AUTOMATION_FEATURE_NONE;
+        return true;
+    }
+
+    return false;
+}
+
+TokenInfo* token_info_clone(const TokenInfo* src) {
+    TokenInfo* clone = token_info_alloc();
+    memcpy(clone, src, sizeof(TokenInfo));
+
+    clone->token = malloc(src->token_length);
+    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);
+
+    return clone;
 }
 }

+ 61 - 0
types/token_info.h

@@ -1,11 +1,22 @@
 #pragma once
 #pragma once
 
 
 #include <inttypes.h>
 #include <inttypes.h>
+#include <stdbool.h>
+#include <furi/furi.h>
 
 
 #define TOTP_TOKEN_DURATION_DEFAULT 30
 #define TOTP_TOKEN_DURATION_DEFAULT 30
 
 
+#define TOTP_TOKEN_ALGO_SHA1_NAME "sha1"
+#define TOTP_TOKEN_ALGO_SHA256_NAME "sha256"
+#define TOTP_TOKEN_ALGO_SHA512_NAME "sha512"
+#define TOTP_TOKEN_MAX_LENGTH 255
+
+#define TOTP_TOKEN_AUTOMATION_FEATURE_NONE_NAME "none"
+#define TOTP_TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter"
+
 typedef uint8_t TokenHashAlgo;
 typedef uint8_t TokenHashAlgo;
 typedef uint8_t TokenDigitsCount;
 typedef uint8_t TokenDigitsCount;
+typedef uint8_t TokenAutomationFeature;
 
 
 /**
 /**
  * @brief Hashing algorithm to be used to generate token
  * @brief Hashing algorithm to be used to generate token
@@ -42,6 +53,21 @@ enum TokenDigitsCounts {
     TOTP_8_DIGITS = 8
     TOTP_8_DIGITS = 8
 };
 };
 
 
+/**
+ * @brief Token automation features.
+ */
+enum TokenAutomationFeatures {
+    /**
+     * @brief No features enabled
+     */
+    TOKEN_AUTOMATION_FEATURE_NONE = 0b00,
+
+    /**
+     * @brief Press "Enter" key at the end as a part of token input automation
+     */
+    TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END = 0b01
+};
+
 #define TOTP_TOKEN_DIGITS_MAX_COUNT 8
 #define TOTP_TOKEN_DIGITS_MAX_COUNT 8
 
 
 /**
 /**
@@ -77,6 +103,11 @@ typedef struct {
      * @brief Desired TOTP token duration in seconds
      * @brief Desired TOTP token duration in seconds
      */
      */
     uint8_t duration;
     uint8_t duration;
+
+    /**
+     * @brief Token input automation features
+     */
+    TokenAutomationFeature automation_features;
 } TokenInfo;
 } TokenInfo;
 
 
 /**
 /**
@@ -120,3 +151,33 @@ bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits);
  * @return \c true if token duration has been updated; \c false otherwise
  * @return \c true if token duration has been updated; \c false otherwise
  */
  */
 bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);
 bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration);
+
+/**
+ * @brief Sets token hashing algorithm from \c str value
+ * @param token_info instance whichs token hashing algorithm should be updated
+ * @param str desired token algorithm
+ * @return \c true if token hahsing algorithm has been updated; \c false otherwise
+ */
+bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str);
+
+/**
+ * @brief Gets token hahsing algorithm name as C-string
+ * @param token_info instance which token hahsing algorithm name should be returned
+ * @return token hashing algorithm name as C-string
+ */
+char* token_info_get_algo_as_cstr(const TokenInfo* token_info);
+
+/**
+ * @brief Sets token automation feature from \c str value
+ * @param token_info instance whichs token automation feature should be updated
+ * @param str desired token automation feature
+ * @return \c true if token automation feature has been set; \c false otherwise
+ */
+bool token_info_set_automation_feature_from_str(TokenInfo* token_info, const FuriString* str);
+
+/**
+ * @brief Clones \c TokenInfo instance
+ * @param src instance to clone
+ * @return cloned instance
+ */
+TokenInfo* token_info_clone(const TokenInfo* src);

+ 33 - 6
ui/scenes/generate_token/totp_scene_generate_token.c

@@ -350,14 +350,34 @@ void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_
             canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
             canvas, SCREEN_WIDTH - 9, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9);
     }
     }
 
 
-#if defined(TOTP_BADBT_TYPE_ENABLED) && defined(TOTP_BADBT_TYPE_ICON_ENABLED)
+#ifdef TOTP_AUTOMATION_ICONS_ENABLED
+    if(plugin_state->automation_method & AutomationMethodBadUsb) {
+        canvas_draw_icon(
+            canvas,
+#ifdef TOTP_BADBT_TYPE_ENABLED
+            SCREEN_WIDTH_CENTER -
+                (plugin_state->automation_method & AutomationMethodBadBt ? 33 : 15),
+#else
+            SCREEN_WIDTH_CENTER - 15,
+#endif
+
+            SCREEN_HEIGHT_CENTER + 12,
+            &I_hid_usb_31x9);
+    }
+
+#ifdef TOTP_BADBT_TYPE_ENABLED
     if(plugin_state->automation_method & AutomationMethodBadBt &&
     if(plugin_state->automation_method & AutomationMethodBadBt &&
        plugin_state->bt_type_code_worker_context != NULL &&
        plugin_state->bt_type_code_worker_context != NULL &&
        plugin_state->bt_type_code_worker_context->is_advertising) {
        plugin_state->bt_type_code_worker_context->is_advertising) {
         canvas_draw_icon(
         canvas_draw_icon(
-            canvas, SCREEN_WIDTH_CENTER - 5, SCREEN_HEIGHT_CENTER + 13, &I_hid_ble_10x7);
+            canvas,
+            SCREEN_WIDTH_CENTER +
+                (plugin_state->automation_method & AutomationMethodBadUsb ? 2 : -15),
+            SCREEN_HEIGHT_CENTER + 12,
+            &I_hid_ble_31x9);
     }
     }
 #endif
 #endif
+#endif
 }
 }
 
 
 bool totp_scene_generate_token_handle_event(
 bool totp_scene_generate_token_handle_event(
@@ -373,20 +393,27 @@ bool totp_scene_generate_token_handle_event(
 
 
     SceneState* scene_state;
     SceneState* scene_state;
     if(event->input.type == InputTypeLong) {
     if(event->input.type == InputTypeLong) {
-        if(event->input.key == InputKeyDown && plugin_state->automation_method & AutomationMethodBadUsb) {
+        if(event->input.key == InputKeyDown &&
+           plugin_state->automation_method & AutomationMethodBadUsb) {
             scene_state = (SceneState*)plugin_state->current_scene_state;
             scene_state = (SceneState*)plugin_state->current_scene_state;
             totp_usb_type_code_worker_notify(
             totp_usb_type_code_worker_notify(
-                scene_state->usb_type_code_worker_context, TotpUsbTypeCodeWorkerEventType);
+                scene_state->usb_type_code_worker_context,
+                TotpUsbTypeCodeWorkerEventType,
+                scene_state->current_token->automation_features);
             notification_message(
             notification_message(
                 plugin_state->notification_app,
                 plugin_state->notification_app,
                 get_notification_sequence_automation(plugin_state, scene_state));
                 get_notification_sequence_automation(plugin_state, scene_state));
             return true;
             return true;
         }
         }
 #ifdef TOTP_BADBT_TYPE_ENABLED
 #ifdef TOTP_BADBT_TYPE_ENABLED
-        else if(event->input.key == InputKeyUp && plugin_state->automation_method & AutomationMethodBadBt) {
+        else if(
+            event->input.key == InputKeyUp &&
+            plugin_state->automation_method & AutomationMethodBadBt) {
             scene_state = (SceneState*)plugin_state->current_scene_state;
             scene_state = (SceneState*)plugin_state->current_scene_state;
             totp_bt_type_code_worker_notify(
             totp_bt_type_code_worker_notify(
-                plugin_state->bt_type_code_worker_context, TotpBtTypeCodeWorkerEventType);
+                plugin_state->bt_type_code_worker_context,
+                TotpBtTypeCodeWorkerEventType,
+                scene_state->current_token->automation_features);
             notification_message(
             notification_message(
                 plugin_state->notification_app,
                 plugin_state->notification_app,
                 get_notification_sequence_automation(plugin_state, scene_state));
                 get_notification_sequence_automation(plugin_state, scene_state));

+ 41 - 13
workers/bt_type_code/bt_type_code.c

@@ -2,6 +2,7 @@
 #include <furi_hal_bt_hid.h>
 #include <furi_hal_bt_hid.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 #include "../../types/common.h"
 #include "../../types/common.h"
+#include "../../types/token_info.h"
 #include "../../services/convert/convert.h"
 #include "../../services/convert/convert.h"
 #include "../constants.h"
 #include "../constants.h"
 
 
@@ -11,26 +12,36 @@ static inline bool totp_type_code_worker_stop_requested() {
     return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop;
     return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop;
 }
 }
 
 
+static void totp_type_code_worker_press_key(uint8_t key) {
+    furi_hal_bt_hid_kb_press(key);
+    furi_delay_ms(30);
+    furi_hal_bt_hid_kb_release(key);
+}
+
 static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) {
 static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) {
+    TokenAutomationFeature features = context->flags;
     uint8_t i = 0;
     uint8_t i = 0;
     do {
     do {
         furi_delay_ms(500);
         furi_delay_ms(500);
         i++;
         i++;
-    } while(!furi_hal_bt_is_active() && i < 100 && !totp_type_code_worker_stop_requested());
+    } while(!context->is_connected && i < 100 && !totp_type_code_worker_stop_requested());
 
 
-    if(furi_hal_bt_is_active() && furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
+    if(context->is_connected && furi_mutex_acquire(context->string_sync, 500) == FuriStatusOk) {
         furi_delay_ms(500);
         furi_delay_ms(500);
         i = 0;
         i = 0;
         while(i < context->string_length && context->string[i] != 0) {
         while(i < context->string_length && context->string[i] != 0) {
             uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
             uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
             if(digit > 9) break;
             if(digit > 9) break;
             uint8_t hid_kb_key = hid_number_keys[digit];
             uint8_t hid_kb_key = hid_number_keys[digit];
-            furi_hal_bt_hid_kb_press(hid_kb_key);
-            furi_delay_ms(30);
-            furi_hal_bt_hid_kb_release(hid_kb_key);
+            totp_type_code_worker_press_key(hid_kb_key);
             i++;
             i++;
         }
         }
 
 
+        if(features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+            furi_delay_ms(30);
+            totp_type_code_worker_press_key(hid_enter_key);
+        }
+
         furi_mutex_release(context->string_sync);
         furi_mutex_release(context->string_sync);
     }
     }
 }
 }
@@ -44,9 +55,6 @@ static int32_t totp_type_code_worker_callback(void* context) {
 
 
     TotpBtTypeCodeWorkerContext* bt_context = context;
     TotpBtTypeCodeWorkerContext* bt_context = context;
 
 
-    furi_hal_bt_start_advertising();
-    bt_context->is_advertising = true;
-
     while(true) {
     while(true) {
         uint32_t flags = furi_thread_flags_wait(
         uint32_t flags = furi_thread_flags_wait(
             TotpBtTypeCodeWorkerEventStop | TotpBtTypeCodeWorkerEventType,
             TotpBtTypeCodeWorkerEventStop | TotpBtTypeCodeWorkerEventType,
@@ -64,15 +72,20 @@ static int32_t totp_type_code_worker_callback(void* context) {
         }
         }
     }
     }
 
 
-    furi_hal_bt_stop_advertising();
-
-    bt_context->is_advertising = false;
-
     furi_mutex_free(context_mutex);
     furi_mutex_free(context_mutex);
 
 
     return 0;
     return 0;
 }
 }
 
 
+static void connection_status_changed_callback(BtStatus status, void* context) {
+    TotpBtTypeCodeWorkerContext* bt_context = context;
+    if(status == BtStatusConnected) {
+        bt_context->is_connected = true;
+    } else if(status < BtStatusConnected) {
+        bt_context->is_connected = false;
+    }
+}
+
 void totp_bt_type_code_worker_start(
 void totp_bt_type_code_worker_start(
     TotpBtTypeCodeWorkerContext* context,
     TotpBtTypeCodeWorkerContext* context,
     char* code_buf,
     char* code_buf,
@@ -100,8 +113,10 @@ void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) {
 
 
 void totp_bt_type_code_worker_notify(
 void totp_bt_type_code_worker_notify(
     TotpBtTypeCodeWorkerContext* context,
     TotpBtTypeCodeWorkerContext* context,
-    TotpBtTypeCodeWorkerEvent event) {
+    TotpBtTypeCodeWorkerEvent event,
+    uint8_t flags) {
     furi_assert(context != NULL);
     furi_assert(context != NULL);
+    context->flags = flags;
     furi_thread_flags_set(furi_thread_get_id(context->thread), event);
     furi_thread_flags_set(furi_thread_get_id(context->thread), event);
 }
 }
 
 
@@ -111,13 +126,19 @@ TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() {
 
 
     context->bt = furi_record_open(RECORD_BT);
     context->bt = furi_record_open(RECORD_BT);
     context->is_advertising = false;
     context->is_advertising = false;
+    context->is_connected = false;
     bt_disconnect(context->bt);
     bt_disconnect(context->bt);
+    furi_hal_bt_reinit();
     furi_delay_ms(200);
     furi_delay_ms(200);
     bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH);
     bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH);
     if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) {
     if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) {
         FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile");
         FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile");
     }
     }
 
 
+    furi_hal_bt_start_advertising();
+    context->is_advertising = true;
+    bt_set_status_changed_callback(context->bt, connection_status_changed_callback, context);
+
     return context;
     return context;
 }
 }
 
 
@@ -128,7 +149,14 @@ void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) {
         totp_bt_type_code_worker_stop(context);
         totp_bt_type_code_worker_stop(context);
     }
     }
 
 
+    bt_set_status_changed_callback(context->bt, NULL, NULL);
+
+    furi_hal_bt_stop_advertising();
+    context->is_advertising = false;
+    context->is_connected = false;
+
     bt_disconnect(context->bt);
     bt_disconnect(context->bt);
+    furi_delay_ms(200);
     bt_keys_storage_set_default_path(context->bt);
     bt_keys_storage_set_default_path(context->bt);
 
 
     if(!bt_set_profile(context->bt, BtProfileSerial)) {
     if(!bt_set_profile(context->bt, BtProfileSerial)) {

+ 7 - 4
workers/bt_type_code/bt_type_code.h

@@ -10,16 +10,18 @@ typedef uint8_t TotpBtTypeCodeWorkerEvent;
 typedef struct {
 typedef struct {
     char* string;
     char* string;
     uint8_t string_length;
     uint8_t string_length;
+    uint8_t flags;
     FuriThread* thread;
     FuriThread* thread;
     FuriMutex* string_sync;
     FuriMutex* string_sync;
     Bt* bt;
     Bt* bt;
     bool is_advertising;
     bool is_advertising;
+    bool is_connected;
 } TotpBtTypeCodeWorkerContext;
 } TotpBtTypeCodeWorkerContext;
 
 
 enum TotpBtTypeCodeWorkerEvents {
 enum TotpBtTypeCodeWorkerEvents {
-    TotpBtTypeCodeWorkerEventReserved = (1 << 0),
-    TotpBtTypeCodeWorkerEventStop = (1 << 1),
-    TotpBtTypeCodeWorkerEventType = (1 << 2)
+    TotpBtTypeCodeWorkerEventReserved = 0b0000,
+    TotpBtTypeCodeWorkerEventStop = 0b0100,
+    TotpBtTypeCodeWorkerEventType = 0b1000
 };
 };
 
 
 TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init();
 TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init();
@@ -32,4 +34,5 @@ void totp_bt_type_code_worker_start(
 void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context);
 void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context);
 void totp_bt_type_code_worker_notify(
 void totp_bt_type_code_worker_notify(
     TotpBtTypeCodeWorkerContext* context,
     TotpBtTypeCodeWorkerContext* context,
-    TotpBtTypeCodeWorkerEvent event);
+    TotpBtTypeCodeWorkerEvent event,
+    uint8_t flags);

+ 3 - 1
workers/constants.c

@@ -11,4 +11,6 @@ const uint8_t hid_number_keys[10] = {
     HID_KEYBOARD_6,
     HID_KEYBOARD_6,
     HID_KEYBOARD_7,
     HID_KEYBOARD_7,
     HID_KEYBOARD_8,
     HID_KEYBOARD_8,
-    HID_KEYBOARD_9};
+    HID_KEYBOARD_9};
+
+const uint8_t hid_enter_key = HID_KEYBOARD_RETURN;

+ 2 - 1
workers/constants.h

@@ -1,4 +1,5 @@
 #pragma once
 #pragma once
 #include <stdint.h>
 #include <stdint.h>
 
 
-extern const uint8_t hid_number_keys[10];
+extern const uint8_t hid_number_keys[10];
+extern const uint8_t hid_enter_key;

+ 17 - 4
workers/usb_type_code/usb_type_code.c

@@ -1,5 +1,6 @@
 #include "usb_type_code.h"
 #include "usb_type_code.h"
 #include "../../services/convert/convert.h"
 #include "../../services/convert/convert.h"
+#include "../../types/token_info.h"
 #include "../constants.h"
 #include "../constants.h"
 
 
 static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) {
 static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) {
@@ -13,7 +14,14 @@ static inline bool totp_type_code_worker_stop_requested() {
     return furi_thread_flags_get() & TotpUsbTypeCodeWorkerEventStop;
     return furi_thread_flags_get() & TotpUsbTypeCodeWorkerEventStop;
 }
 }
 
 
+static void totp_type_code_worker_press_key(uint8_t key) {
+    furi_hal_hid_kb_press(key);
+    furi_delay_ms(30);
+    furi_hal_hid_kb_release(key);
+}
+
 static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* context) {
 static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* context) {
+    TokenAutomationFeature features = context->flags;
     context->usb_mode_prev = furi_hal_usb_get_config();
     context->usb_mode_prev = furi_hal_usb_get_config();
     furi_hal_usb_unlock();
     furi_hal_usb_unlock();
     furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
     furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true);
@@ -31,12 +39,15 @@ static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* contex
             uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
             uint8_t digit = CONVERT_CHAR_TO_DIGIT(context->string[i]);
             if(digit > 9) break;
             if(digit > 9) break;
             uint8_t hid_kb_key = hid_number_keys[digit];
             uint8_t hid_kb_key = hid_number_keys[digit];
-            furi_hal_hid_kb_press(hid_kb_key);
-            furi_delay_ms(30);
-            furi_hal_hid_kb_release(hid_kb_key);
+            totp_type_code_worker_press_key(hid_kb_key);
             i++;
             i++;
         }
         }
 
 
+        if(features & TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END) {
+            furi_delay_ms(30);
+            totp_type_code_worker_press_key(hid_enter_key);
+        }
+
         furi_mutex_release(context->string_sync);
         furi_mutex_release(context->string_sync);
 
 
         furi_delay_ms(100);
         furi_delay_ms(100);
@@ -104,7 +115,9 @@ void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) {
 
 
 void totp_usb_type_code_worker_notify(
 void totp_usb_type_code_worker_notify(
     TotpUsbTypeCodeWorkerContext* context,
     TotpUsbTypeCodeWorkerContext* context,
-    TotpUsbTypeCodeWorkerEvent event) {
+    TotpUsbTypeCodeWorkerEvent event,
+    uint8_t flags) {
     furi_assert(context != NULL);
     furi_assert(context != NULL);
+    context->flags = flags;
     furi_thread_flags_set(furi_thread_get_id(context->thread), event);
     furi_thread_flags_set(furi_thread_get_id(context->thread), event);
 }
 }

+ 6 - 4
workers/usb_type_code/usb_type_code.h

@@ -9,15 +9,16 @@ typedef uint8_t TotpUsbTypeCodeWorkerEvent;
 typedef struct {
 typedef struct {
     char* string;
     char* string;
     uint8_t string_length;
     uint8_t string_length;
+    uint8_t flags;
     FuriThread* thread;
     FuriThread* thread;
     FuriMutex* string_sync;
     FuriMutex* string_sync;
     FuriHalUsbInterface* usb_mode_prev;
     FuriHalUsbInterface* usb_mode_prev;
 } TotpUsbTypeCodeWorkerContext;
 } TotpUsbTypeCodeWorkerContext;
 
 
 enum TotpUsbTypeCodeWorkerEvents {
 enum TotpUsbTypeCodeWorkerEvents {
-    TotpUsbTypeCodeWorkerEventReserved = (1 << 0),
-    TotpUsbTypeCodeWorkerEventStop = (1 << 1),
-    TotpUsbTypeCodeWorkerEventType = (1 << 2)
+    TotpUsbTypeCodeWorkerEventReserved = 0b00,
+    TotpUsbTypeCodeWorkerEventStop = 0b01,
+    TotpUsbTypeCodeWorkerEventType = 0b10
 };
 };
 
 
 TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
 TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
@@ -27,4 +28,5 @@ TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start(
 void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context);
 void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context);
 void totp_usb_type_code_worker_notify(
 void totp_usb_type_code_worker_notify(
     TotpUsbTypeCodeWorkerContext* context,
     TotpUsbTypeCodeWorkerContext* context,
-    TotpUsbTypeCodeWorkerEvent event);
+    TotpUsbTypeCodeWorkerEvent event,
+    uint8_t flags);