From 020928cbfba5344c76764b6743946c5483e3d25a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Aug 2023 04:31:55 +0300 Subject: [PATCH] move base pack here --- LICENSE | 21 + application.fam | 59 + cli/cli.c | 100 ++ cli/cli.h | 19 + cli/cli_helpers.c | 123 ++ cli/cli_helpers.h | 116 ++ cli/commands/add/add.c | 192 +++ cli/commands/add/add.h | 17 + cli/commands/automation/automation.c | 186 +++ cli/commands/automation/automation.h | 15 + cli/commands/delete/delete.c | 108 ++ cli/commands/delete/delete.h | 16 + cli/commands/details/details.c | 94 ++ cli/commands/details/details.h | 14 + cli/commands/help/help.c | 76 ++ cli/commands/help/help.h | 14 + cli/commands/list/list.c | 58 + cli/commands/list/list.h | 14 + cli/commands/move/move.c | 85 ++ cli/commands/move/move.h | 15 + cli/commands/notification/notification.c | 105 ++ cli/commands/notification/notification.h | 14 + cli/commands/pin/pin.c | 193 +++ cli/commands/pin/pin.h | 14 + cli/commands/reset/reset.c | 42 + cli/commands/reset/reset.h | 13 + cli/commands/timezone/timezone.c | 54 + cli/commands/timezone/timezone.h | 15 + cli/commands/update/update.c | 177 +++ cli/commands/update/update.h | 14 + cli/common_command_arguments.c | 141 ++ cli/common_command_arguments.h | 106 ++ config/app/config.h | 42 + config/wolfssl/config.h | 34 + images/DolphinCommon_56x48.png | Bin 0 -> 1416 bytes images/hid_ble_31x9.png | Bin 0 -> 173 bytes images/hid_usb_31x9.png | Bin 0 -> 174 bytes images/totp_arrow_left_8x9.png | Bin 0 -> 149 bytes images/totp_arrow_right_8x9.png | Bin 0 -> 149 bytes lib/base32/base32.c | 60 + lib/base32/base32.h | 40 + lib/base64/base64.c | 73 + lib/base64/base64.h | 14 + lib/fonts/712serif/712serif.c | 941 +++++++++++++ lib/fonts/712serif/712serif.h | 8 + lib/fonts/available_fonts.c | 23 + lib/fonts/available_fonts.h | 7 + lib/fonts/bedstead/bedstead.c | 1057 +++++++++++++++ lib/fonts/bedstead/bedstead.h | 8 + lib/fonts/dpcomic/dpcomic.c | 1115 ++++++++++++++++ lib/fonts/dpcomic/dpcomic.h | 7 + lib/fonts/font_info.h | 24 + lib/fonts/funclimbing/funclimbing.c | 1173 +++++++++++++++++ lib/fonts/funclimbing/funclimbing.h | 7 + lib/fonts/graph35pix/graph35pix.c | 941 +++++++++++++ lib/fonts/graph35pix/graph35pix.h | 8 + lib/fonts/karma_future/karma_future.c | 1173 +++++++++++++++++ lib/fonts/karma_future/karma_future.h | 8 + lib/fonts/mode_nine/mode_nine.c | 942 +++++++++++++ lib/fonts/mode_nine/mode_nine.h | 8 + lib/fonts/pixelflag/pixelflag.c | 1115 ++++++++++++++++ lib/fonts/pixelflag/pixelflag.h | 7 + lib/fonts/redhat_mono/redhat_mono.c | 1058 +++++++++++++++ lib/fonts/redhat_mono/redhat_mono.h | 8 + lib/fonts/zector/zector.c | 1057 +++++++++++++++ lib/fonts/zector/zector.h | 8 + lib/polyfills/memset_s.c | 22 + lib/polyfills/memset_s.h | 23 + lib/polyfills/strnlen.c | 11 + lib/polyfills/strnlen.h | 6 + lib/roll_value/roll_value.c | 28 + lib/roll_value/roll_value.h | 59 + lib/timezone_utils/timezone_utils.c | 16 + lib/timezone_utils/timezone_utils.h | 18 + lib/wolfssl | 1 + services/config/config.c | 728 ++++++++++ services/config/config.h | 104 ++ services/config/config_file_context.h | 3 + services/config/constants.h | 24 + services/config/migrations/common_migration.c | 199 +++ services/config/migrations/common_migration.h | 13 + services/config/token_info_iterator.c | 552 ++++++++ services/config/token_info_iterator.h | 123 ++ services/convert/convert.h | 4 + services/crypto/common_types.h | 23 + services/crypto/constants.h | 14 + services/crypto/crypto_facade.c | 118 ++ services/crypto/crypto_facade.h | 59 + services/crypto/crypto_v1.c | 145 ++ services/crypto/crypto_v1.h | 55 + services/crypto/crypto_v2.c | 190 +++ services/crypto/crypto_v2.h | 55 + services/crypto/crypto_v3.c | 195 +++ services/crypto/crypto_v3.h | 52 + services/crypto/polyfills.h | 14 + services/idle_timeout/idle_timeout.c | 66 + services/idle_timeout/idle_timeout.h | 44 + services/totp/totp.c | 125 ++ services/totp/totp.h | 55 + totp_10px.png | Bin 0 -> 496 bytes totp_app.c | 288 ++++ types/automation_kb_layout.h | 8 + types/automation_method.h | 13 + types/common.c | 3 + types/common.h | 3 + types/crypto_settings.h | 41 + types/event_type.h | 6 + types/notification_method.h | 9 + types/plugin_event.h | 10 + types/plugin_state.h | 93 ++ types/token_info.c | 207 +++ types/token_info.h | 272 ++++ types/user_pin_codes.h | 10 + ui/canvas_extensions.c | 33 + ui/canvas_extensions.h | 22 + ui/common_dialogs.c | 20 + ui/common_dialogs.h | 18 + ui/constants.h | 6 + ui/scene_director.c | 134 ++ ui/scene_director.h | 41 + ui/scenes/add_new_token/totp_input_text.c | 54 + ui/scenes/add_new_token/totp_input_text.h | 16 + .../add_new_token/totp_scene_add_new_token.c | 321 +++++ .../add_new_token/totp_scene_add_new_token.h | 15 + ui/scenes/app_settings/totp_app_settings.c | 373 ++++++ ui/scenes/app_settings/totp_app_settings.h | 12 + .../authenticate/totp_scene_authenticate.c | 166 +++ .../authenticate/totp_scene_authenticate.h | 12 + .../totp_scene_generate_token.c | 432 ++++++ .../totp_scene_generate_token.h | 12 + ui/scenes/standby/standby.c | 12 + ui/scenes/standby/standby.h | 5 + ui/scenes/token_menu/totp_scene_token_menu.c | 193 +++ ui/scenes/token_menu/totp_scene_token_menu.h | 10 + ui/totp_scenes_enum.h | 47 + ui/ui_controls.c | 138 ++ ui/ui_controls.h | 72 + version.h | 5 + workers/bt_type_code/bt_type_code.c | 235 ++++ workers/bt_type_code/bt_type_code.h | 82 ++ .../generate_totp_code/generate_totp_code.c | 199 +++ .../generate_totp_code/generate_totp_code.h | 86 ++ workers/type_code_common.c | 109 ++ workers/type_code_common.h | 23 + workers/usb_type_code/usb_type_code.c | 122 ++ workers/usb_type_code/usb_type_code.h | 62 + 146 files changed, 20390 insertions(+) create mode 100644 LICENSE create mode 100644 application.fam create mode 100644 cli/cli.c create mode 100644 cli/cli.h create mode 100644 cli/cli_helpers.c create mode 100644 cli/cli_helpers.h create mode 100644 cli/commands/add/add.c create mode 100644 cli/commands/add/add.h create mode 100644 cli/commands/automation/automation.c create mode 100644 cli/commands/automation/automation.h create mode 100644 cli/commands/delete/delete.c create mode 100644 cli/commands/delete/delete.h create mode 100644 cli/commands/details/details.c create mode 100644 cli/commands/details/details.h create mode 100644 cli/commands/help/help.c create mode 100644 cli/commands/help/help.h create mode 100644 cli/commands/list/list.c create mode 100644 cli/commands/list/list.h create mode 100644 cli/commands/move/move.c create mode 100644 cli/commands/move/move.h create mode 100644 cli/commands/notification/notification.c create mode 100644 cli/commands/notification/notification.h create mode 100644 cli/commands/pin/pin.c create mode 100644 cli/commands/pin/pin.h create mode 100644 cli/commands/reset/reset.c create mode 100644 cli/commands/reset/reset.h create mode 100644 cli/commands/timezone/timezone.c create mode 100644 cli/commands/timezone/timezone.h create mode 100644 cli/commands/update/update.c create mode 100644 cli/commands/update/update.h create mode 100644 cli/common_command_arguments.c create mode 100644 cli/common_command_arguments.h create mode 100644 config/app/config.h create mode 100644 config/wolfssl/config.h create mode 100644 images/DolphinCommon_56x48.png create mode 100644 images/hid_ble_31x9.png create mode 100644 images/hid_usb_31x9.png create mode 100644 images/totp_arrow_left_8x9.png create mode 100644 images/totp_arrow_right_8x9.png create mode 100644 lib/base32/base32.c create mode 100644 lib/base32/base32.h create mode 100644 lib/base64/base64.c create mode 100644 lib/base64/base64.h create mode 100644 lib/fonts/712serif/712serif.c create mode 100644 lib/fonts/712serif/712serif.h create mode 100644 lib/fonts/available_fonts.c create mode 100644 lib/fonts/available_fonts.h create mode 100644 lib/fonts/bedstead/bedstead.c create mode 100644 lib/fonts/bedstead/bedstead.h create mode 100644 lib/fonts/dpcomic/dpcomic.c create mode 100644 lib/fonts/dpcomic/dpcomic.h create mode 100644 lib/fonts/font_info.h create mode 100644 lib/fonts/funclimbing/funclimbing.c create mode 100644 lib/fonts/funclimbing/funclimbing.h create mode 100644 lib/fonts/graph35pix/graph35pix.c create mode 100644 lib/fonts/graph35pix/graph35pix.h create mode 100644 lib/fonts/karma_future/karma_future.c create mode 100644 lib/fonts/karma_future/karma_future.h create mode 100644 lib/fonts/mode_nine/mode_nine.c create mode 100644 lib/fonts/mode_nine/mode_nine.h create mode 100644 lib/fonts/pixelflag/pixelflag.c create mode 100644 lib/fonts/pixelflag/pixelflag.h create mode 100644 lib/fonts/redhat_mono/redhat_mono.c create mode 100644 lib/fonts/redhat_mono/redhat_mono.h create mode 100644 lib/fonts/zector/zector.c create mode 100644 lib/fonts/zector/zector.h create mode 100644 lib/polyfills/memset_s.c create mode 100644 lib/polyfills/memset_s.h create mode 100644 lib/polyfills/strnlen.c create mode 100644 lib/polyfills/strnlen.h create mode 100644 lib/roll_value/roll_value.c create mode 100644 lib/roll_value/roll_value.h create mode 100644 lib/timezone_utils/timezone_utils.c create mode 100644 lib/timezone_utils/timezone_utils.h create mode 160000 lib/wolfssl create mode 100644 services/config/config.c create mode 100644 services/config/config.h create mode 100644 services/config/config_file_context.h create mode 100644 services/config/constants.h create mode 100644 services/config/migrations/common_migration.c create mode 100644 services/config/migrations/common_migration.h create mode 100644 services/config/token_info_iterator.c create mode 100644 services/config/token_info_iterator.h create mode 100644 services/convert/convert.h create mode 100644 services/crypto/common_types.h create mode 100644 services/crypto/constants.h create mode 100644 services/crypto/crypto_facade.c create mode 100644 services/crypto/crypto_facade.h create mode 100644 services/crypto/crypto_v1.c create mode 100644 services/crypto/crypto_v1.h create mode 100644 services/crypto/crypto_v2.c create mode 100644 services/crypto/crypto_v2.h create mode 100644 services/crypto/crypto_v3.c create mode 100644 services/crypto/crypto_v3.h create mode 100644 services/crypto/polyfills.h create mode 100644 services/idle_timeout/idle_timeout.c create mode 100644 services/idle_timeout/idle_timeout.h create mode 100644 services/totp/totp.c create mode 100644 services/totp/totp.h create mode 100644 totp_10px.png create mode 100644 totp_app.c create mode 100644 types/automation_kb_layout.h create mode 100644 types/automation_method.h create mode 100644 types/common.c create mode 100644 types/common.h create mode 100644 types/crypto_settings.h create mode 100644 types/event_type.h create mode 100644 types/notification_method.h create mode 100644 types/plugin_event.h create mode 100644 types/plugin_state.h create mode 100644 types/token_info.c create mode 100644 types/token_info.h create mode 100644 types/user_pin_codes.h create mode 100644 ui/canvas_extensions.c create mode 100644 ui/canvas_extensions.h create mode 100644 ui/common_dialogs.c create mode 100644 ui/common_dialogs.h create mode 100644 ui/constants.h create mode 100644 ui/scene_director.c create mode 100644 ui/scene_director.h create mode 100644 ui/scenes/add_new_token/totp_input_text.c create mode 100644 ui/scenes/add_new_token/totp_input_text.h create mode 100644 ui/scenes/add_new_token/totp_scene_add_new_token.c create mode 100644 ui/scenes/add_new_token/totp_scene_add_new_token.h create mode 100644 ui/scenes/app_settings/totp_app_settings.c create mode 100644 ui/scenes/app_settings/totp_app_settings.h create mode 100644 ui/scenes/authenticate/totp_scene_authenticate.c create mode 100644 ui/scenes/authenticate/totp_scene_authenticate.h create mode 100644 ui/scenes/generate_token/totp_scene_generate_token.c create mode 100644 ui/scenes/generate_token/totp_scene_generate_token.h create mode 100644 ui/scenes/standby/standby.c create mode 100644 ui/scenes/standby/standby.h create mode 100644 ui/scenes/token_menu/totp_scene_token_menu.c create mode 100644 ui/scenes/token_menu/totp_scene_token_menu.h create mode 100644 ui/totp_scenes_enum.h create mode 100644 ui/ui_controls.c create mode 100644 ui/ui_controls.h create mode 100644 version.h create mode 100644 workers/bt_type_code/bt_type_code.c create mode 100644 workers/bt_type_code/bt_type_code.h create mode 100644 workers/generate_totp_code/generate_totp_code.c create mode 100644 workers/generate_totp_code/generate_totp_code.h create mode 100644 workers/type_code_common.c create mode 100644 workers/type_code_common.h create mode 100644 workers/usb_type_code/usb_type_code.c create mode 100644 workers/usb_type_code/usb_type_code.h diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000000..65504e7b1b3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Alexander Kopachov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/application.fam b/application.fam new file mode 100644 index 00000000000..439c9a8a186 --- /dev/null +++ b/application.fam @@ -0,0 +1,59 @@ +App( + appid="totp", + name="Authenticator", + apptype=FlipperAppType.EXTERNAL, + entry_point="totp_app", + cdefines=["APP_TOTP"], + requires=[ + "gui", + "cli", + "dialogs", + "storage", + "input", + "notification", + "bt" + ], + stack_size=2 * 1024, + order=20, + fap_version="4.01", + fap_author="Alexander Kopachov (@akopachov)", + fap_description="Software-based TOTP authenticator for Flipper Zero device", + fap_weburl="https://github.com/akopachov/flipper-zero_authenticator", + fap_category="Tools", + fap_icon_assets="images", + fap_icon="totp_10px.png", + fap_private_libs=[ + Lib( + name="base32", + ), + Lib( + name="base64", + ), + Lib( + name="timezone_utils", + ), + Lib( + name="polyfills", + ), + Lib( + name="roll_value", + ), + Lib( + name="fonts", + ), + Lib( + name="wolfssl", + sources=[ + "wolfcrypt/src/pwdbased.c", + "wolfcrypt/src/hmac.c", + "wolfcrypt/src/hash.c", + "wolfcrypt/src/sha.c", + "wolfcrypt/src/sha256.c", + "wolfcrypt/src/sha512.c" + ], + cflags=["-Wno-error"], + cdefines=["HAVE_CONFIG_H"], + cincludes=["config/wolfssl"] + ), + ], +) diff --git a/cli/cli.c b/cli/cli.c new file mode 100644 index 00000000000..4cb68ce8320 --- /dev/null +++ b/cli/cli.c @@ -0,0 +1,100 @@ +// Original idea: https://github.com/br0ziliy + +#include "cli.h" +#include +#include "cli_helpers.h" +#include "commands/list/list.h" +#include "commands/add/add.h" +#include "commands/update/update.h" +#include "commands/delete/delete.h" +#include "commands/timezone/timezone.h" +#include "commands/help/help.h" +#include "commands/move/move.h" +#include "commands/pin/pin.h" +#include "commands/notification/notification.h" +#include "commands/reset/reset.h" +#include "commands/automation/automation.h" +#include "commands/details/details.h" + +struct TotpCliContext { + PluginState* plugin_state; +}; + +static void totp_cli_print_unknown_command(const FuriString* unknown_command) { + TOTP_CLI_PRINTF_ERROR( + "Command \"%s\" is unknown. Use \"" TOTP_CLI_COMMAND_HELP + "\" command to get list of available commands.", + furi_string_get_cstr(unknown_command)); +} + +static void totp_cli_handler(Cli* cli, FuriString* args, void* context) { + TotpCliContext* cli_context = context; + PluginState* plugin_state = cli_context->plugin_state; + + FuriString* cmd = furi_string_alloc(); + + args_read_string_and_trim(args, cmd); + + if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_HELP_ALT2) == 0 || furi_string_empty(cmd)) { + totp_cli_command_help_handle(); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_ADD_ALT2) == 0) { + totp_cli_command_add_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_LIST_ALT) == 0) { + totp_cli_command_list_handle(plugin_state, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_DELETE_ALT) == 0) { + totp_cli_command_delete_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_TIMEZONE_ALT) == 0) { + totp_cli_command_timezone_handle(plugin_state, args, cli); + } else if( + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE) == 0 || + furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_MOVE_ALT) == 0) { + totp_cli_command_move_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_PIN) == 0) { + totp_cli_command_pin_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_NOTIFICATION) == 0) { + totp_cli_command_notification_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_AUTOMATION) == 0) { + totp_cli_command_automation_handle(plugin_state, args, cli); + } else if(furi_string_cmp_str(cmd, TOTP_CLI_COMMAND_RESET) == 0) { + totp_cli_command_reset_handle(plugin_state, cli); + } 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 { + totp_cli_print_unknown_command(cmd); + } + + furi_string_free(cmd); +} + +TotpCliContext* totp_cli_register_command_handler(PluginState* plugin_state) { + Cli* cli = furi_record_open(RECORD_CLI); + TotpCliContext* context = malloc(sizeof(TotpCliContext)); + furi_check(context != NULL); + context->plugin_state = plugin_state; + cli_add_command( + cli, TOTP_CLI_COMMAND_NAME, CliCommandFlagParallelSafe, totp_cli_handler, context); + furi_record_close(RECORD_CLI); + return context; +} + +void totp_cli_unregister_command_handler(TotpCliContext* context) { + Cli* cli = furi_record_open(RECORD_CLI); + cli_delete_command(cli, TOTP_CLI_COMMAND_NAME); + furi_record_close(RECORD_CLI); + free(context); +} \ No newline at end of file diff --git a/cli/cli.h b/cli/cli.h new file mode 100644 index 00000000000..13afef78ffb --- /dev/null +++ b/cli/cli.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +typedef struct TotpCliContext TotpCliContext; + +/** + * @brief Registers TOTP CLI handler + * @param plugin_state application state + * @return TOTP CLI context + */ +TotpCliContext* totp_cli_register_command_handler(PluginState* plugin_state); + +/** + * @brief Unregisters TOTP CLI handler + * @param context application state + */ +void totp_cli_unregister_command_handler(TotpCliContext* context); \ No newline at end of file diff --git a/cli/cli_helpers.c b/cli/cli_helpers.c new file mode 100644 index 00000000000..22691723720 --- /dev/null +++ b/cli/cli_helpers.c @@ -0,0 +1,123 @@ +#include "cli_helpers.h" +#include +#include +#include "../types/plugin_event.h" + +const char* TOTP_CLI_COLOR_ERROR = "91m"; +const char* TOTP_CLI_COLOR_WARNING = "93m"; +const char* TOTP_CLI_COLOR_SUCCESS = "92m"; +const char* TOTP_CLI_COLOR_INFO = "96m"; + +bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli) { + if(plugin_state->current_scene == TotpSceneAuthentication) { + TOTP_CLI_PRINTF("Pleases enter PIN on your flipper device\r\n"); + + while((plugin_state->current_scene == TotpSceneAuthentication || + plugin_state->current_scene == TotpSceneNone) && + !cli_cmd_interrupt_received(cli)) { + furi_delay_ms(100); + } + + totp_cli_delete_last_line(); + + if(plugin_state->current_scene == TotpSceneAuthentication || //-V560 + plugin_state->current_scene == TotpSceneNone) { //-V560 + TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); + return false; + } + } + + return true; +} + +void totp_cli_force_close_app(FuriMessageQueue* event_queue) { + PluginEvent event = {.type = EventForceCloseApp}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +bool totp_cli_read_line(Cli* cli, FuriString* out_str, bool mask_user_input) { + uint8_t c; + while(cli_read(cli, &c, 1) == 1) { + if(c == CliSymbolAsciiEsc) { + // Some keys generating escape-sequences + // We need to ignore them as we care about alpha-numerics only + uint8_t c2; + cli_read_timeout(cli, &c2, 1, 0); + cli_read_timeout(cli, &c2, 1, 0); + } else if(c == CliSymbolAsciiETX) { + cli_nl(); + return false; + } else if( + (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + c == '/' || c == '=' || c == '+') { + if(mask_user_input) { + putc('*', stdout); + } else { + putc(c, stdout); + } + fflush(stdout); + furi_string_push_back(out_str, c); + } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { + size_t out_str_size = furi_string_size(out_str); + if(out_str_size > 0) { + totp_cli_delete_last_char(); + furi_string_left(out_str, out_str_size - 1); + } + } else if(c == CliSymbolAsciiCR) { + cli_nl(); + break; + } + } + + 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); +} + +void totp_cli_print_invalid_arguments() { + TOTP_CLI_PRINTF_ERROR( + "Invalid command arguments. use \"help\" command to get list of available commands"); +} + +void totp_cli_print_error_updating_config_file() { + TOTP_CLI_PRINTF_ERROR("An error has occurred during updating config file\r\n"); +} + +void totp_cli_print_error_loading_token_info() { + TOTP_CLI_PRINTF_ERROR("An error has occurred during loading token information\r\n"); +} + +void totp_cli_print_processing() { + TOTP_CLI_PRINTF("Processing, please wait...\r\n"); +} + +void totp_cli_delete_last_char() { + TOTP_CLI_PRINTF("\b \b"); + fflush(stdout); +} + +void totp_cli_delete_current_line() { + TOTP_CLI_PRINTF("\33[2K\r"); + fflush(stdout); +} + +void totp_cli_delete_last_line() { + TOTP_CLI_PRINTF("\033[A\33[2K\r"); + fflush(stdout); +} diff --git a/cli/cli_helpers.h b/cli/cli_helpers.h new file mode 100644 index 00000000000..9c40bff13ff --- /dev/null +++ b/cli/cli_helpers.h @@ -0,0 +1,116 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_NAME "totp" + +#define DOCOPT_ARGUMENT(arg) "<" arg ">" +#define DOCOPT_MULTIPLE(arg) arg "..." +#define DOCOPT_OPTIONAL(param) "[" param "]" +#define DOCOPT_REQUIRED(param) "(" param ")" +#define DOCOPT_OPTION(option, value) option " " value +#define DOCOPT_SWITCH(option) option +#define DOCOPT_OPTIONS "[options]" +#define DOCOPT_DEFAULT(val) "[default: " val "]" + +extern const char* TOTP_CLI_COLOR_ERROR; +extern const char* TOTP_CLI_COLOR_WARNING; +extern const char* TOTP_CLI_COLOR_SUCCESS; +extern const char* TOTP_CLI_COLOR_INFO; + +#define TOTP_CLI_PRINTF(format, ...) printf(format, ##__VA_ARGS__) + +#define TOTP_CLI_PRINTF_COLORFUL(color, format, ...) \ + TOTP_CLI_PRINTF("\e[%s" format "\e[0m", color, ##__VA_ARGS__) + +#define TOTP_CLI_PRINTF_ERROR(format, ...) \ + TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_ERROR, format, ##__VA_ARGS__) +#define TOTP_CLI_PRINTF_WARNING(format, ...) \ + TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_WARNING, format, ##__VA_ARGS__) +#define TOTP_CLI_PRINTF_SUCCESS(format, ...) \ + TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_SUCCESS, format, ##__VA_ARGS__) +#define TOTP_CLI_PRINTF_INFO(format, ...) \ + TOTP_CLI_PRINTF_COLORFUL(TOTP_CLI_COLOR_INFO, format, ##__VA_ARGS__) + +#define TOTP_CLI_LOCK_UI(plugin_state) \ + Scene __previous_scene = plugin_state->current_scene; \ + totp_scene_director_activate_scene(plugin_state, TotpSceneStandby); \ + totp_scene_director_force_redraw(plugin_state) + +#define TOTP_CLI_UNLOCK_UI(plugin_state) \ + totp_scene_director_activate_scene(plugin_state, __previous_scene); \ + totp_scene_director_force_redraw(plugin_state) + +/** + * @brief Checks whether user is authenticated and entered correct PIN. + * If user is not authenticated it prompts user to enter correct PIN to authenticate. + * @param plugin_state application state + * @param cli pointer to the firmware CLI subsystem + * @return \c true if user is already authenticated or successfully authenticated; \c false otherwise + */ +bool totp_cli_ensure_authenticated(const PluginState* plugin_state, Cli* cli); + +/** + * @brief Forces application to be instantly closed + * @param event_queue main app queue + */ +void totp_cli_force_close_app(FuriMessageQueue* event_queue); + +/** + * @brief Reads line of characters from console + * @param cli pointer to the firmware CLI subsystem + * @param out_str pointer to an output string to put read line to + * @param mask_user_input whether to mask input characters in console or not + * @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); + +/** + * @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); + +/** + * @brief Deletes last printed line in console + */ +void totp_cli_delete_last_line(); + +/** + * @brief Deletes current printed line in console + */ +void totp_cli_delete_current_line(); + +/** + * @brief Deletes last printed char in console + */ +void totp_cli_delete_last_char(); + +/** + * @brief Prints error message about invalid command arguments + */ +void totp_cli_print_invalid_arguments(); + +/** + * @brief Prints error message about config file update error + */ +void totp_cli_print_error_updating_config_file(); + +/** + * @brief Prints error message about config file loading error + */ +void totp_cli_print_error_loading_token_info(); + +/** + * @brief Prints message to let user know that command is processing now + */ +void totp_cli_print_processing(); \ No newline at end of file diff --git a/cli/commands/add/add.c b/cli/commands/add/add.c new file mode 100644 index 00000000000..5e5435eecde --- /dev/null +++ b/cli/commands/add/add.c @@ -0,0 +1,192 @@ +#include "add.h" +#include +#include +#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" + +struct TotpAddContext { + FuriString* args; + Cli* cli; + const CryptoSettings* crypto_settings; +}; + +enum TotpIteratorUpdateTokenResultsEx { + TotpIteratorUpdateTokenResultInvalidSecret = 1, + TotpIteratorUpdateTokenResultCancelled = 2, + TotpIteratorUpdateTokenResultInvalidArguments = 3 +}; + +static TotpIteratorUpdateTokenResult + add_token_handler(TokenInfo* token_info, const void* context) { + const struct TotpAddContext* context_t = context; + + // Reading token name + if(!args_read_probably_quoted_string_and_trim(context_t->args, token_info->name)) { + return TotpIteratorUpdateTokenResultInvalidArguments; + } + + FuriString* temp_str = furi_string_alloc(); + + // Read optional arguments + bool mask_user_input = true; + PlainTokenSecretEncoding token_secret_encoding = PlainTokenSecretEncodingBase32; + while(args_read_string_and_trim(context_t->args, temp_str)) { + bool parsed = false; + if(!totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && + !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, context_t->args, &parsed, &token_secret_encoding)) { + totp_cli_printf_unknown_argument(temp_str); + } + + if(!parsed) { + furi_string_free(temp_str); + return TotpIteratorUpdateTokenResultInvalidArguments; + } + } + + // Reading token secret + furi_string_reset(temp_str); + TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]:\r\n"); + if(!totp_cli_read_line(context_t->cli, temp_str, mask_user_input)) { + totp_cli_delete_last_line(); + furi_string_secure_free(temp_str); + return TotpIteratorUpdateTokenResultCancelled; + } + + totp_cli_delete_last_line(); + + bool secret_set = token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + context_t->crypto_settings); + + furi_string_secure_free(temp_str); + + if(!secret_set) { + return TotpIteratorUpdateTokenResultInvalidSecret; + } + + return TotpIteratorUpdateTokenResultSuccess; +} + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_add_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ADD ", " TOTP_CLI_COMMAND_ADD_ALT + ", " TOTP_CLI_COMMAND_ADD_ALT2 " Add new token\r\n"); +} + +void totp_cli_command_add_docopt_usage() { + TOTP_CLI_PRINTF( + " " 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_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_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " 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_add_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ARG_NAME " Token name\r\n"); +} + +void totp_cli_command_add_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_ALGO)) " Token hashing algorithm. Must be one of: " TOKEN_HASH_ALGO_SHA1_NAME + ", " TOKEN_HASH_ALGO_SHA256_NAME + ", " TOKEN_HASH_ALGO_SHA512_NAME + ", " TOKEN_HASH_ALGO_STEAM_NAME + " " DOCOPT_DEFAULT(TOKEN_HASH_ALGO_SHA1_NAME) "\r\n"); + TOTP_CLI_PRINTF( + " " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_DIGITS)) " Number of digits to generate, one of: %" PRIu8 + ", %" PRIu8 ", %" PRIu8 + " " DOCOPT_DEFAULT("%" PRIu8) "\r\n", + TokenDigitsCountFive, + TokenDigitsCountSix, + TokenDigitsCountEight, + TokenDigitsCountSix); + + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_SECRET_ENCODING)) " Token secret encoding, one of " PLAIN_TOKEN_ENCODING_BASE32_NAME + ", " PLAIN_TOKEN_ENCODING_BASE64_NAME + " " DOCOPT_DEFAULT( + PLAIN_TOKEN_ENCODING_BASE32_NAME) "\r\n"); + + TOTP_CLI_PRINTF( + " " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_DURATION)) " Token lifetime duration in seconds, between: %" PRIu8 + " and %" PRIu8 + " " DOCOPT_DEFAULT("%" PRIu8) "\r\n", + TokenDurationMin, + TokenDurationMax, + TokenDurationDefault); + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + 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: " TOKEN_AUTOMATION_FEATURE_NONE_NAME + ", " TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME + ", " TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME + " " DOCOPT_DEFAULT( + TOKEN_AUTOMATION_FEATURE_NONE_NAME) "\r\n"); + TOTP_CLI_PRINTF(" # " TOKEN_AUTOMATION_FEATURE_NONE_NAME " - No features\r\n"); + TOTP_CLI_PRINTF(" # " TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME + " - Type key at the end of token input automation\r\n"); + TOTP_CLI_PRINTF(" # " TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME + " - Type key at the end of token input automation\r\n"); + TOTP_CLI_PRINTF(" # " TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME + " - Type slower\r\n"); +} +#endif + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + + TOTP_CLI_LOCK_UI(plugin_state); + + struct TotpAddContext add_context = { + .args = args, .cli = cli, .crypto_settings = &plugin_state->crypto_settings}; + TotpIteratorUpdateTokenResult add_result = + totp_token_info_iterator_add_new_token(iterator_context, &add_token_handler, &add_context); + + if(add_result == TotpIteratorUpdateTokenResultSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully added\r\n", + furi_string_get_cstr( + totp_token_info_iterator_get_current_token(iterator_context)->name)); + } else if(add_result == TotpIteratorUpdateTokenResultCancelled) { + TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); + } else if(add_result == TotpIteratorUpdateTokenResultInvalidArguments) { + totp_cli_print_invalid_arguments(); + } else if(add_result == TotpIteratorUpdateTokenResultInvalidSecret) { + TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); + } else if(add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) { + totp_cli_print_error_updating_config_file(); + } + + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/add/add.h b/cli/commands/add/add.h new file mode 100644 index 00000000000..ac2006c1295 --- /dev/null +++ b/cli/commands/add/add.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include "../../../config/app/config.h" +#include "../../../types/plugin_state.h" + +#define TOTP_CLI_COMMAND_ADD "add" +#define TOTP_CLI_COMMAND_ADD_ALT "mk" +#define TOTP_CLI_COMMAND_ADD_ALT2 "new" + +void totp_cli_command_add_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_add_docopt_commands(); +void totp_cli_command_add_docopt_usage(); +void totp_cli_command_add_docopt_arguments(); +void totp_cli_command_add_docopt_options(); +#endif \ No newline at end of file diff --git a/cli/commands/automation/automation.c b/cli/commands/automation/automation.c new file mode 100644 index 00000000000..113393130e5 --- /dev/null +++ b/cli/commands/automation/automation.c @@ -0,0 +1,186 @@ +#include "automation.h" +#include +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD "automation" +#define TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE "none" +#define TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB "usb" +#ifdef TOTP_BADBT_AUTOMATION_ENABLED +#define TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT "bt" +#endif +#define TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_QWERTY "QWERTY" +#define TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_AZERTY "AZERTY" +#define TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT_PREFIX "-k" +#define TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT "layout" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_automation_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_AUTOMATION " Get or set automation settings\r\n"); +} + +void totp_cli_command_automation_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_AUTOMATION " " DOCOPT_OPTIONAL(DOCOPT_OPTION( + TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT))) " " DOCOPT_OPTIONAL(DOCOPT_MULTIPLE(DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD))) "\r\n"); +} + +void totp_cli_command_automation_docopt_arguments() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_AUTOMATION_ARG_METHOD + " Automation method to be set. Must be one of: " TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE + ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + ", " TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT +#endif + "\r\n"); +} + +void totp_cli_command_automation_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT)) " Automation keyboard layout. Must be one of: " TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_QWERTY + ", " TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_AZERTY + "\r\n"); +} +#endif + +static void print_method(AutomationMethod method, const char* color) { +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + bool has_previous_method = false; +#endif + if(method & AutomationMethodBadUsb) { + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB "\""); +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + has_previous_method = true; +#endif + } + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if(method & AutomationMethodBadBt) { + if(has_previous_method) { + TOTP_CLI_PRINTF_COLORFUL(color, " and "); + } + + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT "\""); + } +#endif + + if(method == AutomationMethodNone) { + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE "\""); + } +} + +static void print_kb_layout(AutomationKeyboardLayout layout, const char* color) { + char* layoutToPrint; + switch(layout) { + case AutomationKeyboardLayoutQWERTY: + layoutToPrint = TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_QWERTY; + break; + case AutomationKeyboardLayoutAZERTY: + layoutToPrint = TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_AZERTY; + break; + default: + furi_crash("Unknown automation keyboard layout"); + break; + } + + TOTP_CLI_PRINTF_COLORFUL(color, "%s", layoutToPrint); +} + +static bool + parse_automation_keyboard_layout(const FuriString* str, AutomationKeyboardLayout* out) { + bool result = true; + if(furi_string_cmpi_str(str, TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_QWERTY) == 0) { + *out = AutomationKeyboardLayoutQWERTY; + } else if(furi_string_cmpi_str(str, TOTP_CLI_COMMAND_AUTOMATION_LAYOUT_AZERTY) == 0) { + *out = AutomationKeyboardLayoutAZERTY; + } else { + result = false; + } + + return result; +} + +void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool new_method_provided = false; + AutomationMethod new_method = AutomationMethodNone; + AutomationKeyboardLayout new_kb_layout = plugin_state->automation_kb_layout; + bool args_valid = true; + while(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_AUTOMATION_METHOD_NONE) == 0) { + new_method_provided = true; + new_method = AutomationMethodNone; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_AUTOMATION_METHOD_USB) == 0) { + new_method_provided = true; + new_method |= AutomationMethodBadUsb; + } +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_AUTOMATION_METHOD_BT) == 0) { + new_method_provided = true; + new_method |= AutomationMethodBadBt; + } +#endif + else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_AUTOMATION_ARG_KB_LAYOUT_PREFIX) == 0) { + if(!args_read_string_and_trim(args, temp_str) || + !parse_automation_keyboard_layout(temp_str, &new_kb_layout)) { + args_valid = false; + break; + } + } else { + args_valid = false; + break; + } + } + + do { + if(!args_valid) { + totp_cli_print_invalid_arguments(); + break; + } + + if(new_method_provided) { + TOTP_CLI_LOCK_UI(plugin_state); + + plugin_state->automation_method = new_method; + plugin_state->automation_kb_layout = new_kb_layout; + if(totp_config_file_update_automation_method(plugin_state)) { + TOTP_CLI_PRINTF_SUCCESS("Automation method is set to "); + print_method(new_method, TOTP_CLI_COLOR_SUCCESS); + TOTP_CLI_PRINTF_SUCCESS(" ("); + print_kb_layout(plugin_state->automation_kb_layout, TOTP_CLI_COLOR_SUCCESS); + TOTP_CLI_PRINTF_SUCCESS(")"); + cli_nl(); + } else { + totp_cli_print_error_updating_config_file(); + } + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if(!(new_method & AutomationMethodBadBt) && + plugin_state->bt_type_code_worker_context != NULL) { + totp_bt_type_code_worker_free(plugin_state->bt_type_code_worker_context); + plugin_state->bt_type_code_worker_context = NULL; + } +#endif + + TOTP_CLI_UNLOCK_UI(plugin_state); + } else { + TOTP_CLI_PRINTF_INFO("Current automation method is "); + print_method(plugin_state->automation_method, TOTP_CLI_COLOR_INFO); + TOTP_CLI_PRINTF_INFO(" ("); + print_kb_layout(plugin_state->automation_kb_layout, TOTP_CLI_COLOR_INFO); + TOTP_CLI_PRINTF_INFO(")"); + cli_nl(); + } + } while(false); + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/cli/commands/automation/automation.h b/cli/commands/automation/automation.h new file mode 100644 index 00000000000..522bfc5600a --- /dev/null +++ b/cli/commands/automation/automation.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_AUTOMATION "automation" + +void totp_cli_command_automation_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_automation_docopt_commands(); +void totp_cli_command_automation_docopt_usage(); +void totp_cli_command_automation_docopt_arguments(); +void totp_cli_command_automation_docopt_options(); +#endif \ No newline at end of file diff --git a/cli/commands/delete/delete.c b/cli/commands/delete/delete.c new file mode 100644 index 00000000000..d9cea4803f9 --- /dev/null +++ b/cli/commands/delete/delete.c @@ -0,0 +1,108 @@ +#include "delete.h" + +#include +#include +#include +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" +#include "../../common_command_arguments.h" + +#define TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX "-f" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_delete_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_DELETE ", " TOTP_CLI_COMMAND_DELETE_ALT + " Delete existing token\r\n"); +} + +void totp_cli_command_delete_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_DELETE " | " TOTP_CLI_COMMAND_DELETE_ALT) " " DOCOPT_ARGUMENT( + 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() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_ARG_INDEX " Token index in the list\r\n"); +} + +void totp_cli_command_delete_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) " Force command to do not ask user for interactive confirmation\r\n"); +} +#endif + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + + int token_number; + if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 || + (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) { + totp_cli_print_invalid_arguments(); + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool confirm_needed = true; + if(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_DELETE_ARG_FORCE_PREFIX) == 0) { + confirm_needed = false; + } else { + totp_cli_printf_unknown_argument(temp_str); + totp_cli_print_invalid_arguments(); + furi_string_free(temp_str); + return; + } + } + furi_string_free(temp_str); + + TOTP_CLI_LOCK_UI(plugin_state); + + size_t original_token_index = + totp_token_info_iterator_get_current_token_index(iterator_context); + totp_token_info_iterator_go_to(iterator_context, token_number - 1); + const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context); + const char* token_info_name = furi_string_get_cstr(token_info->name); + + bool confirmed = !confirm_needed; + if(confirm_needed) { + TOTP_CLI_PRINTF_WARNING("WARNING!\r\n"); + TOTP_CLI_PRINTF_WARNING( + "TOKEN \"%s\" WILL BE PERMANENTLY DELETED WITHOUT ABILITY TO RECOVER IT.\r\n", + token_info_name); + TOTP_CLI_PRINTF_WARNING("Confirm? [y/n]\r\n"); + fflush(stdout); + char user_pick; + do { + user_pick = tolower(cli_getc(cli)); + } while(user_pick != 'y' && user_pick != 'n' && user_pick != CliSymbolAsciiCR && + user_pick != CliSymbolAsciiETX && user_pick != CliSymbolAsciiEsc); + + confirmed = user_pick == 'y' || user_pick == CliSymbolAsciiCR; + } + + if(confirmed) { + totp_cli_print_processing(); + if(totp_token_info_iterator_remove_current_token_info(iterator_context)) { + totp_cli_delete_last_line(); + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully deleted\r\n", token_info_name); + totp_token_info_iterator_go_to(iterator_context, 0); + } else { + totp_cli_delete_last_line(); + totp_cli_print_error_updating_config_file(); + totp_token_info_iterator_go_to(iterator_context, original_token_index); + } + } else { + TOTP_CLI_PRINTF_INFO("User has not confirmed\r\n"); + totp_token_info_iterator_go_to(iterator_context, original_token_index); + } + + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/delete/delete.h b/cli/commands/delete/delete.h new file mode 100644 index 00000000000..98b15e59557 --- /dev/null +++ b/cli/commands/delete/delete.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_DELETE "delete" +#define TOTP_CLI_COMMAND_DELETE_ALT "rm" + +void totp_cli_command_delete_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_delete_docopt_commands(); +void totp_cli_command_delete_docopt_usage(); +void totp_cli_command_delete_docopt_arguments(); +void totp_cli_command_delete_docopt_options(); +#endif diff --git a/cli/commands/details/details.c b/cli/commands/details/details.c new file mode 100644 index 00000000000..7e714bf6995 --- /dev/null +++ b/cli/commands/details/details.c @@ -0,0 +1,94 @@ +#include "details.h" +#include +#include +#include "../../../types/token_info.h" +#include "../../../services/config/constants.h" +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" +#include "../../common_command_arguments.h" + +#define TOTP_CLI_PRINTF_AUTOMATION_FEATURE(description, header_printed) \ + do { \ + TOTP_CLI_PRINTF( \ + "| %-20s | %-28.28s |\r\n", \ + header_printed ? "" : "Automation features", \ + description); \ + header_printed = true; \ + } while(false) + +static void print_automation_features(const TokenInfo* token_info) { + if(token_info->automation_features == TokenAutomationFeatureNone) { + TOTP_CLI_PRINTF("| %-20s | %-28.28s |\r\n", "Automation features", "None"); + return; + } + + bool header_printed = false; + if(token_info->automation_features & TokenAutomationFeatureEnterAtTheEnd) { + TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed); + } + + if(token_info->automation_features & TokenAutomationFeatureTabAtTheEnd) { + TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type key at the end", header_printed); + } + + if(token_info->automation_features & TokenAutomationFeatureTypeSlower) { + TOTP_CLI_PRINTF_AUTOMATION_FEATURE("Type slower", header_printed); + } +} + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +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"); +} +#endif + +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; + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 || + (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) { + totp_cli_print_invalid_arguments(); + return; + } + + TOTP_CLI_LOCK_UI(plugin_state); + + size_t original_token_index = + totp_token_info_iterator_get_current_token_index(iterator_context); + if(totp_token_info_iterator_go_to(iterator_context, token_number - 1)) { + const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context); + + TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); + TOTP_CLI_PRINTF("| %-20s | %-28s |\r\n", "Property", "Value"); + TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); + TOTP_CLI_PRINTF("| %-20s | %-28d |\r\n", "Index", token_number); + TOTP_CLI_PRINTF( + "| %-20s | %-28.28s |\r\n", "Name", furi_string_get_cstr(token_info->name)); + TOTP_CLI_PRINTF( + "| %-20s | %-28s |\r\n", "Hashing algorithm", token_info_get_algo_as_cstr(token_info)); + TOTP_CLI_PRINTF("| %-20s | %-28" PRIu8 " |\r\n", "Number of digits", token_info->digits); + TOTP_CLI_PRINTF( + "| %-20s | %" PRIu8 " sec.%-21s |\r\n", "Token lifetime", token_info->duration, " "); + print_automation_features(token_info); + TOTP_CLI_PRINTF("+----------------------+------------------------------+\r\n"); + } else { + totp_cli_print_error_loading_token_info(); + } + + totp_token_info_iterator_go_to(iterator_context, original_token_index); + + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/details/details.h b/cli/commands/details/details.h new file mode 100644 index 00000000000..aec1a0184f1 --- /dev/null +++ b/cli/commands/details/details.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.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); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_details_docopt_commands(); +void totp_cli_command_details_docopt_usage(); +#endif diff --git a/cli/commands/help/help.c b/cli/commands/help/help.c new file mode 100644 index 00000000000..e5519d0436f --- /dev/null +++ b/cli/commands/help/help.c @@ -0,0 +1,76 @@ +#include "help.h" +#include "../../cli_helpers.h" +#include "../add/add.h" +#include "../update/update.h" +#include "../delete/delete.h" +#include "../list/list.h" +#include "../timezone/timezone.h" +#include "../move/move.h" +#include "../pin/pin.h" +#include "../notification/notification.h" +#include "../reset/reset.h" +#include "../automation/automation.h" +#include "../details/details.h" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_help_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_HELP ", " TOTP_CLI_COMMAND_HELP_ALT + ", " TOTP_CLI_COMMAND_HELP_ALT2 " Show command usage help\r\n"); +} + +void totp_cli_command_help_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_HELP " | " TOTP_CLI_COMMAND_HELP_ALT + " | " TOTP_CLI_COMMAND_HELP_ALT2) "\r\n"); +} +#endif + +void totp_cli_command_help_handle() { +#ifdef TOTP_CLI_RICH_HELP_ENABLED + TOTP_CLI_PRINTF("Usage:\r\n"); + totp_cli_command_help_docopt_usage(); + totp_cli_command_list_docopt_usage(); + totp_cli_command_details_docopt_usage(); + totp_cli_command_add_docopt_usage(); + totp_cli_command_update_docopt_usage(); + totp_cli_command_delete_docopt_usage(); + totp_cli_command_timezone_docopt_usage(); + totp_cli_command_move_docopt_usage(); + totp_cli_command_pin_docopt_usage(); + totp_cli_command_notification_docopt_usage(); + totp_cli_command_reset_docopt_usage(); + totp_cli_command_automation_docopt_usage(); + cli_nl(); + TOTP_CLI_PRINTF("Commands:\r\n"); + totp_cli_command_help_docopt_commands(); + totp_cli_command_list_docopt_commands(); + totp_cli_command_details_docopt_commands(); + totp_cli_command_add_docopt_commands(); + totp_cli_command_update_docopt_commands(); + totp_cli_command_delete_docopt_commands(); + totp_cli_command_timezone_docopt_commands(); + totp_cli_command_move_docopt_commands(); + totp_cli_command_pin_docopt_commands(); + totp_cli_command_notification_docopt_commands(); + totp_cli_command_reset_docopt_commands(); + totp_cli_command_automation_docopt_commands(); + cli_nl(); + TOTP_CLI_PRINTF("Arguments:\r\n"); + totp_cli_command_add_docopt_arguments(); + totp_cli_command_delete_docopt_arguments(); + totp_cli_command_move_docopt_arguments(); + totp_cli_command_timezone_docopt_arguments(); + totp_cli_command_notification_docopt_arguments(); + totp_cli_command_automation_docopt_arguments(); + cli_nl(); + TOTP_CLI_PRINTF("Options:\r\n"); + totp_cli_command_add_docopt_options(); + totp_cli_command_update_docopt_options(); + totp_cli_command_delete_docopt_options(); + totp_cli_command_pin_docopt_options(); + totp_cli_command_automation_docopt_options(); +#else + TOTP_CLI_PRINTF( + "All the TOTP CLI commands, their arguments, options and usage can be found here https://t.ly/_6pJG"); +#endif +} \ No newline at end of file diff --git a/cli/commands/help/help.h b/cli/commands/help/help.h new file mode 100644 index 00000000000..da7c2fd62ae --- /dev/null +++ b/cli/commands/help/help.h @@ -0,0 +1,14 @@ +#pragma once + +#include "../../../config/app/config.h" +#include + +#define TOTP_CLI_COMMAND_HELP "help" +#define TOTP_CLI_COMMAND_HELP_ALT "h" +#define TOTP_CLI_COMMAND_HELP_ALT2 "?" + +void totp_cli_command_help_handle(); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_help_docopt_commands(); +void totp_cli_command_help_docopt_usage(); +#endif \ No newline at end of file diff --git a/cli/commands/list/list.c b/cli/commands/list/list.c new file mode 100644 index 00000000000..d5c97c6b06d --- /dev/null +++ b/cli/commands/list/list.c @@ -0,0 +1,58 @@ +#include "list.h" +#include +#include "../../../types/token_info.h" +#include "../../../services/config/constants.h" +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_list_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_LIST ", " TOTP_CLI_COMMAND_LIST_ALT + " List all available tokens\r\n"); +} + +void totp_cli_command_list_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " DOCOPT_REQUIRED( + TOTP_CLI_COMMAND_LIST " | " TOTP_CLI_COMMAND_LIST_ALT) "\r\n"); +} +#endif + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + size_t total_count = totp_token_info_iterator_get_total_count(iterator_context); + if(total_count <= 0) { + TOTP_CLI_PRINTF("There are no tokens"); + return; + } + + TOTP_CLI_LOCK_UI(plugin_state); + + size_t original_index = totp_token_info_iterator_get_current_token_index(iterator_context); + + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); + TOTP_CLI_PRINTF("| %-3s | %-25s | %-6s | %-s | %-s |\r\n", "#", "Name", "Algo", "Ln", "Dur"); + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); + for(size_t i = 0; i < total_count; i++) { + totp_token_info_iterator_go_to(iterator_context, i); + const TokenInfo* token_info = totp_token_info_iterator_get_current_token(iterator_context); + TOTP_CLI_PRINTF( + "| %-3" PRIu16 " | %-25.25s | %-6s | %-2" PRIu8 " | %-3" PRIu8 " |\r\n", + i + 1, + furi_string_get_cstr(token_info->name), + token_info_get_algo_as_cstr(token_info), + token_info->digits, + token_info->duration); + } + + TOTP_CLI_PRINTF("+-----+---------------------------+--------+----+-----+\r\n"); + + totp_token_info_iterator_go_to(iterator_context, original_index); + + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/list/list.h b/cli/commands/list/list.h new file mode 100644 index 00000000000..8237bfd2bdd --- /dev/null +++ b/cli/commands/list/list.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_LIST "list" +#define TOTP_CLI_COMMAND_LIST_ALT "ls" + +void totp_cli_command_list_handle(PluginState* plugin_state, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_list_docopt_commands(); +void totp_cli_command_list_docopt_usage(); +#endif diff --git a/cli/commands/move/move.c b/cli/commands/move/move.c new file mode 100644 index 00000000000..fbf82a3bba1 --- /dev/null +++ b/cli/commands/move/move.c @@ -0,0 +1,85 @@ +#include "move.h" + +#include +#include +#include "../../../types/token_info.h" +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" +#include "../../common_command_arguments.h" + +#define TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX "new_index" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_move_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE ", " TOTP_CLI_COMMAND_MOVE_ALT + " Move token\r\n"); +} + +void totp_cli_command_move_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_MOVE " | " TOTP_CLI_COMMAND_MOVE_ALT) " " DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX) "\r\n"); +} + +void totp_cli_command_move_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_MOVE_ARG_NEW_INDEX + " New token index in the list\r\n"); +} +#endif + +void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + int token_number; + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + size_t total_count = totp_token_info_iterator_get_total_count(iterator_context); + if(!args_read_int_and_trim(args, &token_number) || token_number < 1 || + (size_t)token_number > total_count) { + totp_cli_print_invalid_arguments(); + return; + } + + int new_token_number = 0; + + if(!args_read_int_and_trim(args, &new_token_number) || new_token_number < 1 || + (size_t)new_token_number > total_count) { + totp_cli_print_invalid_arguments(); + return; + } + + if(token_number == new_token_number) { + TOTP_CLI_PRINTF_ERROR("New token number matches current token number\r\n"); + return; + } + + TOTP_CLI_LOCK_UI(plugin_state); + + size_t token_index = token_number - 1; + size_t new_token_index = new_token_number - 1; + + size_t original_token_index = + totp_token_info_iterator_get_current_token_index(iterator_context); + + totp_cli_print_processing(); + + if(totp_token_info_iterator_go_to(iterator_context, token_index) && + totp_token_info_iterator_move_current_token_info(iterator_context, new_token_index)) { + totp_cli_delete_last_line(); + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully updated\r\n", + furi_string_get_cstr( + totp_token_info_iterator_get_current_token(iterator_context)->name)); + } else { + totp_cli_delete_last_line(); + totp_cli_print_error_updating_config_file(); + } + + totp_token_info_iterator_go_to(iterator_context, original_token_index); + + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/move/move.h b/cli/commands/move/move.h new file mode 100644 index 00000000000..0dfd90f6bfb --- /dev/null +++ b/cli/commands/move/move.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_MOVE "move" +#define TOTP_CLI_COMMAND_MOVE_ALT "mv" + +void totp_cli_command_move_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_move_docopt_commands(); +void totp_cli_command_move_docopt_usage(); +void totp_cli_command_move_docopt_arguments(); +#endif diff --git a/cli/commands/notification/notification.c b/cli/commands/notification/notification.c new file mode 100644 index 00000000000..d1814fb26e7 --- /dev/null +++ b/cli/commands/notification/notification.c @@ -0,0 +1,105 @@ +#include "notification.h" +#include +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD "notification" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "none" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "sound" +#define TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "vibro" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_notification_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NOTIFICATION + " Get or set notification method\r\n"); +} + +void totp_cli_command_notification_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_NOTIFICATION " " DOCOPT_OPTIONAL( + DOCOPT_MULTIPLE(DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD))) "\r\n"); +} + +void totp_cli_command_notification_docopt_arguments() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NOTIFICATION_ARG_METHOD + " 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_VIBRO "\r\n"); +} +#endif + +static void + totp_cli_command_notification_print_method(NotificationMethod method, const char* color) { + bool has_previous_method = false; + if(method & NotificationMethodSound) { + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND "\""); + has_previous_method = true; + } + if(method & NotificationMethodVibro) { + if(has_previous_method) { + TOTP_CLI_PRINTF_COLORFUL(color, " and "); + } + + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO "\""); + } + if(method == NotificationMethodNone) { + TOTP_CLI_PRINTF_COLORFUL(color, "\"" TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE "\""); + } +} + +void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + bool new_method_provided = false; + NotificationMethod new_method = NotificationMethodNone; + bool args_valid = true; + while(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_NONE) == 0) { + new_method_provided = true; + new_method = NotificationMethodNone; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_SOUND) == 0) { + new_method_provided = true; + new_method |= NotificationMethodSound; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_NOTIFICATION_METHOD_VIBRO) == 0) { + new_method_provided = true; + new_method |= NotificationMethodVibro; + } else { + args_valid = false; + break; + } + } + + do { + if(!args_valid) { + totp_cli_print_invalid_arguments(); + break; + } + + if(new_method_provided) { + TOTP_CLI_LOCK_UI(plugin_state); + + plugin_state->notification_method = new_method; + if(totp_config_file_update_notification_method(plugin_state)) { + TOTP_CLI_PRINTF_SUCCESS("Notification method is set to "); + totp_cli_command_notification_print_method(new_method, TOTP_CLI_COLOR_SUCCESS); + cli_nl(); + } else { + totp_cli_print_error_updating_config_file(); + } + + TOTP_CLI_UNLOCK_UI(plugin_state); + } else { + TOTP_CLI_PRINTF_INFO("Current notification method is "); + totp_cli_command_notification_print_method( + plugin_state->notification_method, TOTP_CLI_COLOR_INFO); + cli_nl(); + } + } while(false); + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/cli/commands/notification/notification.h b/cli/commands/notification/notification.h new file mode 100644 index 00000000000..e4cdb651de4 --- /dev/null +++ b/cli/commands/notification/notification.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_NOTIFICATION "notify" + +void totp_cli_command_notification_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_notification_docopt_commands(); +void totp_cli_command_notification_docopt_usage(); +void totp_cli_command_notification_docopt_arguments(); +#endif \ No newline at end of file diff --git a/cli/commands/pin/pin.c b/cli/commands/pin/pin.c new file mode 100644 index 00000000000..d3179095062 --- /dev/null +++ b/cli/commands/pin/pin.c @@ -0,0 +1,193 @@ +#include "pin.h" + +#include +#include +#include "../../../types/token_info.h" +#include "../../../types/user_pin_codes.h" +#include "../../../services/config/config.h" +#include "../../cli_helpers.h" +#include +#include "../../../services/crypto/crypto_facade.h" +#include "../../../ui/scene_director.h" + +#define TOTP_CLI_COMMAND_PIN_COMMAND_SET "set" +#define TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE "remove" +#define TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT_PREFIX "-c" +#define TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT "slot" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_pin_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_PIN " Set\\change\\remove PIN\r\n"); +} + +void totp_cli_command_pin_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_PIN + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_PIN_COMMAND_SET " | " TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) " " DOCOPT_OPTIONAL( + DOCOPT_OPTION( + TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT))) "\r\n"); +} + +void totp_cli_command_pin_docopt_options() { + TOTP_CLI_PRINTF( + " " DOCOPT_OPTION( + TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT)) " New crypto key slot. Must be between %d and %d\r\n", + ACCEPTABLE_CRYPTO_KEY_SLOT_START, + ACCEPTABLE_CRYPTO_KEY_SLOT_END); +} +#endif + +static inline uint8_t totp_cli_key_to_pin_code(uint8_t key) { + uint8_t code = 0; + switch(key) { + case 0x44: // left + code = PinCodeArrowLeft; + break; + case 0x41: // up + code = PinCodeArrowUp; + break; + case 0x43: // right + code = PinCodeArrowRight; + break; + case 0x42: // down + code = PinCodeArrowDown; + break; + default: + break; + } + + return code; +} + +static bool totp_cli_read_pin(Cli* cli, uint8_t* pin, uint8_t* pin_length) { + TOTP_CLI_PRINTF("Enter new PIN (use arrow keys on your keyboard): "); + fflush(stdout); + uint8_t c; + *pin_length = 0; + while(cli_read(cli, &c, 1) == 1) { + if(c == CliSymbolAsciiEsc) { + uint8_t c2; + uint8_t c3; + if(cli_read_timeout(cli, &c2, 1, 0) == 1 && cli_read_timeout(cli, &c3, 1, 0) == 1 && + c2 == 0x5b) { + uint8_t code = totp_cli_key_to_pin_code(c3); + if(code > 0) { + pin[*pin_length] = code; + *pin_length = *pin_length + 1; + putc('*', stdout); + fflush(stdout); + } + } + } else if(c == CliSymbolAsciiETX) { + totp_cli_delete_current_line(); + TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); + return false; + } else if(c == CliSymbolAsciiBackspace || c == CliSymbolAsciiDel) { + if(*pin_length > 0) { + *pin_length = *pin_length - 1; + pin[*pin_length] = 0; + totp_cli_delete_last_char(); + } + } else if(c == CliSymbolAsciiCR) { + cli_nl(); + break; + } + } + + totp_cli_delete_last_line(); + return true; +} + +void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + UNUSED(plugin_state); + FuriString* temp_str = furi_string_alloc(); + + bool do_change = false; + bool do_remove = false; + uint8_t crypto_key_slot = plugin_state->crypto_settings.crypto_key_slot; + + bool arguments_parsed = true; + while(args_read_string_and_trim(args, temp_str)) { + if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_SET) == 0) { + do_change = true; + } else if(furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_COMMAND_REMOVE) == 0) { + do_remove = true; + } else if( + furi_string_cmpi_str(temp_str, TOTP_CLI_COMMAND_PIN_ARG_NEW_CRYPTO_KEY_SLOT_PREFIX) == + 0) { + if(!args_read_uint8_and_trim(args, &crypto_key_slot) || + !totp_crypto_check_key_slot(crypto_key_slot)) { + TOTP_CLI_PRINTF_ERROR("Slot \"%" PRIu8 "\" can not be used\r\n", crypto_key_slot); + arguments_parsed = false; + break; + } + } else { + totp_cli_print_invalid_arguments(); + arguments_parsed = false; + break; + } + } + + if(!(do_change || do_remove) || (do_change && do_remove)) { + totp_cli_print_invalid_arguments(); + arguments_parsed = false; + } + + if(arguments_parsed && totp_cli_ensure_authenticated(plugin_state, cli)) { + TOTP_CLI_LOCK_UI(plugin_state); + do { + uint8_t new_pin[CRYPTO_IV_LENGTH]; + memset(&new_pin[0], 0, CRYPTO_IV_LENGTH); + uint8_t new_pin_length = 0; + if(do_change) { + if(!totp_cli_read_pin(cli, &new_pin[0], &new_pin_length)) { + memset_s(&new_pin[0], CRYPTO_IV_LENGTH, 0, CRYPTO_IV_LENGTH); + break; + } + } else if(do_remove) { + new_pin_length = 0; + memset(&new_pin[0], 0, CRYPTO_IV_LENGTH); + } + + char* backup_path = totp_config_file_backup(plugin_state); + if(backup_path != NULL) { + TOTP_CLI_PRINTF_WARNING("Backup conf file %s has been created\r\n", backup_path); + TOTP_CLI_PRINTF_WARNING( + "Once you make sure everything is fine and works as expected, please delete this backup file\r\n"); + free(backup_path); + } else { + memset_s(&new_pin[0], CRYPTO_IV_LENGTH, 0, CRYPTO_IV_LENGTH); + TOTP_CLI_PRINTF_ERROR( + "An error has occurred during taking backup of config file\r\n"); + break; + } + + TOTP_CLI_PRINTF("Encrypting...\r\n"); + + bool update_result = totp_config_file_update_encryption( + plugin_state, crypto_key_slot, new_pin, new_pin_length); + + memset_s(&new_pin[0], CRYPTO_IV_LENGTH, 0, CRYPTO_IV_LENGTH); + + totp_cli_delete_last_line(); + + if(update_result) { + if(do_change) { + TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully changed\r\n"); + } else if(do_remove) { + TOTP_CLI_PRINTF_SUCCESS("PIN has been successfully removed\r\n"); + } + } else { + totp_cli_print_error_updating_config_file(); + } + + } while(false); + + TOTP_CLI_UNLOCK_UI(plugin_state); + } + + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/cli/commands/pin/pin.h b/cli/commands/pin/pin.h new file mode 100644 index 00000000000..7083535650d --- /dev/null +++ b/cli/commands/pin/pin.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_PIN "pin" + +void totp_cli_command_pin_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_pin_docopt_commands(); +void totp_cli_command_pin_docopt_usage(); +void totp_cli_command_pin_docopt_options(); +#endif diff --git a/cli/commands/reset/reset.c b/cli/commands/reset/reset.c new file mode 100644 index 00000000000..a02bc0eb01c --- /dev/null +++ b/cli/commands/reset/reset.c @@ -0,0 +1,42 @@ +#include "reset.h" + +#include +#include +#include "../../cli_helpers.h" +#include "../../../ui/scene_director.h" +#include "../../../services/config/config.h" + +#define TOTP_CLI_RESET_CONFIRMATION_KEYWORD "YES" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_reset_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_RESET + " Reset application to default settings\r\n"); +} + +void totp_cli_command_reset_docopt_usage() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_NAME " " TOTP_CLI_COMMAND_RESET "\r\n"); +} +#endif + +void totp_cli_command_reset_handle(PluginState* plugin_state, Cli* cli) { + TOTP_CLI_LOCK_UI(plugin_state); + TOTP_CLI_PRINTF_WARNING( + "As a result of reset all the settings and tokens will be permanently lost.\r\n"); + TOTP_CLI_PRINTF_WARNING("Do you really want to reset application?\r\n"); + TOTP_CLI_PRINTF_WARNING("Type \"" TOTP_CLI_RESET_CONFIRMATION_KEYWORD + "\" and hit to confirm:\r\n"); + FuriString* temp_str = furi_string_alloc(); + bool is_confirmed = totp_cli_read_line(cli, temp_str, false) && + furi_string_cmpi_str(temp_str, TOTP_CLI_RESET_CONFIRMATION_KEYWORD) == 0; + furi_string_free(temp_str); + if(is_confirmed) { + totp_config_file_reset(plugin_state); + TOTP_CLI_PRINTF_SUCCESS("Application has been successfully reset to default.\r\n"); + TOTP_CLI_PRINTF_SUCCESS("Now application will be closed to apply all the changes.\r\n"); + totp_cli_force_close_app(plugin_state->event_queue); + } else { + TOTP_CLI_PRINTF_INFO("Action was not confirmed by user\r\n"); + TOTP_CLI_UNLOCK_UI(plugin_state); + } +} \ No newline at end of file diff --git a/cli/commands/reset/reset.h b/cli/commands/reset/reset.h new file mode 100644 index 00000000000..7f48aa10b64 --- /dev/null +++ b/cli/commands/reset/reset.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_RESET "reset" + +void totp_cli_command_reset_handle(PluginState* plugin_state, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_reset_docopt_commands(); +void totp_cli_command_reset_docopt_usage(); +#endif diff --git a/cli/commands/timezone/timezone.c b/cli/commands/timezone/timezone.c new file mode 100644 index 00000000000..af5db6e6e37 --- /dev/null +++ b/cli/commands/timezone/timezone.c @@ -0,0 +1,54 @@ +#include "timezone.h" +#include +#include "../../../services/config/config.h" +#include "../../../ui/scene_director.h" +#include "../../cli_helpers.h" + +#define TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE "timezone" + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_timezone_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE ", " TOTP_CLI_COMMAND_TIMEZONE_ALT + " Get or set current timezone\r\n"); +} + +void totp_cli_command_timezone_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_TIMEZONE " | " TOTP_CLI_COMMAND_TIMEZONE_ALT) " " DOCOPT_OPTIONAL( + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE)) "\r\n"); +} + +void totp_cli_command_timezone_docopt_arguments() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_TIMEZONE_ARG_TIMEZONE + " Timezone offset in hours to be set\r\n"); +} +#endif + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + FuriString* temp_str = furi_string_alloc(); + if(args_read_string_and_trim(args, temp_str)) { + char* strtof_endptr; + float tz = strtof(furi_string_get_cstr(temp_str), &strtof_endptr); + if(*strtof_endptr == 0 && tz >= -12.75f && tz <= 12.75f) { + TOTP_CLI_LOCK_UI(plugin_state); + plugin_state->timezone_offset = tz; + if(totp_config_file_update_timezone_offset(plugin_state)) { + TOTP_CLI_PRINTF_SUCCESS("Timezone is set to %f\r\n", (double)tz); + } else { + totp_cli_print_error_updating_config_file(); + } + TOTP_CLI_UNLOCK_UI(plugin_state); + } else { + TOTP_CLI_PRINTF_ERROR("Invalid timezone offset\r\n"); + } + } else { + TOTP_CLI_PRINTF_INFO( + "Current timezone offset is %f\r\n", (double)plugin_state->timezone_offset); + } + furi_string_free(temp_str); +} \ No newline at end of file diff --git a/cli/commands/timezone/timezone.h b/cli/commands/timezone/timezone.h new file mode 100644 index 00000000000..1a697f0671d --- /dev/null +++ b/cli/commands/timezone/timezone.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_TIMEZONE "timezone" +#define TOTP_CLI_COMMAND_TIMEZONE_ALT "tz" + +void totp_cli_command_timezone_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_timezone_docopt_commands(); +void totp_cli_command_timezone_docopt_usage(); +void totp_cli_command_timezone_docopt_arguments(); +#endif diff --git a/cli/commands/update/update.c b/cli/commands/update/update.c new file mode 100644 index 00000000000..5c1553d8714 --- /dev/null +++ b/cli/commands/update/update.c @@ -0,0 +1,177 @@ +#include "update.h" +#include +#include +#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" + +struct TotpUpdateContext { + FuriString* args; + Cli* cli; + const CryptoSettings* crypto_settings; +}; + +enum TotpIteratorUpdateTokenResultsEx { + TotpIteratorUpdateTokenResultInvalidSecret = 1, + TotpIteratorUpdateTokenResultCancelled = 2, + TotpIteratorUpdateTokenResultInvalidArguments = 3 +}; + +static bool totp_cli_try_read_name( + TokenInfo* token_info, + const FuriString* arg, + FuriString* args, + bool* parsed) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_NAME_PREFIX) == 0) { + if(!args_read_probably_quoted_string_and_trim(args, token_info->name) || + furi_string_empty(token_info->name)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_NAME_PREFIX); + } else { + *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; +} + +static TotpIteratorUpdateTokenResult + update_token_handler(TokenInfo* token_info, const void* context) { + const struct TotpUpdateContext* context_t = context; + + // Read optional arguments + FuriString* temp_str = furi_string_alloc(); + bool mask_user_input = true; + bool update_token_secret = false; + PlainTokenSecretEncoding token_secret_encoding = PlainTokenSecretEncodingBase32; + while(args_read_string_and_trim(context_t->args, temp_str)) { + bool parsed = false; + if(!totp_cli_try_read_name(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_algo(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_digits(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_duration(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_unsecure_flag(temp_str, &parsed, &mask_user_input) && + !totp_cli_try_read_change_secret_flag(temp_str, &parsed, &update_token_secret) && + !totp_cli_try_read_automation_features(token_info, temp_str, context_t->args, &parsed) && + !totp_cli_try_read_plain_token_secret_encoding( + temp_str, context_t->args, &parsed, &token_secret_encoding)) { + totp_cli_printf_unknown_argument(temp_str); + } + + if(!parsed) { + furi_string_free(temp_str); + return TotpIteratorUpdateTokenResultInvalidArguments; + } + } + + if(update_token_secret) { + // Reading token secret + furi_string_reset(temp_str); + TOTP_CLI_PRINTF("Enter token secret and confirm with [ENTER]:\r\n"); + bool token_secret_read = totp_cli_read_line(context_t->cli, temp_str, mask_user_input); + totp_cli_delete_last_line(); + if(!token_secret_read) { + furi_string_secure_free(temp_str); + return TotpIteratorUpdateTokenResultCancelled; + } + + if(!token_info_set_secret( + token_info, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + token_secret_encoding, + context_t->crypto_settings)) { + furi_string_secure_free(temp_str); + return TotpIteratorUpdateTokenResultInvalidSecret; + } + } + + furi_string_secure_free(temp_str); + + return TotpIteratorUpdateTokenResultSuccess; +} + +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_update_docopt_commands() { + TOTP_CLI_PRINTF(" " TOTP_CLI_COMMAND_UPDATE " Update existing token\r\n"); +} + +void totp_cli_command_update_docopt_usage() { + TOTP_CLI_PRINTF( + " " TOTP_CLI_COMMAND_NAME + " " DOCOPT_REQUIRED(TOTP_CLI_COMMAND_UPDATE) " " DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_INDEX) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_ALGO_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_ALGO))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_NAME_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME))) " " DOCOPT_OPTIONAL( + DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_DIGITS_PREFIX, + DOCOPT_ARGUMENT( + TOTP_CLI_COMMAND_ARG_DIGITS))) " " DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_DURATION_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_DURATION))) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_ARG_UNSECURE_PREFIX)) " " DOCOPT_OPTIONAL(DOCOPT_SWITCH(TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX)) " " DOCOPT_MULTIPLE(DOCOPT_OPTIONAL(DOCOPT_OPTION(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE_PREFIX, DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_AUTOMATION_FEATURE)))) "\r\n"); +} + +void totp_cli_command_update_docopt_options() { + TOTP_CLI_PRINTF(" " DOCOPT_OPTION( + TOTP_CLI_COMMAND_ARG_NAME_PREFIX, + DOCOPT_ARGUMENT(TOTP_CLI_COMMAND_ARG_NAME)) " Token name\r\n"); + + TOTP_CLI_PRINTF(" " DOCOPT_SWITCH( + TOTP_CLI_COMMAND_UPDATE_ARG_SECRET_PREFIX) " Update token secret\r\n"); +} +#endif + +void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli) { + if(!totp_cli_ensure_authenticated(plugin_state, cli)) { + return; + } + + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + + int token_number; + if(!args_read_int_and_trim(args, &token_number) || token_number <= 0 || + (size_t)token_number > totp_token_info_iterator_get_total_count(iterator_context)) { + totp_cli_print_invalid_arguments(); + return; + } + + TOTP_CLI_LOCK_UI(plugin_state); + + size_t previous_index = totp_token_info_iterator_get_current_token_index(iterator_context); + totp_token_info_iterator_go_to(iterator_context, token_number - 1); + + struct TotpUpdateContext update_context = { + .args = args, .cli = cli, .crypto_settings = &plugin_state->crypto_settings}; + TotpIteratorUpdateTokenResult update_result = totp_token_info_iterator_update_current_token( + iterator_context, &update_token_handler, &update_context); + + if(update_result == TotpIteratorUpdateTokenResultSuccess) { + TOTP_CLI_PRINTF_SUCCESS( + "Token \"%s\" has been successfully updated\r\n", + furi_string_get_cstr( + totp_token_info_iterator_get_current_token(iterator_context)->name)); + } else if(update_result == TotpIteratorUpdateTokenResultInvalidArguments) { + totp_cli_print_invalid_arguments(); + } else if(update_result == TotpIteratorUpdateTokenResultCancelled) { + TOTP_CLI_PRINTF_INFO("Cancelled by user\r\n"); + } else if(update_result == TotpIteratorUpdateTokenResultInvalidSecret) { + TOTP_CLI_PRINTF_ERROR("Token secret seems to be invalid and can not be parsed\r\n"); + } else if(update_result == TotpIteratorUpdateTokenResultFileUpdateFailed) { + totp_cli_print_error_updating_config_file(); + } + + totp_token_info_iterator_go_to(iterator_context, previous_index); + TOTP_CLI_UNLOCK_UI(plugin_state); +} \ No newline at end of file diff --git a/cli/commands/update/update.h b/cli/commands/update/update.h new file mode 100644 index 00000000000..6100f4b3886 --- /dev/null +++ b/cli/commands/update/update.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../config/app/config.h" + +#define TOTP_CLI_COMMAND_UPDATE "update" + +void totp_cli_command_update_handle(PluginState* plugin_state, FuriString* args, Cli* cli); +#ifdef TOTP_CLI_RICH_HELP_ENABLED +void totp_cli_command_update_docopt_commands(); +void totp_cli_command_update_docopt_usage(); +void totp_cli_command_update_docopt_options(); +#endif \ No newline at end of file diff --git a/cli/common_command_arguments.c b/cli/common_command_arguments.c new file mode 100644 index 00000000000..c3129a157d4 --- /dev/null +++ b/cli/common_command_arguments.c @@ -0,0 +1,141 @@ +#include "common_command_arguments.h" +#include + +void totp_cli_printf_missed_argument_value(char* arg) { + TOTP_CLI_PRINTF_ERROR("Missed or incorrect value for argument \"%s\"\r\n", arg); +} + +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; +} + +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding) { + if(furi_string_cmpi_str(arg, TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX) == 0) { + if(!args_read_string_and_trim(args, arg)) { + totp_cli_printf_missed_argument_value(TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX); + } else { + if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE32_NAME) == 0) { + *secret_encoding = PlainTokenSecretEncodingBase32; + *parsed = true; + } else if(furi_string_cmpi_str(arg, PLAIN_TOKEN_ENCODING_BASE64_NAME) == 0) { + *secret_encoding = PlainTokenSecretEncodingBase64; + *parsed = true; + } else { + TOTP_CLI_PRINTF_ERROR( + "\"%s\" is incorrect value for argument \"" TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX + "\"\r\n", + furi_string_get_cstr(arg)); + } + } + + return true; + } + + return false; +} \ No newline at end of file diff --git a/cli/common_command_arguments.h b/cli/common_command_arguments.h new file mode 100644 index 00000000000..0bf83422595 --- /dev/null +++ b/cli/common_command_arguments.h @@ -0,0 +1,106 @@ +#pragma once +#include +#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" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING_PREFIX "-e" +#define TOTP_CLI_COMMAND_ARG_SECRET_ENCODING "encoding" + +/** + * @brief Prints information about unknown argument + * @param arg + */ +void totp_cli_printf_unknown_argument(const FuriString* arg); + +/** + * @brief Prints information about missed required argument + * @param arg + */ +void totp_cli_printf_missed_argument_value(char* arg); + +/** + * @brief Tries to read token hashing algo + * @param token_info token info to set parsed algo to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param[out] parsed will be set to \c true if token hashing algo sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token hashing algo argument; \c false otherwise + */ +bool totp_cli_try_read_algo(TokenInfo* token_info, FuriString* arg, FuriString* args, bool* parsed); + +/** + * @brief Tries to read token digits count + * @param token_info token info to set parsed digits count to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param[out] parsed will be set to \c true if token digits count sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token digits count argument; \c false otherwise + */ +bool totp_cli_try_read_digits( + TokenInfo* token_info, + const FuriString* arg, + FuriString* args, + bool* parsed); + +/** + * @brief Tries to read token duration + * @param token_info token info to set parsed duration to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param[out] parsed will be set to \c true if token duration sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token duration argument; \c false otherwise + */ +bool totp_cli_try_read_duration( + TokenInfo* token_info, + const FuriString* arg, + FuriString* args, + bool* parsed); + +/** + * @brief Tries to read token automation features + * @param token_info token info to set parsed automation features to if successfully read and parsed + * @param arg argument to parse + * @param args rest of arguments + * @param[out] parsed will be set to \c true if token automation features sucecssfully read and parsed; \c false otherwise + * @return \c true if \c arg represents token automation features argument; \c false otherwise + */ +bool totp_cli_try_read_automation_features( + TokenInfo* token_info, + FuriString* arg, + FuriString* args, + bool* parsed); + +/** + * @brief Tries to read unsecure flag + * @param arg argument to parse + * @param[out] parsed will be set to \c true if unsecure flag sucecssfully read and parsed; \c false otherwise + * @param[out] unsecure_flag will be set to parsed unsecure flag state if read and parsed successfully + * @return \c true if \c arg represents unsecure flag argument; \c false otherwise + */ +bool totp_cli_try_read_unsecure_flag(const FuriString* arg, bool* parsed, bool* unsecure_flag); + +/** + * @brief Tries to read plain token secret encoding + * @param arg argument to parse + * @param args rest of arguments + * @param[out] parsed will be set to \c true if plain token secret encoding sucecssfully read and parsed; \c false otherwise + * @param[out] secret_encoding will be set to parsed plain token secret encoding if read and parsed successfully + * @return \c true if \c arg represents plain token secret encoding argument; \c false otherwise + */ +bool totp_cli_try_read_plain_token_secret_encoding( + FuriString* arg, + FuriString* args, + bool* parsed, + PlainTokenSecretEncoding* secret_encoding); \ No newline at end of file diff --git a/config/app/config.h b/config/app/config.h new file mode 100644 index 00000000000..8c5fc13145f --- /dev/null +++ b/config/app/config.h @@ -0,0 +1,42 @@ +// Application automatic lock timeout if user IDLE. (ticks) +#ifndef TOTP_AUTO_LOCK_IDLE_TIMEOUT_SEC +#define TOTP_AUTO_LOCK_IDLE_TIMEOUT_SEC (60) +#endif + +// Enables\disables Bluetooth token input automation +#ifndef TOTP_NO_BADBT_AUTOMATION +#define TOTP_BADBT_AUTOMATION_ENABLED +#endif + +// Enables\disables backward compatibility with crypto algorithms v1 +#ifndef TOTP_NO_OBSOLETE_CRYPTO_V1_COMPATIBILITY +#define TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED +#endif + +// Enables\disables backward compatibility with crypto algorithms v2 +#ifndef TOTP_NO_OBSOLETE_CRYPTO_V2_COMPATIBILITY +#define TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED +#endif + +// Enables\disables userfriendly TOTP CLI help text +// If disabled, it will print a link to a wiki page +#ifndef TOTP_CLI_NO_RICH_HELP +#define TOTP_CLI_RICH_HELP_ENABLED +#endif + +// Enables\disables "Add new token" UI +// If disabled it will print a link to wiki page +#ifndef TOTP_UI_NO_ADD_NEW_TOKEN +#define TOTP_UI_ADD_NEW_TOKEN_ENABLED +#endif + +// List of compatible firmwares +#define TOTP_FIRMWARE_OFFICIAL_STABLE (1) +#define TOTP_FIRMWARE_OFFICIAL_DEV (2) +#define TOTP_FIRMWARE_XTREME_UL (3) +// End of list + +// Target firmware +#ifndef TOTP_TARGET_FIRMWARE +#define TOTP_TARGET_FIRMWARE TOTP_FIRMWARE_XTREME_UL +#endif diff --git a/config/wolfssl/config.h b/config/wolfssl/config.h new file mode 100644 index 00000000000..6500d642258 --- /dev/null +++ b/config/wolfssl/config.h @@ -0,0 +1,34 @@ +#pragma once + +#define NO_OLD_SHA_NAMES +#define WOLFCRYPT_ONLY +#define NO_SIG_WRAPPER +#define NO_AES +#define NO_AES_CBC +#define NO_DES3 +#define NO_DSA +#define NO_RSA +#define NO_DH +#define NO_RC4 +#define NO_MD4 +#define NO_MD5 +#define NO_PKCS12 +#define NO_PKCS8 +#define WC_NO_RNG +#define NO_FILESYSTEM +#define NO_WRITEV +#define NO_MAIN_DRIVER +#define NO_DEV_RANDOM +#define WOLFSSL_SHA512 +#define WOLFSSL_NOSHA512_224 +#define WOLFSSL_NOSHA512_256 +#define USE_SLOW_SHA512 +#define USE_SLOW_SHA256 +#define USE_SLOW_SHA +#define NO_CERTS +#define NO_WOLFSSL_MEMORY +#define WOLFSSL_NO_PEM +#define NO_PSK +#define NO_ERROR_STRINGS +#define NO_OLD_TLS +#define SINGLE_THREADED diff --git a/images/DolphinCommon_56x48.png b/images/DolphinCommon_56x48.png new file mode 100644 index 0000000000000000000000000000000000000000..089aaed83507431993a76ca25d32fdd9664c1c84 GIT binary patch literal 1416 zcmaJ>eNYr-7(dh;KXS5&nWVIBjS_NizYg|x=Pr^vz*7zxJO|P-dw2IeZq?gec9-rD zoPZchQ_6}yP{Slc4I!!28K==nodOJ_nsCY-(wOq2uZbLx!rlYU{KIi)_Wj!D_j`WN z^FGgREXdEDF)ewT&1Re7Tj(uBvlG44lnH3;I%IzsO|z`*Vr!`uv?9QOwgs{#Ld+Ki zC9n_zxxBOkx@@+IwMwAaD)#3Ik`}gun2kLe))Crfb7e+#AgzHGCc+X$b>qJuIf`S7 z?8b}I{ghw#z>uiaLknQh@LJUrqHcVYS3v97F^OZN zCe|7^J|?QzUx0Zu17e(=CM1fYFpjtLk|a4~$g}e?hGH0!VoBOT&<=s(1ct%J9~?O} z$)jW_dkX9yTX~%W*i_IM%0{ z7EmP^_pKn`<5>E(SixgJU};7`)7Hidp&+DLnizsebUk}_-GfgbN^il9b`v)f+ z{o5Zry)d<7`fHQ^uw_;+x>mcPw0&8iW69x{k92O{Q}`yFdH=5d$pbf49w1&NS)G+vhr6y}5TMsofQirRDUmKilk5=(KGouJ{H9hW=$X zgi;)vI!jl!_4H3jD(?Jz=8By|i47I&tKA1y9{nfp;_|FxKBDNWp{hN9hJ1nU?z%J6 z?>UxyzWvO}Pgc~rCZ#5%Eq+_hNS~bBdiGlT&f%%e`hHjSySR2=JuK2^+%;$R3#Wz~ z=e_mfqW23bPa0fhe)HdE5+GelU&!jS3ckUZOQ)CC5?mo zo=tzG_4|RuvPUO|mhCwA>y)1c%SWC%a4?a-x|J*?ch~+n=R7o@>p6J2dE=$stKZmK z-xoTRwET2^Wu)&1U7!Ebw!!D?x`xwQX3pMnrRwCT?`4GHt4&?|cIiI{_^XYp-np>6 xE^lPSXzOYCC4X`6tl@OB1M5_S7jml-Y~(TPp{aTIejNKZ`m*!Atyxdk{0EAy49frj literal 0 HcmV?d00001 diff --git a/images/hid_ble_31x9.png b/images/hid_ble_31x9.png new file mode 100644 index 0000000000000000000000000000000000000000..fb999231f11ce30afd79e7720cf18fb81c09631e GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0vp^@<7bV$P6SuyiffDq!^2X+?^QKos)S9vjzBsxc>kDAIJlaTa()7BevL9RguSQ4OyKprC=Li(`ny<>VDW zrTmA#14+Y}xZOYH4_^+Aes5Uw?$#~#q>YS6)&4i1xCWGDXP99w>+a{&upX$F!PC{x JWt~$(69Ak{J23zN literal 0 HcmV?d00001 diff --git a/images/hid_usb_31x9.png b/images/hid_usb_31x9.png new file mode 100644 index 0000000000000000000000000000000000000000..c6b8aa22823c5c5e27eba16837212150de32a769 GIT binary patch literal 174 zcmeAS@N?(olHy`uVBq!ia0vp^@<7bV$P6SuyiffDq!^2X+?^QKos)S9vjzBsxB}__|Nk$&IsYz@#aZAHSNn{1`*#dk*T>t<74`hZOx?BgOI14-?iy0XB4ude`@%$AjKtTyl7sn8Z%gG5T o3F#BkSET>oKk|v^4Nnh)$W|twRlcWOfod2$UHx3vIVCg!0A-gcZvX%Q literal 0 HcmV?d00001 diff --git a/images/totp_arrow_right_8x9.png b/images/totp_arrow_right_8x9.png new file mode 100644 index 0000000000000000000000000000000000000000..8c6a8bfeb91385715c668ae573f6e609cf43ad72 GIT binary patch literal 149 zcmeAS@N?(olHy`uVBq!ia0vp^96-#;$P6S+=l;(HQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`*#dk*T!Hle|NocXoPQU{;w= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + ch = (ch & 0x1F) - 1; + } else if(ch >= '2' && ch <= '7') { + ch -= '2' - 26; + } else { + return 0; + } + + buffer |= ch; + bitsLeft += 5; + if(bitsLeft >= 8) { + result[count++] = buffer >> (bitsLeft - 8); + bitsLeft -= 8; + } + } + if(count < bufSize) { + result[count] = '\000'; + } + return count; +} diff --git a/lib/base32/base32.h b/lib/base32/base32.h new file mode 100644 index 00000000000..a0ec86e8291 --- /dev/null +++ b/lib/base32/base32.h @@ -0,0 +1,40 @@ +// Base32 implementation +// +// Copyright 2010 Google Inc. +// Author: Markus Gutschke +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// Encode and decode from base32 encoding using the following alphabet: +// ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 +// This alphabet is documented in RFC 4648/3548 +// +// We allow white-space and hyphens, but all other characters are considered +// invalid. +// +// All functions return the number of output bytes or -1 on error. If the +// output buffer is too small, the result will silently be truncated. + +#pragma once + +#include +#include + +/** + * @brief Decodes Base-32 encoded bytes into plain bytes. + * @param encoded Base-32 encoded bytes + * @param[out] result result output buffer + * @param bufSize result output buffer size + * @return Decoded result length in bytes if successfully decoded; \c 0 otherwise + */ +size_t base32_decode(const uint8_t* encoded, uint8_t* result, size_t bufSize); diff --git a/lib/base64/base64.c b/lib/base64/base64.c new file mode 100644 index 00000000000..2cb0d2ffc0f --- /dev/null +++ b/lib/base64/base64.c @@ -0,0 +1,73 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * Modified and optimized for Flipepr Zero device purposes by Alex Kopachov (@akopachov) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + */ + +#include "base64.h" +#include + +static const uint8_t dtable[] = {0x3e, 0x80, 0x80, 0x80, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x80, 0x80, 0x80, 0x0, 0x80, + 0x80, 0x80, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, + 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x10, 0x11, + 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33}; + +static uint8_t get_dtable_value(uint8_t index) { + return (index < 43 || index > 122) ? 0x80 : dtable[index - 43]; +} + +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size) { + uint8_t* out; + uint8_t* pos; + uint8_t in[4]; + uint8_t block[4]; + uint8_t tmp; + size_t i; + size_t count; + size_t olen; + + count = 0; + for(i = 0; i < len; i++) { + if(get_dtable_value(src[i]) != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + olen = count / 4 * 3; + pos = out = malloc(olen); + *out_size = olen; + if(out == NULL) return NULL; + count = 0; + for(i = 0; i < len; i++) { + tmp = get_dtable_value(src[i]); + if(tmp == 0x80) continue; + in[count] = src[i]; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + } + } + if(pos > out) { + if(in[2] == '=') + pos -= 2; + else if(in[3] == '=') + pos--; + } + *out_len = pos - out; + return out; +} \ No newline at end of file diff --git a/lib/base64/base64.h b/lib/base64/base64.h new file mode 100644 index 00000000000..059ec5a9099 --- /dev/null +++ b/lib/base64/base64.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +/** + * @brief Decodes Base-64 encoded bytes into plain bytes. + * @param src Base-64 encoded bytes + * @param len Base-64 encoded bytes count + * @param[out] out_len decoded buffer length + * @param[out] out_size decoded buffer allocated size + * @return Decoded result buffer if successfully decoded; \c NULL otherwise + */ +uint8_t* base64_decode(const uint8_t* src, size_t len, size_t* out_len, size_t* out_size); \ No newline at end of file diff --git a/lib/fonts/712serif/712serif.c b/lib/fonts/712serif/712serif.c new file mode 100644 index 00000000000..7d0b9f1e96e --- /dev/null +++ b/lib/fonts/712serif/712serif.c @@ -0,0 +1,941 @@ +#include "712serif.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for 7:12 Serif 24pt +*/ + +/* Character bitmaps for 7:12 Serif 24pt */ +const uint8_t _712Serif_24ptBitmaps[] = { + /* @0 '-' (14 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @28 '0' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @56 '1' (14 pixels wide) */ + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + + /* @84 '2' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0x30, + 0x00, + 0x30, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + + /* @112 '3' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0xC0, + 0x03, + 0xC0, + 0x03, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @140 '4' (14 pixels wide) */ + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x00, + 0x03, + 0x00, + 0x03, + 0xC0, + 0x0F, + 0xC0, + 0x0F, + + /* @168 '5' (14 pixels wide) */ + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @196 '6' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x03, + 0xFC, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @224 '7' (14 pixels wide) */ + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @252 '8' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @280 '9' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @308 'B' (14 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0xFF, + 0x0F, + 0xFF, + 0x0F, + + /* @336 'C' (14 pixels wide) */ + 0xF0, + 0x33, + 0xF0, + 0x33, + 0x0C, + 0x3C, + 0x0C, + 0x3C, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x30, + 0x03, + 0x30, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @364 'D' (14 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xFF, + 0x03, + 0xFF, + 0x03, + + /* @392 'F' (14 pixels wide) */ + 0xFF, + 0x3F, + 0xFF, + 0x3F, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0xFC, + 0x03, + 0xFC, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x3F, + 0x00, + 0x3F, + 0x00, + + /* @420 'G' (14 pixels wide) */ + 0xF0, + 0x33, + 0xF0, + 0x33, + 0x0C, + 0x3C, + 0x0C, + 0x3C, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x3F, + 0x03, + 0x3F, + 0x03, + 0x30, + 0x03, + 0x30, + 0x0C, + 0x3C, + 0x0C, + 0x3C, + 0xF0, + 0x33, + 0xF0, + 0x33, + + /* @448 'H' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x3F, + 0x3F, + 0x3F, + 0x3F, + + /* @476 'J' (14 pixels wide) */ + 0x00, + 0x3F, + 0x00, + 0x3F, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @504 'K' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x3F, + 0x3F, + 0x3F, + 0x3F, + + /* @532 'M' (14 pixels wide) */ + 0x0F, + 0x3C, + 0x0F, + 0x3C, + 0x3C, + 0x0F, + 0x3C, + 0x0F, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x3F, + 0x3F, + 0x3F, + 0x3F, + + /* @560 'N' (14 pixels wide) */ + 0x0F, + 0x3F, + 0x0F, + 0x3F, + 0x3C, + 0x0C, + 0x3C, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0x0C, + 0x0F, + 0x0C, + 0x0F, + 0x3F, + 0x0C, + 0x3F, + 0x0C, + + /* @588 'P' (14 pixels wide) */ + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x3C, + 0x30, + 0x3C, + 0x30, + 0xCC, + 0x0F, + 0xCC, + 0x0F, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x3F, + 0x00, + 0x3F, + 0x00, + + /* @616 'Q' (14 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x03, + 0x30, + 0x03, + 0x30, + 0xF3, + 0x30, + 0xF3, + 0x30, + 0x03, + 0x33, + 0x03, + 0x33, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xF0, + 0x33, + 0xF0, + 0x33, + + /* @644 'R' (14 pixels wide) */ + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0x30, + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x3F, + 0x3F, + 0x3F, + 0x3F, + + /* @672 'T' (14 pixels wide) */ + 0xFF, + 0x3F, + 0xFF, + 0x3F, + 0xC3, + 0x30, + 0xC3, + 0x30, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xF0, + 0x03, + 0xF0, + 0x03, + + /* @700 'V' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @728 'W' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0xCC, + 0x0C, + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + 0x30, + 0x03, + + /* @756 'X' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x30, + 0x03, + 0x30, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x30, + 0x03, + 0x30, + 0x03, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x3F, + 0x3F, + 0x3F, + 0x3F, + + /* @784 'Y' (14 pixels wide) */ + 0x3F, + 0x3F, + 0x3F, + 0x3F, + 0x0C, + 0x0C, + 0x0C, + 0x0C, + 0x30, + 0x03, + 0x30, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xF0, + 0x03, + 0xF0, + 0x03, +}; + +/* Character descriptors for 7:12 Serif 24pt */ +/* { [Char width in bits], [Offset into _712Serif_24ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO _712Serif_24ptDescriptors[] = { + {14, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {14, 28}, /* 0 */ + {14, 56}, /* 1 */ + {14, 84}, /* 2 */ + {14, 112}, /* 3 */ + {14, 140}, /* 4 */ + {14, 168}, /* 5 */ + {14, 196}, /* 6 */ + {14, 224}, /* 7 */ + {14, 252}, /* 8 */ + {14, 280}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {14, 308}, /* B */ + {14, 336}, /* C */ + {14, 364}, /* D */ + {0, 0}, /* E */ + {14, 392}, /* F */ + {14, 420}, /* G */ + {14, 448}, /* H */ + {0, 0}, /* I */ + {14, 476}, /* J */ + {14, 504}, /* K */ + {0, 0}, /* L */ + {14, 532}, /* M */ + {14, 560}, /* N */ + {0, 0}, /* O */ + {14, 588}, /* P */ + {14, 616}, /* Q */ + {14, 644}, /* R */ + {0, 0}, /* S */ + {14, 672}, /* T */ + {0, 0}, /* U */ + {14, 700}, /* V */ + {14, 728}, /* W */ + {14, 756}, /* X */ + {14, 784}, /* Y */ +}; + +/* Font information for 7:12 Serif 24pt */ +const FONT_INFO _712Serif_24ptFontInfo = { + "712 Serif", + 14, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + _712Serif_24ptDescriptors, /* Character descriptor array */ + _712Serif_24ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/712serif/712serif.h b/lib/fonts/712serif/712serif.h new file mode 100644 index 00000000000..81959064d62 --- /dev/null +++ b/lib/fonts/712serif/712serif.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for 7:12 Serif 24pt */ +extern const FONT_INFO _712Serif_24ptFontInfo; diff --git a/lib/fonts/available_fonts.c b/lib/fonts/available_fonts.c new file mode 100644 index 00000000000..dd019a75881 --- /dev/null +++ b/lib/fonts/available_fonts.c @@ -0,0 +1,23 @@ +#include "available_fonts.h" +#include "712serif/712serif.h" +#include "bedstead/bedstead.h" +#include "dpcomic/dpcomic.h" +#include "funclimbing/funclimbing.h" +#include "graph35pix/graph35pix.h" +#include "karma_future/karma_future.h" +#include "mode_nine/mode_nine.h" +#include "pixelflag/pixelflag.h" +#include "redhat_mono/redhat_mono.h" +#include "zector/zector.h" + +const FONT_INFO* const available_fonts[AVAILABLE_FONTS_COUNT] = { + &modeNine_15ptFontInfo, + &_712Serif_24ptFontInfo, + &bedstead_17ptFontInfo, + &dPComic_18ptFontInfo, + &funclimbingDemo_18ptFontInfo, + &graph35pix_12ptFontInfo, + &karmaFuture_14ptFontInfo, + &pixelFlag_18ptFontInfo, + &redHatMono_16ptFontInfo, + &zector_18ptFontInfo}; diff --git a/lib/fonts/available_fonts.h b/lib/fonts/available_fonts.h new file mode 100644 index 00000000000..fd4b0c1da17 --- /dev/null +++ b/lib/fonts/available_fonts.h @@ -0,0 +1,7 @@ +#pragma once + +#include "font_info.h" + +#define AVAILABLE_FONTS_COUNT (10) + +extern const FONT_INFO* const available_fonts[AVAILABLE_FONTS_COUNT]; diff --git a/lib/fonts/bedstead/bedstead.c b/lib/fonts/bedstead/bedstead.c new file mode 100644 index 00000000000..6b23a39d479 --- /dev/null +++ b/lib/fonts/bedstead/bedstead.c @@ -0,0 +1,1057 @@ +#include "bedstead.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for Bedstead 17pt +*/ + +/* Character bitmaps for Bedstead 17pt */ +const uint8_t bedstead_17ptBitmaps[] = { + /* @0 '-' (13 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @32 '0' (13 pixels wide) */ + 0xE0, + 0x00, + 0xF0, + 0x01, + 0xF8, + 0x03, + 0xBC, + 0x07, + 0x1E, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0x1E, + 0x0F, + 0xBC, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0xE0, + 0x00, + + /* @64 '1' (13 pixels wide) */ + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xF8, + 0x00, + 0xF8, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xF8, + 0x03, + 0xF8, + 0x03, + + /* @96 '2' (13 pixels wide) */ + 0xF0, + 0x07, + 0xFC, + 0x0F, + 0x1E, + 0x1E, + 0x1E, + 0x1C, + 0x0E, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1E, + 0xC0, + 0x0F, + 0xE0, + 0x07, + 0xF0, + 0x00, + 0x7C, + 0x00, + 0x3E, + 0x00, + 0x1E, + 0x00, + 0x0E, + 0x00, + 0xFE, + 0x1F, + 0xFE, + 0x1F, + + /* @128 '3' (13 pixels wide) */ + 0xFE, + 0x1F, + 0xFE, + 0x1F, + 0x00, + 0x1C, + 0x00, + 0x1E, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0x00, + 0x07, + 0xE0, + 0x07, + 0xE0, + 0x0F, + 0x00, + 0x1E, + 0x00, + 0x1C, + 0x0E, + 0x1C, + 0x1E, + 0x1C, + 0x3E, + 0x0E, + 0xFC, + 0x0F, + 0xF8, + 0x07, + + /* @160 '4' (13 pixels wide) */ + 0x80, + 0x03, + 0x80, + 0x03, + 0xE0, + 0x03, + 0xF0, + 0x03, + 0xF8, + 0x03, + 0xBC, + 0x03, + 0x9E, + 0x03, + 0x8F, + 0x03, + 0x87, + 0x03, + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x80, + 0x03, + 0x80, + 0x03, + 0x80, + 0x03, + 0x80, + 0x03, + 0x80, + 0x03, + + /* @192 '5' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x07, + 0x00, + 0x07, + 0x00, + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x00, + 0x1E, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0xFE, + 0x0F, + 0xFC, + 0x07, + 0xFC, + 0x07, + + /* @224 '6' (13 pixels wide) */ + 0xE0, + 0x07, + 0xF0, + 0x07, + 0x78, + 0x00, + 0x3C, + 0x00, + 0x1E, + 0x00, + 0x0F, + 0x00, + 0x07, + 0x00, + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0x1E, + 0x0F, + 0xFE, + 0x0F, + 0xFC, + 0x07, + + /* @256 '7' (13 pixels wide) */ + 0xFE, + 0x1F, + 0xFE, + 0x1F, + 0x00, + 0x1C, + 0x00, + 0x1E, + 0x00, + 0x0F, + 0x80, + 0x07, + 0xC0, + 0x03, + 0xE0, + 0x01, + 0xF0, + 0x00, + 0x78, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + + /* @288 '8' (13 pixels wide) */ + 0xFC, + 0x07, + 0xFE, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0xFE, + 0x0F, + 0xFE, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0x1E, + 0x0F, + 0xFE, + 0x0F, + 0xFC, + 0x07, + + /* @320 '9' (13 pixels wide) */ + 0xFC, + 0x07, + 0xFE, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1C, + 0xFE, + 0x1F, + 0xFC, + 0x1F, + 0x00, + 0x1C, + 0x00, + 0x1E, + 0x00, + 0x0F, + 0x80, + 0x07, + 0xC0, + 0x03, + 0xFC, + 0x01, + 0xFC, + 0x00, + + /* @352 'B' (13 pixels wide) */ + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1E, + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1E, + 0x07, + 0x0F, + 0xFF, + 0x0F, + 0xFF, + 0x07, + + /* @384 'C' (13 pixels wide) */ + 0xFC, + 0x01, + 0xFE, + 0x07, + 0x0F, + 0x0F, + 0x07, + 0x0F, + 0x07, + 0x0E, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x0F, + 0x8F, + 0x0F, + 0xFE, + 0x07, + 0xFC, + 0x03, + + /* @416 'D' (13 pixels wide) */ + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1E, + 0x07, + 0x0F, + 0xFF, + 0x0F, + 0xFF, + 0x07, + + /* @448 'F' (13 pixels wide) */ + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + + /* @480 'G' (13 pixels wide) */ + 0xFC, + 0x07, + 0xFE, + 0x0F, + 0x0F, + 0x1F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x87, + 0x1F, + 0x87, + 0x1F, + 0x07, + 0x1C, + 0x0F, + 0x1C, + 0xFE, + 0x1F, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + + /* @512 'H' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + + /* @544 'J' (13 pixels wide) */ + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x00, + 0x1C, + 0x0E, + 0x1C, + 0x1E, + 0x1C, + 0x3E, + 0x1E, + 0xFC, + 0x0F, + 0xF8, + 0x07, + + /* @576 'K' (13 pixels wide) */ + 0x07, + 0x0E, + 0x07, + 0x0F, + 0x87, + 0x07, + 0xC7, + 0x03, + 0xE7, + 0x01, + 0xF7, + 0x00, + 0x7F, + 0x00, + 0x3F, + 0x00, + 0x3F, + 0x00, + 0x7F, + 0x00, + 0xF7, + 0x00, + 0xE7, + 0x01, + 0xC7, + 0x03, + 0x87, + 0x07, + 0x07, + 0x0F, + 0x07, + 0x0E, + + /* @608 'M' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0xBF, + 0x1F, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + + /* @640 'N' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x3F, + 0x1C, + 0x7F, + 0x1C, + 0xFF, + 0x1C, + 0xE7, + 0x1F, + 0xC7, + 0x1F, + 0x87, + 0x1F, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + + /* @672 'P' (13 pixels wide) */ + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1E, + 0xFF, + 0x0F, + 0xFF, + 0x07, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + + /* @704 'Q' (13 pixels wide) */ + 0xFC, + 0x07, + 0xFE, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1F, + 0xC7, + 0x0F, + 0x8F, + 0x07, + 0xDE, + 0x0F, + 0xFE, + 0x1F, + 0xFC, + 0x1C, + + /* @736 'R' (13 pixels wide) */ + 0xFF, + 0x07, + 0xFF, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1E, + 0xFF, + 0x0F, + 0xFF, + 0x07, + 0xE7, + 0x00, + 0xE7, + 0x01, + 0xC7, + 0x03, + 0x87, + 0x07, + 0x07, + 0x0F, + 0x07, + 0x1E, + 0x07, + 0x1C, + + /* @768 'T' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + + /* @800 'V' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x1F, + 0x1F, + 0xBC, + 0x07, + 0xB8, + 0x03, + 0xB8, + 0x03, + 0xF8, + 0x07, + 0xF0, + 0x03, + 0xE0, + 0x01, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + + /* @832 'W' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xE7, + 0x1C, + 0xFF, + 0x1F, + 0xFE, + 0x0F, + 0xFE, + 0x0F, + 0xBC, + 0x07, + + /* @864 'X' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0x1E, + 0x0F, + 0xBC, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0xF0, + 0x01, + 0xF8, + 0x03, + 0xBC, + 0x07, + 0x1E, + 0x0F, + 0x0F, + 0x1E, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + + /* @896 'Y' (13 pixels wide) */ + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x0F, + 0x1E, + 0x1E, + 0x0F, + 0xBC, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, +}; + +/* Character descriptors for Bedstead 17pt */ +/* { [Char width in bits], [Offset into bedstead_17ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO bedstead_17ptDescriptors[] = { + {13, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {13, 32}, /* 0 */ + {13, 64}, /* 1 */ + {13, 96}, /* 2 */ + {13, 128}, /* 3 */ + {13, 160}, /* 4 */ + {13, 192}, /* 5 */ + {13, 224}, /* 6 */ + {13, 256}, /* 7 */ + {13, 288}, /* 8 */ + {13, 320}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {13, 352}, /* B */ + {13, 384}, /* C */ + {13, 416}, /* D */ + {0, 0}, /* E */ + {13, 448}, /* F */ + {13, 480}, /* G */ + {13, 512}, /* H */ + {0, 0}, /* I */ + {13, 544}, /* J */ + {13, 576}, /* K */ + {0, 0}, /* L */ + {13, 608}, /* M */ + {13, 640}, /* N */ + {0, 0}, /* O */ + {13, 672}, /* P */ + {13, 704}, /* Q */ + {13, 736}, /* R */ + {0, 0}, /* S */ + {13, 768}, /* T */ + {0, 0}, /* U */ + {13, 800}, /* V */ + {13, 832}, /* W */ + {13, 864}, /* X */ + {13, 896}, /* Y */ +}; + +/* Font information for Bedstead 17pt */ +const FONT_INFO bedstead_17ptFontInfo = { + "Bedstead", + 16, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + bedstead_17ptDescriptors, /* Character descriptor array */ + bedstead_17ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/bedstead/bedstead.h b/lib/fonts/bedstead/bedstead.h new file mode 100644 index 00000000000..0f4c1094951 --- /dev/null +++ b/lib/fonts/bedstead/bedstead.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for Bedstead 17pt */ +extern const FONT_INFO bedstead_17ptFontInfo; diff --git a/lib/fonts/dpcomic/dpcomic.c b/lib/fonts/dpcomic/dpcomic.c new file mode 100644 index 00000000000..7d215c273ef --- /dev/null +++ b/lib/fonts/dpcomic/dpcomic.c @@ -0,0 +1,1115 @@ +#include "dpcomic.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for DPComic 18pt +*/ + +/* Character bitmaps for DPComic 18pt */ +const uint8_t dPComic_18ptBitmaps[] = { + /* @0 '-' (15 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xE0, + 0x3F, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @34 '0' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x07, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0xE0, + 0x07, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @68 '1' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x01, + 0xF8, + 0x01, + 0xF8, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @102 '2' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x07, + 0xC0, + 0x07, + 0xE0, + 0x0F, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x07, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x3F, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @136 '3' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x3F, + 0xC0, + 0x3F, + 0xE0, + 0x3F, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x07, + 0xC0, + 0x07, + 0xC0, + 0x07, + 0x00, + 0x0F, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0xE0, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @170 '4' (15 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0F, + 0xC0, + 0x0F, + 0xC0, + 0x0F, + 0xE0, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @204 '5' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x3F, + 0xC0, + 0x3F, + 0xC0, + 0x0F, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x07, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0x00, + 0x0E, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0xE0, + 0x07, + 0xF8, + 0x01, + 0xF8, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @238 '6' (15 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0xC0, + 0x07, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x07, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0F, + 0x38, + 0x0F, + 0xF8, + 0x07, + 0xE0, + 0x01, + 0xE0, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @272 '7' (15 pixels wide) */ + 0x00, + 0x00, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0xF8, + 0x0F, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x07, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0xF8, + 0x00, + 0xF8, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @306 '8' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x0F, + 0xC0, + 0x0F, + 0xE0, + 0x38, + 0xE0, + 0x3E, + 0xE0, + 0x3E, + 0xC0, + 0x0F, + 0xE0, + 0x07, + 0xE0, + 0x07, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0F, + 0xE0, + 0x07, + 0xE0, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @340 '9' (15 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x07, + 0xC0, + 0x07, + 0xE0, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0xE0, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0xE0, + 0x01, + 0xF8, + 0x00, + 0xF8, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @374 'B' (15 pixels wide) */ + 0xC0, + 0x0F, + 0xE0, + 0x3F, + 0xE0, + 0x3F, + 0xF8, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0xF8, + 0x0F, + 0xF8, + 0x3F, + 0xF8, + 0x3F, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0xF8, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @408 'C' (15 pixels wide) */ + 0x00, + 0x0F, + 0xC0, + 0x3F, + 0xC0, + 0x3F, + 0xE0, + 0x39, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xF8, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0xF8, + 0x0F, + 0xE0, + 0x07, + 0xE0, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @442 'D' (15 pixels wide) */ + 0xC0, + 0x07, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0xF8, + 0x3E, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x0F, + 0x38, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x01, + 0xF8, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @476 'F' (15 pixels wide) */ + 0x80, + 0x1F, + 0xC0, + 0x0F, + 0xC0, + 0x0F, + 0xF0, + 0x01, + 0x70, + 0x00, + 0x70, + 0x00, + 0xF0, + 0x0F, + 0xF0, + 0x03, + 0xF0, + 0x03, + 0x70, + 0x00, + 0x70, + 0x00, + 0x70, + 0x00, + 0x70, + 0x00, + 0x70, + 0x00, + 0x70, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @510 'G' (15 pixels wide) */ + 0x00, + 0x0F, + 0xC0, + 0x3F, + 0xC0, + 0x3F, + 0xE0, + 0x39, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xF8, + 0x00, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0xF8, + 0x0F, + 0xE0, + 0x07, + 0xE0, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @544 'H' (15 pixels wide) */ + 0x00, + 0x30, + 0x20, + 0x38, + 0x20, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0xF8, + 0x3F, + 0xF8, + 0x3F, + 0xF8, + 0x3F, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @578 'J' (15 pixels wide) */ + 0x00, + 0x08, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x00, + 0x0E, + 0x18, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0xE0, + 0x01, + 0xE0, + 0x01, + + /* @612 'K' (15 pixels wide) */ + 0x20, + 0x30, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x0F, + 0x38, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x38, + 0x0F, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @646 'M' (15 pixels wide) */ + 0x08, + 0x10, + 0x3E, + 0x1C, + 0x3E, + 0x1C, + 0x7E, + 0x1E, + 0xFE, + 0x1F, + 0xFE, + 0x1F, + 0xCE, + 0x1D, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x0E, + 0x1C, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @680 'N' (15 pixels wide) */ + 0x20, + 0x30, + 0xF8, + 0x38, + 0xF8, + 0x38, + 0xF8, + 0x38, + 0xF8, + 0x39, + 0xF8, + 0x39, + 0xF8, + 0x39, + 0x38, + 0x3F, + 0x38, + 0x3F, + 0x38, + 0x3F, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0x38, + 0x38, + 0x38, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @714 'P' (15 pixels wide) */ + 0xC0, + 0x07, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0xF8, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0xF8, + 0x01, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @748 'Q' (15 pixels wide) */ + 0x00, + 0x0F, + 0xC0, + 0x3F, + 0xC0, + 0x3F, + 0xE0, + 0x39, + 0xE0, + 0x38, + 0xE0, + 0x38, + 0xF8, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x3E, + 0x38, + 0x3E, + 0xF8, + 0x0F, + 0xE0, + 0x3F, + 0xE0, + 0x3F, + 0x00, + 0x38, + 0x00, + 0x38, + + /* @782 'R' (15 pixels wide) */ + 0xC0, + 0x07, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0xF8, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0F, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0xF8, + 0x01, + 0x38, + 0x07, + 0x38, + 0x07, + 0x38, + 0x0F, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @816 'T' (15 pixels wide) */ + 0x00, + 0x7E, + 0xE0, + 0x3F, + 0xE0, + 0x3F, + 0xF8, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @850 'V' (15 pixels wide) */ + 0x20, + 0x30, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x07, + 0x38, + 0x07, + 0xE0, + 0x07, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @884 'W' (15 pixels wide) */ + 0x04, + 0x40, + 0x07, + 0x70, + 0x07, + 0x70, + 0x07, + 0x71, + 0xC7, + 0x71, + 0xC7, + 0x71, + 0xC7, + 0x71, + 0xC7, + 0x71, + 0xC7, + 0x71, + 0xC7, + 0x71, + 0xE7, + 0x39, + 0xE7, + 0x39, + 0x3C, + 0x0F, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @918 'X' (15 pixels wide) */ + 0x18, + 0x30, + 0x38, + 0x38, + 0x38, + 0x38, + 0xF8, + 0x3E, + 0xE0, + 0x0F, + 0xE0, + 0x0F, + 0xC0, + 0x07, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x07, + 0x38, + 0x0F, + 0x38, + 0x0F, + 0x38, + 0x3E, + 0x18, + 0x38, + 0x18, + 0x38, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @952 'Y' (15 pixels wide) */ + 0x18, + 0x08, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0x38, + 0x0E, + 0xF8, + 0x0F, + 0xF8, + 0x0F, + 0xE0, + 0x07, + 0xE0, + 0x01, + 0xE0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +/* Character descriptors for DPComic 18pt */ +/* { [Char width in bits], [Offset into dPComic_18ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO dPComic_18ptDescriptors[] = { + {15, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {15, 34}, /* 0 */ + {15, 68}, /* 1 */ + {15, 102}, /* 2 */ + {15, 136}, /* 3 */ + {15, 170}, /* 4 */ + {15, 204}, /* 5 */ + {15, 238}, /* 6 */ + {15, 272}, /* 7 */ + {15, 306}, /* 8 */ + {15, 340}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {15, 374}, /* B */ + {15, 408}, /* C */ + {15, 442}, /* D */ + {0, 0}, /* E */ + {15, 476}, /* F */ + {15, 510}, /* G */ + {15, 544}, /* H */ + {0, 0}, /* I */ + {15, 578}, /* J */ + {15, 612}, /* K */ + {0, 0}, /* L */ + {15, 646}, /* M */ + {15, 680}, /* N */ + {0, 0}, /* O */ + {15, 714}, /* P */ + {15, 748}, /* Q */ + {15, 782}, /* R */ + {0, 0}, /* S */ + {15, 816}, /* T */ + {0, 0}, /* U */ + {15, 850}, /* V */ + {15, 884}, /* W */ + {15, 918}, /* X */ + {15, 952}, /* Y */ +}; + +/* Font information for DPComic 18pt */ +const FONT_INFO dPComic_18ptFontInfo = { + "DP Comic", + 17, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + dPComic_18ptDescriptors, /* Character descriptor array */ + dPComic_18ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/dpcomic/dpcomic.h b/lib/fonts/dpcomic/dpcomic.h new file mode 100644 index 00000000000..bac49b4d64c --- /dev/null +++ b/lib/fonts/dpcomic/dpcomic.h @@ -0,0 +1,7 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +extern const FONT_INFO dPComic_18ptFontInfo; \ No newline at end of file diff --git a/lib/fonts/font_info.h b/lib/fonts/font_info.h new file mode 100644 index 00000000000..b7bf6a045aa --- /dev/null +++ b/lib/fonts/font_info.h @@ -0,0 +1,24 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include + +// This structure describes a single character's display information +typedef struct { + const uint8_t width; // width, in bits (or pixels), of the character + const uint16_t + offset; // offset of the character's bitmap, in bytes, into the the FONT_INFO's data array + +} FONT_CHAR_INFO; + +// Describes a single font +typedef struct { + const char* name; // Font name + const uint8_t height; // height, in pages (8 pixels), of the font's characters + const uint8_t startChar; // the first character in the font (e.g. in charInfo and data) + const uint8_t endChar; // the last character in the font + const uint8_t spacePixels; // number of pixels that a space character takes up + const FONT_CHAR_INFO* charInfo; // pointer to array of char information + const uint8_t* data; // pointer to generated array of character visual representation +} FONT_INFO; \ No newline at end of file diff --git a/lib/fonts/funclimbing/funclimbing.c b/lib/fonts/funclimbing/funclimbing.c new file mode 100644 index 00000000000..4176e1f373e --- /dev/null +++ b/lib/fonts/funclimbing/funclimbing.c @@ -0,0 +1,1173 @@ +#include "funclimbing.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for fun climbing (Demo) 18pt +*/ + +/* Character bitmaps for fun climbing (Demo) 18pt */ +const uint8_t funclimbingDemo_18ptBitmaps[] = { + /* @0 '-' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xE0, + 0x03, + 0xE0, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @36 '0' (16 pixels wide) */ + 0x00, + 0x00, + 0x80, + 0x00, + 0xE0, + 0x03, + 0x20, + 0x02, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x08, + 0x04, + 0x08, + 0x08, + 0x08, + 0x08, + 0x08, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x20, + 0x02, + 0xE0, + 0x01, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @72 '1' (16 pixels wide) */ + 0x00, + 0x02, + 0x00, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x02, + 0x60, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x00, + + /* @108 '2' (16 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x07, + 0x60, + 0x04, + 0x20, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x08, + 0x02, + 0x08, + 0x02, + 0x00, + 0x01, + 0x00, + 0x01, + 0x80, + 0x00, + 0x80, + 0x00, + 0x40, + 0x00, + 0x60, + 0x00, + 0xC0, + 0x03, + 0x00, + 0x1C, + 0x00, + 0x00, + + /* @144 '3' (16 pixels wide) */ + 0x00, + 0x02, + 0x80, + 0x05, + 0x40, + 0x08, + 0x40, + 0x08, + 0x40, + 0x10, + 0x40, + 0x10, + 0x00, + 0x10, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x0C, + 0x00, + 0x06, + 0x00, + 0x03, + 0x80, + 0x0F, + 0x00, + 0x10, + 0x00, + 0x10, + 0x30, + 0x08, + 0xC0, + 0x07, + 0x00, + 0x00, + + /* @180 '4' (16 pixels wide) */ + 0x00, + 0x10, + 0x00, + 0x10, + 0x40, + 0x10, + 0x40, + 0x10, + 0x40, + 0x10, + 0x40, + 0x10, + 0x40, + 0x10, + 0x20, + 0x10, + 0x20, + 0x10, + 0x20, + 0x10, + 0x20, + 0x10, + 0x30, + 0x10, + 0xF0, + 0x1F, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + + /* @216 '5' (16 pixels wide) */ + 0x00, + 0x00, + 0x60, + 0x00, + 0xA0, + 0x03, + 0x20, + 0x04, + 0x20, + 0x00, + 0x30, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0xF0, + 0x00, + 0x80, + 0x03, + 0x00, + 0x04, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x04, + 0x80, + 0x03, + 0xF0, + 0x00, + 0x00, + 0x00, + + /* @252 '6' (16 pixels wide) */ + 0x00, + 0x00, + 0x40, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x18, + 0x03, + 0xC8, + 0x06, + 0x28, + 0x0C, + 0x18, + 0x08, + 0x18, + 0x08, + 0x08, + 0x08, + 0x08, + 0x08, + 0x10, + 0x08, + 0x30, + 0x0C, + 0xC0, + 0x07, + 0x00, + 0x00, + + /* @288 '7' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x0F, + 0xF0, + 0x09, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x06, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x03, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x00, + + /* @324 '8' (16 pixels wide) */ + 0x80, + 0x01, + 0x60, + 0x02, + 0x30, + 0x04, + 0x10, + 0x08, + 0x10, + 0x08, + 0x08, + 0x08, + 0x08, + 0x08, + 0x10, + 0x0C, + 0x10, + 0x04, + 0x60, + 0x03, + 0xC0, + 0x01, + 0x40, + 0x03, + 0x20, + 0x04, + 0x20, + 0x04, + 0x20, + 0x04, + 0x20, + 0x04, + 0xC0, + 0x03, + 0x00, + 0x00, + + /* @360 '9' (16 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x01, + 0x20, + 0x02, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0xE0, + 0x03, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + 0x01, + 0x80, + 0x00, + 0x80, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @396 'B' (16 pixels wide) */ + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0xE8, + 0x0F, + 0x38, + 0x10, + 0x10, + 0x10, + 0x10, + 0x30, + 0x10, + 0x10, + 0x10, + 0x18, + 0xF0, + 0x07, + 0x20, + 0x00, + + /* @432 'C' (16 pixels wide) */ + 0x00, + 0x03, + 0x80, + 0x06, + 0x40, + 0x04, + 0x40, + 0x04, + 0x40, + 0x08, + 0x40, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x40, + 0x00, + 0x40, + 0x08, + 0x40, + 0x08, + 0x40, + 0x08, + 0xC0, + 0x08, + 0x80, + 0x04, + 0x80, + 0x05, + 0x00, + 0x02, + + /* @468 'D' (16 pixels wide) */ + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x80, + 0x07, + 0x40, + 0x04, + 0x20, + 0x04, + 0x20, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x30, + 0x04, + 0x20, + 0x04, + 0x20, + 0x04, + 0x40, + 0x04, + 0x80, + 0x05, + 0x00, + 0x07, + + /* @504 'F' (16 pixels wide) */ + 0x00, + 0x02, + 0x80, + 0x0D, + 0xC0, + 0x08, + 0x40, + 0x10, + 0x60, + 0x10, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0xF8, + 0x01, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x40, + 0x00, + + /* @540 'G' (16 pixels wide) */ + 0x80, + 0x00, + 0x60, + 0x01, + 0x20, + 0x03, + 0x30, + 0x02, + 0x10, + 0x02, + 0x10, + 0x02, + 0x10, + 0x02, + 0x10, + 0x02, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0xD0, + 0x0F, + 0x10, + 0x02, + 0x10, + 0x01, + 0x20, + 0x01, + 0xE0, + 0x00, + 0x40, + 0x00, + + /* @576 'H' (16 pixels wide) */ + 0x00, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x18, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x0F, + 0x90, + 0x09, + 0xD0, + 0x18, + 0x50, + 0x10, + 0x30, + 0x10, + 0x30, + 0x10, + 0x10, + 0x10, + 0x00, + 0x00, + + /* @612 'J' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x10, + 0x04, + 0x20, + 0x04, + 0x20, + 0x02, + 0x20, + 0x02, + 0x60, + 0x03, + 0xC0, + 0x01, + 0x00, + 0x00, + + /* @648 'K' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x08, + 0x10, + 0x0C, + 0x10, + 0x02, + 0xB0, + 0x01, + 0xE0, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0xA0, + 0x00, + 0x20, + 0x01, + 0x20, + 0x02, + 0x20, + 0x0C, + 0x20, + 0x08, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @684 'M' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x10, + 0x10, + 0x30, + 0x18, + 0x30, + 0x18, + 0x50, + 0x34, + 0x50, + 0x24, + 0xC8, + 0x22, + 0x88, + 0x22, + 0x88, + 0x21, + 0x08, + 0x21, + 0x04, + 0x60, + 0x04, + 0x40, + 0x04, + 0x40, + 0x04, + 0x40, + 0x02, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @720 'N' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x02, + 0x01, + 0x86, + 0x07, + 0x44, + 0x04, + 0x24, + 0x08, + 0x24, + 0x08, + 0x14, + 0x18, + 0x14, + 0x10, + 0x1C, + 0x10, + 0x0C, + 0x10, + 0x0C, + 0x10, + 0x08, + 0x10, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @756 'P' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0xE0, + 0x01, + 0x20, + 0x02, + 0x20, + 0x04, + 0x60, + 0x04, + 0x40, + 0x04, + 0x40, + 0x04, + 0x40, + 0x04, + 0x40, + 0x06, + 0x40, + 0x02, + 0xC0, + 0x01, + 0xC0, + 0x00, + 0x40, + 0x00, + 0x40, + 0x00, + 0x40, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @792 'Q' (16 pixels wide) */ + 0x00, + 0x00, + 0xC0, + 0x03, + 0x30, + 0x06, + 0x18, + 0x08, + 0x08, + 0x10, + 0x04, + 0x10, + 0x04, + 0x20, + 0x04, + 0x20, + 0x04, + 0x20, + 0x08, + 0x21, + 0x08, + 0x33, + 0x10, + 0x12, + 0x30, + 0x0E, + 0xC0, + 0x07, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x10, + 0x00, + 0x00, + + /* @828 'R' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x07, + 0x88, + 0x0D, + 0x48, + 0x08, + 0x68, + 0x00, + 0x28, + 0x00, + 0x28, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @864 'T' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0xE0, + 0x03, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x04, + 0x80, + 0x04, + 0x80, + 0x04, + 0x80, + 0x04, + 0x80, + 0x05, + 0x00, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @900 'V' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0x10, + 0x10, + 0x10, + 0x10, + 0x18, + 0x10, + 0x08, + 0x10, + 0x08, + 0x10, + 0x04, + 0x10, + 0x04, + 0x20, + 0x04, + 0x20, + 0x02, + 0x20, + 0x02, + 0x20, + 0x01, + 0x40, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @936 'W' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x03, + 0x80, + 0x02, + 0x80, + 0x02, + 0x40, + 0x02, + 0x40, + 0x02, + 0x41, + 0x86, + 0x42, + 0x84, + 0x42, + 0x44, + 0x44, + 0x44, + 0x64, + 0x24, + 0x28, + 0x28, + 0x28, + 0x18, + 0x30, + 0x18, + 0x30, + 0x18, + 0x20, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @972 'X' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0x08, + 0x10, + 0x04, + 0x20, + 0x04, + 0x40, + 0x02, + 0x80, + 0x03, + 0x80, + 0x01, + 0x80, + 0x03, + 0x40, + 0x02, + 0x30, + 0x04, + 0x18, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @1008 'Y' (16 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x08, + 0x08, + 0x08, + 0x08, + 0x08, + 0x0C, + 0x08, + 0x0C, + 0x08, + 0x0A, + 0x10, + 0x0A, + 0xF0, + 0x09, + 0x00, + 0x0C, + 0x10, + 0x04, + 0x10, + 0x04, + 0x20, + 0x02, + 0xE0, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +/* Character descriptors for fun climbing (Demo) 18pt */ +/* { [Char width in bits], [Offset into funclimbingDemo_18ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO funclimbingDemo_18ptDescriptors[] = { + {16, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {16, 36}, /* 0 */ + {16, 72}, /* 1 */ + {16, 108}, /* 2 */ + {16, 144}, /* 3 */ + {16, 180}, /* 4 */ + {16, 216}, /* 5 */ + {16, 252}, /* 6 */ + {16, 288}, /* 7 */ + {16, 324}, /* 8 */ + {16, 360}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {16, 396}, /* B */ + {16, 432}, /* C */ + {16, 468}, /* D */ + {0, 0}, /* E */ + {16, 504}, /* F */ + {16, 540}, /* G */ + {16, 576}, /* H */ + {0, 0}, /* I */ + {16, 612}, /* J */ + {16, 648}, /* K */ + {0, 0}, /* L */ + {16, 684}, /* M */ + {16, 720}, /* N */ + {0, 0}, /* O */ + {16, 756}, /* P */ + {16, 792}, /* Q */ + {16, 828}, /* R */ + {0, 0}, /* S */ + {16, 864}, /* T */ + {0, 0}, /* U */ + {16, 900}, /* V */ + {16, 936}, /* W */ + {16, 972}, /* X */ + {16, 1008}, /* Y */ +}; + +/* Font information for fun climbing (Demo) 18pt */ +const FONT_INFO funclimbingDemo_18ptFontInfo = { + "Fun Climbing", + 18, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + funclimbingDemo_18ptDescriptors, /* Character descriptor array */ + funclimbingDemo_18ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/funclimbing/funclimbing.h b/lib/fonts/funclimbing/funclimbing.h new file mode 100644 index 00000000000..26614adf125 --- /dev/null +++ b/lib/fonts/funclimbing/funclimbing.h @@ -0,0 +1,7 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +extern const FONT_INFO funclimbingDemo_18ptFontInfo; \ No newline at end of file diff --git a/lib/fonts/graph35pix/graph35pix.c b/lib/fonts/graph35pix/graph35pix.c new file mode 100644 index 00000000000..d4edf52e2dd --- /dev/null +++ b/lib/fonts/graph35pix/graph35pix.c @@ -0,0 +1,941 @@ +#include "graph35pix.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for Graph 35+ pix 12pt +*/ + +/* Character bitmaps for Graph 35+ pix 12pt */ +const uint8_t graph35pix_12ptBitmaps[] = { + /* @0 '-' (10 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @28 '0' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0xC3, + 0x03, + 0xC3, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x0F, + 0x03, + 0x0F, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @56 '1' (10 pixels wide) */ + 0x30, + 0x00, + 0x30, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @84 '2' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + + /* @112 '3' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @140 '4' (10 pixels wide) */ + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xF0, + 0x00, + 0xF0, + 0x00, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @168 '5' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @196 '6' (10 pixels wide) */ + 0xF0, + 0x00, + 0xF0, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @224 '7' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @252 '8' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @280 '9' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + + /* @308 'B' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x00, + 0xFF, + 0x00, + + /* @336 'C' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @364 'D' (10 pixels wide) */ + 0x3F, + 0x00, + 0x3F, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x3F, + 0x00, + 0x3F, + 0x00, + + /* @392 'F' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @420 'G' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0xF3, + 0x03, + 0xF3, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFC, + 0x03, + 0xFC, + 0x03, + + /* @448 'H' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @476 'J' (10 pixels wide) */ + 0xF0, + 0x03, + 0xF0, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + + /* @504 'K' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x33, + 0x00, + 0x33, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0x00, + 0x33, + 0x00, + 0x33, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @532 'M' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0xCF, + 0x03, + 0xCF, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @560 'N' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x0F, + 0x03, + 0x0F, + 0x03, + 0x0F, + 0x03, + 0x0F, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xC3, + 0x03, + 0xC3, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @588 'P' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @616 'Q' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x3C, + 0x03, + 0x3C, + 0x03, + + /* @644 'R' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x33, + 0x00, + 0x33, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @672 'T' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @700 'V' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @728 'W' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xCC, + 0x00, + 0xCC, + 0x00, + + /* @756 'X' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @784 'Y' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, +}; + +/* Character descriptors for Graph 35+ pix 12pt */ +/* { [Char width in bits], [Offset into graph35pix_12ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO graph35pix_12ptDescriptors[] = { + {10, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {10, 28}, /* 0 */ + {10, 56}, /* 1 */ + {10, 84}, /* 2 */ + {10, 112}, /* 3 */ + {10, 140}, /* 4 */ + {10, 168}, /* 5 */ + {10, 196}, /* 6 */ + {10, 224}, /* 7 */ + {10, 252}, /* 8 */ + {10, 280}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {10, 308}, /* B */ + {10, 336}, /* C */ + {10, 364}, /* D */ + {0, 0}, /* E */ + {10, 392}, /* F */ + {10, 420}, /* G */ + {10, 448}, /* H */ + {0, 0}, /* I */ + {10, 476}, /* J */ + {10, 504}, /* K */ + {0, 0}, /* L */ + {10, 532}, /* M */ + {10, 560}, /* N */ + {0, 0}, /* O */ + {10, 588}, /* P */ + {10, 616}, /* Q */ + {10, 644}, /* R */ + {0, 0}, /* S */ + {10, 672}, /* T */ + {0, 0}, /* U */ + {10, 700}, /* V */ + {10, 728}, /* W */ + {10, 756}, /* X */ + {10, 784}, /* Y */ +}; + +/* Font information for Graph 35+ pix 12pt */ +const FONT_INFO graph35pix_12ptFontInfo = { + "Graph 35pix", + 14, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + graph35pix_12ptDescriptors, /* Character descriptor array */ + graph35pix_12ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/graph35pix/graph35pix.h b/lib/fonts/graph35pix/graph35pix.h new file mode 100644 index 00000000000..07838e3ba72 --- /dev/null +++ b/lib/fonts/graph35pix/graph35pix.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for Graph 35+ pix 12pt */ +extern const FONT_INFO graph35pix_12ptFontInfo; diff --git a/lib/fonts/karma_future/karma_future.c b/lib/fonts/karma_future/karma_future.c new file mode 100644 index 00000000000..23bebbc010f --- /dev/null +++ b/lib/fonts/karma_future/karma_future.c @@ -0,0 +1,1173 @@ +#include "karma_future.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for Karma Future 14pt +*/ + +/* Character bitmaps for Karma Future 14pt */ +const uint8_t karmaFuture_14ptBitmaps[] = { + /* @0 '-' (12 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x03, + 0x02, + 0x02, + 0x02, + 0x02, + 0xFE, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @36 '0' (12 pixels wide) */ + 0xF8, + 0x01, + 0x0E, + 0x03, + 0x02, + 0x06, + 0xF3, + 0x06, + 0xF9, + 0x0C, + 0x79, + 0x0C, + 0x39, + 0x0D, + 0x99, + 0x0C, + 0xC9, + 0x0C, + 0xE9, + 0x0C, + 0xF1, + 0x0C, + 0xF1, + 0x0C, + 0xF1, + 0x0C, + 0xF3, + 0x0E, + 0x02, + 0x0E, + 0x0E, + 0x07, + 0xFC, + 0x07, + 0xF8, + 0x01, + + /* @72 '1' (12 pixels wide) */ + 0x70, + 0x00, + 0xD8, + 0x00, + 0xCE, + 0x00, + 0xC2, + 0x00, + 0xC2, + 0x00, + 0xDE, + 0x00, + 0xDC, + 0x00, + 0xDC, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xDE, + 0x03, + 0x02, + 0x02, + 0x02, + 0x02, + 0xFE, + 0x03, + 0xFC, + 0x03, + + /* @108 '2' (12 pixels wide) */ + 0xFC, + 0x03, + 0x06, + 0x03, + 0x03, + 0x06, + 0xF1, + 0x04, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0x7F, + 0x0E, + 0x3E, + 0x0F, + 0x1E, + 0x0F, + 0x90, + 0x07, + 0xC8, + 0x03, + 0xE4, + 0x01, + 0xE6, + 0x00, + 0xF3, + 0x07, + 0x01, + 0x0C, + 0x01, + 0x0C, + 0xFF, + 0x0F, + 0xFE, + 0x0F, + + /* @144 '3' (12 pixels wide) */ + 0xFC, + 0x03, + 0x06, + 0x03, + 0x03, + 0x06, + 0xF1, + 0x04, + 0xF9, + 0x0C, + 0xFF, + 0x0C, + 0xFE, + 0x0C, + 0x0E, + 0x0E, + 0x08, + 0x0F, + 0xF8, + 0x0E, + 0xF8, + 0x04, + 0xFF, + 0x0C, + 0xF9, + 0x0C, + 0xF1, + 0x0C, + 0x03, + 0x0E, + 0x06, + 0x0F, + 0xFC, + 0x07, + 0xFC, + 0x03, + + /* @180 '4' (12 pixels wide) */ + 0xE0, + 0x03, + 0x30, + 0x03, + 0x10, + 0x03, + 0x08, + 0x03, + 0x4C, + 0x03, + 0x64, + 0x03, + 0x72, + 0x03, + 0x73, + 0x03, + 0x79, + 0x03, + 0x01, + 0x04, + 0x01, + 0x0C, + 0x7F, + 0x0F, + 0x7E, + 0x0F, + 0x7E, + 0x0F, + 0x40, + 0x03, + 0x40, + 0x03, + 0xC0, + 0x03, + 0x80, + 0x03, + + /* @216 '5' (12 pixels wide) */ + 0xFF, + 0x07, + 0x01, + 0x0C, + 0x01, + 0x0C, + 0xF9, + 0x0F, + 0xF9, + 0x0F, + 0xF9, + 0x0F, + 0x01, + 0x03, + 0x03, + 0x06, + 0xFE, + 0x04, + 0xFE, + 0x0C, + 0xFC, + 0x0C, + 0xFF, + 0x0C, + 0xF9, + 0x0C, + 0xF1, + 0x0C, + 0x03, + 0x0E, + 0x06, + 0x0F, + 0xFC, + 0x07, + 0xF8, + 0x03, + + /* @252 '6' (12 pixels wide) */ + 0xF0, + 0x03, + 0x10, + 0x03, + 0x08, + 0x03, + 0xCC, + 0x03, + 0xE6, + 0x03, + 0xF2, + 0x03, + 0xF2, + 0x00, + 0x01, + 0x03, + 0x01, + 0x06, + 0xF1, + 0x04, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0xF1, + 0x0C, + 0x03, + 0x0E, + 0x06, + 0x0F, + 0xFC, + 0x07, + 0xF8, + 0x03, + + /* @288 '7' (12 pixels wide) */ + 0xFF, + 0x07, + 0x01, + 0x0C, + 0x01, + 0x0C, + 0xFF, + 0x0E, + 0xFE, + 0x0E, + 0x7E, + 0x0F, + 0x40, + 0x07, + 0x60, + 0x07, + 0x20, + 0x03, + 0x30, + 0x03, + 0x90, + 0x01, + 0x90, + 0x01, + 0xD0, + 0x00, + 0xC8, + 0x00, + 0xE8, + 0x00, + 0xE8, + 0x00, + 0x78, + 0x00, + 0x78, + 0x00, + + /* @324 '8' (12 pixels wide) */ + 0xF8, + 0x01, + 0x0C, + 0x01, + 0x06, + 0x03, + 0x72, + 0x06, + 0xF2, + 0x06, + 0xF2, + 0x06, + 0x72, + 0x06, + 0x06, + 0x07, + 0x02, + 0x06, + 0xF1, + 0x04, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0xF1, + 0x0C, + 0x03, + 0x0E, + 0x06, + 0x0F, + 0xFC, + 0x07, + 0xF8, + 0x03, + + /* @360 '9' (12 pixels wide) */ + 0xFC, + 0x03, + 0x06, + 0x03, + 0x03, + 0x06, + 0xF1, + 0x04, + 0xF9, + 0x0C, + 0xF9, + 0x0C, + 0xF1, + 0x0C, + 0x03, + 0x0C, + 0x06, + 0x0C, + 0xFE, + 0x0E, + 0x7C, + 0x0E, + 0x38, + 0x0F, + 0x30, + 0x07, + 0x1C, + 0x07, + 0x84, + 0x03, + 0xC4, + 0x01, + 0xFC, + 0x00, + 0xF8, + 0x00, + + /* @396 'B' (12 pixels wide) */ + 0xFE, + 0x01, + 0x02, + 0x03, + 0x02, + 0x02, + 0x72, + 0x06, + 0xF2, + 0x06, + 0xF2, + 0x06, + 0x72, + 0x06, + 0x02, + 0x06, + 0x02, + 0x06, + 0xF2, + 0x0C, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0C, + 0x02, + 0x0E, + 0x02, + 0x0E, + 0xFE, + 0x07, + 0xFC, + 0x03, + + /* @432 'C' (12 pixels wide) */ + 0xF0, + 0x03, + 0x18, + 0x02, + 0x0C, + 0x06, + 0xE4, + 0x0C, + 0xE6, + 0x0D, + 0xF2, + 0x0F, + 0x72, + 0x0F, + 0x32, + 0x0F, + 0x12, + 0x00, + 0x12, + 0x00, + 0x12, + 0x00, + 0x32, + 0x07, + 0xA6, + 0x0D, + 0xE4, + 0x0C, + 0x08, + 0x0E, + 0x18, + 0x0E, + 0xF0, + 0x07, + 0xF0, + 0x03, + + /* @468 'D' (12 pixels wide) */ + 0xFE, + 0x01, + 0x02, + 0x03, + 0x02, + 0x02, + 0x72, + 0x06, + 0xF2, + 0x06, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0E, + 0xF2, + 0x0E, + 0x72, + 0x0E, + 0x02, + 0x06, + 0x02, + 0x07, + 0xFE, + 0x03, + 0xFC, + 0x01, + + /* @504 'F' (12 pixels wide) */ + 0xFE, + 0x07, + 0x02, + 0x0C, + 0x02, + 0x0C, + 0xF2, + 0x0F, + 0xF2, + 0x0F, + 0xF2, + 0x0F, + 0x12, + 0x00, + 0xF2, + 0x03, + 0x02, + 0x02, + 0xF2, + 0x03, + 0xF2, + 0x03, + 0xF2, + 0x03, + 0x12, + 0x00, + 0x12, + 0x00, + 0x12, + 0x00, + 0x12, + 0x00, + 0x1E, + 0x00, + 0x1C, + 0x00, + + /* @540 'G' (12 pixels wide) */ + 0xF0, + 0x07, + 0x18, + 0x06, + 0x0C, + 0x0C, + 0xE4, + 0x0D, + 0xE6, + 0x0F, + 0xF2, + 0x0F, + 0xF2, + 0x0F, + 0x32, + 0x04, + 0x32, + 0x0C, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xE6, + 0x0D, + 0xE6, + 0x0D, + 0xE4, + 0x0D, + 0x08, + 0x0C, + 0x18, + 0x0C, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + + /* @576 'H' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0xF2, + 0x0D, + 0x02, + 0x0C, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x1E, + 0x0F, + 0x1C, + 0x0F, + + /* @612 'J' (12 pixels wide) */ + 0x80, + 0x03, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x80, + 0x06, + 0x8E, + 0x06, + 0x8A, + 0x06, + 0xDA, + 0x06, + 0x72, + 0x06, + 0x02, + 0x06, + 0x06, + 0x03, + 0xFC, + 0x03, + 0xF8, + 0x01, + + /* @648 'K' (12 pixels wide) */ + 0x1E, + 0x07, + 0x92, + 0x0D, + 0xD2, + 0x0C, + 0x72, + 0x0E, + 0x32, + 0x0E, + 0x12, + 0x0F, + 0x92, + 0x07, + 0xC2, + 0x03, + 0xC2, + 0x01, + 0x92, + 0x01, + 0x12, + 0x01, + 0x32, + 0x02, + 0x32, + 0x02, + 0x72, + 0x06, + 0xD2, + 0x0C, + 0x92, + 0x0D, + 0x9E, + 0x0F, + 0x1C, + 0x0F, + + /* @684 'M' (12 pixels wide) */ + 0x1E, + 0x07, + 0x92, + 0x0D, + 0xA2, + 0x0C, + 0xE2, + 0x0C, + 0x62, + 0x0C, + 0x62, + 0x0C, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x92, + 0x0D, + 0x92, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x1E, + 0x0F, + 0x1C, + 0x0F, + + /* @720 'N' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0x22, + 0x0D, + 0x62, + 0x0D, + 0x62, + 0x0D, + 0xD2, + 0x0D, + 0xD2, + 0x0D, + 0x92, + 0x0D, + 0x92, + 0x0D, + 0x32, + 0x0D, + 0x32, + 0x0D, + 0x72, + 0x0C, + 0x52, + 0x0C, + 0xD2, + 0x0C, + 0x92, + 0x0C, + 0x92, + 0x0D, + 0x1E, + 0x0F, + 0x1C, + 0x0F, + + /* @756 'P' (12 pixels wide) */ + 0xFE, + 0x03, + 0x02, + 0x02, + 0x02, + 0x06, + 0xF2, + 0x0C, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0C, + 0x02, + 0x0E, + 0x02, + 0x0E, + 0xF2, + 0x0F, + 0xF2, + 0x07, + 0xF2, + 0x03, + 0x12, + 0x00, + 0x12, + 0x00, + 0x12, + 0x00, + 0x1E, + 0x00, + 0x1C, + 0x00, + + /* @792 'Q' (12 pixels wide) */ + 0xF0, + 0x01, + 0x18, + 0x03, + 0x0C, + 0x02, + 0x64, + 0x06, + 0xE6, + 0x06, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0x32, + 0x0D, + 0x26, + 0x0E, + 0x26, + 0x0E, + 0x64, + 0x0E, + 0x08, + 0x0C, + 0x18, + 0x0D, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + + /* @828 'R' (12 pixels wide) */ + 0xFE, + 0x03, + 0x02, + 0x02, + 0x02, + 0x06, + 0xF2, + 0x0C, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0D, + 0xF2, + 0x0C, + 0x02, + 0x0E, + 0x02, + 0x0E, + 0x12, + 0x0F, + 0x32, + 0x06, + 0x32, + 0x06, + 0x72, + 0x06, + 0xD2, + 0x0C, + 0x92, + 0x0D, + 0x9E, + 0x0F, + 0x1C, + 0x0F, + + /* @864 'T' (12 pixels wide) */ + 0xFE, + 0x03, + 0x02, + 0x02, + 0x02, + 0x02, + 0xDE, + 0x03, + 0xDC, + 0x03, + 0xDC, + 0x03, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xD0, + 0x00, + 0xF0, + 0x00, + 0xF0, + 0x00, + + /* @900 'V' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x92, + 0x0D, + 0xA6, + 0x0E, + 0xA4, + 0x0E, + 0xA4, + 0x0E, + 0xE4, + 0x06, + 0x6C, + 0x06, + 0x68, + 0x06, + 0x68, + 0x02, + 0x18, + 0x03, + 0x10, + 0x03, + 0x10, + 0x01, + 0x90, + 0x01, + 0x90, + 0x01, + 0xE0, + 0x01, + 0xE0, + 0x01, + + /* @936 'W' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0x12, + 0x0D, + 0xF2, + 0x0D, + 0x92, + 0x0D, + 0x12, + 0x0D, + 0x62, + 0x0C, + 0x62, + 0x0C, + 0xE2, + 0x0C, + 0xE2, + 0x0C, + 0xB2, + 0x0D, + 0x3E, + 0x0F, + 0x1C, + 0x0F, + + /* @972 'X' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0xB2, + 0x0D, + 0xA6, + 0x0E, + 0xE4, + 0x0E, + 0x6C, + 0x0E, + 0x18, + 0x07, + 0x98, + 0x07, + 0x90, + 0x03, + 0x10, + 0x01, + 0x68, + 0x02, + 0xE4, + 0x02, + 0xE4, + 0x06, + 0xE6, + 0x06, + 0xB2, + 0x0D, + 0x32, + 0x0D, + 0x1E, + 0x0F, + 0x1C, + 0x0E, + + /* @1008 'Y' (12 pixels wide) */ + 0x1E, + 0x07, + 0x12, + 0x0D, + 0xB2, + 0x0D, + 0xA6, + 0x0E, + 0xE4, + 0x0E, + 0x6C, + 0x0E, + 0x68, + 0x06, + 0x18, + 0x07, + 0x18, + 0x03, + 0x90, + 0x03, + 0x90, + 0x01, + 0x90, + 0x01, + 0xA0, + 0x01, + 0xA0, + 0x01, + 0xA0, + 0x01, + 0xE0, + 0x01, + 0xE0, + 0x01, + 0xE0, + 0x01, +}; + +/* Character descriptors for Karma Future 14pt */ +/* { [Char width in bits], [Offset into karmaFuture_14ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO karmaFuture_14ptDescriptors[] = { + {12, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {12, 36}, /* 0 */ + {12, 72}, /* 1 */ + {12, 108}, /* 2 */ + {12, 144}, /* 3 */ + {12, 180}, /* 4 */ + {12, 216}, /* 5 */ + {12, 252}, /* 6 */ + {12, 288}, /* 7 */ + {12, 324}, /* 8 */ + {12, 360}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {12, 396}, /* B */ + {12, 432}, /* C */ + {12, 468}, /* D */ + {0, 0}, /* E */ + {12, 504}, /* F */ + {12, 540}, /* G */ + {12, 576}, /* H */ + {0, 0}, /* I */ + {12, 612}, /* J */ + {12, 648}, /* K */ + {0, 0}, /* L */ + {12, 684}, /* M */ + {12, 720}, /* N */ + {0, 0}, /* O */ + {12, 756}, /* P */ + {12, 792}, /* Q */ + {12, 828}, /* R */ + {0, 0}, /* S */ + {12, 864}, /* T */ + {0, 0}, /* U */ + {12, 900}, /* V */ + {12, 936}, /* W */ + {12, 972}, /* X */ + {12, 1008}, /* Y */ +}; + +/* Font information for Karma Future 14pt */ +const FONT_INFO karmaFuture_14ptFontInfo = { + "Karma Future", + 18, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + karmaFuture_14ptDescriptors, /* Character descriptor array */ + karmaFuture_14ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/karma_future/karma_future.h b/lib/fonts/karma_future/karma_future.h new file mode 100644 index 00000000000..3a6352ef18d --- /dev/null +++ b/lib/fonts/karma_future/karma_future.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for Karma Future 14pt */ +extern const FONT_INFO karmaFuture_14ptFontInfo; diff --git a/lib/fonts/mode_nine/mode_nine.c b/lib/fonts/mode_nine/mode_nine.c new file mode 100644 index 00000000000..5d19f59aa9e --- /dev/null +++ b/lib/fonts/mode_nine/mode_nine.c @@ -0,0 +1,942 @@ +#include "mode_nine.h" +#include + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for ModeNine 15pt +*/ + +/* Character bitmaps for ModeNine 15pt */ +const uint8_t modeNine_15ptBitmaps[] = { + /* @0 '-' (10 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @28 '0' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @56 '1' (10 pixels wide) */ + 0x30, + 0x00, + 0x38, + 0x00, + 0x3C, + 0x00, + 0x3C, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0xFC, + 0x00, + 0xFC, + 0x00, + + /* @84 '2' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x80, + 0x03, + 0xFC, + 0x01, + 0xFE, + 0x00, + 0x07, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + + /* @112 '3' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x70, + 0x00, + 0xF8, + 0x00, + 0xFC, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @140 '4' (10 pixels wide) */ + 0xE0, + 0x00, + 0xF0, + 0x00, + 0xF8, + 0x00, + 0xDC, + 0x00, + 0xCE, + 0x00, + 0xC7, + 0x00, + 0xC3, + 0x00, + 0xC3, + 0x00, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + + /* @168 '5' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x80, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @196 '6' (10 pixels wide) */ + 0xF0, + 0x00, + 0xFC, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @224 '7' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x30, + 0x00, + 0x18, + 0x00, + 0x1C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + + /* @252 '8' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @280 '9' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xFC, + 0x00, + 0x3C, + 0x00, + + /* @308 'B' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @336 'C' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @364 'D' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + + /* @392 'F' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xFF, + 0x00, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @420 'G' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0xC3, + 0x03, + 0xC3, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0xFE, + 0x03, + 0xFC, + 0x03, + + /* @448 'H' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @476 'J' (10 pixels wide) */ + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x00, + + /* @504 'K' (10 pixels wide) */ + 0x83, + 0x03, + 0xC3, + 0x01, + 0xE3, + 0x00, + 0x73, + 0x00, + 0x3B, + 0x00, + 0x1F, + 0x00, + 0x0F, + 0x00, + 0x0F, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @532 'M' (10 pixels wide) */ + 0x03, + 0x03, + 0x87, + 0x03, + 0xCF, + 0x03, + 0xFF, + 0x03, + 0x7B, + 0x03, + 0x33, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @560 'N' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x07, + 0x03, + 0x0F, + 0x03, + 0x1F, + 0x03, + 0x3B, + 0x03, + 0x73, + 0x03, + 0xE3, + 0x03, + 0xC3, + 0x03, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @588 'P' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + + /* @616 'Q' (10 pixels wide) */ + 0xFC, + 0x00, + 0xFE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x73, + 0x03, + 0xE7, + 0x03, + 0xFE, + 0x01, + 0xFC, + 0x03, + + /* @644 'R' (10 pixels wide) */ + 0xFF, + 0x00, + 0xFF, + 0x01, + 0x83, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x83, + 0x03, + 0xFF, + 0x01, + 0xFF, + 0x00, + 0x1F, + 0x00, + 0x3B, + 0x00, + 0x73, + 0x00, + 0xE3, + 0x00, + 0xC3, + 0x01, + 0x83, + 0x03, + + /* @672 'T' (10 pixels wide) */ + 0xFF, + 0x03, + 0xFF, + 0x03, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + + /* @700 'V' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x86, + 0x01, + 0x86, + 0x01, + 0xCC, + 0x00, + 0xCC, + 0x00, + 0x78, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + + /* @728 'W' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0x33, + 0x03, + 0xFF, + 0x03, + 0xFE, + 0x01, + + /* @756 'X' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0xFC, + 0x00, + 0xCE, + 0x01, + 0x87, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + + /* @784 'Y' (10 pixels wide) */ + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x03, + 0x87, + 0x03, + 0xCE, + 0x01, + 0xFC, + 0x00, + 0x78, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, + 0x30, + 0x00, +}; + +/* Character descriptors for ModeNine 15pt */ +/* { [Char width in bits], [Offset into modeNine_15ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO modeNine_15ptDescriptors[] = { + {10, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {10, 28}, /* 0 */ + {10, 56}, /* 1 */ + {10, 84}, /* 2 */ + {10, 112}, /* 3 */ + {10, 140}, /* 4 */ + {10, 168}, /* 5 */ + {10, 196}, /* 6 */ + {10, 224}, /* 7 */ + {10, 252}, /* 8 */ + {10, 280}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {10, 308}, /* B */ + {10, 336}, /* C */ + {10, 364}, /* D */ + {0, 0}, /* E */ + {10, 392}, /* F */ + {10, 420}, /* G */ + {10, 448}, /* H */ + {0, 0}, /* I */ + {10, 476}, /* J */ + {10, 504}, /* K */ + {0, 0}, /* L */ + {10, 532}, /* M */ + {10, 560}, /* N */ + {0, 0}, /* O */ + {10, 588}, /* P */ + {10, 616}, /* Q */ + {10, 644}, /* R */ + {0, 0}, /* S */ + {10, 672}, /* T */ + {0, 0}, /* U */ + {10, 700}, /* V */ + {10, 728}, /* W */ + {10, 756}, /* X */ + {10, 784}, /* Y */ +}; + +/* Font information for ModeNine 15pt */ +const FONT_INFO modeNine_15ptFontInfo = { + "Mode Nine", + 14, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + modeNine_15ptDescriptors, /* Character descriptor array */ + modeNine_15ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/mode_nine/mode_nine.h b/lib/fonts/mode_nine/mode_nine.h new file mode 100644 index 00000000000..516e261f8b4 --- /dev/null +++ b/lib/fonts/mode_nine/mode_nine.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for ModeNine 15pt */ +extern const FONT_INFO modeNine_15ptFontInfo; diff --git a/lib/fonts/pixelflag/pixelflag.c b/lib/fonts/pixelflag/pixelflag.c new file mode 100644 index 00000000000..eacf36b2827 --- /dev/null +++ b/lib/fonts/pixelflag/pixelflag.c @@ -0,0 +1,1115 @@ +#include "pixelflag.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for {PixelFlag} 18pt +*/ + +/* Character bitmaps for {PixelFlag} 18pt */ +const uint8_t pixelFlag_18ptBitmaps[] = { + /* @0 '-' (13 pixels wide) */ + 0xFE, + 0x07, + 0xFE, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFE, + 0x07, + 0xFE, + 0x07, + + /* @34 '0' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @68 '1' (13 pixels wide) */ + 0xff, + 0xff, + 0xff, + 0xff, + 0x00, + 0xe0, + 0xc0, + 0xe0, + 0xc0, + 0xe0, + 0xe0, + 0xe0, + 0xe0, + 0xe0, + 0xc0, + 0xe0, + 0xc0, + 0xe0, + 0xc0, + 0xe0, + 0xc0, + 0xe0, + 0xc0, + 0xe0, + 0xe0, + 0xe3, + 0xe0, + 0xe3, + 0x00, + 0xe0, + 0xff, + 0xff, + 0xff, + 0xff, + + /* @102 '2' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0xF8, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @136 '3' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0xE0, + 0x07, + 0xE0, + 0x1F, + 0x00, + 0x18, + 0x00, + 0x18, + 0xFC, + 0x1F, + 0xFC, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @170 '4' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x07, + 0xC0, + 0x07, + 0xC0, + 0x06, + 0x60, + 0x06, + 0x78, + 0x06, + 0x18, + 0x06, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x00, + 0x06, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @204 '5' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x1F, + 0x00, + 0x18, + 0x00, + 0x18, + 0xFC, + 0x1F, + 0xFC, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @238 '6' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @272 '7' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x03, + 0xC0, + 0x00, + 0xE0, + 0x00, + 0x60, + 0x00, + 0x18, + 0x00, + 0x1C, + 0x00, + 0x0C, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @306 '8' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xF8, + 0x07, + 0xFC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @340 '9' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xF8, + 0x1F, + 0xF8, + 0x1F, + 0x00, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @374 'B' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x07, + 0xFC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xFC, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @408 'C' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @442 'D' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xFC, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @476 'F' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x03, + 0xFC, + 0x03, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @510 'G' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x00, + 0xCC, + 0x1F, + 0xCC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @544 'H' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @578 'J' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @612 'K' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x06, + 0x0C, + 0x07, + 0x0C, + 0x03, + 0xFC, + 0x00, + 0xFC, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x06, + 0x0C, + 0x1E, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @646 'M' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x1C, + 0x1E, + 0x7C, + 0x1F, + 0x6C, + 0x1B, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @680 'N' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x1C, + 0x18, + 0x7C, + 0x18, + 0x6C, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x1B, + 0x0C, + 0x1B, + 0x0C, + 0x1E, + 0x0C, + 0x1E, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @714 'P' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @748 'Q' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xF8, + 0x07, + 0xF8, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x1B, + 0x0C, + 0x1B, + 0x0C, + 0x1E, + 0xFC, + 0x1F, + 0xF8, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @782 'R' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xFC, + 0x07, + 0xFC, + 0x1F, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @816 'T' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0xFC, + 0x1F, + 0xFC, + 0x1F, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @850 'V' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x1C, + 0x1E, + 0x18, + 0x06, + 0x60, + 0x03, + 0xE0, + 0x03, + 0xC0, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @884 'W' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0xCC, + 0x18, + 0xFC, + 0x1F, + 0x78, + 0x07, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @918 'X' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x18, + 0x06, + 0x78, + 0x07, + 0x60, + 0x03, + 0xC0, + 0x00, + 0xE0, + 0x03, + 0x60, + 0x03, + 0x18, + 0x06, + 0x1C, + 0x1E, + 0x0C, + 0x18, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, + + /* @952 'Y' (13 pixels wide) */ + 0xFF, + 0x1F, + 0xFF, + 0x1F, + 0x00, + 0x00, + 0x0C, + 0x18, + 0x0C, + 0x18, + 0x18, + 0x06, + 0x78, + 0x07, + 0x60, + 0x03, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0xC0, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x1F, + 0xFF, + 0x1F, +}; + +/* Character descriptors for {PixelFlag} 18pt */ +/* { [Char width in bits], [Offset into pixelFlag_18ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO pixelFlag_18ptDescriptors[] = { + {13, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {13, 34}, /* 0 */ + {13, 68}, /* 1 */ + {13, 102}, /* 2 */ + {13, 136}, /* 3 */ + {13, 170}, /* 4 */ + {13, 204}, /* 5 */ + {13, 238}, /* 6 */ + {13, 272}, /* 7 */ + {13, 306}, /* 8 */ + {13, 340}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {13, 374}, /* B */ + {13, 408}, /* C */ + {13, 442}, /* D */ + {0, 0}, /* E */ + {13, 476}, /* F */ + {13, 510}, /* G */ + {13, 544}, /* H */ + {0, 0}, /* I */ + {13, 578}, /* J */ + {13, 612}, /* K */ + {0, 0}, /* L */ + {13, 646}, /* M */ + {13, 680}, /* N */ + {0, 0}, /* O */ + {13, 714}, /* P */ + {13, 748}, /* Q */ + {13, 782}, /* R */ + {0, 0}, /* S */ + {13, 816}, /* T */ + {0, 0}, /* U */ + {13, 850}, /* V */ + {13, 884}, /* W */ + {13, 918}, /* X */ + {13, 952}, /* Y */ +}; + +/* Font information for {PixelFlag} 18pt */ +const FONT_INFO pixelFlag_18ptFontInfo = { + "Pixel Flag", + 17, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 0, /* Width, in pixels, of space character */ + pixelFlag_18ptDescriptors, /* Character descriptor array */ + pixelFlag_18ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/pixelflag/pixelflag.h b/lib/fonts/pixelflag/pixelflag.h new file mode 100644 index 00000000000..dc339c84d60 --- /dev/null +++ b/lib/fonts/pixelflag/pixelflag.h @@ -0,0 +1,7 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +extern const FONT_INFO pixelFlag_18ptFontInfo; \ No newline at end of file diff --git a/lib/fonts/redhat_mono/redhat_mono.c b/lib/fonts/redhat_mono/redhat_mono.c new file mode 100644 index 00000000000..e39b6c81c3a --- /dev/null +++ b/lib/fonts/redhat_mono/redhat_mono.c @@ -0,0 +1,1058 @@ +#include "redhat_mono.h" +#include + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for Red Hat Mono 16pt +*/ + +/* Character bitmaps for Red Hat Mono 16pt */ +const uint8_t redHatMono_16ptBitmaps[] = { + /* @0 '-' (12 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xF8, + 0x03, + 0xF8, + 0x03, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @32 '0' (12 pixels wide) */ + 0xF0, + 0x00, + 0xF8, + 0x03, + 0x1C, + 0x07, + 0x0E, + 0x07, + 0x8E, + 0x07, + 0x86, + 0x0E, + 0xC6, + 0x0C, + 0x66, + 0x0C, + 0x66, + 0x0C, + 0x36, + 0x0E, + 0x36, + 0x0E, + 0x1E, + 0x06, + 0x1C, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0x00, + 0x00, + + /* @64 '1' (12 pixels wide) */ + 0x40, + 0x00, + 0x78, + 0x00, + 0x7E, + 0x00, + 0x66, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0x60, + 0x00, + 0xFE, + 0x07, + 0xFE, + 0x07, + 0x00, + 0x00, + + /* @96 '2' (12 pixels wide) */ + 0xF0, + 0x01, + 0xFC, + 0x03, + 0x0E, + 0x07, + 0x04, + 0x06, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x01, + 0xE0, + 0x00, + 0x70, + 0x00, + 0x38, + 0x00, + 0x1C, + 0x00, + 0xFE, + 0x07, + 0xFE, + 0x07, + 0x00, + 0x00, + + /* @128 '3' (12 pixels wide) */ + 0xF0, + 0x00, + 0xFC, + 0x03, + 0x0E, + 0x07, + 0x04, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x80, + 0x03, + 0xE0, + 0x00, + 0xE0, + 0x03, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x0E, + 0x06, + 0x07, + 0xFE, + 0x07, + 0xF8, + 0x01, + 0x00, + 0x00, + + /* @160 '4' (12 pixels wide) */ + 0x00, + 0x03, + 0x80, + 0x03, + 0xC0, + 0x03, + 0xE0, + 0x03, + 0x70, + 0x03, + 0x30, + 0x03, + 0x18, + 0x03, + 0x0C, + 0x03, + 0x0E, + 0x03, + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x03, + 0x00, + 0x00, + + /* @192 '5' (12 pixels wide) */ + 0xFC, + 0x07, + 0xFC, + 0x07, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x01, + 0xFE, + 0x07, + 0x00, + 0x07, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x06, + 0x07, + 0xFE, + 0x03, + 0xF8, + 0x01, + 0x00, + 0x00, + + /* @224 '6' (12 pixels wide) */ + 0xE0, + 0x03, + 0xF8, + 0x07, + 0x3C, + 0x06, + 0x0C, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x00, + 0xE6, + 0x03, + 0xFE, + 0x07, + 0x0E, + 0x0E, + 0x06, + 0x0C, + 0x06, + 0x0C, + 0x0E, + 0x0E, + 0x1C, + 0x06, + 0xF8, + 0x07, + 0xF0, + 0x03, + 0x00, + 0x00, + + /* @256 '7' (12 pixels wide) */ + 0xFE, + 0x0F, + 0xFE, + 0x0F, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x00, + 0x07, + 0x00, + 0x03, + 0x80, + 0x01, + 0xC0, + 0x01, + 0xC0, + 0x00, + 0xE0, + 0x00, + 0x60, + 0x00, + 0x70, + 0x00, + 0x30, + 0x00, + 0x38, + 0x00, + 0x1C, + 0x00, + 0x00, + 0x00, + + /* @288 '8' (12 pixels wide) */ + 0xF0, + 0x01, + 0xFC, + 0x03, + 0x1C, + 0x07, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0C, + 0x06, + 0xBC, + 0x03, + 0xF0, + 0x01, + 0x1C, + 0x07, + 0x0E, + 0x06, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x0E, + 0x06, + 0xFC, + 0x07, + 0xF8, + 0x03, + 0x00, + 0x00, + + /* @320 '9' (12 pixels wide) */ + 0xF8, + 0x01, + 0xFC, + 0x03, + 0x0C, + 0x07, + 0x0E, + 0x0E, + 0x06, + 0x0C, + 0x06, + 0x0C, + 0x0E, + 0x0E, + 0xFC, + 0x0F, + 0xF8, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0E, + 0x00, + 0x06, + 0x8C, + 0x07, + 0xFC, + 0x03, + 0xF8, + 0x00, + 0x00, + 0x00, + + /* @352 'B' (12 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x03, + 0x8C, + 0x07, + 0x0C, + 0x06, + 0x0C, + 0x06, + 0x0C, + 0x07, + 0xFC, + 0x03, + 0xFC, + 0x01, + 0x8C, + 0x07, + 0x0C, + 0x06, + 0x0C, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x07, + 0xFC, + 0x07, + 0xFC, + 0x01, + 0x00, + 0x00, + + /* @384 'C' (12 pixels wide) */ + 0xF0, + 0x01, + 0xF8, + 0x03, + 0xBC, + 0x07, + 0x0C, + 0x06, + 0x0E, + 0x0E, + 0x0E, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x0E, + 0x0E, + 0x0E, + 0x0E, + 0x1C, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0x00, + 0x00, + + /* @416 'D' (12 pixels wide) */ + 0x7E, + 0x00, + 0xFE, + 0x01, + 0xCE, + 0x03, + 0x0E, + 0x07, + 0x0E, + 0x06, + 0x0E, + 0x0E, + 0x0E, + 0x0E, + 0x0E, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x06, + 0x0C, + 0x07, + 0x8C, + 0x07, + 0xFC, + 0x03, + 0xFC, + 0x00, + 0x00, + 0x00, + + /* @448 'F' (12 pixels wide) */ + 0xFC, + 0x0F, + 0xFC, + 0x0F, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0xFC, + 0x01, + 0xFC, + 0x01, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x00, + 0x00, + + /* @480 'G' (12 pixels wide) */ + 0xF0, + 0x01, + 0xF8, + 0x03, + 0xBC, + 0x07, + 0x0C, + 0x06, + 0x0E, + 0x0E, + 0x06, + 0x00, + 0x06, + 0x00, + 0x86, + 0x0F, + 0x86, + 0x0F, + 0x06, + 0x0C, + 0x0E, + 0x0E, + 0x0E, + 0x06, + 0x1C, + 0x07, + 0xF8, + 0x03, + 0xF0, + 0x01, + 0x00, + 0x00, + + /* @512 'H' (12 pixels wide) */ + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0xFE, + 0x07, + 0xFE, + 0x07, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x00, + 0x00, + + /* @544 'J' (12 pixels wide) */ + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x00, + 0x06, + 0x04, + 0x06, + 0x07, + 0x07, + 0x8E, + 0x07, + 0xFC, + 0x03, + 0xF8, + 0x01, + 0x00, + 0x00, + + /* @576 'K' (12 pixels wide) */ + 0x0E, + 0x0E, + 0x0E, + 0x07, + 0x0E, + 0x03, + 0x8E, + 0x01, + 0xCE, + 0x01, + 0xEE, + 0x00, + 0x7E, + 0x00, + 0x3E, + 0x00, + 0x7E, + 0x00, + 0xEE, + 0x00, + 0xCE, + 0x01, + 0x8E, + 0x03, + 0x8E, + 0x03, + 0x0E, + 0x07, + 0x0E, + 0x0E, + 0x00, + 0x00, + + /* @608 'M' (12 pixels wide) */ + 0x0E, + 0x0E, + 0x0E, + 0x0F, + 0x1E, + 0x0F, + 0x9E, + 0x0F, + 0xB6, + 0x0F, + 0xB6, + 0x0F, + 0xF6, + 0x0E, + 0xE6, + 0x0E, + 0x66, + 0x0E, + 0x46, + 0x0E, + 0x26, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x00, + 0x00, + + /* @640 'N' (12 pixels wide) */ + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x1E, + 0x06, + 0x3E, + 0x06, + 0x3E, + 0x06, + 0x76, + 0x06, + 0x66, + 0x06, + 0xE6, + 0x06, + 0xC6, + 0x06, + 0xC6, + 0x07, + 0x86, + 0x07, + 0x86, + 0x07, + 0x06, + 0x07, + 0x06, + 0x07, + 0x06, + 0x06, + 0x00, + 0x00, + + /* @672 'P' (12 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x03, + 0x8C, + 0x07, + 0x0C, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x06, + 0xFC, + 0x07, + 0xFC, + 0x03, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x0C, + 0x00, + 0x00, + 0x00, + + /* @704 'Q' (12 pixels wide) */ + 0xF0, + 0x00, + 0xF8, + 0x03, + 0x9C, + 0x07, + 0x0E, + 0x07, + 0x0E, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0x06, + 0x0E, + 0xEE, + 0x0E, + 0xCE, + 0x07, + 0x9C, + 0x07, + 0xFC, + 0x07, + 0xF0, + 0x07, + 0x00, + 0x0A, + + /* @736 'R' (12 pixels wide) */ + 0xFC, + 0x00, + 0xFC, + 0x03, + 0x8C, + 0x07, + 0x0C, + 0x06, + 0x0C, + 0x0E, + 0x0C, + 0x0E, + 0x0C, + 0x07, + 0xFC, + 0x07, + 0xFC, + 0x01, + 0x8C, + 0x01, + 0x8C, + 0x03, + 0x0C, + 0x03, + 0x0C, + 0x07, + 0x0C, + 0x06, + 0x0C, + 0x0E, + 0x00, + 0x00, + + /* @768 'T' (12 pixels wide) */ + 0xFF, + 0x0F, + 0xFF, + 0x0F, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0x00, + 0x00, + + /* @800 'V' (12 pixels wide) */ + 0x07, + 0x0C, + 0x06, + 0x0C, + 0x06, + 0x0E, + 0x0E, + 0x06, + 0x0C, + 0x06, + 0x0C, + 0x07, + 0x1C, + 0x03, + 0x1C, + 0x03, + 0x98, + 0x03, + 0x98, + 0x03, + 0xB8, + 0x01, + 0xB0, + 0x01, + 0xF0, + 0x01, + 0xF0, + 0x00, + 0xE0, + 0x00, + 0x00, + 0x00, + + /* @832 'W' (12 pixels wide) */ + 0x67, + 0x0C, + 0xE6, + 0x0C, + 0xE6, + 0x0C, + 0xE6, + 0x0C, + 0xE6, + 0x0C, + 0xF6, + 0x0C, + 0xB6, + 0x0E, + 0xB6, + 0x07, + 0xB6, + 0x07, + 0x96, + 0x07, + 0x9C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x1C, + 0x07, + 0x0C, + 0x07, + 0x00, + 0x00, + + /* @864 'X' (12 pixels wide) */ + 0x0E, + 0x0E, + 0x0E, + 0x06, + 0x1C, + 0x07, + 0x98, + 0x03, + 0xB8, + 0x01, + 0xF0, + 0x01, + 0xF0, + 0x00, + 0xE0, + 0x00, + 0xF0, + 0x00, + 0xF0, + 0x01, + 0xB8, + 0x03, + 0x9C, + 0x03, + 0x0C, + 0x07, + 0x0E, + 0x06, + 0x07, + 0x0E, + 0x00, + 0x00, + + /* @896 'Y' (12 pixels wide) */ + 0x07, + 0x0C, + 0x0E, + 0x0E, + 0x0E, + 0x06, + 0x1C, + 0x07, + 0x18, + 0x03, + 0xB8, + 0x03, + 0xF0, + 0x01, + 0xF0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0xE0, + 0x00, + 0x00, + 0x00, +}; + +/* Character descriptors for Red Hat Mono 16pt */ +/* { [Char width in bits], [Offset into redHatMono_16ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO redHatMono_16ptDescriptors[] = { + {12, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {12, 32}, /* 0 */ + {12, 64}, /* 1 */ + {12, 96}, /* 2 */ + {12, 128}, /* 3 */ + {12, 160}, /* 4 */ + {12, 192}, /* 5 */ + {12, 224}, /* 6 */ + {12, 256}, /* 7 */ + {12, 288}, /* 8 */ + {12, 320}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {12, 352}, /* B */ + {12, 384}, /* C */ + {12, 416}, /* D */ + {0, 0}, /* E */ + {12, 448}, /* F */ + {12, 480}, /* G */ + {12, 512}, /* H */ + {0, 0}, /* I */ + {12, 544}, /* J */ + {12, 576}, /* K */ + {0, 0}, /* L */ + {12, 608}, /* M */ + {12, 640}, /* N */ + {0, 0}, /* O */ + {12, 672}, /* P */ + {12, 704}, /* Q */ + {12, 736}, /* R */ + {0, 0}, /* S */ + {12, 768}, /* T */ + {0, 0}, /* U */ + {12, 800}, /* V */ + {12, 832}, /* W */ + {12, 864}, /* X */ + {12, 896}, /* Y */ +}; + +/* Font information for Red Hat Mono 16pt */ +const FONT_INFO redHatMono_16ptFontInfo = { + "RedHat Mono", + 16, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + redHatMono_16ptDescriptors, /* Character descriptor array */ + redHatMono_16ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/redhat_mono/redhat_mono.h b/lib/fonts/redhat_mono/redhat_mono.h new file mode 100644 index 00000000000..ac9214d6b12 --- /dev/null +++ b/lib/fonts/redhat_mono/redhat_mono.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font data for Redhat Mono 16pt */ +extern const FONT_INFO redHatMono_16ptFontInfo; \ No newline at end of file diff --git a/lib/fonts/zector/zector.c b/lib/fonts/zector/zector.c new file mode 100644 index 00000000000..c94e455e44c --- /dev/null +++ b/lib/fonts/zector/zector.c @@ -0,0 +1,1057 @@ +#include "zector.h" + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +/* +** Font data for Zector 18pt +*/ + +/* Character bitmaps for Zector 18pt */ +const uint8_t zector_18ptBitmaps[] = { + /* @0 '-' (9 pixels wide) */ + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0xFF, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + + /* @32 '0' (9 pixels wide) */ + 0x3C, + 0x00, + 0xC2, + 0x00, + 0x83, + 0x00, + 0x85, + 0x00, + 0x85, + 0x00, + 0x89, + 0x00, + 0x89, + 0x00, + 0x91, + 0x00, + 0x91, + 0x00, + 0xA1, + 0x00, + 0xA1, + 0x00, + 0xC1, + 0x00, + 0xC1, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @64 '1' (9 pixels wide) */ + 0x10, + 0x00, + 0x18, + 0x00, + 0x18, + 0x00, + 0x14, + 0x00, + 0x14, + 0x00, + 0x12, + 0x00, + 0x12, + 0x00, + 0x11, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0xFF, + 0x00, + + /* @96 '2' (9 pixels wide) */ + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x40, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x10, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x04, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0xFF, + 0x00, + + /* @128 '3' (9 pixels wide) */ + 0xFF, + 0x01, + 0x80, + 0x00, + 0x80, + 0x00, + 0x40, + 0x00, + 0x40, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x10, + 0x00, + 0x60, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x00, + 0x01, + 0x01, + 0x01, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @160 '4' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0xFF, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + + /* @192 '5' (9 pixels wide) */ + 0xFF, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x3F, + 0x00, + 0x40, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0xC2, + 0x00, + 0x3C, + 0x00, + + /* @224 '6' (9 pixels wide) */ + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x3D, + 0x00, + 0x43, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0xC2, + 0x00, + 0x3C, + 0x00, + + /* @256 '7' (9 pixels wide) */ + 0xFF, + 0x01, + 0x80, + 0x00, + 0x80, + 0x00, + 0x40, + 0x00, + 0x40, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x08, + 0x00, + 0x08, + 0x00, + 0x04, + 0x00, + 0x04, + 0x00, + 0x02, + 0x00, + 0x02, + 0x00, + 0x01, + 0x00, + + /* @288 '8' (9 pixels wide) */ + 0x3C, + 0x00, + 0xC2, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @320 '9' (9 pixels wide) */ + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0xC2, + 0x00, + 0xBC, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @352 'B' (9 pixels wide) */ + 0x3F, + 0x00, + 0xC1, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x41, + 0x00, + 0x3F, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x41, + 0x00, + 0x3F, + 0x00, + + /* @384 'C' (9 pixels wide) */ + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @416 'D' (9 pixels wide) */ + 0x3F, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x41, + 0x00, + 0x3F, + 0x00, + + /* @448 'F' (9 pixels wide) */ + 0xFF, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0xFF, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + + /* @480 'G' (9 pixels wide) */ + 0x3C, + 0x00, + 0xC2, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0xF1, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @512 'H' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0xFF, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + + /* @544 'J' (9 pixels wide) */ + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x80, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x42, + 0x00, + 0x3C, + 0x00, + + /* @576 'K' (9 pixels wide) */ + 0x81, + 0x00, + 0xC1, + 0x00, + 0x61, + 0x00, + 0x21, + 0x00, + 0x11, + 0x00, + 0x09, + 0x00, + 0x05, + 0x00, + 0x03, + 0x00, + 0x01, + 0x00, + 0x03, + 0x00, + 0x05, + 0x00, + 0x09, + 0x00, + 0x11, + 0x00, + 0x21, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + + /* @608 'M' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x83, + 0x00, + 0xC3, + 0x00, + 0xC5, + 0x00, + 0xA5, + 0x00, + 0xA9, + 0x00, + 0x91, + 0x00, + 0x91, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + + /* @640 'N' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x83, + 0x00, + 0x83, + 0x00, + 0x85, + 0x00, + 0x85, + 0x00, + 0x89, + 0x00, + 0x89, + 0x00, + 0x91, + 0x00, + 0xA1, + 0x00, + 0xA1, + 0x00, + 0xC1, + 0x00, + 0xC1, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + + /* @672 'P' (9 pixels wide) */ + 0x3F, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x41, + 0x00, + 0x3F, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + 0x01, + 0x00, + + /* @704 'Q' (9 pixels wide) */ + 0x3C, + 0x00, + 0x42, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x91, + 0x00, + 0xA1, + 0x00, + 0xC2, + 0x00, + 0xBC, + 0x00, + + /* @736 'R' (9 pixels wide) */ + 0x3F, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x41, + 0x00, + 0x3F, + 0x00, + 0x03, + 0x00, + 0x05, + 0x00, + 0x09, + 0x00, + 0x11, + 0x00, + 0x21, + 0x00, + 0x41, + 0x00, + 0x81, + 0x00, + + /* @768 'T' (9 pixels wide) */ + 0xFF, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + + /* @800 'V' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x82, + 0x00, + 0x42, + 0x00, + 0x44, + 0x00, + 0x24, + 0x00, + 0x28, + 0x00, + 0x18, + 0x00, + 0x10, + 0x00, + + /* @832 'W' (9 pixels wide) */ + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + 0x91, + 0x00, + 0x91, + 0x00, + 0x99, + 0x00, + 0xA9, + 0x00, + 0xA5, + 0x00, + 0xC5, + 0x00, + 0x83, + 0x00, + 0x81, + 0x00, + 0x81, + 0x00, + + /* @864 'X' (9 pixels wide) */ + 0x81, + 0x00, + 0x82, + 0x00, + 0x82, + 0x00, + 0x44, + 0x00, + 0x44, + 0x00, + 0x28, + 0x00, + 0x28, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x28, + 0x00, + 0x28, + 0x00, + 0x44, + 0x00, + 0x44, + 0x00, + 0x82, + 0x00, + 0x82, + 0x00, + 0x81, + 0x00, + + /* @896 'Y' (9 pixels wide) */ + 0x81, + 0x00, + 0x82, + 0x00, + 0x82, + 0x00, + 0x44, + 0x00, + 0x44, + 0x00, + 0x28, + 0x00, + 0x28, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, + 0x10, + 0x00, +}; + +/* Character descriptors for Zector 18pt */ +/* { [Char width in bits], [Offset into zector_18ptCharBitmaps in bytes] } */ +const FONT_CHAR_INFO zector_18ptDescriptors[] = { + {9, 0}, /* - */ + {0, 0}, /* . */ + {0, 0}, /* / */ + {9, 32}, /* 0 */ + {9, 64}, /* 1 */ + {9, 96}, /* 2 */ + {9, 128}, /* 3 */ + {9, 160}, /* 4 */ + {9, 192}, /* 5 */ + {9, 224}, /* 6 */ + {9, 256}, /* 7 */ + {9, 288}, /* 8 */ + {9, 320}, /* 9 */ + {0, 0}, /* : */ + {0, 0}, /* ; */ + {0, 0}, /* < */ + {0, 0}, /* = */ + {0, 0}, /* > */ + {0, 0}, /* ? */ + {0, 0}, /* @ */ + {0, 0}, /* A */ + {9, 352}, /* B */ + {9, 384}, /* C */ + {9, 416}, /* D */ + {0, 0}, /* E */ + {9, 448}, /* F */ + {9, 480}, /* G */ + {9, 512}, /* H */ + {0, 0}, /* I */ + {9, 544}, /* J */ + {9, 576}, /* K */ + {0, 0}, /* L */ + {9, 608}, /* M */ + {9, 640}, /* N */ + {0, 0}, /* O */ + {9, 672}, /* P */ + {9, 704}, /* Q */ + {9, 736}, /* R */ + {0, 0}, /* S */ + {9, 768}, /* T */ + {0, 0}, /* U */ + {9, 800}, /* V */ + {9, 832}, /* W */ + {9, 864}, /* X */ + {9, 896}, /* Y */ +}; + +/* Font information for Zector 18pt */ +const FONT_INFO zector_18ptFontInfo = { + "Zector", + 16, /* Character height */ + '-', /* Start character */ + 'Y', /* End character */ + 2, /* Width, in pixels, of space character */ + zector_18ptDescriptors, /* Character descriptor array */ + zector_18ptBitmaps, /* Character bitmap array */ +}; diff --git a/lib/fonts/zector/zector.h b/lib/fonts/zector/zector.h new file mode 100644 index 00000000000..2a5cf590796 --- /dev/null +++ b/lib/fonts/zector/zector.h @@ -0,0 +1,8 @@ +#pragma once + +/* GENERATED BY https://github.com/pavius/the-dot-factory */ + +#include "../font_info.h" + +/* Font information for Zector 18pt */ +extern const FONT_INFO zector_18ptFontInfo; \ No newline at end of file diff --git a/lib/polyfills/memset_s.c b/lib/polyfills/memset_s.c new file mode 100644 index 00000000000..81c285c0da4 --- /dev/null +++ b/lib/polyfills/memset_s.c @@ -0,0 +1,22 @@ +#include "memset_s.h" + +#define RSIZE_MAX 0x7fffffffffffffffUL + +errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n) { + if(!s || smax > RSIZE_MAX) { + return EINVAL; + } + + errno_t violation_present = 0; + if(n > smax) { + n = smax; + violation_present = EINVAL; + } + + volatile unsigned char* v = s; + for(rsize_t i = 0u; i < n; ++i) { + *v++ = (unsigned char)c; + } + + return violation_present; +} \ No newline at end of file diff --git a/lib/polyfills/memset_s.h b/lib/polyfills/memset_s.h new file mode 100644 index 00000000000..54628860d08 --- /dev/null +++ b/lib/polyfills/memset_s.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#ifndef _RSIZE_T_DECLARED +typedef uint64_t rsize_t; +#define _RSIZE_T_DECLARED +#endif +#ifndef _ERRNOT_DECLARED +typedef int16_t errno_t; //-V677 +#define _ERRNOT_DECLARED +#endif + +/** + * @brief Copies the value \p c into each of the first \p n characters of the object pointed to by \p s. + * @param s pointer to the object to fill + * @param smax size of the destination object + * @param c fill byte + * @param n number of bytes to fill + * @return \c 0 on success; non-zero otherwise + */ +errno_t memset_s(void* s, rsize_t smax, int c, rsize_t n); \ No newline at end of file diff --git a/lib/polyfills/strnlen.c b/lib/polyfills/strnlen.c new file mode 100644 index 00000000000..54d1838958a --- /dev/null +++ b/lib/polyfills/strnlen.c @@ -0,0 +1,11 @@ +#include "strnlen.h" + +size_t strnlen(const char* s, size_t maxlen) { + size_t len; + + for(len = 0; len < maxlen; len++, s++) { + if(!*s) break; + } + + return len; +} \ No newline at end of file diff --git a/lib/polyfills/strnlen.h b/lib/polyfills/strnlen.h new file mode 100644 index 00000000000..4fe0d540c5d --- /dev/null +++ b/lib/polyfills/strnlen.h @@ -0,0 +1,6 @@ +#pragma once +#pragma weak strnlen + +#include + +size_t strnlen(const char* s, size_t maxlen); \ No newline at end of file diff --git a/lib/roll_value/roll_value.c b/lib/roll_value/roll_value.c new file mode 100644 index 00000000000..326c7846a88 --- /dev/null +++ b/lib/roll_value/roll_value.c @@ -0,0 +1,28 @@ +#include "roll_value.h" + +#define TOTP_ROLL_VALUE_FN(type, step_type) \ + TOTP_ROLL_VALUE_FN_HEADER(type, step_type) { \ + type v = *value; \ + if(step > 0 && v > max - step) { \ + if(overflow_behavior == RollOverflowBehaviorRoll) { \ + v = min; \ + } else if(overflow_behavior == RollOverflowBehaviorStop) { \ + v = max; \ + } \ + } else if(step < 0 && v < min - step) { \ + if(overflow_behavior == RollOverflowBehaviorRoll) { \ + v = max; \ + } else if(overflow_behavior == RollOverflowBehaviorStop) { \ + v = min; \ + } \ + } else { \ + v += step; \ + } \ + *value = v; \ + } + +TOTP_ROLL_VALUE_FN(int8_t, int8_t) + +TOTP_ROLL_VALUE_FN(uint8_t, int8_t) + +TOTP_ROLL_VALUE_FN(size_t, int16_t); \ No newline at end of file diff --git a/lib/roll_value/roll_value.h b/lib/roll_value/roll_value.h new file mode 100644 index 00000000000..0c1894fd6c1 --- /dev/null +++ b/lib/roll_value/roll_value.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +typedef uint8_t TotpRollValueOverflowBehavior; + +enum TotpRollValueOverflowBehaviors { + /** + * @brief Do not change value if it reached constraint + */ + RollOverflowBehaviorStop, + + /** + * @brief Set value to opposite constraint value if it reached constraint + */ + RollOverflowBehaviorRoll +}; + +#define TOTP_ROLL_VALUE_FN_HEADER(type, step_type) \ + void totp_roll_value_##type( \ + type* value, \ + step_type step, \ + type min, \ + type max, \ + TotpRollValueOverflowBehavior overflow_behavior) + +/** + * @brief Rolls \c int8_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(int8_t, int8_t); + +/** + * @brief Rolls \c uint8_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(uint8_t, int8_t); + +/** + * @brief Rolls \c size_t \p value using \p min and \p max as an value constraints with \p step step. + * When value reaches constraint value \p overflow_behavior defines what to do next. + * @param[in,out] value value to roll + * @param step step to be used to change value + * @param min minimal possible value + * @param max maximum possible value + * @param overflow_behavior defines what to do when value reaches constraint value + */ +TOTP_ROLL_VALUE_FN_HEADER(size_t, int16_t); \ No newline at end of file diff --git a/lib/timezone_utils/timezone_utils.c b/lib/timezone_utils/timezone_utils.c new file mode 100644 index 00000000000..31df3bbbaee --- /dev/null +++ b/lib/timezone_utils/timezone_utils.c @@ -0,0 +1,16 @@ +#include "timezone_utils.h" + +int32_t timezone_offset_from_hours(float hours) { + return hours * 3600.0f; +} + +uint64_t timezone_offset_apply(uint64_t time, int32_t offset) { + uint64_t for_time_adjusted; + if(offset > 0) { + for_time_adjusted = time - offset; + } else { + for_time_adjusted = time + (-offset); + } + + return for_time_adjusted; +} diff --git a/lib/timezone_utils/timezone_utils.h b/lib/timezone_utils/timezone_utils.h new file mode 100644 index 00000000000..5bb3b8eada2 --- /dev/null +++ b/lib/timezone_utils/timezone_utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +/** + * @brief Calculates timezone offset in seconds given timezone offset in hours. + * @param hours timezone offset in hours + * @return Timezone offset in seconds. + */ +int32_t timezone_offset_from_hours(float hours); + +/** + * @brief Applies timezone offset to a given time. + * @param time time to apply offset to. + * @param offset timezone offset in seconds. + * @return Time with timezone offset applied. + */ +uint64_t timezone_offset_apply(uint64_t time, int32_t offset); diff --git a/lib/wolfssl b/lib/wolfssl new file mode 160000 index 00000000000..3b3c175af0e --- /dev/null +++ b/lib/wolfssl @@ -0,0 +1 @@ +Subproject commit 3b3c175af0e993ffaae251871421e206cc41963f diff --git a/services/config/config.c b/services/config/config.c new file mode 100644 index 00000000000..9320f4507c3 --- /dev/null +++ b/services/config/config.c @@ -0,0 +1,728 @@ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include "../../types/common.h" +#include "../../types/token_info.h" +#include "../../config/app/config.h" +#include "../crypto/crypto_facade.h" +#include "../crypto/constants.h" +#include "migrations/common_migration.h" + +#define CONFIG_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf" +#define CONFIG_FILE_BACKUP_DIR CONFIG_FILE_DIRECTORY_PATH "/backups" +#define CONFIG_FILE_BACKUP_BASE_PATH CONFIG_FILE_BACKUP_DIR "/totp.conf" + +struct ConfigFileContext { + /** + * @brief Config file reference + */ + FlipperFormat* config_file; + + /** + * @brief Storage reference + */ + Storage* storage; + + /** + * @brief Token list iterator context + */ + TokenInfoIteratorContext* token_info_iterator_context; +}; + +/** + * @brief Opens storage record + * @return Storage record + */ +static Storage* totp_open_storage() { + return furi_record_open(RECORD_STORAGE); +} + +/** + * @brief Closes storage record + */ +static void totp_close_storage() { + furi_record_close(RECORD_STORAGE); +} + +/** + * @brief Closes config file + * @param file config file reference + */ +static void totp_close_config_file(FlipperFormat* file) { + if(file == NULL) return; + flipper_format_file_close(file); + flipper_format_free(file); +} + +/** + * @brief Tries to take a config file backup + * @param storage storage record + * @return backup path if backup successfully taken; \c NULL otherwise + */ +static char* totp_config_file_backup_i(Storage* storage) { + if(!storage_dir_exists(storage, CONFIG_FILE_BACKUP_DIR) && + !storage_simply_mkdir(storage, CONFIG_FILE_BACKUP_DIR)) { + return NULL; + } + + FuriHalRtcDateTime current_datetime; + furi_hal_rtc_get_datetime(¤t_datetime); + + uint8_t backup_path_size = sizeof(CONFIG_FILE_BACKUP_BASE_PATH) + 14; + char* backup_path = malloc(backup_path_size); + furi_check(backup_path != NULL); + memcpy(backup_path, CONFIG_FILE_BACKUP_BASE_PATH, sizeof(CONFIG_FILE_BACKUP_BASE_PATH)); + uint16_t i = 1; + bool backup_file_exists; + do { + snprintf( + backup_path, + backup_path_size, + CONFIG_FILE_BACKUP_BASE_PATH ".%4" PRIu16 "%02" PRIu8 "%02" PRIu8 "-%" PRIu16, + current_datetime.year, + current_datetime.month, + current_datetime.day, + i); + i++; + } while((backup_file_exists = storage_common_exists(storage, backup_path)) && i <= 9999); + + if(backup_file_exists || + storage_common_copy(storage, CONFIG_FILE_PATH, backup_path) != FSE_OK) { + FURI_LOG_E(LOGGING_TAG, "Unable to take a backup"); + free(backup_path); + return NULL; + } + + FURI_LOG_I(LOGGING_TAG, "Took config file backup to %s", backup_path); + return backup_path; +} + +/** + * @brief Opens or creates TOTP application standard config file + * @param storage storage record to use + * @param[out] file opened config file + * @return Config file open result + */ +static bool totp_open_config_file(Storage* storage, FlipperFormat** file) { + FlipperFormat* fff_data_file = flipper_format_file_alloc(storage); + + bool conf_file_exists = storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK; + if(!conf_file_exists && + storage_common_stat(storage, EXT_PATH("authenticator"), NULL) == FSE_OK) { + FURI_LOG_I(LOGGING_TAG, "Application catalog needs to be migrated"); + FS_Error migration_result = + storage_common_migrate(storage, EXT_PATH("authenticator"), CONFIG_FILE_DIRECTORY_PATH); + FURI_LOG_I(LOGGING_TAG, "Migrated catalog. Result code: %d", (int)migration_result); + conf_file_exists = storage_common_stat(storage, CONFIG_FILE_PATH, NULL) == FSE_OK; + } + + if(conf_file_exists) { + FURI_LOG_D(LOGGING_TAG, "Config file %s found", CONFIG_FILE_PATH); + if(!flipper_format_file_open_existing(fff_data_file, CONFIG_FILE_PATH)) { + FURI_LOG_E(LOGGING_TAG, "Error opening existing file %s", CONFIG_FILE_PATH); + totp_close_config_file(fff_data_file); + return false; + } + } else { + FURI_LOG_D(LOGGING_TAG, "Config file %s is not found. Will create new.", CONFIG_FILE_PATH); + + if(!flipper_format_file_open_new(fff_data_file, CONFIG_FILE_PATH)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Error creating new file %s", CONFIG_FILE_PATH); + return false; + } + + flipper_format_write_header_cstr( + fff_data_file, CONFIG_FILE_HEADER, CONFIG_FILE_ACTUAL_VERSION); + + uint32_t tmp_uint32 = CRYPTO_LATEST_VERSION; + flipper_format_write_uint32(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, &tmp_uint32, 1); + + tmp_uint32 = DEFAULT_CRYPTO_KEY_SLOT; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, &tmp_uint32, 1); + + flipper_format_write_comment_cstr( + fff_data_file, + "Config file format specification can be found here: https://t.ly/zwQjE"); + + float tmp_tz = 0; + flipper_format_write_float(fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &tmp_tz, 1); + + tmp_uint32 = NotificationMethodSound | NotificationMethodVibro; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1); + + tmp_uint32 = AutomationMethodBadUsb; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1); + + tmp_uint32 = AutomationKeyboardLayoutQWERTY; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, &tmp_uint32, 1); + + tmp_uint32 = 0; //-V1048 + flipper_format_write_uint32(fff_data_file, TOTP_CONFIG_KEY_FONT, &tmp_uint32, 1); + + if(!flipper_format_rewind(fff_data_file)) { + totp_close_config_file(fff_data_file); + FURI_LOG_E(LOGGING_TAG, "Rewind error"); + return false; + } + } + + *file = fff_data_file; + return true; +} + +char* totp_config_file_backup(const PluginState* plugin_state) { + if(plugin_state->config_file_context == NULL) return NULL; + + totp_close_config_file(plugin_state->config_file_context->config_file); + + char* result = totp_config_file_backup_i(plugin_state->config_file_context->storage); + + totp_open_config_file( + plugin_state->config_file_context->storage, + &plugin_state->config_file_context->config_file); + + totp_token_info_iterator_attach_to_config_file( + plugin_state->config_file_context->token_info_iterator_context, + plugin_state->config_file_context->config_file); + + return result; +} + +bool totp_config_file_update_timezone_offset(const PluginState* plugin_state) { + FlipperFormat* file = plugin_state->config_file_context->config_file; + flipper_format_rewind(file); + bool update_result = false; + + do { + if(!flipper_format_insert_or_update_float( + file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + break; + } + + update_result = true; + } while(false); + + return update_result; +} + +bool totp_config_file_update_notification_method(const PluginState* plugin_state) { + FlipperFormat* file = plugin_state->config_file_context->config_file; + flipper_format_rewind(file); + bool update_result = false; + + do { + uint32_t tmp_uint32 = plugin_state->notification_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + break; + } + + update_result = true; + } while(false); + + return update_result; +} + +bool totp_config_file_update_automation_method(const PluginState* plugin_state) { + FlipperFormat* file = plugin_state->config_file_context->config_file; + flipper_format_rewind(file); + bool update_result = false; + + do { + uint32_t tmp_uint32 = plugin_state->automation_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = plugin_state->automation_kb_layout; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, &tmp_uint32, 1)) { + break; + } + + update_result = true; + } while(false); + + return update_result; +} + +bool totp_config_file_update_user_settings(const PluginState* plugin_state) { + FlipperFormat* file = plugin_state->config_file_context->config_file; + flipper_format_rewind(file); + bool update_result = false; + do { + if(!flipper_format_insert_or_update_float( + file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + break; + } + uint32_t tmp_uint32 = plugin_state->notification_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = plugin_state->automation_method; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = plugin_state->active_font_index; + if(!flipper_format_insert_or_update_uint32(file, TOTP_CONFIG_KEY_FONT, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = plugin_state->automation_kb_layout; + if(!flipper_format_insert_or_update_uint32( + file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, &tmp_uint32, 1)) { + break; + } + + update_result = true; + } while(false); + + return update_result; +} + +bool totp_config_file_load(PluginState* const plugin_state) { + Storage* storage = totp_open_storage(); + FlipperFormat* fff_data_file; + if(!totp_open_config_file(storage, &fff_data_file)) { + totp_close_storage(); + return false; + } + + flipper_format_rewind(fff_data_file); + + bool result = false; + + plugin_state->timezone_offset = 0; + + FuriString* temp_str = furi_string_alloc(); + + do { + uint32_t file_version; + if(!flipper_format_read_header(fff_data_file, temp_str, &file_version)) { + FURI_LOG_E(LOGGING_TAG, "Missing or incorrect header"); + break; + } + + if(file_version < CONFIG_FILE_ACTUAL_VERSION) { + FURI_LOG_I( + LOGGING_TAG, + "Obsolete config file version detected. Current version: %" PRIu32 + "; Actual version: %" PRId16, + file_version, + CONFIG_FILE_ACTUAL_VERSION); + totp_close_config_file(fff_data_file); + + char* backup_path = totp_config_file_backup_i(storage); + + if(backup_path != NULL) { + if(totp_open_config_file(storage, &fff_data_file) != true) { + break; + } + + FlipperFormat* fff_backup_data_file = flipper_format_file_alloc(storage); + if(!flipper_format_file_open_existing(fff_backup_data_file, backup_path)) { + flipper_format_file_close(fff_backup_data_file); + flipper_format_free(fff_backup_data_file); + 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); + break; + } + + flipper_format_file_close(fff_backup_data_file); + flipper_format_free(fff_backup_data_file); + flipper_format_rewind(fff_data_file); + free(backup_path); + } else { + FURI_LOG_E( + LOGGING_TAG, + "An error occurred during taking backup of %s before migration", + CONFIG_FILE_PATH); + break; + } + } + + uint32_t tmp_uint32; + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, &tmp_uint32, 1)) { + FURI_LOG_E(LOGGING_TAG, "Missing required " TOTP_CONFIG_KEY_CRYPTO_VERSION "property"); + break; + } + + plugin_state->crypto_settings.crypto_version = tmp_uint32; + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, &tmp_uint32, 1)) { + FURI_LOG_E( + LOGGING_TAG, "Missing required " TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT "property"); + break; + } + + plugin_state->crypto_settings.crypto_key_slot = tmp_uint32; + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_SALT, + &plugin_state->crypto_settings.salt[0], + CRYPTO_SALT_LENGTH)) { + FURI_LOG_D(LOGGING_TAG, "Missing salt"); + } + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + uint32_t crypto_size; + if(flipper_format_get_value_count( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERIFY, &crypto_size) && + crypto_size > 0) { + plugin_state->crypto_settings.crypto_verify_data = + malloc(sizeof(uint8_t) * crypto_size); + furi_check(plugin_state->crypto_settings.crypto_verify_data != NULL); + plugin_state->crypto_settings.crypto_verify_data_length = crypto_size; + if(!flipper_format_read_hex( + fff_data_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_settings.crypto_verify_data, + crypto_size)) { + FURI_LOG_D(LOGGING_TAG, "Missing crypto verify token"); + free(plugin_state->crypto_settings.crypto_verify_data); + plugin_state->crypto_settings.crypto_verify_data = NULL; + plugin_state->crypto_settings.crypto_verify_data_length = 0; + } + } else { + plugin_state->crypto_settings.crypto_verify_data = NULL; + plugin_state->crypto_settings.crypto_verify_data_length = 0; + } + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_float( + fff_data_file, TOTP_CONFIG_KEY_TIMEZONE, &plugin_state->timezone_offset, 1)) { + plugin_state->timezone_offset = 0; + FURI_LOG_D(LOGGING_TAG, "Missing timezone offset information, defaulting to 0"); + } + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_bool( + fff_data_file, + TOTP_CONFIG_KEY_PINSET, + &plugin_state->crypto_settings.pin_required, + 1)) { + plugin_state->crypto_settings.pin_required = true; + } + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_NOTIFICATION_METHOD, &tmp_uint32, 1)) { + tmp_uint32 = NotificationMethodSound | NotificationMethodVibro; + } + + plugin_state->notification_method = tmp_uint32; + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_METHOD, &tmp_uint32, 1)) { + tmp_uint32 = AutomationMethodBadUsb; + } + + plugin_state->automation_method = tmp_uint32; + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_uint32( + fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, &tmp_uint32, 1)) { + tmp_uint32 = AutomationKeyboardLayoutQWERTY; + } + + plugin_state->automation_kb_layout = tmp_uint32; + + if(!flipper_format_rewind(fff_data_file)) { + break; + } + + if(!flipper_format_read_uint32(fff_data_file, TOTP_CONFIG_KEY_FONT, &tmp_uint32, 1)) { + tmp_uint32 = 0; + } + + plugin_state->active_font_index = tmp_uint32; + + plugin_state->config_file_context = malloc(sizeof(ConfigFileContext)); + furi_check(plugin_state->config_file_context != NULL); + plugin_state->config_file_context->storage = storage; + plugin_state->config_file_context->config_file = fff_data_file; + plugin_state->config_file_context->token_info_iterator_context = + totp_token_info_iterator_alloc( + storage, + plugin_state->config_file_context->config_file, + &plugin_state->crypto_settings); + result = true; + } while(false); + + furi_string_free(temp_str); + return result; +} + +bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state) { + FlipperFormat* config_file = plugin_state->config_file_context->config_file; + flipper_format_rewind(config_file); + bool update_result = false; + do { + uint32_t tmp_uint32 = plugin_state->crypto_settings.crypto_version; + if(!flipper_format_insert_or_update_uint32( + config_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = plugin_state->crypto_settings.crypto_key_slot; + if(!flipper_format_insert_or_update_uint32( + config_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, &tmp_uint32, 1)) { + break; + } + + if(!flipper_format_insert_or_update_hex( + config_file, + TOTP_CONFIG_KEY_SALT, + &plugin_state->crypto_settings.salt[0], + CRYPTO_SALT_LENGTH)) { + break; + } + + if(!flipper_format_insert_or_update_hex( + config_file, + TOTP_CONFIG_KEY_CRYPTO_VERIFY, + plugin_state->crypto_settings.crypto_verify_data, + plugin_state->crypto_settings.crypto_verify_data_length)) { + break; + } + + if(!flipper_format_insert_or_update_bool( + config_file, + TOTP_CONFIG_KEY_PINSET, + &plugin_state->crypto_settings.pin_required, + 1)) { + break; + } + + update_result = true; + } while(false); + + return update_result; +} + +void totp_config_file_close(PluginState* const plugin_state) { + if(plugin_state->config_file_context == NULL) return; + totp_token_info_iterator_free(plugin_state->config_file_context->token_info_iterator_context); + totp_close_config_file(plugin_state->config_file_context->config_file); + free(plugin_state->config_file_context); + plugin_state->config_file_context = NULL; + totp_close_storage(); +} + +void totp_config_file_reset(PluginState* const plugin_state) { + totp_config_file_close(plugin_state); + Storage* storage = totp_open_storage(); + storage_simply_remove(storage, CONFIG_FILE_PATH); + totp_close_storage(); +} + +bool totp_config_file_update_encryption( + PluginState* plugin_state, + uint8_t new_crypto_key_slot, + const uint8_t* new_pin, + uint8_t new_pin_length) { + FlipperFormat* config_file = plugin_state->config_file_context->config_file; + Stream* stream = flipper_format_get_raw_stream(config_file); + size_t original_offset = stream_tell(stream); + if(!stream_rewind(stream)) { + return false; + } + + if(!totp_crypto_check_key_slot(new_crypto_key_slot)) { + return false; + } + + CryptoSettings old_crypto_settings = plugin_state->crypto_settings; + + memset(&plugin_state->crypto_settings.iv[0], 0, CRYPTO_IV_LENGTH); + memset(&plugin_state->crypto_settings.salt[0], 0, CRYPTO_SALT_LENGTH); + if(plugin_state->crypto_settings.crypto_verify_data != NULL) { + free(plugin_state->crypto_settings.crypto_verify_data); + plugin_state->crypto_settings.crypto_verify_data = NULL; + } + + plugin_state->crypto_settings.crypto_key_slot = new_crypto_key_slot; + plugin_state->crypto_settings.crypto_version = CRYPTO_LATEST_VERSION; + + CryptoSeedIVResult seed_result = totp_crypto_seed_iv( + &plugin_state->crypto_settings, new_pin_length > 0 ? new_pin : NULL, new_pin_length); + if(seed_result & CryptoSeedIVResultFlagSuccess && + seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData && + !totp_config_file_update_crypto_signatures(plugin_state)) { + return false; + } else if(seed_result == CryptoSeedIVResultFailed) { + return false; + } + + char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_SECRET) + 1]; + bool result = true; + + while(true) { + if(!stream_seek_to_char(stream, '\n', StreamDirectionForward)) { + break; + } + + size_t buffer_read_size; + if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) { + break; + } + + if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) { + result = false; + break; + } + + if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_SECRET ":", sizeof(buffer)) == 0) { + uint32_t secret_bytes_count; + if(!flipper_format_get_value_count( + config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) { + secret_bytes_count = 0; + } + + if(secret_bytes_count > 1) { + size_t secret_token_start = stream_tell(stream) + 1; + uint8_t* encrypted_token = malloc(secret_bytes_count); + furi_check(encrypted_token != NULL); + + if(!flipper_format_read_hex( + config_file, + TOTP_CONFIG_KEY_TOKEN_SECRET, + encrypted_token, + secret_bytes_count)) { + result = false; + free(encrypted_token); + break; + } + + size_t plain_token_length; + uint8_t* plain_token = totp_crypto_decrypt( + encrypted_token, secret_bytes_count, &old_crypto_settings, &plain_token_length); + + free(encrypted_token); + size_t encrypted_token_length; + encrypted_token = totp_crypto_encrypt( + plain_token, + plain_token_length, + &plugin_state->crypto_settings, + &encrypted_token_length); + + memset_s(plain_token, plain_token_length, 0, plain_token_length); + free(plain_token); + + if(!stream_seek(stream, secret_token_start, StreamOffsetFromStart)) { + result = false; + free(encrypted_token); + break; + } + + if(!flipper_format_write_hex( + config_file, + TOTP_CONFIG_KEY_TOKEN_SECRET, + encrypted_token, + encrypted_token_length)) { + free(encrypted_token); + result = false; + break; + } + + free(encrypted_token); + } + } + } + + stream_seek(stream, original_offset, StreamOffsetFromStart); + + return result; +} + +bool totp_config_file_ensure_latest_encryption( + PluginState* plugin_state, + const uint8_t* pin, + uint8_t pin_length) { + bool result = true; + if(plugin_state->crypto_settings.crypto_version < CRYPTO_LATEST_VERSION) { + FURI_LOG_I(LOGGING_TAG, "Migration to crypto v%d is needed", CRYPTO_LATEST_VERSION); + char* backup_path = totp_config_file_backup(plugin_state); + if(backup_path != NULL) { + free(backup_path); + uint8_t crypto_key_slot = plugin_state->crypto_settings.crypto_key_slot; + if(!totp_crypto_check_key_slot(crypto_key_slot)) { + crypto_key_slot = DEFAULT_CRYPTO_KEY_SLOT; + } + + result = + totp_config_file_update_encryption(plugin_state, crypto_key_slot, pin, pin_length); + FURI_LOG_I( + LOGGING_TAG, + "Migration to crypto v%d is done. Result: %d", + CRYPTO_LATEST_VERSION, + result); + } else { + result = false; + } + } + + return result; +} + +TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state) { + return plugin_state->config_file_context->token_info_iterator_context; +} \ No newline at end of file diff --git a/services/config/config.h b/services/config/config.h new file mode 100644 index 00000000000..38bc06ba2d7 --- /dev/null +++ b/services/config/config.h @@ -0,0 +1,104 @@ +#pragma once + +#include "../../types/plugin_state.h" +#include "../../types/token_info.h" +#include "config_file_context.h" +#include "constants.h" +#include "token_info_iterator.h" + +typedef uint8_t TotpConfigFileOpenResult; +typedef uint8_t TotpConfigFileUpdateResult; + +/** + * @brief Tries to take a config file backup + * @param plugin_state application state + * @return backup path if backup successfully taken; \c NULL otherwise + */ +char* totp_config_file_backup(const PluginState* plugin_state); + +/** + * @brief Loads basic information from an application config file into application state without loading all the tokens + * @param plugin_state application state + * @return Config file open result + */ +bool totp_config_file_load(PluginState* const plugin_state); + +/** + * @brief Updates timezone offset in an application config file + * @param plugin_state application state + * @return Config file update result + */ +bool totp_config_file_update_timezone_offset(const PluginState* plugin_state); + +/** + * @brief Updates notification method in an application config file + * @param plugin_state application state + * @return Config file update result + */ +bool totp_config_file_update_notification_method(const PluginState* plugin_state); + +/** + * @brief Updates automation method in an application config file + * @param plugin_state application state + * @return Config file update result + */ +bool totp_config_file_update_automation_method(const PluginState* plugin_state); + +/** + * @brief Updates application user settings + * @param plugin_state application state + * @return Config file update result + */ +bool totp_config_file_update_user_settings(const PluginState* plugin_state); + +/** + * @brief Updates crypto signatures information + * @param plugin_state application state + * @return Config file update result + */ +bool totp_config_file_update_crypto_signatures(const PluginState* plugin_state); + +/** + * @brief Reset all the settings to default + * @param plugin_state application state + */ +void totp_config_file_reset(PluginState* const plugin_state); + +/** + * @brief Closes config file and releases all the resources + * @param plugin_state application state + */ +void totp_config_file_close(PluginState* const plugin_state); + +/** + * @brief Updates config file encryption by re-encrypting it using new user's PIN and new randomly generated IV + * @param plugin_state application state + * @param new_crypto_key_slot new crypto key slot to be used + * @param new_pin new user's PIN + * @param new_pin_length new user's PIN length + * @return \c true if config file encryption successfully updated; \c false otherwise + */ +bool totp_config_file_update_encryption( + PluginState* plugin_state, + uint8_t new_crypto_key_slot, + const uint8_t* new_pin, + uint8_t new_pin_length); + +/** + * @brief Ensures application config file uses latest encryption and upgrades encryption if needed + * @param plugin_state application state + * @param pin user's PIN + * @param pin_length user's PIN length + * @return \c true if operation succeeded; \c false otherwise + */ +bool totp_config_file_ensure_latest_encryption( + PluginState* plugin_state, + const uint8_t* pin, + uint8_t pin_length); + +/** + * @brief Gets token info iterator context + * @param plugin_state application state + * @return token info iterator context + */ +TokenInfoIteratorContext* totp_config_get_token_iterator_context(const PluginState* plugin_state); \ No newline at end of file diff --git a/services/config/config_file_context.h b/services/config/config_file_context.h new file mode 100644 index 00000000000..98badbcbb4a --- /dev/null +++ b/services/config/config_file_context.h @@ -0,0 +1,3 @@ +#pragma once + +typedef struct ConfigFileContext ConfigFileContext; \ No newline at end of file diff --git a/services/config/constants.h b/services/config/constants.h new file mode 100644 index 00000000000..97c8f0d0a5a --- /dev/null +++ b/services/config/constants.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/totp") +#define CONFIG_FILE_HEADER "Flipper TOTP plugin config file" +#define CONFIG_FILE_ACTUAL_VERSION (9) + +#define TOTP_CONFIG_KEY_TIMEZONE "Timezone" +#define TOTP_CONFIG_KEY_TOKEN_NAME "TokenName" +#define TOTP_CONFIG_KEY_TOKEN_SECRET "TokenSecret" +#define TOTP_CONFIG_KEY_TOKEN_ALGO "TokenAlgo" +#define TOTP_CONFIG_KEY_TOKEN_DIGITS "TokenDigits" +#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_SALT "Salt" +#define TOTP_CONFIG_KEY_PINSET "PinIsSet" +#define TOTP_CONFIG_KEY_NOTIFICATION_METHOD "NotificationMethod" +#define TOTP_CONFIG_KEY_AUTOMATION_METHOD "AutomationMethod" +#define TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT "AutomationKbLayout" +#define TOTP_CONFIG_KEY_FONT "Font" +#define TOTP_CONFIG_KEY_CRYPTO_VERSION "CryptoVersion" +#define TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT "CryptoKeySlot" diff --git a/services/config/migrations/common_migration.c b/services/config/migrations/common_migration.c new file mode 100644 index 00000000000..33a441e724c --- /dev/null +++ b/services/config/migrations/common_migration.c @@ -0,0 +1,199 @@ +#include "common_migration.h" +#include "../constants.h" +#include "../../../types/token_info.h" +#include "../../../types/automation_kb_layout.h" +#include + +#define TOTP_OLD_CONFIG_KEY_BASE_IV "BaseIV" + +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, ¤t_version)) { + break; + } + + if(flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, temp_str); + } else { + uint32_t old_crypto_version = 1; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_VERSION, &old_crypto_version, 1); + } + + flipper_format_rewind(fff_backup_data_file); + + if(flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, temp_str); + } else { + uint32_t default_old_key_slot = 2; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_CRYPTO_KEY_SLOT, &default_old_key_slot, 1); + } + + flipper_format_rewind(fff_backup_data_file); + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_SALT, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_SALT, temp_str); + } else if( + flipper_format_rewind(fff_backup_data_file) && + flipper_format_read_string( + fff_backup_data_file, TOTP_OLD_CONFIG_KEY_BASE_IV, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_SALT, 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); + + if(flipper_format_read_string(fff_backup_data_file, TOTP_CONFIG_KEY_FONT, temp_str)) { + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_FONT, temp_str); + } else { + uint32_t default_font_index = 0; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_FONT, &default_font_index, 1); + } + + flipper_format_rewind(fff_backup_data_file); + + if(flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, temp_str)) { + flipper_format_write_string( + fff_data_file, TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, temp_str); + } else { + uint32_t default_automation_kb_layout = AutomationKeyboardLayoutQWERTY; + flipper_format_write_uint32( + fff_data_file, + TOTP_CONFIG_KEY_AUTOMATION_KB_LAYOUT, + &default_automation_kb_layout, + 1); + } + + 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); + + if(current_version > 1) { + flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str); + + if(current_version < 5) { + uint32_t algo_as_uint32t = TokenHashAlgoDefault; + if(furi_string_cmpi_str(temp_str, TOKEN_HASH_ALGO_SHA256_NAME) == 0) { + algo_as_uint32t = TokenHashAlgoSha256; + } else if(furi_string_cmpi_str(temp_str, TOKEN_HASH_ALGO_SHA512_NAME) == 0) { + algo_as_uint32t = TokenHashAlgoSha512; + } else if(furi_string_cmpi_str(temp_str, TOKEN_HASH_ALGO_STEAM_NAME) == 0) { + algo_as_uint32t = TokenHashAlgoSteam; + } + + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &algo_as_uint32t, 1); + } else { + flipper_format_write_string( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, temp_str); + } + + flipper_format_read_string( + fff_backup_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str); + flipper_format_write_string(fff_data_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, temp_str); + } else { + const uint32_t default_algo = TokenHashAlgoDefault; + flipper_format_write_uint32( + fff_data_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &default_algo, 1); + const uint32_t default_digits = TokenDigitsCountSix; + 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 = TokenDurationDefault; + 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 = TokenAutomationFeatureNone; + flipper_format_write_uint32( + fff_data_file, + TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, + &default_automation_features, + 1); + } + } + + Stream* stream = flipper_format_get_raw_stream(fff_data_file); + size_t current_pos = stream_tell(stream); + size_t total_size = stream_size(stream); + if(current_pos < total_size) { + stream_delete(stream, total_size - current_pos); + } + + result = true; + } while(false); + + furi_string_free(temp_str); + return result; +} \ No newline at end of file diff --git a/services/config/migrations/common_migration.h b/services/config/migrations/common_migration.h new file mode 100644 index 00000000000..326277f1474 --- /dev/null +++ b/services/config/migrations/common_migration.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +/** + * @brief Migrates config file to the latest version + * @param fff_data_file original config file to be migrated + * @param fff_backup_data_file backup copy of original config file + * @return \c true if operation succeeded; \c false otherwise + */ +bool totp_config_migrate_to_latest( + FlipperFormat* fff_data_file, + FlipperFormat* fff_backup_data_file); diff --git a/services/config/token_info_iterator.c b/services/config/token_info_iterator.c new file mode 100644 index 00000000000..3d47b9616c8 --- /dev/null +++ b/services/config/token_info_iterator.c @@ -0,0 +1,552 @@ +#include "token_info_iterator.h" + +#include +#include +#include +#include "../../types/common.h" +#include "../../types/crypto_settings.h" + +#define CONFIG_FILE_PART_FILE_PATH CONFIG_FILE_DIRECTORY_PATH "/totp.conf.part" +#define STREAM_COPY_BUFFER_SIZE (128) + +struct TokenInfoIteratorContext { + size_t total_count; + size_t current_index; + size_t last_seek_offset; + size_t last_seek_index; + TokenInfo* current_token; + FlipperFormat* config_file; + CryptoSettings* crypto_settings; + Storage* storage; +}; + +static bool + flipper_format_seek_to_siblinig_token_start(Stream* stream, StreamDirection direction) { + char buffer[sizeof(TOTP_CONFIG_KEY_TOKEN_NAME) + 1]; + bool found = false; + while(!found) { + if(!stream_seek_to_char(stream, '\n', direction)) { + break; + } + + size_t buffer_read_size; + if((buffer_read_size = stream_read(stream, (uint8_t*)&buffer[0], sizeof(buffer))) == 0) { + break; + } + + if(!stream_seek(stream, -(int32_t)buffer_read_size, StreamOffsetFromCurrent)) { + break; + } + + if(strncmp(buffer, "\n" TOTP_CONFIG_KEY_TOKEN_NAME ":", sizeof(buffer)) == 0) { + found = true; + } + } + + return found; +} + +static bool seek_to_token(size_t token_index, TokenInfoIteratorContext* context) { + furi_check(context != NULL && context->config_file != NULL); + if(token_index >= context->total_count) { + return false; + } + + Stream* stream = flipper_format_get_raw_stream(context->config_file); + long token_index_diff = (long)token_index - (long)context->last_seek_index; + size_t token_index_diff_weight = (size_t)labs(token_index_diff); + StreamDirection direction = token_index_diff >= 0 ? StreamDirectionForward : + StreamDirectionBackward; + if(token_index_diff_weight > token_index || context->last_seek_offset == 0) { + context->last_seek_offset = 0; + context->last_seek_index = 0; + token_index_diff = token_index + 1; + direction = StreamDirectionForward; + } else if(token_index_diff_weight > (context->total_count - token_index - 1)) { + context->last_seek_offset = stream_size(stream); + context->last_seek_index = context->total_count - 1; + token_index_diff = -(long)(context->total_count - token_index); + direction = StreamDirectionBackward; + } + + if(!stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart)) { + return false; + } + + if(token_index_diff != 0) { + long i = 0; + long i_inc = token_index_diff >= 0 ? 1 : -1; + do { + if(!flipper_format_seek_to_siblinig_token_start(stream, direction)) { + break; + } + + i += i_inc; + } while((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff)); + + if((i_inc > 0 && i < token_index_diff) || (i_inc < 0 && i > token_index_diff)) { + context->last_seek_offset = 0; + FURI_LOG_D(LOGGING_TAG, "Was not able to move"); + return false; + } + + context->last_seek_offset = stream_tell(stream); + context->last_seek_index = token_index; + } + + return true; +} + +static bool stream_insert_stream(Stream* dst, Stream* src) { + uint8_t buffer[STREAM_COPY_BUFFER_SIZE]; + size_t buffer_read_size; + while((buffer_read_size = stream_read(src, buffer, sizeof(buffer))) != 0) { + if(!stream_insert(dst, buffer, buffer_read_size)) { + return false; + } + } + + return true; +} + +static bool ensure_stream_ends_with_lf(Stream* stream) { + uint8_t last_char; + size_t original_pos = stream_tell(stream); + if(!stream_seek(stream, -1, StreamOffsetFromEnd) || stream_read(stream, &last_char, 1) < 1) { + return false; + } + + const uint8_t lf = '\n'; + if(last_char != lf && !stream_write(stream, &lf, 1)) { + return false; + } + + if(!stream_seek(stream, original_pos, StreamOffsetFromStart)) { + return false; + } + + return true; +} + +static bool + totp_token_info_iterator_save_current_token_info_changes(TokenInfoIteratorContext* context) { + bool is_new_token = context->current_index >= context->total_count; + Stream* stream = flipper_format_get_raw_stream(context->config_file); + if(is_new_token) { + if(!ensure_stream_ends_with_lf(stream) || + !flipper_format_seek_to_end(context->config_file)) { + return false; + } + } else { + if(!seek_to_token(context->current_index, context)) { + return false; + } + } + + size_t offset_start = stream_tell(stream); + + size_t offset_end; + if(is_new_token) { + offset_end = offset_start; + } else if(context->current_index + 1 >= context->total_count) { + offset_end = stream_size(stream); + } else if(seek_to_token(context->current_index + 1, context)) { + offset_end = stream_tell(stream); + } else { + return false; + } + + FlipperFormat* temp_ff = flipper_format_file_alloc(context->storage); + if(!flipper_format_file_open_always(temp_ff, CONFIG_FILE_PART_FILE_PATH)) { + flipper_format_free(temp_ff); + return false; + } + + TokenInfo* token_info = context->current_token; + bool result = false; + + do { + if(!flipper_format_write_string(temp_ff, TOTP_CONFIG_KEY_TOKEN_NAME, token_info->name)) { + break; + } + + if(!flipper_format_write_hex( + temp_ff, + TOTP_CONFIG_KEY_TOKEN_SECRET, + token_info->token, + token_info->token_length)) { + break; + } + + uint32_t tmp_uint32 = token_info->algo; + if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_ALGO, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = token_info->digits; + if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DIGITS, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = token_info->duration; + if(!flipper_format_write_uint32(temp_ff, TOTP_CONFIG_KEY_TOKEN_DURATION, &tmp_uint32, 1)) { + break; + } + + tmp_uint32 = token_info->automation_features; + if(!flipper_format_write_uint32( + temp_ff, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &tmp_uint32, 1)) { + break; + } + + Stream* temp_stream = flipper_format_get_raw_stream(temp_ff); + + if(!stream_rewind(temp_stream)) { + break; + } + + if(!stream_seek(stream, offset_start, StreamOffsetFromStart)) { + break; + } + + if(offset_end != offset_start && !stream_delete(stream, offset_end - offset_start)) { + break; + } + + if(!is_new_token && !stream_write_char(stream, '\n')) { + break; + } + + if(!stream_insert_stream(stream, temp_stream)) { + break; + } + + if(is_new_token) { + context->total_count++; + } + + result = true; + } while(false); + + flipper_format_free(temp_ff); + storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH); + + stream_seek(stream, offset_start, StreamOffsetFromStart); + context->last_seek_offset = offset_start; + context->last_seek_index = context->current_index; + + return result; +} + +TokenInfoIteratorContext* totp_token_info_iterator_alloc( + Storage* storage, + FlipperFormat* config_file, + CryptoSettings* crypto_settings) { + Stream* stream = flipper_format_get_raw_stream(config_file); + stream_rewind(stream); + size_t tokens_count = 0; + while(true) { + if(!flipper_format_seek_to_siblinig_token_start(stream, StreamDirectionForward)) { + break; + } + + tokens_count++; + } + + TokenInfoIteratorContext* context = malloc(sizeof(TokenInfoIteratorContext)); + furi_check(context != NULL); + + context->total_count = tokens_count; + context->current_token = token_info_alloc(); + context->config_file = config_file; + context->crypto_settings = crypto_settings; + context->storage = storage; + return context; +} + +void totp_token_info_iterator_free(TokenInfoIteratorContext* context) { + if(context == NULL) return; + token_info_free(context->current_token); + free(context); +} + +bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context) { + if(!seek_to_token(context->current_index, context)) { + return false; + } + + Stream* stream = flipper_format_get_raw_stream(context->config_file); + size_t begin_offset = stream_tell(stream); + size_t end_offset; + if(!ensure_stream_ends_with_lf(stream)) { + return false; + } + + if(context->current_index >= context->total_count - 1) { + end_offset = stream_size(stream) - 1; + } else if(seek_to_token(context->current_index + 1, context)) { + end_offset = stream_tell(stream); + } else { + return false; + } + + if(!stream_seek(stream, begin_offset, StreamOffsetFromStart) || + !stream_delete(stream, end_offset - begin_offset)) { + return false; + } + + context->total_count--; + if(context->current_index >= context->total_count) { + context->current_index = context->total_count - 1; + } + + return true; +} + +bool totp_token_info_iterator_move_current_token_info( + TokenInfoIteratorContext* context, + size_t new_index) { + if(context->current_index == new_index) return true; + + Stream* stream = flipper_format_get_raw_stream(context->config_file); + + if(!ensure_stream_ends_with_lf(stream)) { + return false; + } + + if(!seek_to_token(context->current_index, context)) { + return false; + } + + size_t begin_offset = stream_tell(stream); + size_t end_offset; + if(context->current_index >= context->total_count - 1) { + end_offset = stream_size(stream) - 1; + } else if(seek_to_token(context->current_index + 1, context)) { + end_offset = stream_tell(stream); + } else { + return false; + } + + Stream* temp_stream = file_stream_alloc(context->storage); + if(!file_stream_open( + temp_stream, CONFIG_FILE_PART_FILE_PATH, FSAM_READ_WRITE, FSOM_CREATE_ALWAYS)) { + stream_free(temp_stream); + return false; + } + + size_t moving_size = end_offset - begin_offset; + + bool result = false; + do { + if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) { + break; + } + + if(stream_copy(stream, temp_stream, moving_size) < moving_size) { + break; + } + + if(!stream_rewind(temp_stream)) { + break; + } + + if(!stream_seek(stream, begin_offset, StreamOffsetFromStart)) { + break; + } + + if(!stream_delete(stream, moving_size)) { + break; + } + + context->last_seek_offset = 0; + context->last_seek_index = 0; + if(new_index >= context->total_count - 1) { + if(!stream_seek(stream, stream_size(stream) - 1, StreamOffsetFromStart)) { + break; + } + } else if(!seek_to_token(new_index, context)) { + break; + } + + result = stream_insert_stream(stream, temp_stream); + } while(false); + + stream_free(temp_stream); + storage_common_remove(context->storage, CONFIG_FILE_PART_FILE_PATH); + + context->last_seek_offset = 0; + context->last_seek_index = 0; + + return result; +} + +TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token( + TokenInfoIteratorContext* context, + TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, + const void* update_context) { + TotpIteratorUpdateTokenResult result = update(context->current_token, update_context); + if(result == TotpIteratorUpdateTokenResultSuccess) { + if(!totp_token_info_iterator_save_current_token_info_changes(context)) { + result = TotpIteratorUpdateTokenResultFileUpdateFailed; + } + + return result; + } + + totp_token_info_iterator_go_to(context, context->current_index); + return result; +} + +TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token( + TokenInfoIteratorContext* context, + TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, + const void* update_context) { + size_t previous_index = context->current_index; + context->current_index = context->total_count; + token_info_set_defaults(context->current_token); + TotpIteratorUpdateTokenResult result = update(context->current_token, update_context); + if(result == TotpIteratorUpdateTokenResultSuccess && + !totp_token_info_iterator_save_current_token_info_changes(context)) { + result = TotpIteratorUpdateTokenResultFileUpdateFailed; + } + + if(result != TotpIteratorUpdateTokenResultSuccess) { + totp_token_info_iterator_go_to(context, previous_index); + } + + return result; +} + +bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index) { + furi_check(context != NULL); + context->current_index = token_index; + if(!seek_to_token(context->current_index, context)) { + return false; + } + + Stream* stream = flipper_format_get_raw_stream(context->config_file); + size_t original_offset = stream_tell(stream); + + if(!flipper_format_read_string( + context->config_file, TOTP_CONFIG_KEY_TOKEN_NAME, context->current_token->name)) { + stream_seek(stream, original_offset, StreamOffsetFromStart); + return false; + } + + uint32_t secret_bytes_count; + if(!flipper_format_get_value_count( + context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, &secret_bytes_count)) { + secret_bytes_count = 0; + } + TokenInfo* tokenInfo = context->current_token; + bool token_update_needed = false; + if(tokenInfo->token != NULL) { + free(tokenInfo->token); + tokenInfo->token_length = 0; + } + + if(secret_bytes_count == 1) { // Plain secret key + FuriString* temp_str = furi_string_alloc(); + + if(flipper_format_read_string( + context->config_file, TOTP_CONFIG_KEY_TOKEN_SECRET, temp_str)) { + if(token_info_set_secret( + tokenInfo, + furi_string_get_cstr(temp_str), + furi_string_size(temp_str), + PlainTokenSecretEncodingBase32, + context->crypto_settings)) { + FURI_LOG_W( + LOGGING_TAG, + "Token \"%s\" has plain secret", + furi_string_get_cstr(tokenInfo->name)); + token_update_needed = true; + } else { + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + FURI_LOG_W( + LOGGING_TAG, + "Token \"%s\" has invalid secret", + furi_string_get_cstr(tokenInfo->name)); + } + } else { + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + } + + furi_string_free(temp_str); + } else { // encrypted + tokenInfo->token_length = secret_bytes_count; + if(secret_bytes_count > 0) { + tokenInfo->token = malloc(tokenInfo->token_length); + furi_check(tokenInfo->token != NULL); + if(!flipper_format_read_hex( + context->config_file, + TOTP_CONFIG_KEY_TOKEN_SECRET, + tokenInfo->token, + tokenInfo->token_length)) { + free(tokenInfo->token); + tokenInfo->token = NULL; + tokenInfo->token_length = 0; + } + } else { + tokenInfo->token = NULL; + } + } + + uint32_t temp_data32; + if(!flipper_format_read_uint32( + context->config_file, TOTP_CONFIG_KEY_TOKEN_ALGO, &temp_data32, 1) || + !token_info_set_algo_from_int(tokenInfo, temp_data32)) { + tokenInfo->algo = TokenHashAlgoDefault; + } + + if(!flipper_format_read_uint32( + context->config_file, TOTP_CONFIG_KEY_TOKEN_DIGITS, &temp_data32, 1) || + !token_info_set_digits_from_int(tokenInfo, temp_data32)) { + tokenInfo->digits = TokenDigitsCountSix; + } + + if(!flipper_format_read_uint32( + context->config_file, TOTP_CONFIG_KEY_TOKEN_DURATION, &temp_data32, 1) || + !token_info_set_duration_from_int(tokenInfo, temp_data32)) { + tokenInfo->duration = TokenDurationDefault; + } + + if(flipper_format_read_uint32( + context->config_file, TOTP_CONFIG_KEY_TOKEN_AUTOMATION_FEATURES, &temp_data32, 1)) { + tokenInfo->automation_features = temp_data32; + } else { + tokenInfo->automation_features = TokenAutomationFeatureNone; + } + + stream_seek(stream, original_offset, StreamOffsetFromStart); + + if(token_update_needed && !totp_token_info_iterator_save_current_token_info_changes(context)) { + return false; + } + + return true; +} + +const TokenInfo* + totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context) { + return context->current_token; +} + +size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context) { + return context->current_index; +} + +size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context) { + return context->total_count; +} + +void totp_token_info_iterator_attach_to_config_file( + TokenInfoIteratorContext* context, + FlipperFormat* config_file) { + context->config_file = config_file; + Stream* stream = flipper_format_get_raw_stream(context->config_file); + stream_seek(stream, context->last_seek_offset, StreamOffsetFromStart); +} \ No newline at end of file diff --git a/services/config/token_info_iterator.h b/services/config/token_info_iterator.h new file mode 100644 index 00000000000..ce4d8c72b2e --- /dev/null +++ b/services/config/token_info_iterator.h @@ -0,0 +1,123 @@ +#pragma once + +#include "../../types/token_info.h" +#include +#include "constants.h" + +typedef int TotpIteratorUpdateTokenResult; + +typedef TotpIteratorUpdateTokenResult ( + *TOTP_ITERATOR_UPDATE_TOKEN_ACTION)(TokenInfo* const token_info, const void* context); + +typedef struct TokenInfoIteratorContext TokenInfoIteratorContext; + +enum TotpIteratorUpdateTokenResults { + + /** + * @brief Token successfully updated + */ + TotpIteratorUpdateTokenResultSuccess = 0, + + /** + * @brief An error ocurred during updating config file + */ + TotpIteratorUpdateTokenResultFileUpdateFailed = -1 +}; + +/** + * @brief Initializes a new token info iterator + * @param storage storage reference + * @param config_file config file to use + * @param crypto_settings crypto settings + * @return Token info iterator context + */ +TokenInfoIteratorContext* totp_token_info_iterator_alloc( + Storage* storage, + FlipperFormat* config_file, + CryptoSettings* crypto_settings); + +/** + * @brief Navigates iterator to the token with given index + * @param context token info iterator context + * @param token_index token index to navigate to + * @return \c true if navigation succeeded; \c false otherwise + */ +bool totp_token_info_iterator_go_to(TokenInfoIteratorContext* context, size_t token_index); + +/** + * @brief Moves current token to a given new index + * @param context token info iterator context + * @param new_index new token index to move current token to + * @return \c true if operation succeeded; \c false otherwise + */ +bool totp_token_info_iterator_move_current_token_info( + TokenInfoIteratorContext* context, + size_t new_index); + +/** + * @brief Updates current token info using given update action + * @param context token info iterator context + * @param update action which is responsible to make all the necessary updates to token info + * @param update_context update action context + * @return \c true if operation succeeded; \c false otherwise + */ +TotpIteratorUpdateTokenResult totp_token_info_iterator_update_current_token( + TokenInfoIteratorContext* context, + TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, + const void* update_context); + +/** + * @brief Adds new token info to the end of the list using given update action + * @param context token info iterator context + * @param update action which is responsible to make all the necessary updates to token info + * @param update_context update action context + * @return \c true if operation succeeded; \c false otherwise + */ +TotpIteratorUpdateTokenResult totp_token_info_iterator_add_new_token( + TokenInfoIteratorContext* context, + TOTP_ITERATOR_UPDATE_TOKEN_ACTION update, + const void* update_context); + +/** + * @brief Remvoves current token info + * @param context token info iterator context + * @return \c true if operation succeeded; \c false otherwise + */ +bool totp_token_info_iterator_remove_current_token_info(TokenInfoIteratorContext* context); + +/** + * @brief Disposes token info iterator and releases all the resources + * @param context token info iterator context + */ +void totp_token_info_iterator_free(TokenInfoIteratorContext* context); + +/** + * @brief Gets current token info + * @param context token info iterator context + * @return current token info + */ +const TokenInfo* + totp_token_info_iterator_get_current_token(const TokenInfoIteratorContext* context); + +/** + * @brief Gets current token info index + * @param context token info iterator context + * @return current token info index + */ +size_t totp_token_info_iterator_get_current_token_index(const TokenInfoIteratorContext* context); + +/** + * @brief Gets total amount of token infos found + * @param context token info iterator context + * @return amount of token infos found + */ +size_t totp_token_info_iterator_get_total_count(const TokenInfoIteratorContext* context); + +/** + * @brief Attaches token info iterator to another config file + * @param context token info iterator context + * @param config_file config file reference to attach token info iterator to + */ +void totp_token_info_iterator_attach_to_config_file( + TokenInfoIteratorContext* context, + FlipperFormat* config_file); diff --git a/services/convert/convert.h b/services/convert/convert.h new file mode 100644 index 00000000000..740d47ace17 --- /dev/null +++ b/services/convert/convert.h @@ -0,0 +1,4 @@ +#pragma once + +#define CONVERT_DIGIT_TO_CHAR(digit) ((digit) + '0') +#define CONVERT_CHAR_TO_DIGIT(ch) ((ch) - '0') diff --git a/services/crypto/common_types.h b/services/crypto/common_types.h new file mode 100644 index 00000000000..fb8e4c83c03 --- /dev/null +++ b/services/crypto/common_types.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +typedef uint8_t CryptoSeedIVResult; + +enum CryptoSeedIVResults { + + /** + * @brief IV seeding operation failed + */ + CryptoSeedIVResultFailed = 0b00, + + /** + * @brief IV seeding operation succeeded + */ + CryptoSeedIVResultFlagSuccess = 0b01, + + /** + * @brief As a part of IV seeding operation new crypto verify data has been generated + */ + CryptoSeedIVResultFlagNewCryptoVerifyData = 0b10 +}; \ No newline at end of file diff --git a/services/crypto/constants.h b/services/crypto/constants.h new file mode 100644 index 00000000000..bb3affbd038 --- /dev/null +++ b/services/crypto/constants.h @@ -0,0 +1,14 @@ +#pragma once + +#include "polyfills.h" + +#define CRYPTO_IV_LENGTH (16) +#define CRYPTO_SALT_LENGTH (16) + +// According to this explanation: https://github.com/flipperdevices/flipperzero-firmware/issues/2885#issuecomment-1646664666 +// disabling usage of any key which is "the same across all devices" +#define ACCEPTABLE_CRYPTO_KEY_SLOT_START FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_START +#define ACCEPTABLE_CRYPTO_KEY_SLOT_END FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_END + +#define DEFAULT_CRYPTO_KEY_SLOT ACCEPTABLE_CRYPTO_KEY_SLOT_START +#define CRYPTO_LATEST_VERSION (3) \ No newline at end of file diff --git a/services/crypto/crypto_facade.c b/services/crypto/crypto_facade.c new file mode 100644 index 00000000000..29e5880731d --- /dev/null +++ b/services/crypto/crypto_facade.c @@ -0,0 +1,118 @@ +#include "crypto_facade.h" +#include "../../config/app/config.h" +#include +#include +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED +#include "crypto_v1.h" +#endif +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED +#include "crypto_v2.h" +#endif +#include "crypto_v3.h" +#include "constants.h" + +bool totp_crypto_check_key_slot(uint8_t key_slot) { + uint8_t empty_iv[CRYPTO_IV_LENGTH] = {0}; + if(key_slot < ACCEPTABLE_CRYPTO_KEY_SLOT_START || key_slot > ACCEPTABLE_CRYPTO_KEY_SLOT_END) { + return false; + } + + return furi_hal_crypto_enclave_ensure_key(key_slot) && + furi_hal_crypto_enclave_load_key(key_slot, empty_iv) && + furi_hal_crypto_enclave_unload_key(key_slot); +} + +uint8_t* totp_crypto_encrypt( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length) { +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 1) { + return totp_crypto_encrypt_v1( + plain_data, plain_data_length, crypto_settings, encrypted_data_length); + } +#endif + +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 2) { + return totp_crypto_encrypt_v2( + plain_data, plain_data_length, crypto_settings, encrypted_data_length); + } +#endif + + if(crypto_settings->crypto_version == 3) { + return totp_crypto_encrypt_v3( + plain_data, plain_data_length, crypto_settings, encrypted_data_length); + } + + furi_crash("Unsupported crypto version"); +} + +uint8_t* totp_crypto_decrypt( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length) { +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 1) { + return totp_crypto_decrypt_v1( + encrypted_data, encrypted_data_length, crypto_settings, decrypted_data_length); + } +#endif + +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 2) { + return totp_crypto_decrypt_v2( + encrypted_data, encrypted_data_length, crypto_settings, decrypted_data_length); + } +#endif + + if(crypto_settings->crypto_version == 3) { + return totp_crypto_decrypt_v3( + encrypted_data, encrypted_data_length, crypto_settings, decrypted_data_length); + } + + furi_crash("Unsupported crypto version"); +} + +CryptoSeedIVResult + totp_crypto_seed_iv(CryptoSettings* crypto_settings, const uint8_t* pin, uint8_t pin_length) { +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 1) { + return totp_crypto_seed_iv_v1(crypto_settings, pin, pin_length); + } +#endif + +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 2) { + return totp_crypto_seed_iv_v2(crypto_settings, pin, pin_length); + } +#endif + + if(crypto_settings->crypto_version == 3) { + return totp_crypto_seed_iv_v3(crypto_settings, pin, pin_length); + } + + furi_crash("Unsupported crypto version"); +} + +bool totp_crypto_verify_key(const CryptoSettings* crypto_settings) { +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 1) { + return totp_crypto_verify_key_v1(crypto_settings); + } +#endif + +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED + if(crypto_settings->crypto_version == 2) { + return totp_crypto_verify_key_v2(crypto_settings); + } +#endif + + if(crypto_settings->crypto_version == 3) { + return totp_crypto_verify_key_v3(crypto_settings); + } + + furi_crash("Unsupported crypto version"); +} \ No newline at end of file diff --git a/services/crypto/crypto_facade.h b/services/crypto/crypto_facade.h new file mode 100644 index 00000000000..bbcbf7c0074 --- /dev/null +++ b/services/crypto/crypto_facade.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include "../../types/crypto_settings.h" +#include "common_types.h" + +/** + * @brief Checks whether key slot can be used for encryption purposes + * @param key_slot key slot index + * @return \c true if key slot can be used for encryption; \c false otherwise + */ +bool totp_crypto_check_key_slot(uint8_t key_slot); + +/** + * @brief Encrypts plain data using built-in certificate and given initialization vector (IV) + * @param plain_data plain data to be encrypted + * @param plain_data_length plain data length + * @param crypto_settings crypto settings + * @param[out] encrypted_data_length encrypted data length + * @return Encrypted data + */ +uint8_t* totp_crypto_encrypt( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length); + +/** + * @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV) + * @param encrypted_data encrypted data to be decrypted + * @param encrypted_data_length encrypted data length + * @param crypto_settings crypto settings + * @param[out] decrypted_data_length decrypted data length + * @return Decrypted data + */ +uint8_t* totp_crypto_decrypt( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length); + +/** + * @brief Seed initialization vector (IV) using user's PIN + * @param crypto_settings crypto settings + * @param pin user's PIN + * @param pin_length user's PIN length + * @return Results of seeding IV + */ +CryptoSeedIVResult + totp_crypto_seed_iv(CryptoSettings* crypto_settings, const uint8_t* pin, uint8_t pin_length); + +/** + * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption + * @param crypto_settings crypto settings + * @return \c true if cryptographic information is valid; \c false otherwise + */ +bool totp_crypto_verify_key(const CryptoSettings* crypto_settings); diff --git a/services/crypto/crypto_v1.c b/services/crypto/crypto_v1.c new file mode 100644 index 00000000000..b6ead80b1df --- /dev/null +++ b/services/crypto/crypto_v1.c @@ -0,0 +1,145 @@ +#include "crypto_v1.h" +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED +#include +#include +#include +#include +#include +#include "../../types/common.h" +#include "memset_s.h" +#include "polyfills.h" + +#define CRYPTO_KEY_SLOT (2) +#define CRYPTO_VERIFY_KEY_LENGTH (16) +#define CRYPTO_ALIGNMENT_FACTOR (16) +#define TOTP_IV_SIZE (16) + +static const char* CRYPTO_VERIFY_KEY = "FFF_Crypto_pass"; + +uint8_t* totp_crypto_encrypt_v1( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length) { + uint8_t* encrypted_data; + size_t remain = plain_data_length % CRYPTO_ALIGNMENT_FACTOR; + if(remain) { + size_t plain_data_aligned_length = plain_data_length - remain + CRYPTO_ALIGNMENT_FACTOR; + uint8_t* plain_data_aligned = malloc(plain_data_aligned_length); + furi_check(plain_data_aligned != NULL); + memset(plain_data_aligned, 0, plain_data_aligned_length); + memcpy(plain_data_aligned, plain_data, plain_data_length); + + encrypted_data = malloc(plain_data_aligned_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_aligned_length; + + furi_hal_crypto_enclave_load_key(CRYPTO_KEY_SLOT, crypto_settings->iv); + furi_hal_crypto_encrypt(plain_data_aligned, encrypted_data, plain_data_aligned_length); + furi_hal_crypto_enclave_unload_key(CRYPTO_KEY_SLOT); + + memset_s(plain_data_aligned, plain_data_aligned_length, 0, plain_data_aligned_length); + free(plain_data_aligned); + } else { + encrypted_data = malloc(plain_data_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_length; + + furi_hal_crypto_enclave_load_key(CRYPTO_KEY_SLOT, crypto_settings->iv); + furi_hal_crypto_encrypt(plain_data, encrypted_data, plain_data_length); + furi_hal_crypto_enclave_unload_key(CRYPTO_KEY_SLOT); + } + + return encrypted_data; +} + +uint8_t* totp_crypto_decrypt_v1( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length) { + *decrypted_data_length = encrypted_data_length; + uint8_t* decrypted_data = malloc(*decrypted_data_length); + furi_check(decrypted_data != NULL); + furi_hal_crypto_enclave_load_key(CRYPTO_KEY_SLOT, crypto_settings->iv); + furi_hal_crypto_decrypt(encrypted_data, decrypted_data, encrypted_data_length); + furi_hal_crypto_enclave_unload_key(CRYPTO_KEY_SLOT); + return decrypted_data; +} + +CryptoSeedIVResult totp_crypto_seed_iv_v1( + CryptoSettings* crypto_settings, + const uint8_t* pin, + uint8_t pin_length) { + CryptoSeedIVResult result; + if(crypto_settings->crypto_verify_data == NULL) { + FURI_LOG_I(LOGGING_TAG, "Generating new IV"); + furi_hal_random_fill_buf(&crypto_settings->salt[0], CRYPTO_SALT_LENGTH); + } + + memcpy(&crypto_settings->iv[0], &crypto_settings->salt[0], TOTP_IV_SIZE); + if(pin != NULL && pin_length > 0) { + uint8_t max_i; + if(pin_length > TOTP_IV_SIZE) { + max_i = TOTP_IV_SIZE; + } else { + max_i = pin_length; + } + + for(uint8_t i = 0; i < max_i; i++) { + crypto_settings->iv[i] = crypto_settings->iv[i] ^ (uint8_t)(pin[i] * (i + 1)); + } + } else { + uint8_t max_i; + size_t uid_size = furi_hal_version_uid_size(); + if(uid_size > TOTP_IV_SIZE) { + max_i = TOTP_IV_SIZE; + } else { + max_i = uid_size; + } + + const uint8_t* uid = (const uint8_t*)UID64_BASE; //-V566 + for(uint8_t i = 0; i < max_i; i++) { + crypto_settings->iv[i] = crypto_settings->iv[i] ^ uid[i]; + } + } + + result = CryptoSeedIVResultFlagSuccess; + if(crypto_settings->crypto_verify_data == NULL) { + FURI_LOG_I(LOGGING_TAG, "Generating crypto verify data"); + crypto_settings->crypto_verify_data = malloc(CRYPTO_VERIFY_KEY_LENGTH); + furi_check(crypto_settings->crypto_verify_data != NULL); + crypto_settings->crypto_verify_data_length = CRYPTO_VERIFY_KEY_LENGTH; + + crypto_settings->crypto_verify_data = totp_crypto_encrypt_v1( + (const uint8_t*)CRYPTO_VERIFY_KEY, + CRYPTO_VERIFY_KEY_LENGTH, + crypto_settings, + &crypto_settings->crypto_verify_data_length); + + crypto_settings->pin_required = pin != NULL && pin_length > 0; + + result |= CryptoSeedIVResultFlagNewCryptoVerifyData; + } + + return result; +} + +bool totp_crypto_verify_key_v1(const CryptoSettings* crypto_settings) { + size_t decrypted_key_length; + uint8_t* decrypted_key = totp_crypto_decrypt_v1( + crypto_settings->crypto_verify_data, + crypto_settings->crypto_verify_data_length, + crypto_settings, + &decrypted_key_length); + + bool key_valid = true; + for(uint8_t i = 0; i < CRYPTO_VERIFY_KEY_LENGTH && key_valid; i++) { + if(decrypted_key[i] != CRYPTO_VERIFY_KEY[i]) key_valid = false; + } + + free(decrypted_key); + + return key_valid; +} +#endif \ No newline at end of file diff --git a/services/crypto/crypto_v1.h b/services/crypto/crypto_v1.h new file mode 100644 index 00000000000..859615ae353 --- /dev/null +++ b/services/crypto/crypto_v1.h @@ -0,0 +1,55 @@ +#pragma once + +#include "../../config/app/config.h" +#ifdef TOTP_OBSOLETE_CRYPTO_V1_COMPATIBILITY_ENABLED +#include +#include +#include +#include "../../types/crypto_settings.h" +#include "common_types.h" + +/** + * @brief Encrypts plain data using built-in certificate and given initialization vector (IV) + * @param plain_data plain data to be encrypted + * @param plain_data_length plain data length + * @param crypto_settings crypto settings + * @param[out] encrypted_data_length encrypted data length + * @return Encrypted data + */ +uint8_t* totp_crypto_encrypt_v1( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length); + +/** + * @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV) + * @param encrypted_data encrypted data to be decrypted + * @param encrypted_data_length encrypted data length + * @param crypto_settings crypto settings + * @param[out] decrypted_data_length decrypted data length + * @return Decrypted data + */ +uint8_t* totp_crypto_decrypt_v1( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length); + +/** + * @brief Seed initialization vector (IV) using user's PIN + * @param crypto_settings crypto settings + * @param pin user's PIN + * @param pin_length user's PIN length + * @return Results of seeding IV + */ +CryptoSeedIVResult + totp_crypto_seed_iv_v1(CryptoSettings* crypto_settings, const uint8_t* pin, uint8_t pin_length); + +/** + * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption + * @param crypto_settings crypto settings + * @return \c true if cryptographic information is valid; \c false otherwise + */ +bool totp_crypto_verify_key_v1(const CryptoSettings* crypto_settings); +#endif diff --git a/services/crypto/crypto_v2.c b/services/crypto/crypto_v2.c new file mode 100644 index 00000000000..49899561959 --- /dev/null +++ b/services/crypto/crypto_v2.c @@ -0,0 +1,190 @@ +#include "crypto_v2.h" +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED +#include +#include +#include +#include +#include +#include "../../types/common.h" +#include "../../config/wolfssl/config.h" +#include +#include "memset_s.h" +#include "constants.h" +#include "polyfills.h" + +#define CRYPTO_ALIGNMENT_FACTOR (16) + +static const uint8_t* get_device_uid() { + return (const uint8_t*)UID64_BASE; //-V566 +} + +static uint8_t get_device_uid_length() { + return furi_hal_version_uid_size(); +} + +static const uint8_t* get_crypto_verify_key() { + return get_device_uid(); +} + +static uint8_t get_crypto_verify_key_length() { + return get_device_uid_length(); +} + +uint8_t* totp_crypto_encrypt_v2( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length) { + uint8_t* encrypted_data; + size_t remain = plain_data_length % CRYPTO_ALIGNMENT_FACTOR; + if(remain) { + size_t plain_data_aligned_length = plain_data_length - remain + CRYPTO_ALIGNMENT_FACTOR; + uint8_t* plain_data_aligned = malloc(plain_data_aligned_length); + furi_check(plain_data_aligned != NULL); + memset(plain_data_aligned, 0, plain_data_aligned_length); + memcpy(plain_data_aligned, plain_data, plain_data_length); + + encrypted_data = malloc(plain_data_aligned_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_aligned_length; + + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Encryption failed: enclave_load_key"); + furi_check( + furi_hal_crypto_encrypt(plain_data_aligned, encrypted_data, plain_data_aligned_length), + "Encryption failed: encrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Encryption failed: enclave_unload_key"); + + memset_s(plain_data_aligned, plain_data_aligned_length, 0, plain_data_aligned_length); + free(plain_data_aligned); + } else { + encrypted_data = malloc(plain_data_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_length; + + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Encryption failed: store_load_key"); + furi_check( + furi_hal_crypto_encrypt(plain_data, encrypted_data, plain_data_length), + "Encryption failed: encrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Encryption failed: store_unload_key"); + } + + return encrypted_data; +} + +uint8_t* totp_crypto_decrypt_v2( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length) { + *decrypted_data_length = encrypted_data_length; + uint8_t* decrypted_data = malloc(*decrypted_data_length); + furi_check(decrypted_data != NULL); + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Decryption failed: enclave_load_key"); + furi_check( + furi_hal_crypto_decrypt(encrypted_data, decrypted_data, encrypted_data_length), + "Decryption failed: decrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Decryption failed: enclave_unload_key"); + return decrypted_data; +} + +CryptoSeedIVResult totp_crypto_seed_iv_v2( + CryptoSettings* crypto_settings, + const uint8_t* pin, + uint8_t pin_length) { + CryptoSeedIVResult result; + if(crypto_settings->crypto_verify_data == NULL) { + FURI_LOG_I(LOGGING_TAG, "Generating new salt"); + furi_hal_random_fill_buf(&crypto_settings->salt[0], CRYPTO_SALT_LENGTH); + } + + const uint8_t* device_uid = get_device_uid(); + uint8_t device_uid_length = get_device_uid_length(); + + uint8_t hmac_key_length = device_uid_length; + if(pin != NULL && pin_length > 0) { + hmac_key_length += pin_length; + } + + uint8_t* hmac_key = malloc(hmac_key_length); + furi_check(hmac_key != NULL); + + memcpy(hmac_key, device_uid, device_uid_length); + + if(pin != NULL && pin_length > 0) { + memcpy(hmac_key + device_uid_length, pin, pin_length); + } + + uint8_t hmac[WC_SHA512_DIGEST_SIZE] = {0}; + + Hmac hmac_context; + wc_HmacSetKey(&hmac_context, WC_SHA512, hmac_key, hmac_key_length); + wc_HmacUpdate(&hmac_context, &crypto_settings->salt[0], CRYPTO_SALT_LENGTH); + int hmac_result_code = wc_HmacFinal(&hmac_context, &hmac[0]); + wc_HmacFree(&hmac_context); + + memset_s(hmac_key, hmac_key_length, 0, hmac_key_length); + free(hmac_key); + + if(hmac_result_code == 0) { + uint8_t offset = + hmac[WC_SHA512_DIGEST_SIZE - 1] % (WC_SHA512_DIGEST_SIZE - CRYPTO_IV_LENGTH - 1); + memcpy(&crypto_settings->iv[0], &hmac[offset], CRYPTO_IV_LENGTH); + + result = CryptoSeedIVResultFlagSuccess; + if(crypto_settings->crypto_verify_data == NULL) { + const uint8_t* crypto_vkey = get_crypto_verify_key(); + uint8_t crypto_vkey_length = get_crypto_verify_key_length(); + FURI_LOG_I(LOGGING_TAG, "Generating crypto verify data"); + crypto_settings->crypto_verify_data = malloc(crypto_vkey_length); + furi_check(crypto_settings->crypto_verify_data != NULL); + crypto_settings->crypto_verify_data_length = crypto_vkey_length; + + crypto_settings->crypto_verify_data = totp_crypto_encrypt_v2( + crypto_vkey, + crypto_vkey_length, + crypto_settings, + &crypto_settings->crypto_verify_data_length); + + crypto_settings->pin_required = pin != NULL && pin_length > 0; + + result |= CryptoSeedIVResultFlagNewCryptoVerifyData; + } + } else { + result = CryptoSeedIVResultFailed; + } + + return result; +} + +bool totp_crypto_verify_key_v2(const CryptoSettings* crypto_settings) { + size_t decrypted_key_length; + uint8_t* decrypted_key = totp_crypto_decrypt_v2( + crypto_settings->crypto_verify_data, + crypto_settings->crypto_verify_data_length, + crypto_settings, + &decrypted_key_length); + + const uint8_t* crypto_vkey = get_crypto_verify_key(); + uint8_t crypto_vkey_length = get_crypto_verify_key_length(); + bool key_valid = true; + for(uint8_t i = 0; i < crypto_vkey_length && key_valid; i++) { + if(decrypted_key[i] != crypto_vkey[i]) key_valid = false; + } + + free(decrypted_key); + + return key_valid; +} +#endif diff --git a/services/crypto/crypto_v2.h b/services/crypto/crypto_v2.h new file mode 100644 index 00000000000..c21edae20bb --- /dev/null +++ b/services/crypto/crypto_v2.h @@ -0,0 +1,55 @@ +#pragma once + +#include "../../config/app/config.h" +#ifdef TOTP_OBSOLETE_CRYPTO_V2_COMPATIBILITY_ENABLED +#include +#include +#include +#include "../../types/crypto_settings.h" +#include "common_types.h" + +/** + * @brief Encrypts plain data using built-in certificate and given initialization vector (IV) + * @param plain_data plain data to be encrypted + * @param plain_data_length plain data length + * @param crypto_settings crypto settings + * @param[out] encrypted_data_length encrypted data length + * @return Encrypted data + */ +uint8_t* totp_crypto_encrypt_v2( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length); + +/** + * @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV) + * @param encrypted_data encrypted data to be decrypted + * @param encrypted_data_length encrypted data length + * @param crypto_settings crypto settings + * @param[out] decrypted_data_length decrypted data length + * @return Decrypted data + */ +uint8_t* totp_crypto_decrypt_v2( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length); + +/** + * @brief Seed initialization vector (IV) using user's PIN + * @param crypto_settings crypto settings + * @param pin user's PIN + * @param pin_length user's PIN length + * @return Results of seeding IV + */ +CryptoSeedIVResult + totp_crypto_seed_iv_v2(CryptoSettings* crypto_settings, const uint8_t* pin, uint8_t pin_length); + +/** + * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption + * @param crypto_settings crypto settings + * @return \c true if cryptographic information is valid; \c false otherwise + */ +bool totp_crypto_verify_key_v2(const CryptoSettings* crypto_settings); +#endif diff --git a/services/crypto/crypto_v3.c b/services/crypto/crypto_v3.c new file mode 100644 index 00000000000..79ecb3d7242 --- /dev/null +++ b/services/crypto/crypto_v3.c @@ -0,0 +1,195 @@ +#include "crypto_v3.h" +#include +#include +#include +#include +#include +#include "../../types/common.h" +#include "../../config/wolfssl/config.h" +#include +#include +#include "memset_s.h" +#include "constants.h" +#include "polyfills.h" + +#define CRYPTO_ALIGNMENT_FACTOR (16) +#define PBKDF2_ITERATIONS_COUNT (200) + +static const uint8_t* get_device_uid() { + return (const uint8_t*)UID64_BASE; //-V566 +} + +static uint8_t get_device_uid_length() { + return furi_hal_version_uid_size(); +} + +static const uint8_t* get_crypto_verify_key() { + return get_device_uid(); +} + +static uint8_t get_crypto_verify_key_length() { + return get_device_uid_length(); +} + +uint8_t* totp_crypto_encrypt_v3( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length) { + uint8_t* encrypted_data; + size_t remain = plain_data_length % CRYPTO_ALIGNMENT_FACTOR; + if(remain) { + size_t plain_data_aligned_length = plain_data_length - remain + CRYPTO_ALIGNMENT_FACTOR; + uint8_t* plain_data_aligned = malloc(plain_data_aligned_length); + furi_check(plain_data_aligned != NULL); + memset(plain_data_aligned, 0, plain_data_aligned_length); + memcpy(plain_data_aligned, plain_data, plain_data_length); + + encrypted_data = malloc(plain_data_aligned_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_aligned_length; + + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Encryption failed: enclave_load_key"); + furi_check( + furi_hal_crypto_encrypt(plain_data_aligned, encrypted_data, plain_data_aligned_length), + "Encryption failed: encrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Encryption failed: enclave_unload_key"); + + memset_s(plain_data_aligned, plain_data_aligned_length, 0, plain_data_aligned_length); + free(plain_data_aligned); + } else { + encrypted_data = malloc(plain_data_length); + furi_check(encrypted_data != NULL); + *encrypted_data_length = plain_data_length; + + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Encryption failed: enclave_load_key"); + furi_check( + furi_hal_crypto_encrypt(plain_data, encrypted_data, plain_data_length), + "Encryption failed: encrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Encryption failed: enclave_unload_key"); + } + + return encrypted_data; +} + +uint8_t* totp_crypto_decrypt_v3( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length) { + *decrypted_data_length = encrypted_data_length; + uint8_t* decrypted_data = malloc(*decrypted_data_length); + furi_check(decrypted_data != NULL); + furi_check( + furi_hal_crypto_enclave_load_key(crypto_settings->crypto_key_slot, crypto_settings->iv), + "Decryption failed: enclave_load_key"); + furi_check( + furi_hal_crypto_decrypt(encrypted_data, decrypted_data, encrypted_data_length), + "Decryption failed: decrypt"); + furi_check( + furi_hal_crypto_enclave_unload_key(crypto_settings->crypto_key_slot), + "Decryption failed: enclave_unload_key"); + return decrypted_data; +} + +CryptoSeedIVResult totp_crypto_seed_iv_v3( + CryptoSettings* crypto_settings, + const uint8_t* pin, + uint8_t pin_length) { + CryptoSeedIVResult result; + if(crypto_settings->crypto_verify_data == NULL) { + FURI_LOG_I(LOGGING_TAG, "Generating new salt"); + furi_hal_random_fill_buf(&crypto_settings->salt[0], CRYPTO_SALT_LENGTH); + } + + const uint8_t* device_uid = get_device_uid(); + uint8_t device_uid_length = get_device_uid_length(); + + uint8_t pbkdf_key_length = device_uid_length; + if(pin != NULL && pin_length > 0) { + pbkdf_key_length += pin_length; + } + + uint8_t* pbkdf_key = malloc(pbkdf_key_length); + furi_check(pbkdf_key != NULL); + + memcpy(pbkdf_key, device_uid, device_uid_length); + + if(pin != NULL && pin_length > 0) { + memcpy(pbkdf_key + device_uid_length, pin, pin_length); + } + + uint8_t pbkdf_output[WC_SHA512_DIGEST_SIZE] = {0}; + + int pbkdf_result_code = wc_PBKDF2( + &pbkdf_output[0], + pbkdf_key, + pbkdf_key_length, + &crypto_settings->salt[0], + CRYPTO_SALT_LENGTH, + PBKDF2_ITERATIONS_COUNT, + WC_SHA512_DIGEST_SIZE, + WC_SHA512); + + memset_s(pbkdf_key, pbkdf_key_length, 0, pbkdf_key_length); + free(pbkdf_key); + + if(pbkdf_result_code == 0) { + uint8_t offset = pbkdf_output[WC_SHA512_DIGEST_SIZE - 1] % + (WC_SHA512_DIGEST_SIZE - CRYPTO_IV_LENGTH - 1); + memcpy(&crypto_settings->iv[0], &pbkdf_output[offset], CRYPTO_IV_LENGTH); + result = CryptoSeedIVResultFlagSuccess; + if(crypto_settings->crypto_verify_data == NULL) { + const uint8_t* crypto_vkey = get_crypto_verify_key(); + uint8_t crypto_vkey_length = get_crypto_verify_key_length(); + FURI_LOG_I(LOGGING_TAG, "Generating crypto verify data"); + crypto_settings->crypto_verify_data = malloc(crypto_vkey_length); + furi_check(crypto_settings->crypto_verify_data != NULL); + crypto_settings->crypto_verify_data_length = crypto_vkey_length; + + crypto_settings->crypto_verify_data = totp_crypto_encrypt_v3( + crypto_vkey, + crypto_vkey_length, + crypto_settings, + &crypto_settings->crypto_verify_data_length); + + crypto_settings->pin_required = pin != NULL && pin_length > 0; + + result |= CryptoSeedIVResultFlagNewCryptoVerifyData; + } + } else { + result = CryptoSeedIVResultFailed; + } + + memset_s(&pbkdf_output[0], WC_SHA512_DIGEST_SIZE, 0, WC_SHA512_DIGEST_SIZE); + + return result; +} + +bool totp_crypto_verify_key_v3(const CryptoSettings* crypto_settings) { + size_t decrypted_key_length; + uint8_t* decrypted_key = totp_crypto_decrypt_v3( + crypto_settings->crypto_verify_data, + crypto_settings->crypto_verify_data_length, + crypto_settings, + &decrypted_key_length); + + const uint8_t* crypto_vkey = get_crypto_verify_key(); + uint8_t crypto_vkey_length = get_crypto_verify_key_length(); + bool key_valid = true; + for(uint8_t i = 0; i < crypto_vkey_length && key_valid; i++) { + if(decrypted_key[i] != crypto_vkey[i]) key_valid = false; + } + + free(decrypted_key); + + return key_valid; +} \ No newline at end of file diff --git a/services/crypto/crypto_v3.h b/services/crypto/crypto_v3.h new file mode 100644 index 00000000000..39c718aab53 --- /dev/null +++ b/services/crypto/crypto_v3.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include "../../types/crypto_settings.h" +#include "common_types.h" + +/** + * @brief Encrypts plain data using built-in certificate and given initialization vector (IV) + * @param plain_data plain data to be encrypted + * @param plain_data_length plain data length + * @param crypto_settings crypto settings + * @param[out] encrypted_data_length encrypted data length + * @return Encrypted data + */ +uint8_t* totp_crypto_encrypt_v3( + const uint8_t* plain_data, + const size_t plain_data_length, + const CryptoSettings* crypto_settings, + size_t* encrypted_data_length); + +/** + * @brief Decrypts encrypted data using built-in certificate and given initialization vector (IV) + * @param encrypted_data encrypted data to be decrypted + * @param encrypted_data_length encrypted data length + * @param crypto_settings crypto settings + * @param[out] decrypted_data_length decrypted data length + * @return Decrypted data + */ +uint8_t* totp_crypto_decrypt_v3( + const uint8_t* encrypted_data, + const size_t encrypted_data_length, + const CryptoSettings* crypto_settings, + size_t* decrypted_data_length); + +/** + * @brief Seed initialization vector (IV) using user's PIN + * @param crypto_settings crypto settings + * @param pin user's PIN + * @param pin_length user's PIN length + * @return Results of seeding IV + */ +CryptoSeedIVResult + totp_crypto_seed_iv_v3(CryptoSettings* crypto_settings, const uint8_t* pin, uint8_t pin_length); + +/** + * @brief Verifies whether cryptographic information (certificate + IV) is valid and can be used for encryption and decryption + * @param crypto_settings crypto settings + * @return \c true if cryptographic information is valid; \c false otherwise + */ +bool totp_crypto_verify_key_v3(const CryptoSettings* crypto_settings); \ No newline at end of file diff --git a/services/crypto/polyfills.h b/services/crypto/polyfills.h new file mode 100644 index 00000000000..6e66d03d2e1 --- /dev/null +++ b/services/crypto/polyfills.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#ifndef FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_START + +// FW Crypto API is outdated, let's polyfill it +#define FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_START (12u) +#define FURI_HAL_CRYPTO_ENCLAVE_USER_KEY_SLOT_END (100u) +#define furi_hal_crypto_enclave_ensure_key furi_hal_crypto_verify_key +#define furi_hal_crypto_enclave_load_key furi_hal_crypto_store_load_key +#define furi_hal_crypto_enclave_unload_key furi_hal_crypto_store_unload_key + +#endif \ No newline at end of file diff --git a/services/idle_timeout/idle_timeout.c b/services/idle_timeout/idle_timeout.c new file mode 100644 index 00000000000..66369eb93ff --- /dev/null +++ b/services/idle_timeout/idle_timeout.c @@ -0,0 +1,66 @@ +#include "idle_timeout.h" +#include +#include + +#define IDLE_TIMER_CHECK_PERIODICITY_SEC (1) +#define SEC_TO_TICKS(sec) ((sec)*1000) + +struct IdleTimeoutContext { + FuriTimer* timer; + bool activity_reported; + void* on_idle_callback_context; + IDLE_TIMEOUT_CALLBACK on_idle_callback; + uint16_t timeout_sec; + uint16_t idle_period_sec; + bool idle_handled; +}; + +static void idle_timer_callback(void* context) { + IdleTimeoutContext* instance = context; + if(instance->activity_reported) { + instance->idle_period_sec = 0; + instance->idle_handled = false; + instance->activity_reported = false; + } else if(!instance->idle_handled) { + if(instance->idle_period_sec >= instance->timeout_sec) { + instance->idle_handled = + instance->on_idle_callback(instance->on_idle_callback_context); + } else { + instance->idle_period_sec += IDLE_TIMER_CHECK_PERIODICITY_SEC; + } + } +} + +IdleTimeoutContext* idle_timeout_alloc( + uint16_t timeout_sec, + IDLE_TIMEOUT_CALLBACK on_idle_callback, + void* on_idle_callback_context) { + IdleTimeoutContext* instance = malloc(sizeof(IdleTimeoutContext)); + if(instance == NULL) return NULL; + + instance->timer = furi_timer_alloc(&idle_timer_callback, FuriTimerTypePeriodic, instance); + if(instance->timer == NULL) return NULL; + + instance->timeout_sec = timeout_sec; + instance->on_idle_callback = on_idle_callback; + instance->on_idle_callback_context = on_idle_callback_context; + return instance; +} + +void idle_timeout_start(IdleTimeoutContext* context) { + furi_timer_start(context->timer, SEC_TO_TICKS(IDLE_TIMER_CHECK_PERIODICITY_SEC)); +} + +void idle_timeout_stop(IdleTimeoutContext* context) { + furi_timer_stop(context->timer); +} + +void idle_timeout_report_activity(IdleTimeoutContext* context) { + context->activity_reported = true; +} + +void idle_timeout_free(IdleTimeoutContext* context) { + furi_timer_stop(context->timer); + furi_timer_free(context->timer); + free(context); +} \ No newline at end of file diff --git a/services/idle_timeout/idle_timeout.h b/services/idle_timeout/idle_timeout.h new file mode 100644 index 00000000000..5e2359876a0 --- /dev/null +++ b/services/idle_timeout/idle_timeout.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +typedef struct IdleTimeoutContext IdleTimeoutContext; + +typedef bool (*IDLE_TIMEOUT_CALLBACK)(void* context); + +/** + * @brief Initializes a new instance of IDLE timeout + * @param timeout_sec IDLE timeout in seconds + * @param on_idle_callback callback function to trigger when IDLE timeout happened + * @param on_idle_callback_context callback function context + * @return IDLE timeout context + */ +IdleTimeoutContext* idle_timeout_alloc( + uint16_t timeout_sec, + IDLE_TIMEOUT_CALLBACK on_idle_callback, + void* on_idle_callback_context); + +/** + * @brief Starts IDLE timeout + * @param context IDLE timeout context + */ +void idle_timeout_start(IdleTimeoutContext* context); + +/** + * @brief Stops IDLE timeout + * @param context IDLE timeout context + */ +void idle_timeout_stop(IdleTimeoutContext* context); + +/** + * @brief Reports activity to IDLE timeout + * @param context IDLE timeout context + */ +void idle_timeout_report_activity(IdleTimeoutContext* context); + +/** + * @brief Disposes IDLE timeout and releases all the resources + * @param context IDLE timeout context + */ +void idle_timeout_free(IdleTimeoutContext* context); \ No newline at end of file diff --git a/services/totp/totp.c b/services/totp/totp.c new file mode 100644 index 00000000000..32232f21a7c --- /dev/null +++ b/services/totp/totp.c @@ -0,0 +1,125 @@ +#include "totp.h" + +#include +#include +#include +#include +#include "../../config/wolfssl/config.h" +#include + +#define HMAC_MAX_RESULT_SIZE WC_SHA512_DIGEST_SIZE + +static uint64_t swap_uint64(uint64_t val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); +} + +/** + * @brief Generates the timeblock for a time in seconds. + * Timeblocks are the amount of intervals in a given time. For example, + * if 1,000,000 seconds has passed for 30 second intervals, you would get + * 33,333 timeblocks (intervals), where timeblock++ is effectively +30 seconds. + * @param interval in seconds + * @param for_time a time in seconds to get the current timeblocks + * @return Timeblock given \p for_time using \p interval + */ +uint64_t totp_timecode(uint8_t interval, uint64_t for_time) { + return for_time / interval; +} + +/** + * @brief Generates an OTP (One Time Password) + * @param algo hashing algorithm to be used + * @param plain_secret plain token secret + * @param plain_secret_length plain token secret length + * @param input input data for OTP code generation + * @return OTP code if code was successfully generated; 0 otherwise + */ +uint64_t otp_generate( + TOTP_ALGO algo, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t input) { + uint8_t hmac[HMAC_MAX_RESULT_SIZE] = {0}; + + uint64_t input_swapped = swap_uint64(input); + + int hmac_len = + (*algo)(plain_secret, plain_secret_length, (uint8_t*)&input_swapped, 8, &hmac[0]); + if(hmac_len == 0) { + return OTP_ERROR; + } + + uint64_t offset = (hmac[hmac_len - 1] & 0xF); + uint64_t i_code = + ((hmac[offset] & 0x7F) << 24 | (hmac[offset + 1] & 0xFF) << 16 | + (hmac[offset + 2] & 0xFF) << 8 | (hmac[offset + 3] & 0xFF)); + + return i_code; +} + +uint64_t totp_at( + TOTP_ALGO algo, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval) { + uint64_t for_time_adjusted = + timezone_offset_apply(for_time, timezone_offset_from_hours(timezone)); + return otp_generate( + algo, plain_secret, plain_secret_length, totp_timecode(interval, for_time_adjusted)); +} + +static int totp_algo_common( + int type, + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + Hmac hmac; + int ret = wc_HmacSetKey(&hmac, type, key, key_length); + if(ret == 0) { + ret = wc_HmacUpdate(&hmac, input, input_length); + } + + if(ret == 0) { + ret = wc_HmacFinal(&hmac, output); + } + + wc_HmacFree(&hmac); + return ret == 0 ? wc_HmacSizeByType(type) : 0; +} + +static int totp_algo_sha1( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + return totp_algo_common(WC_SHA, key, key_length, input, input_length, output); +} + +static int totp_algo_sha256( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + return totp_algo_common(WC_SHA256, key, key_length, input, input_length, output); +} + +static int totp_algo_sha512( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output) { + return totp_algo_common(WC_SHA512, key, key_length, input, input_length, output); +} + +const TOTP_ALGO TOTP_ALGO_SHA1 = (TOTP_ALGO)(&totp_algo_sha1); +const TOTP_ALGO TOTP_ALGO_SHA256 = (TOTP_ALGO)(&totp_algo_sha256); +const TOTP_ALGO TOTP_ALGO_SHA512 = (TOTP_ALGO)(&totp_algo_sha512); diff --git a/services/totp/totp.h b/services/totp/totp.h new file mode 100644 index 00000000000..d578f6ea99d --- /dev/null +++ b/services/totp/totp.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#define OTP_ERROR (0) + +/** + * @brief Must compute HMAC using passed arguments, output as char array through output. + * \p key is secret key buffer. + * \p key_length is secret key buffer length. + * \p input is input buffer. + * \p input_length is input buffer length. + * \p output is an output buffer of the resulting HMAC operation. + * Must return 0 if error, or the length in bytes of the HMAC operation. + */ +typedef int (*TOTP_ALGO)( + const uint8_t* key, + size_t key_length, + const uint8_t* input, + size_t input_length, + uint8_t* output); + +/** + * @brief Computes HMAC using SHA1 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA1; + +/** + * @brief Computes HMAC using SHA256 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA256; + +/** + * @brief Computes HMAC using SHA512 + */ +extern const TOTP_ALGO TOTP_ALGO_SHA512; + +/** + * @brief Generates a OTP key using the totp algorithm. + * @param algo hashing algorithm to be used + * @param plain_secret plain token secret + * @param plain_secret_length plain token secret length + * @param for_time the time the generated key will be created for + * @param timezone UTC timezone adjustment for the generated key + * @param interval token lifetime in seconds + * @return TOTP code if code was successfully generated; 0 otherwise + */ +uint64_t totp_at( + TOTP_ALGO algo, + const uint8_t* plain_secret, + size_t plain_secret_length, + uint64_t for_time, + float timezone, + uint8_t interval); diff --git a/totp_10px.png b/totp_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..70ed56d988f87d376ea545a6202375f224308f5a GIT binary patch literal 496 zcmV4Tx04UFWkg-bwVHn0Atq{tFsKKG(rf9fIG{~hv4WtMU-jA2>$HViyyu5%vq%v0O22eI^ zGo4CEE16X(@CqM2r&P^iaYdOI{PX{=j|bO#HzsB^TebbzUh1lRda!Q~^tS(nco&r&)`d_yd0MKwp!+mK|%Bn|D@wjVuRcjn3OA^2%9o-Qp5zEcoqgU zj6)`lAWR(*clPdg@7+&sYr8MNbc1t#-qz+Cz{wPytlri}W57Tka9wArsX9^@wLhN+ z-j0F(3@~_0nKdgPjkwzf6F|>1=kFsx-~eb +#include +#include +#include +#include +#include +#include +#include "config/app/config.h" +#include "services/config/config.h" +#include "types/plugin_state.h" +#include "types/token_info.h" +#include "types/plugin_event.h" +#include "types/event_type.h" +#include "types/common.h" +#include "ui/scene_director.h" +#include "ui/constants.h" +#include "ui/common_dialogs.h" +#include "services/crypto/crypto_facade.h" +#include "cli/cli.h" +#include "version.h" +#include + +struct TotpRenderCallbackContext { + FuriMutex* mutex; + PluginState* plugin_state; +}; + +static void render_callback(Canvas* const canvas, void* const ctx) { + furi_assert(ctx); + const struct TotpRenderCallbackContext* context = ctx; + if(furi_mutex_acquire(context->mutex, 25) == FuriStatusOk) { + totp_scene_director_render(canvas, context->plugin_state); + furi_mutex_release(context->mutex); + } +} + +static void input_callback(InputEvent* const input_event, void* const ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static bool first_run_init(PluginState* const plugin_state) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_text( + message, + "Would you like to setup PIN?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + if(!totp_crypto_check_key_slot(plugin_state->crypto_settings.crypto_key_slot)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + + if(dialog_result == DialogMessageButtonRight) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication); + } else { + CryptoSeedIVResult seed_result = + totp_crypto_seed_iv(&plugin_state->crypto_settings, NULL, 0); + if(seed_result & CryptoSeedIVResultFlagSuccess && + seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) { + if(!totp_config_file_update_crypto_signatures(plugin_state)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + } else if(seed_result == CryptoSeedIVResultFailed) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + } + + return true; +} + +static bool pinless_activation(PluginState* const plugin_state) { + CryptoSeedIVResult seed_result = totp_crypto_seed_iv(&plugin_state->crypto_settings, NULL, 0); + if(seed_result & CryptoSeedIVResultFlagSuccess && + seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) { + if(!totp_config_file_update_crypto_signatures(plugin_state)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + } else if(seed_result == CryptoSeedIVResultFailed) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + + if(totp_crypto_verify_key(&plugin_state->crypto_settings)) { + totp_config_file_ensure_latest_encryption(plugin_state, NULL, 0); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + } else { + FURI_LOG_E( + LOGGING_TAG, + "Digital signature verification failed. Looks like conf file was created on another device and can't be used on any other"); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Exit", NULL, NULL); + dialog_message_set_text( + message, + "Digital signature verification failed", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + return false; + } + + return true; +} + +static bool pin_activation(PluginState* const plugin_state) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication); + return true; +} + +static bool totp_activate_initial_scene(PluginState* const plugin_state) { + if(plugin_state->crypto_settings.crypto_verify_data == NULL) { + if(!first_run_init(plugin_state)) { + return false; + } + } else if(plugin_state->crypto_settings.pin_required) { + if(!pin_activation(plugin_state)) { + return false; + } + } else { + if(!pinless_activation(plugin_state)) { + return false; + } + } + + return true; +} + +static bool on_user_idle(void* context) { + PluginState* plugin_state = context; + if(plugin_state->current_scene != TotpSceneAuthentication && + plugin_state->current_scene != TotpSceneStandby) { + totp_scene_director_activate_scene(plugin_state, TotpSceneAuthentication); + totp_scene_director_force_redraw(plugin_state); + return true; + } + + return false; +} + +static bool totp_plugin_state_init(PluginState* const plugin_state) { + plugin_state->gui = furi_record_open(RECORD_GUI); + plugin_state->dialogs_app = furi_record_open(RECORD_DIALOGS); + memset(&plugin_state->crypto_settings.iv[0], 0, CRYPTO_IV_LENGTH); + + if(!totp_config_file_load(plugin_state)) { + totp_dialogs_config_loading_error(plugin_state); + return false; + } + + plugin_state->event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if(plugin_state->automation_method & AutomationMethodBadBt) { + plugin_state->bt_type_code_worker_context = totp_bt_type_code_worker_init(); + } else { + plugin_state->bt_type_code_worker_context = NULL; + } +#endif + + if(plugin_state->crypto_settings.pin_required) { + plugin_state->idle_timeout_context = + idle_timeout_alloc(TOTP_AUTO_LOCK_IDLE_TIMEOUT_SEC, &on_user_idle, plugin_state); + idle_timeout_start(plugin_state->idle_timeout_context); + } else { + plugin_state->idle_timeout_context = NULL; + } + + return true; +} + +static void totp_plugin_state_free(PluginState* plugin_state) { + if(plugin_state->idle_timeout_context != NULL) { + idle_timeout_stop(plugin_state->idle_timeout_context); + idle_timeout_free(plugin_state->idle_timeout_context); + } + + furi_record_close(RECORD_GUI); + furi_record_close(RECORD_DIALOGS); + + totp_config_file_close(plugin_state); + + if(plugin_state->crypto_settings.crypto_verify_data != NULL) { + free(plugin_state->crypto_settings.crypto_verify_data); + } + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if(plugin_state->bt_type_code_worker_context != NULL) { + totp_bt_type_code_worker_free(plugin_state->bt_type_code_worker_context); + plugin_state->bt_type_code_worker_context = NULL; + } +#endif + + if(plugin_state->event_queue != NULL) { + furi_message_queue_free(plugin_state->event_queue); + } + + free(plugin_state); +} + +int32_t totp_app() { + FURI_LOG_I( + LOGGING_TAG, + "App version: %" PRIu8 ".%" PRIu8 ".%" PRIu8, + TOTP_APP_VERSION_MAJOR, + TOTP_APP_VERSION_MINOR, + TOTP_APP_VERSION_PATCH); + FURI_LOG_I(LOGGING_TAG, "WolfSSL version: " LIBWOLFSSL_VERSION_STRING); + + PluginState* plugin_state = malloc(sizeof(PluginState)); + furi_check(plugin_state != NULL); + + if(!totp_plugin_state_init(plugin_state)) { + FURI_LOG_E(LOGGING_TAG, "App state initialization failed\r\n"); + totp_plugin_state_free(plugin_state); + return 254; + } + + TotpCliContext* cli_context = totp_cli_register_command_handler(plugin_state); + + if(!totp_activate_initial_scene(plugin_state)) { + FURI_LOG_E(LOGGING_TAG, "An error ocurred during activating initial scene\r\n"); + totp_plugin_state_free(plugin_state); + return 253; + } + + // Affecting dolphin level + dolphin_deed(DolphinDeedPluginStart); + + FuriMutex* main_loop_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + struct TotpRenderCallbackContext render_context = { + .plugin_state = plugin_state, .mutex = main_loop_mutex}; + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, &render_context); + view_port_input_callback_set(view_port, input_callback, plugin_state->event_queue); + + // Open GUI and register view_port + gui_add_view_port(plugin_state->gui, view_port, GuiLayerFullscreen); + + PluginEvent event; + bool processing = true; + while(processing) { + if(furi_message_queue_get(plugin_state->event_queue, &event, FuriWaitForever) == + FuriStatusOk) { + if(event.type == EventForceCloseApp) { + processing = false; + } else if(event.type == EventForceRedraw) { + processing = true; //-V1048 + } else if(furi_mutex_acquire(main_loop_mutex, FuriWaitForever) == FuriStatusOk) { + if(event.type == EventTypeKey && plugin_state->idle_timeout_context != NULL) { + idle_timeout_report_activity(plugin_state->idle_timeout_context); + } + + processing = totp_scene_director_handle_event(&event, plugin_state); + + furi_mutex_release(main_loop_mutex); + } + } + + view_port_update(view_port); + } + + totp_cli_unregister_command_handler(cli_context); + totp_scene_director_deactivate_active_scene(plugin_state); + + view_port_enabled_set(view_port, false); + gui_remove_view_port(plugin_state->gui, view_port); + view_port_free(view_port); + furi_mutex_free(main_loop_mutex); + totp_plugin_state_free(plugin_state); + return 0; +} diff --git a/types/automation_kb_layout.h b/types/automation_kb_layout.h new file mode 100644 index 00000000000..9c23e91ab44 --- /dev/null +++ b/types/automation_kb_layout.h @@ -0,0 +1,8 @@ +#pragma once + +typedef uint8_t AutomationKeyboardLayout; + +enum AutomationKeyboardLayouts { + AutomationKeyboardLayoutQWERTY = 0, + AutomationKeyboardLayoutAZERTY = 1 +}; \ No newline at end of file diff --git a/types/automation_method.h b/types/automation_method.h new file mode 100644 index 00000000000..24c85ee7613 --- /dev/null +++ b/types/automation_method.h @@ -0,0 +1,13 @@ +#pragma once + +#include "../config/app/config.h" + +typedef uint8_t AutomationMethod; + +enum AutomationMethods { + AutomationMethodNone = 0b00, + AutomationMethodBadUsb = 0b01, +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + AutomationMethodBadBt = 0b10, +#endif +}; diff --git a/types/common.c b/types/common.c new file mode 100644 index 00000000000..ec5eb3ebd4d --- /dev/null +++ b/types/common.c @@ -0,0 +1,3 @@ +#include "common.h" + +const char* LOGGING_TAG = "TOTP APP"; \ No newline at end of file diff --git a/types/common.h b/types/common.h new file mode 100644 index 00000000000..737adb82db0 --- /dev/null +++ b/types/common.h @@ -0,0 +1,3 @@ +#pragma once + +extern const char* LOGGING_TAG; diff --git a/types/crypto_settings.h b/types/crypto_settings.h new file mode 100644 index 00000000000..22daf147e2d --- /dev/null +++ b/types/crypto_settings.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "../services/crypto/constants.h" + +typedef struct { + /** + * @brief Crypto key slot to be used + */ + uint8_t crypto_key_slot; + + /** + * @brief Crypto algorithms version to be used + */ + uint8_t crypto_version; + + /** + * @brief Initialization vector (IV) to be used for encryption\decryption + */ + uint8_t iv[CRYPTO_IV_LENGTH]; + + /** + * @brief Randomly-generated salt + */ + uint8_t salt[CRYPTO_SALT_LENGTH]; + + /** + * @brief Encrypted well-known data + */ + uint8_t* crypto_verify_data; + + /** + * @brief Encrypted well-known data length + */ + size_t crypto_verify_data_length; + + /** + * @brief Whether user's PIN is required or not + */ + bool pin_required; +} CryptoSettings; \ No newline at end of file diff --git a/types/event_type.h b/types/event_type.h new file mode 100644 index 00000000000..138f528d85e --- /dev/null +++ b/types/event_type.h @@ -0,0 +1,6 @@ +#pragma once +#include + +typedef uint8_t EventType; + +enum EventTypes { EventTypeTick, EventTypeKey, EventForceCloseApp, EventForceRedraw }; diff --git a/types/notification_method.h b/types/notification_method.h new file mode 100644 index 00000000000..f8661335245 --- /dev/null +++ b/types/notification_method.h @@ -0,0 +1,9 @@ +#pragma once + +typedef uint8_t NotificationMethod; + +enum NotificationMethods { + NotificationMethodNone = 0b00, + NotificationMethodSound = 0b01, + NotificationMethodVibro = 0b10, +}; diff --git a/types/plugin_event.h b/types/plugin_event.h new file mode 100644 index 00000000000..76c22af5981 --- /dev/null +++ b/types/plugin_event.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include "event_type.h" + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; diff --git a/types/plugin_state.h b/types/plugin_state.h new file mode 100644 index 00000000000..388da5edfb2 --- /dev/null +++ b/types/plugin_state.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include "../config/app/config.h" +#include "../ui/totp_scenes_enum.h" +#include "../services/config/config_file_context.h" +#include "../services/idle_timeout/idle_timeout.h" +#include "notification_method.h" +#include "automation_method.h" +#include "automation_kb_layout.h" +#ifdef TOTP_BADBT_AUTOMATION_ENABLED +#include "../workers/bt_type_code/bt_type_code.h" +#endif +#include "crypto_settings.h" + +/** + * @brief Application state structure + */ +typedef struct { + /** + * @brief Application current scene + */ + Scene current_scene; + + /** + * @brief Application current scene state + */ + void* current_scene_state; + + /** + * @brief Reference to the firmware dialogs subsystem + */ + DialogsApp* dialogs_app; + + /** + * @brief Reference to the firmware GUI subsystem + */ + Gui* gui; + + /** + * @brief Timezone UTC offset in hours + */ + float timezone_offset; + + /** + * @brief Config file context + */ + ConfigFileContext* config_file_context; + + /** + * @brief Notification method + */ + NotificationMethod notification_method; + + /** + * @brief Automation method + */ + AutomationMethod automation_method; + + /** + * @brief Automation keyboard layout to be used + */ + AutomationKeyboardLayout automation_kb_layout; + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + /** + * @brief Bad-Bluetooth worker context + */ + TotpBtTypeCodeWorkerContext* bt_type_code_worker_context; +#endif + + /** + * @brief IDLE timeout context + */ + IdleTimeoutContext* idle_timeout_context; + + /** + * @brief Font index to be used to draw TOTP token + */ + uint8_t active_font_index; + + /** + * @brief Application even queue + */ + FuriMessageQueue* event_queue; + + /** + * @brief Crypto settings + */ + CryptoSettings crypto_settings; +} PluginState; diff --git a/types/token_info.c b/types/token_info.c new file mode 100644 index 00000000000..1d1e7316066 --- /dev/null +++ b/types/token_info.c @@ -0,0 +1,207 @@ +#include "token_info.h" +#include +#include +#include +#include +#include "common.h" +#include "../services/crypto/crypto_facade.h" + +TokenInfo* token_info_alloc() { + TokenInfo* tokenInfo = malloc(sizeof(TokenInfo)); + furi_check(tokenInfo != NULL); + tokenInfo->name = furi_string_alloc(); + token_info_set_defaults(tokenInfo); + return tokenInfo; +} + +void token_info_free(TokenInfo* token_info) { + if(token_info == NULL) return; + free(token_info->token); + furi_string_free(token_info->name); + free(token_info); +} + +bool token_info_set_secret( + TokenInfo* token_info, + const char* plain_token_secret, + size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, + const CryptoSettings* crypto_settings) { + if(token_secret_length == 0) return false; + uint8_t* plain_secret; + size_t plain_secret_length; + size_t plain_secret_size; + if(plain_token_secret_encoding == PlainTokenSecretEncodingBase32) { + plain_secret_size = token_secret_length; + plain_secret = malloc(plain_secret_size); + furi_check(plain_secret != NULL); + plain_secret_length = + base32_decode((const uint8_t*)plain_token_secret, plain_secret, plain_secret_size); + } else if(plain_token_secret_encoding == PlainTokenSecretEncodingBase64) { + plain_secret_length = 0; + plain_secret = base64_decode( + (const uint8_t*)plain_token_secret, + token_secret_length, + &plain_secret_length, + &plain_secret_size); + furi_check(plain_secret != NULL); + } else { + return false; + } + + bool result; + if(plain_secret_length > 0) { + if(token_info->token != NULL) { + free(token_info->token); + } + + token_info->token = totp_crypto_encrypt( + plain_secret, plain_secret_length, crypto_settings, &token_info->token_length); + result = true; + } else { + result = false; + } + + memset_s(plain_secret, plain_secret_size, 0, plain_secret_size); + free(plain_secret); + return result; +} + +bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits) { + switch(digits) { + case 5: + token_info->digits = TokenDigitsCountFive; + return true; + case 6: + token_info->digits = TokenDigitsCountSix; + return true; + case 8: + token_info->digits = TokenDigitsCountEight; + return true; + default: + break; + } + + return false; +} + +bool token_info_set_duration_from_int(TokenInfo* token_info, uint8_t duration) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" + if(duration >= TokenDurationMin && duration <= TokenDurationMax) { //-V560 + token_info->duration = duration; + return true; + } +#pragma GCC diagnostic pop + + return false; +} + +bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str) { + if(furi_string_cmpi_str(str, TOKEN_HASH_ALGO_SHA1_NAME) == 0) { + token_info->algo = TokenHashAlgoSha1; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_HASH_ALGO_SHA256_NAME) == 0) { + token_info->algo = TokenHashAlgoSha256; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_HASH_ALGO_SHA512_NAME) == 0) { + token_info->algo = TokenHashAlgoSha512; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_HASH_ALGO_STEAM_NAME) == 0) { + token_info->algo = TokenHashAlgoSteam; + return true; + } + + return false; +} + +bool token_info_set_algo_from_int(TokenInfo* token_info, uint8_t algo_code) { + switch(algo_code) { + case TokenHashAlgoSha1: + token_info->algo = TokenHashAlgoSha1; + break; + case TokenHashAlgoSha256: + token_info->algo = TokenHashAlgoSha256; + break; + case TokenHashAlgoSha512: + token_info->algo = TokenHashAlgoSha512; + break; + case TokenHashAlgoSteam: + token_info->algo = TokenHashAlgoSteam; + break; + default: + return false; + } + + return true; +} + +const char* token_info_get_algo_as_cstr(const TokenInfo* token_info) { + switch(token_info->algo) { + case TokenHashAlgoSha1: + return TOKEN_HASH_ALGO_SHA1_NAME; + case TokenHashAlgoSha256: + return TOKEN_HASH_ALGO_SHA256_NAME; + case TokenHashAlgoSha512: + return TOKEN_HASH_ALGO_SHA512_NAME; + case TokenHashAlgoSteam: + return TOKEN_HASH_ALGO_STEAM_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, TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME) == 0) { + token_info->automation_features |= TokenAutomationFeatureEnterAtTheEnd; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME) == 0) { + token_info->automation_features |= TokenAutomationFeatureTabAtTheEnd; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME) == 0) { + token_info->automation_features |= TokenAutomationFeatureTypeSlower; + return true; + } + + if(furi_string_cmpi_str(str, TOKEN_AUTOMATION_FEATURE_NONE_NAME) == 0) { + token_info->automation_features = TokenAutomationFeatureNone; + 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); + + clone->name = furi_string_alloc(); + furi_string_set(clone->name, src->name); + + return clone; +} + +void token_info_set_defaults(TokenInfo* token_info) { + furi_check(token_info != NULL); + token_info->algo = TokenHashAlgoDefault; + token_info->digits = TokenDigitsCountDefault; + token_info->duration = TokenDurationDefault; + token_info->automation_features = TokenAutomationFeatureNone; + furi_string_reset(token_info->name); +} \ No newline at end of file diff --git a/types/token_info.h b/types/token_info.h new file mode 100644 index 00000000000..969445dff5b --- /dev/null +++ b/types/token_info.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include +#include +#include "crypto_settings.h" + +#define TOKEN_HASH_ALGO_SHA1_NAME "sha1" +#define TOKEN_HASH_ALGO_STEAM_NAME "steam" +#define TOKEN_HASH_ALGO_SHA256_NAME "sha256" +#define TOKEN_HASH_ALGO_SHA512_NAME "sha512" +#define PLAIN_TOKEN_ENCODING_BASE32_NAME "base32" +#define PLAIN_TOKEN_ENCODING_BASE64_NAME "base64" +#define TOKEN_AUTOMATION_FEATURE_NONE_NAME "none" +#define TOKEN_AUTOMATION_FEATURE_ENTER_AT_THE_END_NAME "enter" +#define TOKEN_AUTOMATION_FEATURE_TAB_AT_THE_END_NAME "tab" +#define TOKEN_AUTOMATION_FEATURE_TYPE_SLOWER_NAME "slower" + +typedef uint8_t TokenHashAlgo; +typedef uint8_t TokenDigitsCount; +typedef uint8_t TokenDuration; +typedef uint8_t TokenAutomationFeature; +typedef uint8_t PlainTokenSecretEncoding; + +/** + * @brief Hashing algorithm to be used to generate token + */ +enum TokenHashAlgos { + /** + * @brief SHA1 hashing algorithm + */ + TokenHashAlgoSha1 = 0, + + /** + * @brief SHA256 hashing algorithm + */ + TokenHashAlgoSha256 = 1, + + /** + * @brief SHA512 hashing algorithm + */ + TokenHashAlgoSha512 = 2, + + /** + * @brief Algorithm used by Steam (Valve) + */ + TokenHashAlgoSteam = 3, + + /** + * @brief Default token hashing algorithm + */ + TokenHashAlgoDefault = TokenHashAlgoSha1 +}; + +/** + * @brief Token digits count to be generated. + */ +enum TokenDigitsCounts { + /** + * @brief 5 digits + */ + TokenDigitsCountFive = 5, + + /** + * @brief 6 digits + */ + TokenDigitsCountSix = 6, + + /** + * @brief 8 digits + */ + TokenDigitsCountEight = 8, + + /** + * @brief Default digits count + */ + TokenDigitsCountDefault = TokenDigitsCountSix, + + /** + * @brief Maximum digits count + */ + TokenDigitsCountMax = TokenDigitsCountEight +}; + +/** + * @brief Token durations + */ +enum TokenDurations { + /** + * @brief Default token duration + */ + TokenDurationDefault = 30, + + /** + * @brief Minimum token duration + */ + TokenDurationMin = 15, + + /** + * @brief Maximum token duration + */ + TokenDurationMax = UINT8_MAX +}; + +/** + * @brief Token automation features. + */ +enum TokenAutomationFeatures { + /** + * @brief No features enabled + */ + TokenAutomationFeatureNone = 0b000, + + /** + * @brief Press "Enter" key at the end as a part of token input automation + */ + TokenAutomationFeatureEnterAtTheEnd = 0b001, + + /** + * @brief Press "Tab" key at the end as a part of token input automation + */ + TokenAutomationFeatureTabAtTheEnd = 0b010, + + /** + * @brief Press keys slower and wait longer between keystrokes + */ + TokenAutomationFeatureTypeSlower = 0b100 +}; + +/** + * @brief Plain token secret encodings. + */ +enum PlainTokenSecretEncodings { + + /** + * @brief Base32 encoding + */ + PlainTokenSecretEncodingBase32 = 0, + + /** + * @brief Base64 encoding + */ + PlainTokenSecretEncodingBase64 = 1 +}; + +/** + * @brief TOTP token information + */ +typedef struct { + /** + * @brief Encrypted token secret + */ + uint8_t* token; + + /** + * @brief Encrypted token secret length + */ + size_t token_length; + + /** + * @brief User-friendly token name + */ + FuriString* name; + + /** + * @brief Hashing algorithm + */ + TokenHashAlgo algo; + + /** + * @brief Desired TOTP token length + */ + TokenDigitsCount digits; + + /** + * @brief Desired TOTP token duration in seconds + */ + TokenDuration duration; + + /** + * @brief Token input automation features + */ + TokenAutomationFeature automation_features; +} TokenInfo; + +/** + * @brief Allocates a new instance of \c TokenInfo + * @return + */ +TokenInfo* token_info_alloc(); + +/** + * @brief Disposes all the resources allocated by the given \c TokenInfo instance + * @param token_info instance to be disposed + */ +void token_info_free(TokenInfo* token_info); + +/** + * @brief Encrypts & sets plain token secret to the given instance of \c TokenInfo + * @param token_info instance where secret should be updated + * @param plain_token_secret plain token secret + * @param token_secret_length plain token secret length + * @param plain_token_secret_encoding plain token secret encoding + * @param crypto_settings crypto settings + * @return \c true if token successfully set; \c false otherwise + */ +bool token_info_set_secret( + TokenInfo* token_info, + const char* plain_token_secret, + size_t token_secret_length, + PlainTokenSecretEncoding plain_token_secret_encoding, + const CryptoSettings* crypto_settings); + +/** + * @brief Sets token digits count from \c uint8_t value + * @param token_info instance whichs token digits count length should be updated + * @param digits desired token digits count length + * @return \c true if token digits count length has been updated; \c false otherwise + */ +bool token_info_set_digits_from_int(TokenInfo* token_info, uint8_t digits); + +/** + * @brief Sets token duration from \c uint8_t value + * @param token_info instance whichs token digits count length should be updated + * @param duration desired token duration in seconds + * @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); + +/** + * @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 hashing algorithm has been updated; \c false otherwise + */ +bool token_info_set_algo_from_str(TokenInfo* token_info, const FuriString* str); + +/** + * @brief Sets token hashing algorithm from \c algo_code code + * @param token_info instance whichs token hashing algorithm should be updated + * @param algo_code desired token algorithm code + * @return \c true if token hashing algorithm has been updated; \c false otherwise + */ +bool token_info_set_algo_from_int(TokenInfo* token_info, uint8_t algo_code); + +/** + * @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 + */ +const 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); + +/** + * @brief Sets default values to all the properties of \c token_info + * @param token_info instance to set defaults to + */ +void token_info_set_defaults(TokenInfo* token_info); diff --git a/types/user_pin_codes.h b/types/user_pin_codes.h new file mode 100644 index 00000000000..b5d72d398c5 --- /dev/null +++ b/types/user_pin_codes.h @@ -0,0 +1,10 @@ +#pragma once + +typedef uint8_t TotpUserPinCode; + +enum TotpUserPinCodes { + PinCodeArrowUp = 2, + PinCodeArrowRight = 8, + PinCodeArrowDown = 11, + PinCodeArrowLeft = 5 +}; \ No newline at end of file diff --git a/ui/canvas_extensions.c b/ui/canvas_extensions.c new file mode 100644 index 00000000000..d3f044b7753 --- /dev/null +++ b/ui/canvas_extensions.c @@ -0,0 +1,33 @@ +#include "canvas_extensions.h" + +void canvas_draw_str_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const char* text, + size_t text_length, + const FONT_INFO* const font) { + const char* p_ch = text; + char ch; + size_t i = 0; + uint8_t offset_x = x; + uint8_t char_width = font->charInfo[0].width; + uint8_t offset_x_inc = char_width + font->spacePixels; + while(i < text_length && (ch = *p_ch) != 0) { + if(ch >= font->startChar && ch <= font->endChar) { + uint8_t char_index = ch - font->startChar; + canvas_draw_xbm( + canvas, + offset_x, + y, + char_width, + font->height, + &font->data[font->charInfo[char_index].offset]); + } + + offset_x += offset_x_inc; + + p_ch++; + i++; + } +} \ No newline at end of file diff --git a/ui/canvas_extensions.h b/ui/canvas_extensions.h new file mode 100644 index 00000000000..2e053b488d1 --- /dev/null +++ b/ui/canvas_extensions.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +/** + * @brief Draw string using given font + * @param canvas canvas to draw string at + * @param x horizontal position + * @param y vertical position + * @param text string to draw + * @param text_length string length + * @param font font to be used to draw string + */ +void canvas_draw_str_ex( + Canvas* canvas, + uint8_t x, + uint8_t y, + const char* text, + size_t text_length, + const FONT_INFO* const font); \ No newline at end of file diff --git a/ui/common_dialogs.c b/ui/common_dialogs.c new file mode 100644 index 00000000000..0a10389e17d --- /dev/null +++ b/ui/common_dialogs.c @@ -0,0 +1,20 @@ +#include "common_dialogs.h" +#include "constants.h" + +static DialogMessageButton totp_dialogs_common(PluginState* plugin_state, const char* text) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Exit", NULL, NULL); + dialog_message_set_text( + message, text, SCREEN_WIDTH_CENTER, SCREEN_HEIGHT_CENTER, AlignCenter, AlignCenter); + DialogMessageButton result = dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + return result; +} + +DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state) { + return totp_dialogs_common(plugin_state, "An error has occurred\nduring loading config file"); +} + +DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state) { + return totp_dialogs_common(plugin_state, "An error has occurred\nduring updating config file"); +} \ No newline at end of file diff --git a/ui/common_dialogs.h b/ui/common_dialogs.h new file mode 100644 index 00000000000..1ddd80a7588 --- /dev/null +++ b/ui/common_dialogs.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include "../types/plugin_state.h" + +/** + * @brief Shows standard dialog about the fact that error occurred when loading config file + * @param plugin_state application state + * @return dialog button which user pressed to close the dialog + */ +DialogMessageButton totp_dialogs_config_loading_error(PluginState* plugin_state); + +/** + * @brief Shows standard dialog about the fact that error occurred when updating config file + * @param plugin_state application state + * @return dialog button which user pressed to close the dialog + */ +DialogMessageButton totp_dialogs_config_updating_error(PluginState* plugin_state); \ No newline at end of file diff --git a/ui/constants.h b/ui/constants.h new file mode 100644 index 00000000000..81c2edf9281 --- /dev/null +++ b/ui/constants.h @@ -0,0 +1,6 @@ +#pragma once + +#define SCREEN_WIDTH (128) +#define SCREEN_HEIGHT (64) +#define SCREEN_WIDTH_CENTER (SCREEN_WIDTH >> 1) +#define SCREEN_HEIGHT_CENTER (SCREEN_HEIGHT >> 1) diff --git a/ui/scene_director.c b/ui/scene_director.c new file mode 100644 index 00000000000..cddbd713657 --- /dev/null +++ b/ui/scene_director.c @@ -0,0 +1,134 @@ +#include "../types/common.h" +#include "../config/app/config.h" +#include "scene_director.h" +#include "scenes/authenticate/totp_scene_authenticate.h" +#include "scenes/generate_token/totp_scene_generate_token.h" +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED +#include "scenes/add_new_token/totp_scene_add_new_token.h" +#endif +#include "scenes/token_menu/totp_scene_token_menu.h" +#include "scenes/app_settings/totp_app_settings.h" +#include "scenes/standby/standby.h" + +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene) { + totp_scene_director_deactivate_active_scene(plugin_state); + switch(scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_activate(plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_activate(plugin_state); + break; +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + case TotpSceneAddNewToken: + totp_scene_add_new_token_activate(plugin_state); + break; +#endif + case TotpSceneTokenMenu: + totp_scene_token_menu_activate(plugin_state); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_activate(plugin_state); + break; + case TotpSceneNone: + case TotpSceneStandby: + break; + default: + break; + } + + plugin_state->current_scene = scene; +} + +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state) { + Scene current_scene = plugin_state->current_scene; + plugin_state->current_scene = TotpSceneNone; + switch(current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_deactivate(plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_deactivate(plugin_state); + break; +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + case TotpSceneAddNewToken: + totp_scene_add_new_token_deactivate(plugin_state); + break; +#endif + case TotpSceneTokenMenu: + totp_scene_token_menu_deactivate(plugin_state); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_deactivate(plugin_state); + break; + case TotpSceneNone: + case TotpSceneStandby: + break; + default: + break; + } +} + +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state) { + switch(plugin_state->current_scene) { + case TotpSceneGenerateToken: + totp_scene_generate_token_render(canvas, plugin_state); + break; + case TotpSceneAuthentication: + totp_scene_authenticate_render(canvas, plugin_state); + break; +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + case TotpSceneAddNewToken: + totp_scene_add_new_token_render(canvas, plugin_state); + break; +#endif + case TotpSceneTokenMenu: + totp_scene_token_menu_render(canvas, plugin_state); + break; + case TotpSceneAppSettings: + totp_scene_app_settings_render(canvas, plugin_state); + break; + case TotpSceneNone: + break; + case TotpSceneStandby: + totp_scene_standby_render(canvas); + break; + default: + break; + } +} + +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state) { + bool processing = true; + switch(plugin_state->current_scene) { + case TotpSceneGenerateToken: + processing = totp_scene_generate_token_handle_event(event, plugin_state); + break; + case TotpSceneAuthentication: + processing = totp_scene_authenticate_handle_event(event, plugin_state); + break; +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + case TotpSceneAddNewToken: + processing = totp_scene_add_new_token_handle_event(event, plugin_state); + break; +#endif + case TotpSceneTokenMenu: + processing = totp_scene_token_menu_handle_event(event, plugin_state); + break; + case TotpSceneAppSettings: + processing = totp_scene_app_settings_handle_event(event, plugin_state); + break; + case TotpSceneNone: + case TotpSceneStandby: + break; + default: + break; + } + + return processing; +} + +void totp_scene_director_force_redraw(PluginState* const plugin_state) { + PluginEvent event = {.type = EventForceRedraw}; + furi_message_queue_put(plugin_state->event_queue, &event, FuriWaitForever); +} \ No newline at end of file diff --git a/ui/scene_director.h b/ui/scene_director.h new file mode 100644 index 00000000000..1f09f9ea988 --- /dev/null +++ b/ui/scene_director.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "../types/plugin_state.h" +#include "../types/plugin_event.h" +#include "totp_scenes_enum.h" + +/** + * @brief Activates scene + * @param plugin_state application state + * @param scene scene to be activated + * @param context scene context to be passed to the scene activation method + */ +void totp_scene_director_activate_scene(PluginState* const plugin_state, Scene scene); + +/** + * @brief Deactivate current scene + * @param plugin_state application state + */ +void totp_scene_director_deactivate_active_scene(PluginState* const plugin_state); + +/** + * @brief Renders current scene + * @param canvas canvas to render at + * @param plugin_state application state + */ +void totp_scene_director_render(Canvas* const canvas, PluginState* const plugin_state); + +/** + * @brief Handles application event for the current scene + * @param event event to be handled + * @param plugin_state application state + * @return \c true if event handled and applilcation should continue; \c false if application should be closed + */ +bool totp_scene_director_handle_event(PluginEvent* const event, PluginState* const plugin_state); + +/** + * @brief Forces screen to be redraw\updated + * @param plugin_state application state + */ +void totp_scene_director_force_redraw(PluginState* const plugin_state); \ No newline at end of file diff --git a/ui/scenes/add_new_token/totp_input_text.c b/ui/scenes/add_new_token/totp_input_text.c new file mode 100644 index 00000000000..389d651e77a --- /dev/null +++ b/ui/scenes/add_new_token/totp_input_text.c @@ -0,0 +1,54 @@ +#include "totp_input_text.h" + +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED +#include +#include + +typedef struct { + InputTextResult* result; + ViewDispatcher* view_dispatcher; +} InputTextContext; + +static void commit_text_input_callback(void* ctx) { + InputTextContext* context = ctx; + context->result->user_input_length = strnlen(context->result->user_input, INPUT_BUFFER_SIZE); + context->result->success = true; + view_dispatcher_stop(context->view_dispatcher); +} + +static bool back_event_callback(void* ctx) { + InputTextContext* context = ctx; + context->result->success = false; + view_dispatcher_stop(context->view_dispatcher); + return false; +} + +void totp_input_text(Gui* gui, const char* header_text, InputTextResult* result) { + ViewDispatcher* view_dispatcher = view_dispatcher_alloc(); + TextInput* text_input = text_input_alloc(); + InputTextContext context = {.result = result, .view_dispatcher = view_dispatcher}; + text_input_set_header_text(text_input, header_text); + text_input_set_result_callback( + text_input, + commit_text_input_callback, + &context, + result->user_input, + INPUT_BUFFER_SIZE, + true); + + view_dispatcher_enable_queue(view_dispatcher); + view_dispatcher_add_view(view_dispatcher, 0, text_input_get_view(text_input)); + + view_dispatcher_attach_to_gui(view_dispatcher, gui, ViewDispatcherTypeFullscreen); + + view_dispatcher_set_navigation_event_callback(view_dispatcher, &back_event_callback); + view_dispatcher_set_event_callback_context(view_dispatcher, &context); + view_dispatcher_switch_to_view(view_dispatcher, 0); + + view_dispatcher_run(view_dispatcher); + + view_dispatcher_remove_view(view_dispatcher, 0); + view_dispatcher_free(view_dispatcher); + text_input_free(text_input); +} +#endif diff --git a/ui/scenes/add_new_token/totp_input_text.h b/ui/scenes/add_new_token/totp_input_text.h new file mode 100644 index 00000000000..89980ad35a1 --- /dev/null +++ b/ui/scenes/add_new_token/totp_input_text.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../../../config/app/config.h" +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED +#include + +#define INPUT_BUFFER_SIZE (255) + +typedef struct { + char user_input[INPUT_BUFFER_SIZE]; + size_t user_input_length; + bool success; +} InputTextResult; + +void totp_input_text(Gui* gui, const char* header_text, InputTextResult* result); +#endif diff --git a/ui/scenes/add_new_token/totp_scene_add_new_token.c b/ui/scenes/add_new_token/totp_scene_add_new_token.c new file mode 100644 index 00000000000..ae0a7bd488b --- /dev/null +++ b/ui/scenes/add_new_token/totp_scene_add_new_token.c @@ -0,0 +1,321 @@ +#include "totp_scene_add_new_token.h" +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../scene_director.h" +#include "totp_input_text.h" +#include "../../../types/token_info.h" +#include "../../../services/config/config.h" +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include + +char* TOKEN_ALGO_LIST[] = {"SHA1", "SHA256", "SHA512", "Steam"}; +char* TOKEN_DIGITS_TEXT_LIST[] = {"5 digits", "6 digits", "8 digits"}; +TokenDigitsCount TOKEN_DIGITS_VALUE_LIST[] = { + TokenDigitsCountFive, + TokenDigitsCountSix, + TokenDigitsCountEight}; + +typedef enum { + TokenNameTextBox, + TokenSecretTextBox, + TokenAlgoSelect, + TokenLengthSelect, + TokenDurationSelect, + ConfirmButton, +} Control; + +typedef struct { + char* token_name; + size_t token_name_length; + char* token_secret; + size_t token_secret_length; + bool saved; + Control selected_control; + int16_t screen_y_offset; + TokenHashAlgo algo; + uint8_t digits_count_index; + uint8_t duration; + FuriString* duration_text; +} SceneState; + +struct TotpAddContext { + SceneState* scene_state; + const CryptoSettings* crypto_settings; +}; + +enum TotpIteratorUpdateTokenResultsEx { TotpIteratorUpdateTokenResultInvalidSecret = 1 }; + +static void update_duration_text(SceneState* scene_state) { + furi_string_printf(scene_state->duration_text, "%d sec.", scene_state->duration); +} + +static TotpIteratorUpdateTokenResult add_token_handler(TokenInfo* tokenInfo, const void* context) { + const struct TotpAddContext* context_t = context; + if(!token_info_set_secret( + tokenInfo, + context_t->scene_state->token_secret, + context_t->scene_state->token_secret_length, + PlainTokenSecretEncodingBase32, + context_t->crypto_settings)) { + return TotpIteratorUpdateTokenResultInvalidSecret; + } + + furi_string_set_strn( + tokenInfo->name, + context_t->scene_state->token_name, + context_t->scene_state->token_name_length + 1); + tokenInfo->algo = context_t->scene_state->algo; + tokenInfo->digits = TOKEN_DIGITS_VALUE_LIST[context_t->scene_state->digits_count_index]; + tokenInfo->duration = context_t->scene_state->duration; + + return TotpIteratorUpdateTokenResultSuccess; +} + +static void ask_user_input( + const PluginState* plugin_state, + const char* header, + char** user_input, + size_t* user_input_length) { + InputTextResult input_result; + if(*user_input != NULL) { + strlcpy(input_result.user_input, *user_input, INPUT_BUFFER_SIZE); + } + + totp_input_text(plugin_state->gui, header, &input_result); + if(input_result.success) { + if(*user_input != NULL) { + free(*user_input); + } + *user_input = strdup(input_result.user_input); + *user_input_length = input_result.user_input_length; + } +} + +void totp_scene_add_new_token_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; + scene_state->token_name = "Name"; + scene_state->token_name_length = strlen(scene_state->token_name); + scene_state->token_secret = "Secret"; + scene_state->token_secret_length = strlen(scene_state->token_secret); + + scene_state->screen_y_offset = 0; + + scene_state->digits_count_index = 1; + + scene_state->duration = TokenDurationDefault; + scene_state->duration_text = furi_string_alloc(); + update_duration_text(scene_state); +} + +void totp_scene_add_new_token_render(Canvas* const canvas, const PluginState* plugin_state) { + const SceneState* scene_state = plugin_state->current_scene_state; + + ui_control_text_box_render( + canvas, + 10 - scene_state->screen_y_offset, + scene_state->token_name, + scene_state->selected_control == TokenNameTextBox); + ui_control_text_box_render( + canvas, + 27 - scene_state->screen_y_offset, + scene_state->token_secret, + scene_state->selected_control == TokenSecretTextBox); + ui_control_select_render( + canvas, + 0, + 44 - scene_state->screen_y_offset, + SCREEN_WIDTH, + TOKEN_ALGO_LIST[scene_state->algo], + scene_state->selected_control == TokenAlgoSelect); + ui_control_select_render( + canvas, + 0, + 61 - scene_state->screen_y_offset, + SCREEN_WIDTH, + TOKEN_DIGITS_TEXT_LIST[scene_state->digits_count_index], + scene_state->selected_control == TokenLengthSelect); + + ui_control_select_render( + canvas, + 0, + 78 - scene_state->screen_y_offset, + SCREEN_WIDTH, + furi_string_get_cstr(scene_state->duration_text), + scene_state->selected_control == TokenDurationSelect); + + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 24, + 101 - scene_state->screen_y_offset, + 48, + 13, + "Confirm", + scene_state->selected_control == ConfirmButton); + + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, 0, SCREEN_WIDTH, 10); + canvas_set_color(canvas, ColorBlack); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Add new token"); + canvas_set_font(canvas, FontSecondary); +} + +void update_screen_y_offset(SceneState* scene_state) { + if(scene_state->selected_control > TokenLengthSelect) { + scene_state->screen_y_offset = 51; + } else if(scene_state->selected_control > TokenAlgoSelect) { + scene_state->screen_y_offset = 34; + } else if(scene_state->selected_control > TokenSecretTextBox) { + scene_state->screen_y_offset = 17; + } else { + scene_state->screen_y_offset = 0; + } +} + +bool totp_scene_add_new_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = plugin_state->current_scene_state; + + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + totp_roll_value_uint8_t( + &scene_state->selected_control, + -1, + TokenNameTextBox, + ConfirmButton, + RollOverflowBehaviorStop); + update_screen_y_offset(scene_state); + break; + case InputKeyDown: + totp_roll_value_uint8_t( + &scene_state->selected_control, + 1, + TokenNameTextBox, + ConfirmButton, + RollOverflowBehaviorStop); + update_screen_y_offset(scene_state); + break; + case InputKeyRight: + if(scene_state->selected_control == TokenAlgoSelect) { + totp_roll_value_uint8_t( + &scene_state->algo, + 1, + TokenHashAlgoSha1, + TokenHashAlgoSteam, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenLengthSelect) { + totp_roll_value_uint8_t( + &scene_state->digits_count_index, 1, 0, 2, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenDurationSelect) { + totp_roll_value_uint8_t( + &scene_state->duration, 15, 15, 255, RollOverflowBehaviorStop); + update_duration_text(scene_state); + } + break; + case InputKeyLeft: + if(scene_state->selected_control == TokenAlgoSelect) { + totp_roll_value_uint8_t( + &scene_state->algo, + -1, + TokenHashAlgoSha1, + TokenHashAlgoSteam, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenLengthSelect) { + totp_roll_value_uint8_t( + &scene_state->digits_count_index, -1, 0, 2, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == TokenDurationSelect) { + totp_roll_value_uint8_t( + &scene_state->duration, -15, 15, 255, RollOverflowBehaviorStop); + update_duration_text(scene_state); + } + break; + case InputKeyOk: + break; + case InputKeyBack: + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + break; + default: + break; + } + } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + switch(scene_state->selected_control) { + case TokenNameTextBox: + ask_user_input( + plugin_state, + "Token name", + &scene_state->token_name, + &scene_state->token_name_length); + break; + case TokenSecretTextBox: + ask_user_input( + plugin_state, + "Token secret", + &scene_state->token_secret, + &scene_state->token_secret_length); + break; + case TokenAlgoSelect: + break; + case TokenLengthSelect: + break; + case TokenDurationSelect: + break; + case ConfirmButton: { + struct TotpAddContext add_context = { + .scene_state = scene_state, .crypto_settings = &plugin_state->crypto_settings}; + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + TotpIteratorUpdateTokenResult add_result = totp_token_info_iterator_add_new_token( + iterator_context, &add_token_handler, &add_context); + + if(add_result == TotpIteratorUpdateTokenResultSuccess) { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + } else if(add_result == TotpIteratorUpdateTokenResultInvalidSecret) { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_text( + message, + "Token secret is invalid", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + scene_state->selected_control = TokenSecretTextBox; + update_screen_y_offset(scene_state); + } else if(add_result == TotpIteratorUpdateTokenResultFileUpdateFailed) { + totp_dialogs_config_updating_error(plugin_state); + } + + break; + } + default: + break; + } + } + + return true; +} + +void totp_scene_add_new_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + free(scene_state->token_name); + free(scene_state->token_secret); + + furi_string_free(scene_state->duration_text); + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} +#endif diff --git a/ui/scenes/add_new_token/totp_scene_add_new_token.h b/ui/scenes/add_new_token/totp_scene_add_new_token.h new file mode 100644 index 00000000000..686cbe37cef --- /dev/null +++ b/ui/scenes/add_new_token/totp_scene_add_new_token.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../../../config/app/config.h" +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_add_new_token_activate(PluginState* plugin_state); +void totp_scene_add_new_token_render(Canvas* const canvas, const PluginState* plugin_state); +bool totp_scene_add_new_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_add_new_token_deactivate(PluginState* plugin_state); +#endif diff --git a/ui/scenes/app_settings/totp_app_settings.c b/ui/scenes/app_settings/totp_app_settings.c new file mode 100644 index 00000000000..252829432f0 --- /dev/null +++ b/ui/scenes/app_settings/totp_app_settings.c @@ -0,0 +1,373 @@ +#include "totp_app_settings.h" +#include +#include +#include +#include "../../canvas_extensions.h" +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include "../../scene_director.h" +#include "../../constants.h" +#include "../../../services/config/config.h" +#include "../../../services/convert/convert.h" +#include +#include "../../../config/app/config.h" +#ifdef TOTP_BADBT_AUTOMATION_ENABLED +#include "../../../workers/bt_type_code/bt_type_code.h" +#endif + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED +#define AUTOMATION_LIST_MAX_INDEX (3) +#else +#define AUTOMATION_LIST_MAX_INDEX (1) +#endif +#define BAD_KB_LAYOUT_LIST_MAX_INDEX (1) +#define FONT_TEST_STR_LENGTH (7) + +static const char* YES_NO_LIST[] = {"NO", "YES"}; +static const char* AUTOMATION_LIST[] = { + "None", + "USB" +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + , + "Bluetooth", + "BT and USB" +#endif +}; +static const char* BAD_KB_LAYOUT_LIST[] = {"QWERTY", "AZERTY"}; +static const char* FONT_TEST_STR = "0123BCD"; + +typedef enum { + HoursInput, + MinutesInput, + FontSelect, + SoundSwitch, + VibroSwitch, + AutomationSwitch, + BadKeyboardLayoutSelect, + ConfirmButton +} Control; + +typedef struct { + int8_t tz_offset_hours; + uint8_t tz_offset_minutes; + bool notification_sound; + bool notification_vibro; + AutomationMethod automation_method; + uint16_t y_offset; + AutomationKeyboardLayout automation_kb_layout; + Control selected_control; + uint8_t active_font; +} SceneState; + +void totp_scene_app_settings_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; + + float off_int; + float off_dec = modff(plugin_state->timezone_offset, &off_int); + scene_state->tz_offset_hours = off_int; + scene_state->tz_offset_minutes = 60.0f * off_dec; + scene_state->notification_sound = plugin_state->notification_method & NotificationMethodSound; + scene_state->notification_vibro = plugin_state->notification_method & NotificationMethodVibro; + scene_state->automation_method = + MIN(plugin_state->automation_method, AUTOMATION_LIST_MAX_INDEX); + scene_state->automation_kb_layout = + MIN(plugin_state->automation_kb_layout, BAD_KB_LAYOUT_LIST_MAX_INDEX); + + scene_state->active_font = plugin_state->active_font_index; +} + +static void two_digit_to_str(int8_t num, char* str) { + char* s = str; + if(num < 0) { + *(s++) = '-'; + num = -num; + } + + uint8_t d1 = (num / 10) % 10; + uint8_t d2 = num % 10; + *(s++) = CONVERT_DIGIT_TO_CHAR(d1); + *(s++) = CONVERT_DIGIT_TO_CHAR(d2); + *(s++) = '\0'; +} + +void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state) { + const SceneState* scene_state = plugin_state->current_scene_state; + if(scene_state->selected_control < FontSelect) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 0 - scene_state->y_offset, AlignLeft, AlignTop, "Timezone offset"); + canvas_set_font(canvas, FontSecondary); + + char tmp_str[4]; + two_digit_to_str(scene_state->tz_offset_hours, &tmp_str[0]); + canvas_draw_str_aligned( + canvas, 0, 17 - scene_state->y_offset, AlignLeft, AlignTop, "Hours:"); + ui_control_select_render( + canvas, + 36, + 10 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + &tmp_str[0], + scene_state->selected_control == HoursInput); + + two_digit_to_str(scene_state->tz_offset_minutes, &tmp_str[0]); + canvas_draw_str_aligned( + canvas, 0, 35 - scene_state->y_offset, AlignLeft, AlignTop, "Minutes:"); + ui_control_select_render( + canvas, + 36, + 28 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + &tmp_str[0], + scene_state->selected_control == MinutesInput); + + } else if(scene_state->selected_control < SoundSwitch) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 64 - scene_state->y_offset, AlignLeft, AlignTop, "Font"); + canvas_set_font(canvas, FontSecondary); + + const FONT_INFO* const font = available_fonts[scene_state->active_font]; + ui_control_select_render( + canvas, + 0, + 74 - scene_state->y_offset, + SCREEN_WIDTH - UI_CONTROL_VSCROLL_WIDTH, + font->name, + scene_state->selected_control == FontSelect); + + uint8_t font_x_offset = + SCREEN_WIDTH_CENTER - + (((font->charInfo[0].width + font->spacePixels) * FONT_TEST_STR_LENGTH) >> 1); + uint8_t font_y_offset = 108 - scene_state->y_offset - (font->height >> 1); + canvas_draw_str_ex( + canvas, font_x_offset, font_y_offset, FONT_TEST_STR, FONT_TEST_STR_LENGTH, font); + + } else if(scene_state->selected_control < AutomationSwitch) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 128 - scene_state->y_offset, AlignLeft, AlignTop, "Notifications"); + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned( + canvas, 0, 145 - scene_state->y_offset, AlignLeft, AlignTop, "Sound:"); + ui_control_select_render( + canvas, + 36, + 138 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + YES_NO_LIST[scene_state->notification_sound], + scene_state->selected_control == SoundSwitch); + + canvas_draw_str_aligned( + canvas, 0, 163 - scene_state->y_offset, AlignLeft, AlignTop, "Vibro:"); + ui_control_select_render( + canvas, + 36, + 156 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + YES_NO_LIST[scene_state->notification_vibro], + scene_state->selected_control == VibroSwitch); + } else { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 0, 192 - scene_state->y_offset, AlignLeft, AlignTop, "Automation"); + canvas_set_font(canvas, FontSecondary); + + canvas_draw_str_aligned( + canvas, 0, 209 - scene_state->y_offset, AlignLeft, AlignTop, "Method:"); + ui_control_select_render( + canvas, + 36, + 202 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + AUTOMATION_LIST[scene_state->automation_method], + scene_state->selected_control == AutomationSwitch); + + canvas_draw_str_aligned( + canvas, 0, 227 - scene_state->y_offset, AlignLeft, AlignTop, "Layout:"); + + ui_control_select_render( + canvas, + 36, + 220 - scene_state->y_offset, + SCREEN_WIDTH - 36 - UI_CONTROL_VSCROLL_WIDTH, + BAD_KB_LAYOUT_LIST[scene_state->automation_kb_layout], + scene_state->selected_control == BadKeyboardLayoutSelect); + + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 24, + 242 - scene_state->y_offset, + 48, + 13, + "Confirm", + scene_state->selected_control == ConfirmButton); + } + + ui_control_vscroll_render( + canvas, SCREEN_WIDTH - 3, 0, SCREEN_HEIGHT, scene_state->selected_control, ConfirmButton); +} + +bool totp_scene_app_settings_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(event->input.type == InputTypePress || event->input.type == InputTypeRepeat) { + switch(event->input.key) { + case InputKeyUp: + totp_roll_value_uint8_t( + &scene_state->selected_control, + -1, + HoursInput, + ConfirmButton, + RollOverflowBehaviorStop); + if(scene_state->selected_control > VibroSwitch) { + scene_state->y_offset = SCREEN_HEIGHT * 3; + } else if(scene_state->selected_control > FontSelect) { + scene_state->y_offset = SCREEN_HEIGHT * 2; + } else if(scene_state->selected_control > MinutesInput) { + scene_state->y_offset = SCREEN_HEIGHT; + } else { + scene_state->y_offset = 0; + } + break; + case InputKeyDown: + totp_roll_value_uint8_t( + &scene_state->selected_control, + 1, + HoursInput, + ConfirmButton, + RollOverflowBehaviorStop); + if(scene_state->selected_control > VibroSwitch) { + scene_state->y_offset = SCREEN_HEIGHT * 3; + } else if(scene_state->selected_control > FontSelect) { + scene_state->y_offset = SCREEN_HEIGHT * 2; + } else if(scene_state->selected_control > MinutesInput) { + scene_state->y_offset = SCREEN_HEIGHT; + } else { + scene_state->y_offset = 0; + } + break; + case InputKeyRight: + if(scene_state->selected_control == HoursInput) { + totp_roll_value_int8_t( + &scene_state->tz_offset_hours, 1, -12, 12, RollOverflowBehaviorStop); + } else if(scene_state->selected_control == MinutesInput) { + totp_roll_value_uint8_t( + &scene_state->tz_offset_minutes, 15, 0, 45, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == FontSelect) { + totp_roll_value_uint8_t( + &scene_state->active_font, + 1, + 0, + AVAILABLE_FONTS_COUNT - 1, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == SoundSwitch) { + scene_state->notification_sound = !scene_state->notification_sound; + } else if(scene_state->selected_control == VibroSwitch) { + scene_state->notification_vibro = !scene_state->notification_vibro; + } else if(scene_state->selected_control == AutomationSwitch) { + totp_roll_value_uint8_t( + &scene_state->automation_method, + 1, + 0, + AUTOMATION_LIST_MAX_INDEX, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == BadKeyboardLayoutSelect) { + totp_roll_value_uint8_t( + &scene_state->automation_kb_layout, + 1, + 0, + BAD_KB_LAYOUT_LIST_MAX_INDEX, + RollOverflowBehaviorRoll); + } + break; + case InputKeyLeft: + if(scene_state->selected_control == HoursInput) { + totp_roll_value_int8_t( + &scene_state->tz_offset_hours, -1, -12, 12, RollOverflowBehaviorStop); + } else if(scene_state->selected_control == MinutesInput) { + totp_roll_value_uint8_t( + &scene_state->tz_offset_minutes, -15, 0, 45, RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == FontSelect) { + totp_roll_value_uint8_t( + &scene_state->active_font, + -1, + 0, + AVAILABLE_FONTS_COUNT - 1, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == SoundSwitch) { + scene_state->notification_sound = !scene_state->notification_sound; + } else if(scene_state->selected_control == VibroSwitch) { + scene_state->notification_vibro = !scene_state->notification_vibro; + } else if(scene_state->selected_control == AutomationSwitch) { + totp_roll_value_uint8_t( + &scene_state->automation_method, + -1, + 0, + AUTOMATION_LIST_MAX_INDEX, + RollOverflowBehaviorRoll); + } else if(scene_state->selected_control == BadKeyboardLayoutSelect) { + totp_roll_value_uint8_t( + &scene_state->automation_kb_layout, + -1, + 0, + BAD_KB_LAYOUT_LIST_MAX_INDEX, + RollOverflowBehaviorRoll); + } + break; + case InputKeyOk: + break; + case InputKeyBack: { + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu); + break; + } + default: + break; + } + } else if( + event->input.type == InputTypeRelease && event->input.key == InputKeyOk && + scene_state->selected_control == ConfirmButton) { + plugin_state->timezone_offset = + (float)scene_state->tz_offset_hours + (float)scene_state->tz_offset_minutes / 60.0f; + + plugin_state->notification_method = + (scene_state->notification_sound ? NotificationMethodSound : NotificationMethodNone) | + (scene_state->notification_vibro ? NotificationMethodVibro : NotificationMethodNone); + + plugin_state->automation_method = scene_state->automation_method; + plugin_state->active_font_index = scene_state->active_font; + plugin_state->automation_kb_layout = scene_state->automation_kb_layout; + + if(!totp_config_file_update_user_settings(plugin_state)) { + totp_dialogs_config_updating_error(plugin_state); + return false; + } + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if((scene_state->automation_method & AutomationMethodBadBt) == 0 && + plugin_state->bt_type_code_worker_context != NULL) { + totp_bt_type_code_worker_free(plugin_state->bt_type_code_worker_context); + plugin_state->bt_type_code_worker_context = NULL; + } +#endif + + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu); + } + + return true; +} + +void totp_scene_app_settings_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} diff --git a/ui/scenes/app_settings/totp_app_settings.h b/ui/scenes/app_settings/totp_app_settings.h new file mode 100644 index 00000000000..e54aab87b0e --- /dev/null +++ b/ui/scenes/app_settings/totp_app_settings.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_app_settings_activate(PluginState* plugin_state); +void totp_scene_app_settings_render(Canvas* const canvas, const PluginState* plugin_state); +bool totp_scene_app_settings_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_app_settings_deactivate(PluginState* plugin_state); \ No newline at end of file diff --git a/ui/scenes/authenticate/totp_scene_authenticate.c b/ui/scenes/authenticate/totp_scene_authenticate.c new file mode 100644 index 00000000000..ea407395a2d --- /dev/null +++ b/ui/scenes/authenticate/totp_scene_authenticate.c @@ -0,0 +1,166 @@ +#include "totp_scene_authenticate.h" +#include +#include +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../../services/config/config.h" +#include "../../scene_director.h" +#include "../../totp_scenes_enum.h" +#include "../../../services/crypto/crypto_facade.h" +#include "../../../types/user_pin_codes.h" + +#define MAX_CODE_LENGTH CRYPTO_IV_LENGTH +static const uint8_t PIN_ASTERISK_RADIUS = 3; +static const uint8_t PIN_ASTERISK_STEP = (PIN_ASTERISK_RADIUS << 1) + 2; + +typedef struct { + TotpUserPinCode code_input[MAX_CODE_LENGTH]; + uint8_t code_length; +} SceneState; + +void totp_scene_authenticate_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + scene_state->code_length = 0; + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + plugin_state->current_scene_state = scene_state; + memset(&plugin_state->crypto_settings.iv[0], 0, CRYPTO_IV_LENGTH); +} + +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state) { + const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + int v_shift = 0; + if(scene_state->code_length > 0) { + v_shift = -10; + } + + if(plugin_state->crypto_settings.crypto_verify_data == NULL) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10 + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 5 + v_shift, + AlignCenter, + AlignCenter, + "to setup new PIN"); + } else { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + v_shift, + AlignCenter, + AlignCenter, + "Use arrow keys to enter PIN"); + } + + if(scene_state->code_length > 0) { + uint8_t left_start_x = ((scene_state->code_length - 1) * PIN_ASTERISK_STEP) >> 1; + for(uint8_t i = 0; i < scene_state->code_length; i++) { + canvas_draw_disc( + canvas, + SCREEN_WIDTH_CENTER - left_start_x + i * PIN_ASTERISK_STEP, + SCREEN_HEIGHT_CENTER + 10, + PIN_ASTERISK_RADIUS); + } + } +} + +bool totp_scene_authenticate_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } + + SceneState* scene_state = plugin_state->current_scene_state; + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowUp; + scene_state->code_length++; + } + break; + case InputKeyDown: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowDown; + scene_state->code_length++; + } + break; + case InputKeyRight: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowRight; + scene_state->code_length++; + } + break; + case InputKeyLeft: + if(scene_state->code_length < MAX_CODE_LENGTH) { + scene_state->code_input[scene_state->code_length] = PinCodeArrowLeft; + scene_state->code_length++; + } + break; + case InputKeyOk: + break; + case InputKeyBack: + if(scene_state->code_length > 0) { + scene_state->code_input[scene_state->code_length - 1] = 0; + scene_state->code_length--; + } + break; + default: + break; + } + } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + CryptoSeedIVResult seed_result = totp_crypto_seed_iv( + &plugin_state->crypto_settings, &scene_state->code_input[0], scene_state->code_length); + + if(seed_result & CryptoSeedIVResultFlagSuccess && + seed_result & CryptoSeedIVResultFlagNewCryptoVerifyData) { + totp_config_file_update_crypto_signatures(plugin_state); + } + + if(totp_crypto_verify_key(&plugin_state->crypto_settings)) { + FURI_LOG_D(LOGGING_TAG, "PIN is valid"); + totp_config_file_ensure_latest_encryption( + plugin_state, &scene_state->code_input[0], scene_state->code_length); + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + } else { + FURI_LOG_D(LOGGING_TAG, "PIN is NOT valid"); + memset(&scene_state->code_input[0], 0, MAX_CODE_LENGTH); + memset(&plugin_state->crypto_settings.iv[0], 0, CRYPTO_IV_LENGTH); + scene_state->code_length = 0; + + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Try again", NULL, NULL); + dialog_message_set_header( + message, + "You entered\ninvalid PIN", + SCREEN_WIDTH_CENTER - 25, + SCREEN_HEIGHT_CENTER - 5, + AlignCenter, + AlignCenter); + dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + } + } + + return true; +} + +void totp_scene_authenticate_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} diff --git a/ui/scenes/authenticate/totp_scene_authenticate.h b/ui/scenes/authenticate/totp_scene_authenticate.h new file mode 100644 index 00000000000..5ddd44a4a7b --- /dev/null +++ b/ui/scenes/authenticate/totp_scene_authenticate.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_authenticate_activate(PluginState* plugin_state); +void totp_scene_authenticate_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_authenticate_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_authenticate_deactivate(PluginState* plugin_state); diff --git a/ui/scenes/generate_token/totp_scene_generate_token.c b/ui/scenes/generate_token/totp_scene_generate_token.c new file mode 100644 index 00000000000..b769d364d75 --- /dev/null +++ b/ui/scenes/generate_token/totp_scene_generate_token.c @@ -0,0 +1,432 @@ +#include "totp_scene_generate_token.h" +#include +#include +#include +#include +#include +#include +#include "../../canvas_extensions.h" +#include "../../../types/token_info.h" +#include "../../../types/common.h" +#include "../../constants.h" +#include "../../../services/config/config.h" +#include "../../scene_director.h" +#include "../../../config/app/config.h" +#include "../../../workers/generate_totp_code/generate_totp_code.h" +#include "../../../workers/usb_type_code/usb_type_code.h" +#ifdef TOTP_BADBT_AUTOMATION_ENABLED +#include "../../../workers/bt_type_code/bt_type_code.h" +#endif + +#define PROGRESS_BAR_MARGIN (3) +#define PROGRESS_BAR_HEIGHT (4) + +typedef struct { + uint8_t progress_bar_x; + uint8_t progress_bar_width; + uint8_t code_total_length; + uint8_t code_offset_x; + uint8_t code_offset_y; +} UiPrecalculatedDimensions; + +typedef struct { + char last_code[TokenDigitsCountMax + 1]; + TotpUsbTypeCodeWorkerContext* usb_type_code_worker_context; + NotificationMessage const* notification_sequence_new_token[8]; + NotificationMessage const* notification_sequence_automation[11]; + FuriMutex* last_code_update_sync; + TotpGenerateCodeWorkerContext* generate_code_worker_context; + UiPrecalculatedDimensions ui_precalculated_dimensions; + const FONT_INFO* active_font; + NotificationApp* notification_app; +} SceneState; + +static const NotificationSequence* + get_notification_sequence_new_token(const PluginState* plugin_state, SceneState* scene_state) { + if(scene_state->notification_sequence_new_token[0] == NULL) { + NotificationMessage const** sequence = &scene_state->notification_sequence_new_token[0]; + *(sequence++) = &message_display_backlight_on; + *(sequence++) = &message_green_255; + if(plugin_state->notification_method & NotificationMethodVibro) { + *(sequence++) = &message_vibro_on; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + *(sequence++) = &message_note_c5; + } + + *(sequence++) = &message_delay_50; + + if(plugin_state->notification_method & NotificationMethodVibro) { + *(sequence++) = &message_vibro_off; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + *(sequence++) = &message_sound_off; + } + + *(sequence++) = NULL; + } + + return (NotificationSequence*)scene_state->notification_sequence_new_token; +} + +static const NotificationSequence* + get_notification_sequence_automation(const PluginState* plugin_state, SceneState* scene_state) { + if(scene_state->notification_sequence_automation[0] == NULL) { + NotificationMessage const** sequence = &scene_state->notification_sequence_automation[0]; + + *(sequence++) = &message_blue_255; + if(plugin_state->notification_method & NotificationMethodVibro) { + *(sequence++) = &message_vibro_on; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + *(sequence++) = &message_note_d5; //-V525 + *(sequence++) = &message_delay_50; + *(sequence++) = &message_note_e4; + *(sequence++) = &message_delay_50; + *(sequence++) = &message_note_f3; + } + + *(sequence++) = &message_delay_50; + + if(plugin_state->notification_method & NotificationMethodVibro) { + *(sequence++) = &message_vibro_off; + } + + if(plugin_state->notification_method & NotificationMethodSound) { + *(sequence++) = &message_sound_off; + } + + *(sequence++) = NULL; + } + + return (NotificationSequence*)scene_state->notification_sequence_automation; +} + +static void update_totp_params(PluginState* const plugin_state, size_t token_index) { + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(totp_token_info_iterator_go_to(iterator_context, token_index)) { + totp_generate_code_worker_notify( + scene_state->generate_code_worker_context, TotpGenerateCodeWorkerEventForceUpdate); + } +} + +static void draw_totp_code(Canvas* const canvas, const PluginState* const plugin_state) { + const SceneState* scene_state = plugin_state->current_scene_state; + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + uint8_t code_length = totp_token_info_iterator_get_current_token(iterator_context)->digits; + + canvas_draw_str_ex( + canvas, + scene_state->ui_precalculated_dimensions.code_offset_x, + scene_state->ui_precalculated_dimensions.code_offset_y, + scene_state->last_code, + code_length, + scene_state->active_font); +} + +static void on_new_token_code_generated(bool time_left, void* context) { + PluginState* const plugin_state = context; + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(totp_token_info_iterator_get_total_count(iterator_context) == 0) { + return; + } + + SceneState* scene_state = plugin_state->current_scene_state; + const TokenInfo* current_token = totp_token_info_iterator_get_current_token(iterator_context); + const FONT_INFO* const font = scene_state->active_font; + + uint8_t char_width = font->charInfo[0].width; + scene_state->ui_precalculated_dimensions.code_total_length = + current_token->digits * (char_width + font->spacePixels); + scene_state->ui_precalculated_dimensions.code_offset_x = + (SCREEN_WIDTH - scene_state->ui_precalculated_dimensions.code_total_length) >> 1; + scene_state->ui_precalculated_dimensions.code_offset_y = + SCREEN_HEIGHT_CENTER - (font->height >> 1); + + if(time_left) { + notification_message( + scene_state->notification_app, + get_notification_sequence_new_token(plugin_state, scene_state)); + } + + totp_scene_director_force_redraw(plugin_state); +} + +static void on_code_lifetime_updated_generated(float code_lifetime_percent, void* context) { + PluginState* const plugin_state = context; + SceneState* scene_state = plugin_state->current_scene_state; + scene_state->ui_precalculated_dimensions.progress_bar_width = + (uint8_t)((float)(SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1)) * code_lifetime_percent); + scene_state->ui_precalculated_dimensions.progress_bar_x = + ((SCREEN_WIDTH - (PROGRESS_BAR_MARGIN << 1) - + scene_state->ui_precalculated_dimensions.progress_bar_width) >> + 1) + + PROGRESS_BAR_MARGIN; + totp_scene_director_force_redraw(plugin_state); +} + +void totp_scene_generate_token_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + + plugin_state->current_scene_state = scene_state; + FURI_LOG_D(LOGGING_TAG, "Timezone set to: %f", (double)plugin_state->timezone_offset); + + scene_state->last_code_update_sync = furi_mutex_alloc(FuriMutexTypeNormal); + if(plugin_state->automation_method & AutomationMethodBadUsb) { + scene_state->usb_type_code_worker_context = totp_usb_type_code_worker_start( + scene_state->last_code, + TokenDigitsCountMax + 1, + scene_state->last_code_update_sync, + plugin_state->automation_kb_layout); + } + + scene_state->active_font = available_fonts[plugin_state->active_font_index]; + scene_state->notification_app = furi_record_open(RECORD_NOTIFICATION); + scene_state->notification_sequence_automation[0] = NULL; + scene_state->notification_sequence_new_token[0] = NULL; + +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + + if(plugin_state->automation_method & AutomationMethodBadBt) { + if(plugin_state->bt_type_code_worker_context == NULL) { + plugin_state->bt_type_code_worker_context = totp_bt_type_code_worker_init(); + } + totp_bt_type_code_worker_start( + plugin_state->bt_type_code_worker_context, + scene_state->last_code, + TokenDigitsCountMax + 1, + scene_state->last_code_update_sync, + plugin_state->automation_kb_layout); + } +#endif + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + scene_state->generate_code_worker_context = totp_generate_code_worker_start( + scene_state->last_code, + totp_token_info_iterator_get_current_token(iterator_context), + scene_state->last_code_update_sync, + plugin_state->timezone_offset, + &plugin_state->crypto_settings); + + totp_generate_code_worker_set_code_generated_handler( + scene_state->generate_code_worker_context, &on_new_token_code_generated, plugin_state); + + totp_generate_code_worker_set_lifetime_changed_handler( + scene_state->generate_code_worker_context, + &on_code_lifetime_updated_generated, + plugin_state); + + update_totp_params( + plugin_state, totp_token_info_iterator_get_current_token_index(iterator_context)); +} + +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state) { + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(totp_token_info_iterator_get_total_count(iterator_context) == 0) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 10, + AlignCenter, + AlignCenter, + "Token list is empty"); + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER + 10, + AlignCenter, + AlignCenter, + "Press OK button to access menu"); + return; + } + + const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + canvas_set_font(canvas, FontPrimary); + const char* token_name_cstr = + furi_string_get_cstr(totp_token_info_iterator_get_current_token(iterator_context)->name); + uint16_t token_name_width = canvas_string_width(canvas, token_name_cstr); + if(SCREEN_WIDTH - token_name_width > 18) { + canvas_draw_str_aligned( + canvas, + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER - 20, + AlignCenter, + AlignCenter, + token_name_cstr); + } else { + canvas_draw_str_aligned( + canvas, 9, SCREEN_HEIGHT_CENTER - 20, AlignLeft, AlignCenter, token_name_cstr); + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 0, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_draw_box(canvas, SCREEN_WIDTH - 10, SCREEN_HEIGHT_CENTER - 24, 9, 9); + canvas_set_color(canvas, ColorBlack); + } + + draw_totp_code(canvas, plugin_state); + + canvas_draw_box( + canvas, + scene_state->ui_precalculated_dimensions.progress_bar_x, + SCREEN_HEIGHT - PROGRESS_BAR_MARGIN - PROGRESS_BAR_HEIGHT, + scene_state->ui_precalculated_dimensions.progress_bar_width, + PROGRESS_BAR_HEIGHT); + if(totp_token_info_iterator_get_total_count(iterator_context) > 1) { + canvas_draw_icon(canvas, 0, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_left_8x9); + canvas_draw_icon( + canvas, SCREEN_WIDTH - 8, SCREEN_HEIGHT_CENTER - 24, &I_totp_arrow_right_8x9); + } + + if(plugin_state->automation_method & AutomationMethodBadUsb) { + canvas_draw_icon( + canvas, +#ifdef TOTP_BADBT_AUTOMATION_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_AUTOMATION_ENABLED + if(plugin_state->automation_method & AutomationMethodBadBt && + plugin_state->bt_type_code_worker_context != NULL && + totp_bt_type_code_worker_is_advertising(plugin_state->bt_type_code_worker_context)) { + canvas_draw_icon( + canvas, + SCREEN_WIDTH_CENTER + + (plugin_state->automation_method & AutomationMethodBadUsb ? 2 : -15), + SCREEN_HEIGHT_CENTER + 12, + &I_hid_ble_31x9); + } +#endif +} + +bool totp_scene_generate_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + if(event->input.type == InputTypeLong && event->input.key == InputKeyBack) { + return false; + } + + SceneState* scene_state; + if(event->input.type == InputTypeLong) { + if(event->input.key == InputKeyDown && + plugin_state->automation_method & AutomationMethodBadUsb) { + scene_state = (SceneState*)plugin_state->current_scene_state; + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + totp_usb_type_code_worker_notify( + scene_state->usb_type_code_worker_context, + TotpUsbTypeCodeWorkerEventType, + totp_token_info_iterator_get_current_token(iterator_context)->automation_features); + notification_message( + scene_state->notification_app, + get_notification_sequence_automation(plugin_state, scene_state)); + return true; + } +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + else if( + event->input.key == InputKeyUp && + plugin_state->automation_method & AutomationMethodBadBt) { + scene_state = (SceneState*)plugin_state->current_scene_state; + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + totp_bt_type_code_worker_notify( + plugin_state->bt_type_code_worker_context, + TotpBtTypeCodeWorkerEventType, + totp_token_info_iterator_get_current_token(iterator_context)->automation_features); + notification_message( + scene_state->notification_app, + get_notification_sequence_automation(plugin_state, scene_state)); + return true; + } +#endif + } else if(event->input.type == InputTypePress || event->input.type == InputTypeRepeat) { + switch(event->input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: { + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + size_t current_token_index = + totp_token_info_iterator_get_current_token_index(iterator_context); + totp_roll_value_size_t( + ¤t_token_index, + 1, + 0, + totp_token_info_iterator_get_total_count(iterator_context) - 1, + RollOverflowBehaviorRoll); + + update_totp_params(plugin_state, current_token_index); + break; + } + case InputKeyLeft: { + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + size_t current_token_index = + totp_token_info_iterator_get_current_token_index(iterator_context); + totp_roll_value_size_t( + ¤t_token_index, + -1, + 0, + totp_token_info_iterator_get_total_count(iterator_context) - 1, + RollOverflowBehaviorRoll); + + update_totp_params(plugin_state, current_token_index); + break; + } + case InputKeyOk: + break; + case InputKeyBack: + break; + default: + break; + } + } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + totp_scene_director_activate_scene(plugin_state, TotpSceneTokenMenu); + } + + return true; +} + +void totp_scene_generate_token_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + + totp_generate_code_worker_stop(scene_state->generate_code_worker_context); + + furi_record_close(RECORD_NOTIFICATION); + + if(plugin_state->automation_method & AutomationMethodBadUsb) { + totp_usb_type_code_worker_stop(scene_state->usb_type_code_worker_context); + } +#ifdef TOTP_BADBT_AUTOMATION_ENABLED + if(plugin_state->automation_method & AutomationMethodBadBt) { + totp_bt_type_code_worker_stop(plugin_state->bt_type_code_worker_context); + } +#endif + + furi_mutex_free(scene_state->last_code_update_sync); + + free(scene_state); + plugin_state->current_scene_state = NULL; +} diff --git a/ui/scenes/generate_token/totp_scene_generate_token.h b/ui/scenes/generate_token/totp_scene_generate_token.h new file mode 100644 index 00000000000..3f7bc0408a3 --- /dev/null +++ b/ui/scenes/generate_token/totp_scene_generate_token.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_generate_token_activate(PluginState* plugin_state); +void totp_scene_generate_token_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_generate_token_handle_event( + const PluginEvent* const event, + PluginState* plugin_state); +void totp_scene_generate_token_deactivate(PluginState* plugin_state); diff --git a/ui/scenes/standby/standby.c b/ui/scenes/standby/standby.c new file mode 100644 index 00000000000..5cd6bae6a76 --- /dev/null +++ b/ui/scenes/standby/standby.c @@ -0,0 +1,12 @@ +#include "standby.h" +#include +#include "../../constants.h" + +void totp_scene_standby_render(Canvas* const canvas) { + canvas_draw_icon(canvas, SCREEN_WIDTH - 56, SCREEN_HEIGHT - 48, &I_DolphinCommon_56x48); + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 10, AlignLeft, AlignTop, "CLI command"); + + canvas_draw_str_aligned(canvas, 5, 24, AlignLeft, AlignTop, "is running now"); +} \ No newline at end of file diff --git a/ui/scenes/standby/standby.h b/ui/scenes/standby/standby.h new file mode 100644 index 00000000000..78e2b0915df --- /dev/null +++ b/ui/scenes/standby/standby.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void totp_scene_standby_render(Canvas* const canvas); \ No newline at end of file diff --git a/ui/scenes/token_menu/totp_scene_token_menu.c b/ui/scenes/token_menu/totp_scene_token_menu.c new file mode 100644 index 00000000000..7eb4ea87cce --- /dev/null +++ b/ui/scenes/token_menu/totp_scene_token_menu.c @@ -0,0 +1,193 @@ +#include "totp_scene_token_menu.h" +#include +#include +#include "../../ui_controls.h" +#include "../../common_dialogs.h" +#include "../../constants.h" +#include "../../scene_director.h" +#include "../../../services/config/config.h" +#include "../../../types/token_info.h" +#include "../../../config/app/config.h" +#include + +#define SCREEN_HEIGHT_THIRD (SCREEN_HEIGHT / 3) +#define SCREEN_HEIGHT_THIRD_CENTER (SCREEN_HEIGHT_THIRD >> 1) + +typedef enum { AddNewToken, DeleteToken, AppSettings } Control; + +typedef struct { + Control selected_control; +} SceneState; + +void totp_scene_token_menu_activate(PluginState* plugin_state) { + SceneState* scene_state = malloc(sizeof(SceneState)); + furi_check(scene_state != NULL); + plugin_state->current_scene_state = scene_state; +} + +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state) { + const SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(totp_token_info_iterator_get_total_count(iterator_context) == 0) { + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 5, + 72, + 21, + "Add new token", + scene_state->selected_control == AddNewToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + 39, + 72, + 21, + "Settings", + scene_state->selected_control == AppSettings); + } else { + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Add new token", + scene_state->selected_control == AddNewToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Delete token", + scene_state->selected_control == DeleteToken); + ui_control_button_render( + canvas, + SCREEN_WIDTH_CENTER - 36, + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD + SCREEN_HEIGHT_THIRD_CENTER - 8, + 72, + 16, + "Settings", + scene_state->selected_control == AppSettings); + } +} + +bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state) { + if(event->type != EventTypeKey) { + return true; + } + + SceneState* scene_state = (SceneState*)plugin_state->current_scene_state; + if(event->input.type == InputTypePress) { + switch(event->input.key) { + case InputKeyUp: { + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + totp_roll_value_uint8_t( + &scene_state->selected_control, + -1, + AddNewToken, + AppSettings, + RollOverflowBehaviorRoll); + if(scene_state->selected_control == DeleteToken && + totp_token_info_iterator_get_total_count(iterator_context) == 0) { + scene_state->selected_control--; + } + break; + } + case InputKeyDown: { + const TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + totp_roll_value_uint8_t( + &scene_state->selected_control, + 1, + AddNewToken, + AppSettings, + RollOverflowBehaviorRoll); + if(scene_state->selected_control == DeleteToken && + totp_token_info_iterator_get_total_count(iterator_context) == 0) { + scene_state->selected_control++; + } + break; + } + case InputKeyRight: + break; + case InputKeyLeft: + break; + case InputKeyOk: + break; + case InputKeyBack: { + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + break; + } + default: + break; + } + } else if(event->input.type == InputTypeRelease && event->input.key == InputKeyOk) { + switch(scene_state->selected_control) { + case AddNewToken: { +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + totp_scene_director_activate_scene(plugin_state, TotpSceneAddNewToken); +#else + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "Back", NULL, NULL); + dialog_message_set_header(message, "Information", 0, 0, AlignLeft, AlignTop); + dialog_message_set_text( + message, + "Read here\nhttps://t.ly/8ZOtj\n how to add new token", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); +#endif + break; + } + case DeleteToken: { + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, "No", NULL, "Yes"); + dialog_message_set_header(message, "Confirmation", 0, 0, AlignLeft, AlignTop); + dialog_message_set_text( + message, + "Are you sure want to delete?", + SCREEN_WIDTH_CENTER, + SCREEN_HEIGHT_CENTER, + AlignCenter, + AlignCenter); + DialogMessageButton dialog_result = + dialog_message_show(plugin_state->dialogs_app, message); + dialog_message_free(message); + TokenInfoIteratorContext* iterator_context = + totp_config_get_token_iterator_context(plugin_state); + if(dialog_result == DialogMessageButtonRight && + totp_token_info_iterator_get_total_count(iterator_context) > 0) { + if(!totp_token_info_iterator_remove_current_token_info(iterator_context)) { + totp_dialogs_config_updating_error(plugin_state); + return false; + } + + totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken); + } + break; + } + case AppSettings: { + totp_scene_director_activate_scene(plugin_state, TotpSceneAppSettings); + break; + } + default: + break; + } + } + + return true; +} + +void totp_scene_token_menu_deactivate(PluginState* plugin_state) { + if(plugin_state->current_scene_state == NULL) return; + + free(plugin_state->current_scene_state); + plugin_state->current_scene_state = NULL; +} diff --git a/ui/scenes/token_menu/totp_scene_token_menu.h b/ui/scenes/token_menu/totp_scene_token_menu.h new file mode 100644 index 00000000000..a715c97480b --- /dev/null +++ b/ui/scenes/token_menu/totp_scene_token_menu.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../../../types/plugin_state.h" +#include "../../../types/plugin_event.h" + +void totp_scene_token_menu_activate(PluginState* plugin_state); +void totp_scene_token_menu_render(Canvas* const canvas, PluginState* plugin_state); +bool totp_scene_token_menu_handle_event(const PluginEvent* const event, PluginState* plugin_state); +void totp_scene_token_menu_deactivate(PluginState* plugin_state); diff --git a/ui/totp_scenes_enum.h b/ui/totp_scenes_enum.h new file mode 100644 index 00000000000..fa7c427dc80 --- /dev/null +++ b/ui/totp_scenes_enum.h @@ -0,0 +1,47 @@ +#pragma once + +#include "../config/app/config.h" + +typedef uint8_t Scene; + +/** + * @brief TOTP application scenes + */ +enum Scenes { + /** + * @brief Empty scene which does nothing + */ + TotpSceneNone, + + /** + * @brief Scene where user have to enter PIN to authenticate + */ + TotpSceneAuthentication, + + /** + * @brief Scene where actual TOTP token is getting generated and displayed to the user + */ + TotpSceneGenerateToken, + +#ifdef TOTP_UI_ADD_NEW_TOKEN_ENABLED + /** + * @brief Scene where user can add new token + */ + TotpSceneAddNewToken, +#endif + + /** + * @brief Scene with a menu for given token, allowing user to do multiple actions + */ + TotpSceneTokenMenu, + + /** + * @brief Scene where user can change application settings + */ + TotpSceneAppSettings, + + /** + * @brief Scene which informs user that CLI command is running + */ + TotpSceneStandby +}; diff --git a/ui/ui_controls.c b/ui/ui_controls.c new file mode 100644 index 00000000000..28a7d57dae2 --- /dev/null +++ b/ui/ui_controls.c @@ -0,0 +1,138 @@ +#include "ui_controls.h" +#include +#include "constants.h" + +#define TEXT_BOX_HEIGHT (13) +#define TEXT_BOX_MARGIN (4) + +void ui_control_text_box_render( + Canvas* const canvas, + int16_t y, + const char* text, + bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + SCREEN_WIDTH - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 3 + y, AlignLeft, AlignTop, text); +} + +void ui_control_select_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + const char* text, + bool is_selected) { + if(y < -TEXT_BOX_HEIGHT) { + return; + } + + if(is_selected) { + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 0); + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN - 1, + TEXT_BOX_MARGIN + y - 1, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN + 2, + TEXT_BOX_HEIGHT + 2, + 1); + } else { + canvas_draw_rframe( + canvas, + x + TEXT_BOX_MARGIN, + TEXT_BOX_MARGIN + y, + width - TEXT_BOX_MARGIN - TEXT_BOX_MARGIN, + TEXT_BOX_HEIGHT, + 1); + } + + canvas_draw_str_aligned( + canvas, x + (width >> 1), TEXT_BOX_MARGIN + 3 + y, AlignCenter, AlignTop, text); + canvas_draw_icon( + canvas, x + TEXT_BOX_MARGIN + 2, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_left_8x9); + canvas_draw_icon( + canvas, x + width - TEXT_BOX_MARGIN - 10, TEXT_BOX_MARGIN + 2 + y, &I_totp_arrow_right_8x9); +} + +void ui_control_button_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + uint8_t height, + const char* text, + bool is_selected) { + if(y < -height) { + return; + } + + if(is_selected) { + canvas_draw_rbox(canvas, x, y, width, height, 1); + canvas_set_color(canvas, ColorWhite); + } else { + canvas_draw_rframe(canvas, x, y, width, height, 1); + } + + canvas_draw_str_aligned( + canvas, x + (width >> 1), y + (height >> 1) + 1, AlignCenter, AlignCenter, text); + if(is_selected) { + canvas_set_color(canvas, ColorBlack); + } +} + +void ui_control_vscroll_render( + Canvas* const canvas, + uint8_t x, + uint8_t y, + uint8_t height, + uint8_t position, + uint8_t max_position) { + canvas_draw_line(canvas, x, y, x, y + height); + uint8_t block_height = height / MIN(10, max_position); + uint8_t block_position_y = + height * ((float)position / (float)max_position) - (block_height >> 1); + uint8_t block_position_y_abs = y + block_position_y; + if(block_position_y_abs + block_height > height) { + block_position_y_abs = height - block_height; + } + + canvas_draw_box( + canvas, + x - (UI_CONTROL_VSCROLL_WIDTH >> 1), + block_position_y_abs, + UI_CONTROL_VSCROLL_WIDTH, + block_height); +} diff --git a/ui/ui_controls.h b/ui/ui_controls.h new file mode 100644 index 00000000000..ccee4edfca2 --- /dev/null +++ b/ui/ui_controls.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#define UI_CONTROL_VSCROLL_WIDTH (3) + +/** + * @brief Renders TextBox control + * @param canvas canvas to render control at + * @param y vertical position of a control to be rendered at + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_text_box_render( + Canvas* const canvas, + int16_t y, + const char* text, + bool is_selected); + +/** + * @brief Renders Button control + * @param canvas canvas to render control at + * @param x horizontal position of a control to be rendered at + * @param y vertical position of a control to be rendered at + * @param width control width + * @param height control height + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_button_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + uint8_t height, + const char* text, + bool is_selected); + +/** + * @brief Renders Select control + * @param canvas canvas to render control at + * @param x horizontal position of a control to be rendered at + * @param y vertical position of a control to be rendered at + * @param width control width + * @param text text to be rendered inside control + * @param is_selected whether control should be rendered as focused or not + */ +void ui_control_select_render( + Canvas* const canvas, + int16_t x, + int16_t y, + uint8_t width, + const char* text, + bool is_selected); + +/** + * @brief Renders vertical scroll bar + * @param canvas canvas to render control at + * @param x horizontal position of a control to be rendered at + * @param y vertical position of a control to be rendered at + * @param height control height + * @param position current position + * @param max_position maximal position + */ +void ui_control_vscroll_render( + Canvas* const canvas, + uint8_t x, + uint8_t y, + uint8_t height, + uint8_t position, + uint8_t max_position); diff --git a/version.h b/version.h new file mode 100644 index 00000000000..8b77b9cd39b --- /dev/null +++ b/version.h @@ -0,0 +1,5 @@ +#pragma once + +#define TOTP_APP_VERSION_MAJOR (4) +#define TOTP_APP_VERSION_MINOR (0) +#define TOTP_APP_VERSION_PATCH (1) \ No newline at end of file diff --git a/workers/bt_type_code/bt_type_code.c b/workers/bt_type_code/bt_type_code.c new file mode 100644 index 00000000000..54e3553211f --- /dev/null +++ b/workers/bt_type_code/bt_type_code.c @@ -0,0 +1,235 @@ +#include "bt_type_code.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../types/common.h" +#include "../../types/token_info.h" +#include "../type_code_common.h" +#include "../../config/app/config.h" +#include "../../services/config/constants.h" + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL +#define TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH +#define TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE +#endif + +#define HID_BT_KEYS_STORAGE_PATH CONFIG_FILE_DIRECTORY_PATH "/.bt_hid.keys" + +struct TotpBtTypeCodeWorkerContext { + char* code_buffer; + uint8_t code_buffer_size; + uint8_t flags; + FuriThread* thread; + FuriMutex* code_buffer_sync; + Bt* bt; + bool is_advertising; + bool is_connected; +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL + char previous_bt_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN]; + uint8_t previous_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; +#endif + AutomationKeyboardLayout keyboard_layout; +}; + +static inline bool totp_type_code_worker_stop_requested() { + return furi_thread_flags_get() & TotpBtTypeCodeWorkerEventStop; +} + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL +static void totp_type_code_worker_bt_set_app_mac(uint8_t* mac) { + uint8_t max_i; + size_t uid_size = furi_hal_version_uid_size(); + if(uid_size < TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN) { + max_i = uid_size; + } else { + max_i = TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN; + } + + const uint8_t* uid = (const uint8_t*)UID64_BASE; //-V566 + memcpy(mac, uid, max_i); + for(uint8_t i = max_i; i < TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN; i++) { + mac[i] = 0; + } + + mac[0] = 0b10; +} +#endif + +static void totp_type_code_worker_type_code(TotpBtTypeCodeWorkerContext* context) { + uint8_t i = 0; + do { + furi_delay_ms(500); + i++; + } while(!context->is_connected && i < 100 && !totp_type_code_worker_stop_requested()); + + if(context->is_connected && + furi_mutex_acquire(context->code_buffer_sync, 500) == FuriStatusOk) { + totp_type_code_worker_execute_automation( + &furi_hal_bt_hid_kb_press, + &furi_hal_bt_hid_kb_release, + context->code_buffer, + context->code_buffer_size, + context->flags, + context->keyboard_layout); + furi_mutex_release(context->code_buffer_sync); + } +} + +static int32_t totp_type_code_worker_callback(void* context) { + furi_check(context); + FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + TotpBtTypeCodeWorkerContext* bt_context = context; + + while(true) { + uint32_t flags = furi_thread_flags_wait( + TotpBtTypeCodeWorkerEventStop | TotpBtTypeCodeWorkerEventType, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); //-V562 + if(flags & TotpBtTypeCodeWorkerEventStop) break; + + if(furi_mutex_acquire(context_mutex, FuriWaitForever) == FuriStatusOk) { + if(flags & TotpBtTypeCodeWorkerEventType) { + totp_type_code_worker_type_code(bt_context); + } + + furi_mutex_release(context_mutex); + } + } + + furi_mutex_free(context_mutex); + + 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( + TotpBtTypeCodeWorkerContext* context, + char* code_buffer, + uint8_t code_buffer_size, + FuriMutex* code_buffer_sync, + AutomationKeyboardLayout keyboard_layout) { + furi_check(context != NULL); + context->code_buffer = code_buffer; + context->code_buffer_size = code_buffer_size; + context->code_buffer_sync = code_buffer_sync; + context->keyboard_layout = keyboard_layout; + context->thread = furi_thread_alloc(); + furi_thread_set_name(context->thread, "TOTPBtHidWorker"); + furi_thread_set_stack_size(context->thread, 1024); + furi_thread_set_context(context->thread, context); + furi_thread_set_callback(context->thread, totp_type_code_worker_callback); + furi_thread_start(context->thread); +} + +void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context) { + furi_check(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), TotpBtTypeCodeWorkerEventStop); + furi_thread_join(context->thread); + furi_thread_free(context->thread); + context->thread = NULL; +} + +void totp_bt_type_code_worker_notify( + TotpBtTypeCodeWorkerContext* context, + TotpBtTypeCodeWorkerEvent event, + uint8_t flags) { + furi_check(context != NULL); + context->flags = flags; + furi_thread_flags_set(furi_thread_get_id(context->thread), event); +} + +TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init() { + TotpBtTypeCodeWorkerContext* context = malloc(sizeof(TotpBtTypeCodeWorkerContext)); + furi_check(context != NULL); + + context->bt = furi_record_open(RECORD_BT); + context->is_advertising = false; + context->is_connected = false; + bt_disconnect(context->bt); + furi_hal_bt_reinit(); + furi_delay_ms(200); + bt_keys_storage_set_storage_path(context->bt, HID_BT_KEYS_STORAGE_PATH); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL + memcpy( + &context->previous_bt_name[0], + furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN); + memcpy( + &context->previous_bt_mac[0], + furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard), + TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN); + char new_name[TOTP_BT_WORKER_BT_ADV_NAME_MAX_LEN]; + snprintf(new_name, sizeof(new_name), "%s TOTP Auth", furi_hal_version_get_name_ptr()); + uint8_t new_bt_mac[TOTP_BT_WORKER_BT_MAC_ADDRESS_LEN]; + totp_type_code_worker_bt_set_app_mac(new_bt_mac); + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, new_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, new_bt_mac); +#endif + + if(!bt_set_profile(context->bt, BtProfileHidKeyboard)) { + FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to keyboard HID profile"); + } + + furi_hal_bt_start_advertising(); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL + bt_enable_peer_key_update(context->bt); +#endif + + context->is_advertising = true; + bt_set_status_changed_callback(context->bt, connection_status_changed_callback, context); + + return context; +} + +void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context) { + furi_check(context != NULL); + + if(context->thread != NULL) { + 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); + furi_delay_ms(200); + bt_keys_storage_set_default_path(context->bt); + +#if TOTP_TARGET_FIRMWARE == TOTP_FIRMWARE_XTREME_UL + furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, context->previous_bt_name); + furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, context->previous_bt_mac); +#endif + + if(!bt_set_profile(context->bt, BtProfileSerial)) { + FURI_LOG_E(LOGGING_TAG, "Failed to switch BT to Serial profile"); + } + furi_record_close(RECORD_BT); + context->bt = NULL; + + free(context); +} + +bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context) { + return context->is_advertising; +} \ No newline at end of file diff --git a/workers/bt_type_code/bt_type_code.h b/workers/bt_type_code/bt_type_code.h new file mode 100644 index 00000000000..1b9db9bee97 --- /dev/null +++ b/workers/bt_type_code/bt_type_code.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include +#include "../../types/automation_kb_layout.h" + +typedef uint8_t TotpBtTypeCodeWorkerEvent; + +typedef struct TotpBtTypeCodeWorkerContext TotpBtTypeCodeWorkerContext; + +/** + * @brief Bluetooth token input automation worker events + */ +enum TotpBtTypeCodeWorkerEvents { + + /** + * @brief Reserved, should not be used anywhere + */ + TotpBtTypeCodeWorkerEventReserved = 0b00, + + /** + * @brief Stop worker + */ + TotpBtTypeCodeWorkerEventStop = 0b01, + + /** + * @brief Trigger token input automation + */ + TotpBtTypeCodeWorkerEventType = 0b10 +}; + +/** + * @brief Initializes bluetooth token input automation worker + * @return worker context + */ +TotpBtTypeCodeWorkerContext* totp_bt_type_code_worker_init(); + +/** + * @brief Disposes bluetooth token input automation worker and releases all the allocated resources + * @param context worker context + */ +void totp_bt_type_code_worker_free(TotpBtTypeCodeWorkerContext* context); + +/** + * @brief Starts bluetooth token input automation worker + * @param context worker context + * @param code_buffer code buffer to be used to automate + * @param code_buffer_size code buffer size + * @param code_buffer_sync code buffer synchronization primitive + * @param keyboard_layout keyboard layout to be used + */ +void totp_bt_type_code_worker_start( + TotpBtTypeCodeWorkerContext* context, + char* code_buffer, + uint8_t code_buffer_size, + FuriMutex* code_buffer_sync, + AutomationKeyboardLayout keyboard_layout); + +/** + * @brief Stops bluetooth token input automation worker + * @param context worker context + */ +void totp_bt_type_code_worker_stop(TotpBtTypeCodeWorkerContext* context); + +/** + * @brief Notifies bluetooth token input automation worker with a given event + * @param context worker context + * @param event event to notify worker with + * @param flags event flags + */ +void totp_bt_type_code_worker_notify( + TotpBtTypeCodeWorkerContext* context, + TotpBtTypeCodeWorkerEvent event, + uint8_t flags); + +/** + * @brief Gets information whether Bluetooth is advertising now or not + * @param context worker context + * @return \c true if Bluetooth is advertising now; \c false otherwise + */ +bool totp_bt_type_code_worker_is_advertising(const TotpBtTypeCodeWorkerContext* context); diff --git a/workers/generate_totp_code/generate_totp_code.c b/workers/generate_totp_code/generate_totp_code.c new file mode 100644 index 00000000000..20a7bb54c47 --- /dev/null +++ b/workers/generate_totp_code/generate_totp_code.c @@ -0,0 +1,199 @@ +#include "generate_totp_code.h" +#include +#include +#include "../../services/crypto/crypto_facade.h" +#include "../../services/totp/totp.h" +#include "../../services/convert/convert.h" +#include +#include + +#define ONE_SEC_MS (1000) + +struct TotpGenerateCodeWorkerContext { + char* code_buffer; + FuriThread* thread; + FuriMutex* code_buffer_sync; + const TokenInfo* token_info; + float timezone_offset; + const CryptoSettings* crypto_settings; + TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler; + void* on_new_code_generated_handler_context; + TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler; + void* on_code_lifetime_changed_handler_context; +}; + +static const char STEAM_ALGO_ALPHABET[] = "23456789BCDFGHJKMNPQRTVWXY"; + +static void + int_token_to_str(uint64_t i_token_code, char* str, TokenDigitsCount len, TokenHashAlgo algo) { + char* last_char = str + len; + *last_char = '\0'; + if(i_token_code == OTP_ERROR) { + memset(str, '-', len); + } else { + if(algo == TokenHashAlgoSteam) { + char* s = str; + for(uint8_t i = 0; i < len; i++, s++) { + *s = STEAM_ALGO_ALPHABET[i_token_code % 26]; + i_token_code = i_token_code / 26; + } + } else { + char* s = --last_char; + for(int8_t i = len - 1; i >= 0; i--, s--) { + *s = CONVERT_DIGIT_TO_CHAR(i_token_code % 10); + i_token_code = i_token_code / 10; + } + } + } +} + +static TOTP_ALGO get_totp_algo_impl(TokenHashAlgo algo) { + switch(algo) { + case TokenHashAlgoSha1: + case TokenHashAlgoSteam: + return TOTP_ALGO_SHA1; + case TokenHashAlgoSha256: + return TOTP_ALGO_SHA256; + case TokenHashAlgoSha512: + return TOTP_ALGO_SHA512; + default: + break; + } + + return NULL; +} + +static void generate_totp_code( + TotpGenerateCodeWorkerContext* context, + const TokenInfo* token_info, + uint32_t current_ts) { + if(token_info->token != NULL && token_info->token_length > 0) { + size_t key_length; + uint8_t* key = totp_crypto_decrypt( + token_info->token, token_info->token_length, context->crypto_settings, &key_length); + + int_token_to_str( + totp_at( + get_totp_algo_impl(token_info->algo), + key, + key_length, + current_ts, + context->timezone_offset, + token_info->duration), + context->code_buffer, + token_info->digits, + token_info->algo); + memset_s(key, key_length, 0, key_length); + free(key); + } else { + int_token_to_str(0, context->code_buffer, token_info->digits, token_info->algo); + } +} + +static int32_t totp_generate_worker_callback(void* context) { + furi_check(context); + + TotpGenerateCodeWorkerContext* t_context = context; + + while(true) { + uint32_t flags = furi_thread_flags_wait( + TotpGenerateCodeWorkerEventStop | TotpGenerateCodeWorkerEventForceUpdate, + FuriFlagWaitAny, + ONE_SEC_MS); + + if(flags == + (uint32_t) + FuriFlagErrorTimeout) { // If timeout, consider as no error, as we expect this and can handle gracefully + flags = 0; + } + + furi_check((flags & FuriFlagError) == 0); //-V562 + + if(flags & TotpGenerateCodeWorkerEventStop) break; + + const TokenInfo* token_info = t_context->token_info; + if(token_info == NULL) { + continue; + } + + uint32_t curr_ts = furi_hal_rtc_get_timestamp(); + + bool time_left = false; + if(flags & TotpGenerateCodeWorkerEventForceUpdate || + (time_left = (curr_ts % token_info->duration) == 0)) { + if(furi_mutex_acquire(t_context->code_buffer_sync, FuriWaitForever) == FuriStatusOk) { + generate_totp_code(t_context, token_info, curr_ts); + curr_ts = furi_hal_rtc_get_timestamp(); + furi_mutex_release(t_context->code_buffer_sync); + if(t_context->on_new_code_generated_handler != NULL) { + (*(t_context->on_new_code_generated_handler))( + time_left, t_context->on_new_code_generated_handler_context); + } + } + } + + if(t_context->on_code_lifetime_changed_handler != NULL) { + (*(t_context->on_code_lifetime_changed_handler))( + (float)(token_info->duration - curr_ts % token_info->duration) / + (float)token_info->duration, + t_context->on_code_lifetime_changed_handler_context); + } + } + + return 0; +} + +TotpGenerateCodeWorkerContext* totp_generate_code_worker_start( + char* code_buffer, + const TokenInfo* token_info, + FuriMutex* code_buffer_sync, + float timezone_offset, + const CryptoSettings* crypto_settings) { + TotpGenerateCodeWorkerContext* context = malloc(sizeof(TotpGenerateCodeWorkerContext)); + furi_check(context != NULL); + context->code_buffer = code_buffer; + context->token_info = token_info; + context->code_buffer_sync = code_buffer_sync; + context->timezone_offset = timezone_offset; + context->crypto_settings = crypto_settings; + context->thread = furi_thread_alloc(); + furi_thread_set_name(context->thread, "TOTPGenerateWorker"); + furi_thread_set_stack_size(context->thread, 2048); + furi_thread_set_context(context->thread, context); + furi_thread_set_callback(context->thread, totp_generate_worker_callback); + furi_thread_start(context->thread); + return context; +} + +void totp_generate_code_worker_stop(TotpGenerateCodeWorkerContext* context) { + furi_check(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), TotpGenerateCodeWorkerEventStop); + furi_thread_join(context->thread); + furi_thread_free(context->thread); + free(context); +} + +void totp_generate_code_worker_notify( + TotpGenerateCodeWorkerContext* context, + TotpGenerateCodeWorkerEvent event) { + furi_check(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), event); +} + +void totp_generate_code_worker_set_code_generated_handler( + TotpGenerateCodeWorkerContext* context, + TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler, + void* on_new_code_generated_handler_context) { + furi_check(context != NULL); + context->on_new_code_generated_handler = on_new_code_generated_handler; + context->on_new_code_generated_handler_context = on_new_code_generated_handler_context; +} + +void totp_generate_code_worker_set_lifetime_changed_handler( + TotpGenerateCodeWorkerContext* context, + TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler, + void* on_code_lifetime_changed_handler_context) { + furi_check(context != NULL); + context->on_code_lifetime_changed_handler = on_code_lifetime_changed_handler; + context->on_code_lifetime_changed_handler_context = on_code_lifetime_changed_handler_context; +} \ No newline at end of file diff --git a/workers/generate_totp_code/generate_totp_code.h b/workers/generate_totp_code/generate_totp_code.h new file mode 100644 index 00000000000..eb234313e3a --- /dev/null +++ b/workers/generate_totp_code/generate_totp_code.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include "../../types/token_info.h" + +typedef uint8_t TotpGenerateCodeWorkerEvent; + +typedef void (*TOTP_NEW_CODE_GENERATED_HANDLER)(bool time_left, void* context); +typedef void (*TOTP_CODE_LIFETIME_CHANGED_HANDLER)(float code_lifetime_percent, void* context); + +typedef struct TotpGenerateCodeWorkerContext TotpGenerateCodeWorkerContext; + +/** + * @brief Generate token worker events + */ +enum TotGenerateCodeWorkerEvents { + + /** + * @brief Reserved, should not be used anywhere + */ + TotpGenerateCodeWorkerEventReserved = 0b00, + + /** + * @brief Stop worker + */ + TotpGenerateCodeWorkerEventStop = 0b01, + + /** + * @brief Trigger token input automation + */ + TotpGenerateCodeWorkerEventForceUpdate = 0b10 +}; + +/** + * @brief Starts generate code worker + * @param code_buffer code buffer to generate code to + * @param token_info token info to be used to generate code + * @param code_buffer_sync code buffer synchronization primitive + * @param timezone_offset timezone offset to be used to generate code + * @param crypto_settings crypto settings + * @return worker context + */ +TotpGenerateCodeWorkerContext* totp_generate_code_worker_start( + char* code_buffer, + const TokenInfo* token_info, + FuriMutex* code_buffer_sync, + float timezone_offset, + const CryptoSettings* crypto_settings); + +/** + * @brief Stops generate code worker + * @param context worker context + */ +void totp_generate_code_worker_stop(TotpGenerateCodeWorkerContext* context); + +/** + * @brief Notifies generate code worker with a given event + * @param context worker context + * @param event event to notify worker with + */ +void totp_generate_code_worker_notify( + TotpGenerateCodeWorkerContext* context, + TotpGenerateCodeWorkerEvent event); + +/** + * @brief Sets new handler for "on new code generated" event + * @param context worker context + * @param on_new_code_generated_handler handler + * @param on_new_code_generated_handler_context handler context + */ +void totp_generate_code_worker_set_code_generated_handler( + TotpGenerateCodeWorkerContext* context, + TOTP_NEW_CODE_GENERATED_HANDLER on_new_code_generated_handler, + void* on_new_code_generated_handler_context); + +/** + * @brief Sets new handler for "on code lifetime changed" event + * @param context worker context + * @param on_code_lifetime_changed_handler handler + * @param on_code_lifetime_changed_handler_context handler context + */ +void totp_generate_code_worker_set_lifetime_changed_handler( + TotpGenerateCodeWorkerContext* context, + TOTP_CODE_LIFETIME_CHANGED_HANDLER on_code_lifetime_changed_handler, + void* on_code_lifetime_changed_handler_context); \ No newline at end of file diff --git a/workers/type_code_common.c b/workers/type_code_common.c new file mode 100644 index 00000000000..122c0b2a513 --- /dev/null +++ b/workers/type_code_common.c @@ -0,0 +1,109 @@ +#include "type_code_common.h" +#include +#include +#include "../../services/convert/convert.h" + +#define HID_KEYS_MAP_LENGTH (36) + +static const uint8_t hid_qwerty_keys_map[HID_KEYS_MAP_LENGTH] = { + HID_KEYBOARD_0, HID_KEYBOARD_1, HID_KEYBOARD_2, HID_KEYBOARD_3, HID_KEYBOARD_4, + HID_KEYBOARD_5, HID_KEYBOARD_6, HID_KEYBOARD_7, HID_KEYBOARD_8, HID_KEYBOARD_9, + HID_KEYBOARD_A, HID_KEYBOARD_B, HID_KEYBOARD_C, HID_KEYBOARD_D, HID_KEYBOARD_E, + HID_KEYBOARD_F, HID_KEYBOARD_G, HID_KEYBOARD_H, HID_KEYBOARD_I, HID_KEYBOARD_J, + HID_KEYBOARD_K, HID_KEYBOARD_L, HID_KEYBOARD_M, HID_KEYBOARD_N, HID_KEYBOARD_O, + HID_KEYBOARD_P, HID_KEYBOARD_Q, HID_KEYBOARD_R, HID_KEYBOARD_S, HID_KEYBOARD_T, + HID_KEYBOARD_U, HID_KEYBOARD_V, HID_KEYBOARD_W, HID_KEYBOARD_X, HID_KEYBOARD_Y, + HID_KEYBOARD_Z}; + +static const uint8_t hid_azerty_keys_map[HID_KEYS_MAP_LENGTH] = { + HID_KEYBOARD_0, HID_KEYBOARD_1, HID_KEYBOARD_2, HID_KEYBOARD_3, HID_KEYBOARD_4, + HID_KEYBOARD_5, HID_KEYBOARD_6, HID_KEYBOARD_7, HID_KEYBOARD_8, HID_KEYBOARD_9, + HID_KEYBOARD_Q, HID_KEYBOARD_B, HID_KEYBOARD_C, HID_KEYBOARD_D, HID_KEYBOARD_E, + HID_KEYBOARD_F, HID_KEYBOARD_G, HID_KEYBOARD_H, HID_KEYBOARD_I, HID_KEYBOARD_J, + HID_KEYBOARD_K, HID_KEYBOARD_L, HID_KEYBOARD_SEMICOLON, HID_KEYBOARD_N, HID_KEYBOARD_O, + HID_KEYBOARD_P, HID_KEYBOARD_A, HID_KEYBOARD_R, HID_KEYBOARD_S, HID_KEYBOARD_T, + HID_KEYBOARD_U, HID_KEYBOARD_V, HID_KEYBOARD_Z, HID_KEYBOARD_X, HID_KEYBOARD_Y, + HID_KEYBOARD_W}; + +static uint32_t get_keystroke_delay(TokenAutomationFeature features) { + if(features & TokenAutomationFeatureTypeSlower) { + return 100; + } + + return 30; +} + +static uint32_t get_keypress_delay(TokenAutomationFeature features) { + if(features & TokenAutomationFeatureTypeSlower) { + return 60; + } + + return 30; +} + +static void totp_type_code_worker_press_key( + uint16_t key, + TOTP_AUTOMATION_KEY_HANDLER key_press_fn, + TOTP_AUTOMATION_KEY_HANDLER key_release_fn, + TokenAutomationFeature features) { + (*key_press_fn)(key); + furi_delay_ms(get_keypress_delay(features)); + (*key_release_fn)(key); +} + +void totp_type_code_worker_execute_automation( + TOTP_AUTOMATION_KEY_HANDLER key_press_fn, + TOTP_AUTOMATION_KEY_HANDLER key_release_fn, + const char* code_buffer, + uint8_t code_buffer_size, + TokenAutomationFeature features, + AutomationKeyboardLayout keyboard_layout) { + furi_delay_ms(500); + uint8_t i = 0; + char cb_char; + + const uint8_t* keyboard_layout_dict; + switch(keyboard_layout) { + case AutomationKeyboardLayoutQWERTY: + keyboard_layout_dict = &hid_qwerty_keys_map[0]; + break; + case AutomationKeyboardLayoutAZERTY: + keyboard_layout_dict = &hid_azerty_keys_map[0]; + break; + + default: + return; + } + + while(i < code_buffer_size && (cb_char = code_buffer[i]) != 0) { + uint8_t char_index = CONVERT_CHAR_TO_DIGIT(cb_char); + if(char_index > 9) { + char_index = cb_char - 'A' + 10; + } + + if(char_index >= HID_KEYS_MAP_LENGTH) break; + + uint16_t hid_kb_key = keyboard_layout_dict[char_index]; + + // For non-AZERTY press shift for all non-digit chars + // For AZERTY press shift for all characters + if(char_index > 9 || keyboard_layout == AutomationKeyboardLayoutAZERTY) { + hid_kb_key |= KEY_MOD_LEFT_SHIFT; + } + + totp_type_code_worker_press_key(hid_kb_key, key_press_fn, key_release_fn, features); + furi_delay_ms(get_keystroke_delay(features)); + i++; + } + + if(features & TokenAutomationFeatureEnterAtTheEnd) { + furi_delay_ms(get_keystroke_delay(features)); + totp_type_code_worker_press_key( + HID_KEYBOARD_RETURN, key_press_fn, key_release_fn, features); + } + + if(features & TokenAutomationFeatureTabAtTheEnd) { + furi_delay_ms(get_keystroke_delay(features)); + totp_type_code_worker_press_key(HID_KEYBOARD_TAB, key_press_fn, key_release_fn, features); + } +} \ No newline at end of file diff --git a/workers/type_code_common.h b/workers/type_code_common.h new file mode 100644 index 00000000000..81b273c367d --- /dev/null +++ b/workers/type_code_common.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include "../types/token_info.h" +#include "../types/automation_kb_layout.h" + +typedef bool (*TOTP_AUTOMATION_KEY_HANDLER)(uint16_t key); + +/** + * @brief Executes token input automation using given key press\release handlers + * @param key_press_fn key press handler + * @param key_release_fn key release handler + * @param code_buffer code buffer to be typed + * @param code_buffer_size code buffer size + * @param features automation features + * @param keyboard_layout keyboard layout to be used + */ +void totp_type_code_worker_execute_automation( + TOTP_AUTOMATION_KEY_HANDLER key_press_fn, + TOTP_AUTOMATION_KEY_HANDLER key_release_fn, + const char* code_buffer, + uint8_t code_buffer_size, + TokenAutomationFeature features, + AutomationKeyboardLayout keyboard_layout); \ No newline at end of file diff --git a/workers/usb_type_code/usb_type_code.c b/workers/usb_type_code/usb_type_code.c new file mode 100644 index 00000000000..4e3259424d7 --- /dev/null +++ b/workers/usb_type_code/usb_type_code.c @@ -0,0 +1,122 @@ +#include "usb_type_code.h" +#include +#include +#include +#include +#include +#include "../../services/convert/convert.h" +#include "../../types/token_info.h" +#include "../type_code_common.h" + +struct TotpUsbTypeCodeWorkerContext { + char* code_buffer; + uint8_t code_buffer_size; + uint8_t flags; + FuriThread* thread; + FuriMutex* code_buffer_sync; + FuriHalUsbInterface* usb_mode_prev; + AutomationKeyboardLayout keyboard_layout; +}; + +static void totp_type_code_worker_restore_usb_mode(TotpUsbTypeCodeWorkerContext* context) { + if(context->usb_mode_prev != NULL) { + furi_hal_usb_set_config(context->usb_mode_prev, NULL); + context->usb_mode_prev = NULL; + } +} + +static inline bool totp_type_code_worker_stop_requested() { + return furi_thread_flags_get() & TotpUsbTypeCodeWorkerEventStop; +} + +static void totp_type_code_worker_type_code(TotpUsbTypeCodeWorkerContext* context) { + context->usb_mode_prev = furi_hal_usb_get_config(); + furi_hal_usb_unlock(); + furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); + uint8_t i = 0; + do { + furi_delay_ms(500); + i++; + } while(!furi_hal_hid_is_connected() && i < 100 && !totp_type_code_worker_stop_requested()); + + if(furi_hal_hid_is_connected() && + furi_mutex_acquire(context->code_buffer_sync, 500) == FuriStatusOk) { + totp_type_code_worker_execute_automation( + &furi_hal_hid_kb_press, + &furi_hal_hid_kb_release, + context->code_buffer, + context->code_buffer_size, + context->flags, + context->keyboard_layout); + furi_mutex_release(context->code_buffer_sync); + + furi_delay_ms(100); + } + + totp_type_code_worker_restore_usb_mode(context); +} + +static int32_t totp_type_code_worker_callback(void* context) { + furi_check(context); + FuriMutex* context_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + while(true) { + uint32_t flags = furi_thread_flags_wait( + TotpUsbTypeCodeWorkerEventStop | TotpUsbTypeCodeWorkerEventType, + FuriFlagWaitAny, + FuriWaitForever); + furi_check((flags & FuriFlagError) == 0); //-V562 + if(flags & TotpUsbTypeCodeWorkerEventStop) break; + + if(furi_mutex_acquire(context_mutex, FuriWaitForever) == FuriStatusOk) { + if(flags & TotpUsbTypeCodeWorkerEventType) { + totp_type_code_worker_type_code(context); + } + + furi_mutex_release(context_mutex); + } + } + + furi_mutex_free(context_mutex); + + return 0; +} + +TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start( + char* code_buffer, + uint8_t code_buffer_size, + FuriMutex* code_buffer_sync, + AutomationKeyboardLayout keyboard_layout) { + TotpUsbTypeCodeWorkerContext* context = malloc(sizeof(TotpUsbTypeCodeWorkerContext)); + furi_check(context != NULL); + context->code_buffer = code_buffer; + context->code_buffer_size = code_buffer_size; + context->code_buffer_sync = code_buffer_sync; + context->thread = furi_thread_alloc(); + context->usb_mode_prev = NULL; + context->keyboard_layout = keyboard_layout; + furi_thread_set_name(context->thread, "TOTPUsbHidWorker"); + furi_thread_set_stack_size(context->thread, 1024); + furi_thread_set_context(context->thread, context); + furi_thread_set_callback(context->thread, totp_type_code_worker_callback); + furi_thread_start(context->thread); + return context; +} + +void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context) { + furi_check(context != NULL); + furi_thread_flags_set(furi_thread_get_id(context->thread), TotpUsbTypeCodeWorkerEventStop); + furi_thread_join(context->thread); + furi_thread_free(context->thread); + totp_type_code_worker_restore_usb_mode(context); + free(context); +} + +void totp_usb_type_code_worker_notify( + TotpUsbTypeCodeWorkerContext* context, + TotpUsbTypeCodeWorkerEvent event, + uint8_t flags) { + furi_check(context != NULL); + context->flags = flags; + furi_thread_flags_set(furi_thread_get_id(context->thread), event); +} \ No newline at end of file diff --git a/workers/usb_type_code/usb_type_code.h b/workers/usb_type_code/usb_type_code.h new file mode 100644 index 00000000000..d2d1bdf82bb --- /dev/null +++ b/workers/usb_type_code/usb_type_code.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include "../../types/automation_kb_layout.h" + +typedef uint8_t TotpUsbTypeCodeWorkerEvent; + +typedef struct TotpUsbTypeCodeWorkerContext TotpUsbTypeCodeWorkerContext; + +/** + * @brief USB token input automation worker events + */ +enum TotpUsbTypeCodeWorkerEvents { + + /** + * @brief Reserved, should not be used anywhere + */ + TotpUsbTypeCodeWorkerEventReserved = 0b00, + + /** + * @brief Stop worker + */ + TotpUsbTypeCodeWorkerEventStop = 0b01, + + /** + * @brief Trigger token input automation + */ + TotpUsbTypeCodeWorkerEventType = 0b10 +}; + +/** + * @brief Starts USB token input automation worker + * @param code_buffer code buffer to be used to automate + * @param code_buffer_size code buffer size + * @param code_buffer_sync code buffer synchronization primitive + * @param keyboard_layout keyboard layout to be used + * @return worker context + */ +TotpUsbTypeCodeWorkerContext* totp_usb_type_code_worker_start( + char* code_buffer, + uint8_t code_buffer_size, + FuriMutex* code_buffer_sync, + AutomationKeyboardLayout keyboard_layout); + +/** + * @brief Stops USB token input automation worker + * @param context worker context + */ +void totp_usb_type_code_worker_stop(TotpUsbTypeCodeWorkerContext* context); + +/** + * @brief Notifies USB token input automation worker with a given event + * @param context worker context + * @param event event to notify worker with + * @param flags event flags + */ +void totp_usb_type_code_worker_notify( + TotpUsbTypeCodeWorkerContext* context, + TotpUsbTypeCodeWorkerEvent event, + uint8_t flags);