Kaynağa Gözat

Add 'multi_fuzzer/' from commit 'bc1f4b0945792e2442d32e65b31aa7f989b17b24'

git-subtree-dir: multi_fuzzer
git-subtree-mainline: a26e7980178ae7086a8171ed0761caa2edfa0741
git-subtree-split: bc1f4b0945792e2442d32e65b31aa7f989b17b24
Willy-JL 2 yıl önce
ebeveyn
işleme
2c8ef60bea
43 değiştirilmiş dosya ile 3720 ekleme ve 0 silme
  1. 4 0
      multi_fuzzer/.github/CODEOWNERS
  2. 4 0
      multi_fuzzer/.gitignore
  3. 5 0
      multi_fuzzer/.gitremotes
  4. 27 0
      multi_fuzzer/CHANGELOG.md
  5. 21 0
      multi_fuzzer/LICENSE
  6. 40 0
      multi_fuzzer/README.md
  7. 59 0
      multi_fuzzer/application.fam
  8. 19 0
      multi_fuzzer/catalog/docs/ibtn/Changelog.md
  9. 29 0
      multi_fuzzer/catalog/docs/ibtn/README.md
  10. 21 0
      multi_fuzzer/catalog/docs/rfid/Changelog.md
  11. 31 0
      multi_fuzzer/catalog/docs/rfid/README.md
  12. BIN
      multi_fuzzer/catalog/screenshots/Attack_screen.png
  13. BIN
      multi_fuzzer/catalog/screenshots/Field_editor.png
  14. BIN
      multi_fuzzer/catalog/screenshots/Main_menu.png
  15. BIN
      multi_fuzzer/catalog/screenshots/Pause.png
  16. 171 0
      multi_fuzzer/fuzzer.c
  17. 62 0
      multi_fuzzer/fuzzer_i.h
  18. 27 0
      multi_fuzzer/helpers/fuzzer_custom_event.h
  19. 33 0
      multi_fuzzer/helpers/fuzzer_types.h
  20. BIN
      multi_fuzzer/ibutt_10px.png
  21. BIN
      multi_fuzzer/icons/rfid_10px.png
  22. 508 0
      multi_fuzzer/lib/worker/fake_worker.c
  23. 153 0
      multi_fuzzer/lib/worker/fake_worker.h
  24. 198 0
      multi_fuzzer/lib/worker/helpers/hardware_worker.c
  25. 48 0
      multi_fuzzer/lib/worker/helpers/hardware_worker.h
  26. 291 0
      multi_fuzzer/lib/worker/protocol.c
  27. 86 0
      multi_fuzzer/lib/worker/protocol.h
  28. 51 0
      multi_fuzzer/lib/worker/protocol_i.h
  29. 30 0
      multi_fuzzer/scenes/fuzzer_scene.c
  30. 29 0
      multi_fuzzer/scenes/fuzzer_scene.h
  31. 170 0
      multi_fuzzer/scenes/fuzzer_scene_attack.c
  32. 5 0
      multi_fuzzer/scenes/fuzzer_scene_config.h
  33. 67 0
      multi_fuzzer/scenes/fuzzer_scene_field_editor.c
  34. 200 0
      multi_fuzzer/scenes/fuzzer_scene_main.c
  35. 67 0
      multi_fuzzer/scenes/fuzzer_scene_save_name.c
  36. 44 0
      multi_fuzzer/scenes/fuzzer_scene_save_success.c
  37. 44 0
      multi_fuzzer/todo.md
  38. 495 0
      multi_fuzzer/views/attack.c
  39. 36 0
      multi_fuzzer/views/attack.h
  40. 358 0
      multi_fuzzer/views/field_editor.c
  41. 29 0
      multi_fuzzer/views/field_editor.h
  42. 235 0
      multi_fuzzer/views/main_menu.c
  43. 23 0
      multi_fuzzer/views/main_menu.h

+ 4 - 0
multi_fuzzer/.github/CODEOWNERS

@@ -0,0 +1,4 @@
+* @gid9798 @xMasterX
+
+# Default dictionaries
+/lib/protocol.c @xMasterX @G4N4P4T1

+ 4 - 0
multi_fuzzer/.gitignore

@@ -0,0 +1,4 @@
+dist/*
+.vscode
+.clang-format
+.editorconfig

+ 5 - 0
multi_fuzzer/.gitremotes

@@ -0,0 +1,5 @@
+[remote "upstream"]
+    url = https://github.com/DarkFlippers/Multi_Fuzzer
+    fetch = +refs/heads/*:refs/remotes/multi-fuzzer/*
+[alias]
+    merge-upstream = pull --no-edit upstream dev

+ 27 - 0
multi_fuzzer/CHANGELOG.md

@@ -0,0 +1,27 @@
+## v1.2
+- Fixes for new auto-naming system
+## v1.1
+- Improved pause during attack
+    - Added the ability to switch UID
+    - Added the ability to emulate the current UID
+    - Added the ability to save UID
+- Load key file attack
+    - Key file loading now does not depend on the selected protocol
+
+## v1.0
+
+**Supported protocols**
+| iButton | RFID        |
+|:-:      | :-:         |
+| DS1990  | EM4100      |
+| Metakom | HIDProx     |
+| Cyfral  | PAC/Stanley |
+|         | H10301      |
+
+**Suported attack**
+|                     | iButton | RFID |
+| -                   | :-:     | :-:  |
+| Default Values      | +       | +    |
+| Load key file       | +       | +    |
+| Load UIDs from file | +       | +    |
+| BFCustomer ID       | -       | +    |

+ 21 - 0
multi_fuzzer/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 @gid9798 @xMasterX @G4N4P4T1
+
+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.

+ 40 - 0
multi_fuzzer/README.md

@@ -0,0 +1,40 @@
+## Multi Fuzzer
+### Flipper Zero app for checking ibutton and RFID<sub>(125khz)</sub> readers .
+
+**Not meant for any illegal activity/purposes.**
+
+You can check your readers for vulnerabilities using the default UIDs and frequently used UIDs.
+
+The application will also help to identify the "denial of service" vulnerability when providing an incorrect uid or when accessing the reader too often.
+
+This is a completely remade app, visual style inspired by [iButton fuzzer](https://github.com/DarkFlippers/unleashed-firmware/tree/58338ff51f6f9857f39ef07d5eb4495cdc02290d/applications/external/ibtn_fuzzer), the compatibility of user dictionaries is also preserved
+
+## Supported protocols
+
+### **iButton**
+- DS1990 (Dallas)
+- Metakom
+- Cyfral
+
+### **LFRFID**
+- EM4100
+- HIDProx
+- PAC/Stanley
+- H10301
+
+## Application Features
+### Main screen
+- **Header** - selected protocol
+- **Menu** - available attacks
+    - **Default Values** - Using the dictionary from the app
+    - **Load UIDs from file** - Loading a custom dictionary from an SD card
+    - **Load file** - Loading UID from ***FlipperFormat*** key file with the ability to edit and further iterate over the selected byte
+    - **BFCustomer ID** - Iterates over the selected byte with the remaining bytes equal to zero
+
+### Attack screen
+- **Header** - selected attack
+- **2nd line**
+    - **Time delay (TD)** - idle time between UID submissions
+    - **Emulation time (EmT)** - transmission time of one UID
+- **3rd line** - Prtocol name
+- **4th line** - Current UID

+ 59 - 0
multi_fuzzer/application.fam

@@ -0,0 +1,59 @@
+App(
+    appid="fuzzer_ibtn",
+    name="iButton Fuzzer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="fuzzer_start_ibtn",
+    requires=[
+        "gui",
+        "storage",
+        "dialogs",
+        "input",
+        "notification",
+    ],
+    stack_size=2 * 1024,
+    fap_author="gid9798 xMasterX",
+    fap_weburl="https://github.com/DarkFlippers/Multi_Fuzzer",
+    fap_version="1.2",
+    targets=["f7"],
+    fap_description="Fuzzer for ibutton readers",
+    fap_icon="ibutt_10px.png",
+    fap_category="iButton",
+    fap_private_libs=[
+        Lib(
+            name="worker",
+            cdefines=["IBUTTON_PROTOCOL"],
+        ),
+    ],
+    fap_icon_assets="icons",
+    fap_icon_assets_symbol="fuzzer",
+)
+
+App(
+    appid="fuzzer_rfid",
+    name="RFID Fuzzer",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="fuzzer_start_rfid",
+    requires=[
+        "gui",
+        "storage",
+        "dialogs",
+        "input",
+        "notification",
+    ],
+    stack_size=2 * 1024,
+    fap_author="gid9798 xMasterX",
+    fap_weburl="https://github.com/DarkFlippers/Multi_Fuzzer",
+    fap_version="1.2",
+    targets=["f7"],
+    fap_description="Fuzzer for lfrfid readers",
+    fap_icon="icons/rfid_10px.png",
+    fap_category="RFID",
+    fap_private_libs=[
+        Lib(
+            name="worker",
+            cdefines=["RFID_125_PROTOCOL"],
+        ),
+    ],
+    fap_icon_assets="icons",
+    fap_icon_assets_symbol="fuzzer",
+)

+ 19 - 0
multi_fuzzer/catalog/docs/ibtn/Changelog.md

@@ -0,0 +1,19 @@
+## v1.1
+- Improved pause during attack
+    - Added the ability to switch UID
+    - Added the ability to emulate the current UID
+    - Added the ability to save UID
+- Load key file attack
+    - Key file loading now does not depend on the selected protocol
+
+## v1.0
+
+**Supported protocols**
+- DS1990 (Dallas)
+- Metakom
+- Cyfral
+
+**Suported attack**
+- Default Values
+- Load key file
+- Load UIDs from file

+ 29 - 0
multi_fuzzer/catalog/docs/ibtn/README.md

@@ -0,0 +1,29 @@
+# Multi Fuzzer
+## Flipper Zero app for checking ibutton readers .
+
+**Not meant for any illegal activity/purposes.**
+
+You can check your readers for vulnerabilities using the default UIDs and frequently used UIDs.
+
+The application will also help to identify the "denial of service" vulnerability when providing an incorrect uid or when accessing the reader too often.
+
+## Supported protocols
+- DS1990 (Dallas)
+- Metakom
+- Cyfral
+
+# Application Features
+## Main screen
+- **Header** - selected protocol
+- **Menu** - available attacks
+    - **Default Values** - Using the dictionary from the app
+    - **Load UIDs from file** - Loading a custom dictionary from an SD card
+    - **Load file** - Loading UID from ***FlipperFormat*** key file with the ability to edit and further iterate over the selected byte
+
+## Attack screen
+- **Header** - selected attack
+- **2nd line**
+    - **Time delay (TD)** - idle time between UID submissions
+    - **Emulation time (EmT)** - transmission time of one UID
+- **3rd line** - Prtocol name
+- **4th line** - Current UID

+ 21 - 0
multi_fuzzer/catalog/docs/rfid/Changelog.md

@@ -0,0 +1,21 @@
+## v1.1
+- Improved pause during attack
+    - Added the ability to switch UID
+    - Added the ability to emulate the current UID
+    - Added the ability to save UID
+- Load key file attack
+    - Key file loading now does not depend on the selected protocol
+
+## v1.0
+
+**Supported protocols**
+- EM4100
+- HIDProx
+- PAC/Stanley
+- H10301
+
+**Suported attack**
+- Default Values
+- Load key file
+- Load UIDs from file
+- BFCustomer ID

+ 31 - 0
multi_fuzzer/catalog/docs/rfid/README.md

@@ -0,0 +1,31 @@
+# Multi Fuzzer
+## Flipper Zero app for checking LF RFID readers .
+
+**Not meant for any illegal activity/purposes.**
+
+You can check your readers for vulnerabilities using the default UIDs and frequently used UIDs.
+
+The application will also help to identify the "denial of service" vulnerability when providing an incorrect uid or when accessing the reader too often.
+
+## Supported protocols
+- EM4100
+- HIDProx
+- PAC/Stanley
+- H10301
+
+# Application Features
+## Main screen
+- **Header** - selected protocol
+- **Menu** - available attacks
+    - **Default Values** - Using the dictionary from the app
+    - **Load UIDs from file** - Loading a custom dictionary from an SD card
+    - **Load file** - Loading UID from ***FlipperFormat*** key file with the ability to edit and further iterate over the selected byte
+    - **BFCustomer ID** - Iterates over the selected byte with the remaining bytes equal to zero
+
+## Attack screen
+- **Header** - selected attack
+- **2nd line**
+    - **Time delay (TD)** - idle time between UID submissions
+    - **Emulation time (EmT)** - transmission time of one UID
+- **3rd line** - Prtocol name
+- **4th line** - Current UID

BIN
multi_fuzzer/catalog/screenshots/Attack_screen.png


BIN
multi_fuzzer/catalog/screenshots/Field_editor.png


BIN
multi_fuzzer/catalog/screenshots/Main_menu.png


BIN
multi_fuzzer/catalog/screenshots/Pause.png


+ 171 - 0
multi_fuzzer/fuzzer.c

@@ -0,0 +1,171 @@
+#include "fuzzer_i.h"
+#include "helpers/fuzzer_types.h"
+
+static bool fuzzer_app_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool fuzzer_app_back_event_callback(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void fuzzer_app_tick_event_callback(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+PacsFuzzerApp* fuzzer_app_alloc() {
+    PacsFuzzerApp* app = malloc(sizeof(PacsFuzzerApp));
+
+    app->fuzzer_state.menu_index = 0;
+    app->fuzzer_state.proto_index = 0;
+
+    app->worker = fuzzer_worker_alloc();
+    app->payload = fuzzer_payload_alloc();
+
+    app->file_path = furi_string_alloc();
+
+    // GUI
+    app->gui = furi_record_open(RECORD_GUI);
+
+    // Dialog
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Open Notification record
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // View Dispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+
+    // Popup
+    app->popup = popup_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, FuzzerViewIDPopup, popup_get_view(app->popup));
+
+    // TextInput
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, FuzzerViewIDTextInput, text_input_get_view(app->text_input));
+
+    // Main view
+    app->main_view = fuzzer_view_main_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, FuzzerViewIDMain, fuzzer_view_main_get_view(app->main_view));
+
+    // Attack view
+    app->attack_view = fuzzer_view_attack_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, FuzzerViewIDAttack, fuzzer_view_attack_get_view(app->attack_view));
+
+    // FieldEditor view
+    app->field_editor_view = fuzzer_view_field_editor_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        FuzzerViewIDFieldEditor,
+        fuzzer_view_field_editor_get_view(app->field_editor_view));
+
+    app->scene_manager = scene_manager_alloc(&fuzzer_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, fuzzer_app_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, fuzzer_app_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, fuzzer_app_tick_event_callback, 100);
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    scene_manager_next_scene(app->scene_manager, FuzzerSceneMain);
+
+    return app;
+}
+
+void fuzzer_app_free(PacsFuzzerApp* app) {
+    furi_assert(app);
+
+    // Remote view
+    view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDMain);
+    fuzzer_view_main_free(app->main_view);
+
+    // Attack view
+    view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDAttack);
+    fuzzer_view_attack_free(app->attack_view);
+
+    // FieldEditor view
+    view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDFieldEditor);
+    fuzzer_view_field_editor_free(app->field_editor_view);
+
+    // Popup
+    view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDPopup);
+    popup_free(app->popup);
+
+    // TextInput
+    view_dispatcher_remove_view(app->view_dispatcher, FuzzerViewIDTextInput);
+    text_input_free(app->text_input);
+
+    scene_manager_free(app->scene_manager);
+    view_dispatcher_free(app->view_dispatcher);
+
+    // Dialog
+    furi_record_close(RECORD_DIALOGS);
+
+    // Close records
+    furi_record_close(RECORD_GUI);
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    app->notifications = NULL;
+
+    furi_string_free(app->file_path);
+
+    fuzzer_payload_free(app->payload);
+    fuzzer_worker_free(app->worker);
+
+    free(app);
+}
+
+int32_t fuzzer_start_ibtn(void* p) {
+    UNUSED(p);
+    PacsFuzzerApp* fuzzer_app = fuzzer_app_alloc();
+
+    FuzzerConsts app_const = {
+        .custom_dict_folder = "/ext/ibutton_fuzzer",
+        .custom_dict_extension = ".txt",
+        .key_extension = ".ibtn",
+        .path_key_folder = "/ext/ibutton",
+        .key_icon = &I_ibutt_10px,
+        .file_prefix = "iBtn",
+    };
+    fuzzer_app->fuzzer_const = &app_const;
+
+    view_dispatcher_run(fuzzer_app->view_dispatcher);
+
+    fuzzer_app_free(fuzzer_app);
+    return 0;
+}
+
+int32_t fuzzer_start_rfid(void* p) {
+    UNUSED(p);
+    PacsFuzzerApp* fuzzer_app = fuzzer_app_alloc();
+
+    FuzzerConsts app_const = {
+        .custom_dict_folder = "/ext/lfrfid_fuzzer",
+        .custom_dict_extension = ".txt",
+        .key_extension = ".rfid",
+        .path_key_folder = "/ext/lfrfid",
+        .key_icon = &I_125_10px,
+        .file_prefix = "RFID",
+    };
+    fuzzer_app->fuzzer_const = &app_const;
+
+    view_dispatcher_run(fuzzer_app->view_dispatcher);
+
+    fuzzer_app_free(fuzzer_app);
+    return 0;
+}

+ 62 - 0
multi_fuzzer/fuzzer_i.h

@@ -0,0 +1,62 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_input.h>
+
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+
+#include "scenes/fuzzer_scene.h"
+#include "views/main_menu.h"
+#include "views/attack.h"
+#include "views/field_editor.h"
+
+#include "helpers/fuzzer_types.h"
+#include "lib/worker/fake_worker.h"
+
+#include <flipper_format/flipper_format_i.h>
+#include "fuzzer_icons.h"
+
+#include <assets_icons.h>
+
+#define FUZZ_TIME_DELAY_MAX (80)
+#define KEY_NAME_SIZE 24
+
+typedef struct {
+    const char* custom_dict_extension;
+    const char* custom_dict_folder;
+    const char* key_extension;
+    const char* path_key_folder;
+    const Icon* key_icon;
+    const char* file_prefix;
+} FuzzerConsts;
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notifications;
+
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+
+    Popup* popup;
+    DialogsApp* dialogs;
+    TextInput* text_input;
+    FuzzerViewMain* main_view;
+    FuzzerViewAttack* attack_view;
+    FuzzerViewFieldEditor* field_editor_view;
+
+    FuriString* file_path;
+    char key_name[KEY_NAME_SIZE + 1];
+
+    FuzzerState fuzzer_state;
+    FuzzerConsts* fuzzer_const;
+
+    FuzzerWorker* worker;
+    FuzzerPayload* payload;
+} PacsFuzzerApp;

+ 27 - 0
multi_fuzzer/helpers/fuzzer_custom_event.h

@@ -0,0 +1,27 @@
+#pragma once
+
+typedef enum {
+
+    // FuzzerCustomEvent
+    FuzzerCustomEventViewMainBack = 100,
+    FuzzerCustomEventViewMainOk,
+    FuzzerCustomEventViewMainPopupErr,
+
+    FuzzerCustomEventViewAttackEnd,
+
+    FuzzerCustomEventViewAttackExit,
+    FuzzerCustomEventViewAttackRunAttack,
+    FuzzerCustomEventViewAttackPause,
+    FuzzerCustomEventViewAttackIdle, // Setup
+    FuzzerCustomEventViewAttackEmulateCurrent,
+    FuzzerCustomEventViewAttackSave,
+    FuzzerCustomEventViewAttackNextUid,
+    FuzzerCustomEventViewAttackPrevUid,
+
+    FuzzerCustomEventViewFieldEditorBack,
+    FuzzerCustomEventViewFieldEditorOk,
+
+    FuzzerCustomEventTextEditResult,
+
+    FuzzerCustomEventPopupClosed,
+} FuzzerCustomEvent;

+ 33 - 0
multi_fuzzer/helpers/fuzzer_types.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <furi.h>
+
+typedef struct {
+    uint8_t menu_index;
+    uint8_t proto_index;
+} FuzzerState;
+
+typedef enum {
+    FuzzerAttackStateOff = 0,
+    FuzzerAttackStateIdle,
+    FuzzerAttackStateAttacking,
+    FuzzerAttackStateEmulating,
+    FuzzerAttackStatePause,
+    FuzzerAttackStateEnd,
+
+} FuzzerAttackState;
+
+typedef enum {
+    FuzzerFieldEditorStateEditingOn = 0,
+    FuzzerFieldEditorStateEditingOff,
+
+} FuzzerFieldEditorState;
+
+typedef enum {
+    FuzzerViewIDPopup,
+    FuzzerViewIDTextInput,
+
+    FuzzerViewIDMain,
+    FuzzerViewIDAttack,
+    FuzzerViewIDFieldEditor,
+} FuzzerViewID;

BIN
multi_fuzzer/ibutt_10px.png


BIN
multi_fuzzer/icons/rfid_10px.png


+ 508 - 0
multi_fuzzer/lib/worker/fake_worker.c

@@ -0,0 +1,508 @@
+#include "fake_worker.h"
+#include "helpers/hardware_worker.h"
+#include "protocol_i.h"
+
+#include <timer.h>
+
+#include <lib/toolbox/hex.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/buffered_file_stream.h>
+
+#define TAG "Fuzzer worker"
+#define TOTAL_PROTOCOL_COUNT fuzzer_proto_get_count_of_protocols()
+#define PROTOCOL_KEY_FOLDER EXT_PATH(PROTOCOL_KEY_FOLDER_NAME)
+
+typedef uint8_t FuzzerWorkerPayload[MAX_PAYLOAD_SIZE];
+
+struct FuzzerWorker {
+    HardwareWorker* hw_worker;
+
+    const FuzzerProtocol* protocol;
+    HwProtocolID* suported_proto;
+
+    FuzzerWorkerPayload payload;
+
+    FuzzerWorkerAttackType attack_type;
+    uint16_t index;
+    Stream* uids_stream;
+
+    bool in_emu_phase;
+    FuriTimer* timer;
+    uint16_t timer_idle_time_ms;
+    uint16_t timer_emu_time_ms;
+
+    FuzzerWorkerUidChagedCallback tick_callback;
+    void* tick_context;
+
+    FuzzerWorkerEndCallback end_callback;
+    void* end_context;
+};
+
+static bool fuzzer_worker_set_protocol(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) {
+    if(!(protocol_index < TOTAL_PROTOCOL_COUNT)) {
+        return false;
+    }
+
+    instance->protocol = &fuzzer_proto_items[protocol_index];
+    return hardware_worker_set_protocol_id_by_name(
+        instance->hw_worker, fuzzer_proto_items[protocol_index].name);
+}
+
+static FuzzerProtocolsID
+    fuzzer_worker_is_protocol_valid(FuzzerWorker* instance, HwProtocolID protocol_id) {
+    for(FuzzerProtocolsID i = 0; i < TOTAL_PROTOCOL_COUNT; i++) {
+        if(protocol_id == instance->suported_proto[i]) {
+            return i;
+        }
+    }
+    return TOTAL_PROTOCOL_COUNT;
+}
+
+FuzzerWorkerLoadKeyState fuzzer_worker_load_key_from_file(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID* protocol_index,
+    const char* filename) {
+    furi_assert(instance);
+
+    FuzzerWorkerLoadKeyState res = FuzzerWorkerLoadKeyStateUnsuportedProto;
+    if(!hardware_worker_load_key_from_file(instance->hw_worker, filename)) {
+        FURI_LOG_E(TAG, "Load key file: cant load file");
+        res = FuzzerWorkerLoadKeyStateBadFile;
+    } else {
+        FuzzerProtocolsID loaded_id = fuzzer_worker_is_protocol_valid(
+            instance, hardware_worker_get_protocol_id(instance->hw_worker));
+
+        if(!fuzzer_worker_set_protocol(instance, loaded_id)) {
+            FURI_LOG_E(TAG, "Load key file: Unsuported protocol");
+            res = FuzzerWorkerLoadKeyStateUnsuportedProto;
+        } else {
+            if(*protocol_index != loaded_id) {
+                res = FuzzerWorkerLoadKeyStateDifferentProto;
+            } else {
+                res = FuzzerWorkerLoadKeyStateOk;
+            }
+            *protocol_index = loaded_id;
+
+            hardware_worker_get_protocol_data(
+                instance->hw_worker, &instance->payload[0], MAX_PAYLOAD_SIZE);
+        }
+    }
+
+    return res;
+}
+
+static bool fuzer_worker_make_key_folder() {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    const bool res = storage_simply_mkdir(storage, PROTOCOL_KEY_FOLDER);
+
+    furi_record_close(RECORD_STORAGE);
+
+    return res;
+}
+
+bool fuzzer_worker_save_key(FuzzerWorker* instance, const char* path) {
+    furi_assert(instance);
+    bool res = false;
+
+    if(!fuzer_worker_make_key_folder()) {
+        FURI_LOG_E(TAG, "Cannot create key folder");
+    } else if(!hardware_worker_save_key(instance->hw_worker, path)) {
+        FURI_LOG_E(TAG, "Cannot save key file");
+    } else {
+        FURI_LOG_D(TAG, "Save key Success");
+        res = true;
+    }
+
+    return res;
+}
+
+static bool fuzzer_worker_load_key(FuzzerWorker* instance, bool next) {
+    furi_assert(instance);
+    furi_assert(instance->protocol);
+    bool res = false;
+
+    const FuzzerProtocol* protocol = instance->protocol;
+
+    switch(instance->attack_type) {
+    case FuzzerWorkerAttackTypeDefaultDict:
+        if(next) {
+            if(instance->index < (protocol->dict.len - 1)) {
+                instance->index++;
+            } else {
+                break;
+            }
+        }
+        if(instance->index < protocol->dict.len) {
+            memcpy(
+                instance->payload,
+                &protocol->dict.val[instance->index * protocol->data_size],
+                protocol->data_size);
+            res = true;
+        }
+        break;
+
+    case FuzzerWorkerAttackTypeLoadFileCustomUids: {
+        if(next) {
+            instance->index++;
+        }
+        uint8_t str_len = protocol->data_size * 2 + 1;
+        FuriString* data_str = furi_string_alloc();
+        while(true) {
+            furi_string_reset(data_str);
+            if(!stream_read_line(instance->uids_stream, data_str)) {
+                stream_rewind(instance->uids_stream);
+                // TODO Check empty file & close stream and storage
+                break;
+            } else if(furi_string_get_char(data_str, 0) == '#') {
+                // Skip comment string
+                continue;
+            } else if(furi_string_size(data_str) != str_len) {
+                // Ignore strin with bad length
+                FURI_LOG_W(TAG, "Bad string length");
+                continue;
+            } else {
+                FURI_LOG_D(TAG, "Uid candidate: \"%s\"", furi_string_get_cstr(data_str));
+                bool parse_ok = true;
+                for(uint8_t i = 0; i < protocol->data_size; i++) {
+                    if(!hex_char_to_uint8(
+                           furi_string_get_cstr(data_str)[i * 2],
+                           furi_string_get_cstr(data_str)[i * 2 + 1],
+                           &instance->payload[i])) {
+                        parse_ok = false;
+                        break;
+                    }
+                }
+                res = parse_ok;
+            }
+            break;
+        }
+        furi_string_free(data_str);
+    }
+
+    break;
+
+    case FuzzerWorkerAttackTypeLoadFile:
+        if(instance->payload[instance->index] != 0xFF) {
+            instance->payload[instance->index]++;
+            res = true;
+        }
+
+        break;
+
+    default:
+        break;
+    }
+
+    if(res) {
+        hardware_worker_set_protocol_data(
+            instance->hw_worker, &instance->payload[0], protocol->data_size);
+    }
+
+    return res;
+}
+
+static bool fuzzer_worker_load_previous_key(FuzzerWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->protocol);
+    bool res = false;
+
+    const FuzzerProtocol* protocol = instance->protocol;
+
+    switch(instance->attack_type) {
+    case FuzzerWorkerAttackTypeDefaultDict:
+        if(instance->index > 0) {
+            instance->index--;
+            memcpy(
+                instance->payload,
+                &protocol->dict.val[instance->index * protocol->data_size],
+                protocol->data_size);
+            res = true;
+        }
+        break;
+
+    case FuzzerWorkerAttackTypeLoadFile:
+        if(instance->payload[instance->index] != 0x00) {
+            instance->payload[instance->index]--;
+            res = true;
+        }
+
+        break;
+
+    default:
+        break;
+    }
+
+    if(res) {
+        hardware_worker_set_protocol_data(
+            instance->hw_worker, &instance->payload[0], protocol->data_size);
+    }
+
+    return res;
+}
+
+static void fuzzer_worker_on_tick_callback(void* context) {
+    furi_assert(context);
+
+    FuzzerWorker* instance = context;
+
+    if(instance->in_emu_phase) {
+        hardware_worker_stop(instance->hw_worker);
+        instance->in_emu_phase = false;
+        furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_idle_time_ms));
+    } else {
+        if(!fuzzer_worker_load_key(instance, true)) {
+            fuzzer_worker_pause(instance); // XXX
+            if(instance->end_callback) {
+                instance->end_callback(instance->end_context);
+            }
+        } else {
+            hardware_worker_emulate_start(instance->hw_worker);
+            instance->in_emu_phase = true;
+            furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms));
+            if(instance->tick_callback) {
+                instance->tick_callback(instance->tick_context);
+            }
+        }
+    }
+}
+
+void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output_key) {
+    furi_assert(instance);
+    furi_assert(output_key);
+    furi_assert(instance->protocol);
+
+    output_key->data_size = instance->protocol->data_size;
+    memcpy(output_key->data, instance->payload, instance->protocol->data_size);
+}
+
+bool fuzzer_worker_next_key(FuzzerWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->protocol);
+
+    return fuzzer_worker_load_key(instance, true);
+}
+
+bool fuzzer_worker_previous_key(FuzzerWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->protocol);
+
+    return fuzzer_worker_load_previous_key(instance);
+}
+
+bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) {
+    furi_assert(instance);
+
+    bool res = false;
+
+    if(!fuzzer_worker_set_protocol(instance, protocol_index)) {
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+        return res;
+    }
+
+    instance->attack_type = FuzzerWorkerAttackTypeDefaultDict;
+    instance->index = 0;
+
+    if(!fuzzer_worker_load_key(instance, false)) {
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+    } else {
+        res = true;
+    }
+
+    return res;
+}
+
+bool fuzzer_worker_init_attack_file_dict(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    FuriString* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    bool res = false;
+
+    if(!fuzzer_worker_set_protocol(instance, protocol_index)) {
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+        return res;
+    }
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    instance->uids_stream = buffered_file_stream_alloc(storage);
+    furi_record_close(RECORD_STORAGE);
+
+    if(!buffered_file_stream_open(
+           instance->uids_stream, furi_string_get_cstr(file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+        buffered_file_stream_close(instance->uids_stream);
+        return res;
+    }
+
+    instance->attack_type = FuzzerWorkerAttackTypeLoadFileCustomUids;
+    instance->index = 0;
+
+    if(!fuzzer_worker_load_key(instance, false)) {
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+        buffered_file_stream_close(instance->uids_stream);
+        stream_free(instance->uids_stream);
+    } else {
+        res = true;
+    }
+
+    return res;
+}
+
+bool fuzzer_worker_init_attack_bf_byte(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    const FuzzerPayload* new_uid,
+    uint8_t chusen) {
+    furi_assert(instance);
+
+    bool res = false;
+    if(!fuzzer_worker_set_protocol(instance, protocol_index)) {
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+        return res;
+    }
+
+    instance->attack_type = FuzzerWorkerAttackTypeLoadFile;
+    instance->index = chusen;
+
+    memcpy(instance->payload, new_uid->data, instance->protocol->data_size);
+
+    hardware_worker_set_protocol_data(
+        instance->hw_worker, &instance->payload[0], instance->protocol->data_size);
+
+    return true;
+}
+
+FuzzerWorker* fuzzer_worker_alloc() {
+    FuzzerWorker* instance = malloc(sizeof(FuzzerWorker));
+
+    instance->hw_worker = hardware_worker_alloc();
+    hardware_worker_start_thread(instance->hw_worker);
+
+    instance->suported_proto = malloc(sizeof(HwProtocolID) * TOTAL_PROTOCOL_COUNT);
+
+    for(uint8_t i = 0; i < TOTAL_PROTOCOL_COUNT; i++) {
+        if(!hardware_worker_set_protocol_id_by_name(
+               instance->hw_worker, fuzzer_proto_items[i].name)) {
+            // Check protocol support
+            FURI_LOG_E(TAG, "Not supported protocol name: %s", fuzzer_proto_items[i].name);
+            furi_crash("Not supported protocol name");
+        } else {
+            instance->suported_proto[i] = hardware_worker_get_protocol_id(instance->hw_worker);
+            FURI_LOG_D(
+                TAG,
+                "%u: %15s Protocol_id: %lu",
+                i + 1,
+                fuzzer_proto_items[i].name,
+                instance->suported_proto[i]);
+        }
+    }
+
+    instance->attack_type = FuzzerWorkerAttackTypeMax;
+    instance->index = 0;
+    instance->in_emu_phase = false;
+
+    memset(instance->payload, 0x00, sizeof(instance->payload));
+
+    instance->timer_idle_time_ms = PROTOCOL_DEF_IDLE_TIME * 100;
+    instance->timer_emu_time_ms = PROTOCOL_DEF_EMU_TIME * 100;
+
+    instance->timer =
+        furi_timer_alloc(fuzzer_worker_on_tick_callback, FuriTimerTypeOnce, instance);
+
+    return instance;
+}
+
+void fuzzer_worker_free(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    fuzzer_worker_stop(instance);
+
+    furi_timer_free(instance->timer);
+
+    free(instance->suported_proto);
+
+    hardware_worker_stop_thread(instance->hw_worker);
+    hardware_worker_free(instance->hw_worker);
+
+    free(instance);
+}
+
+bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_time) {
+    furi_assert(instance);
+
+    if(instance->attack_type < FuzzerWorkerAttackTypeMax) {
+        if(idle_time == 0) {
+            instance->timer_idle_time_ms = 10;
+        } else {
+            instance->timer_idle_time_ms = idle_time * 100;
+        }
+        if(emu_time == 0) {
+            instance->timer_emu_time_ms = 10;
+        } else {
+            instance->timer_emu_time_ms = emu_time * 100;
+        }
+
+        FURI_LOG_D(
+            TAG,
+            "Emu_time %u ms  Idle_time %u ms",
+            instance->timer_emu_time_ms,
+            instance->timer_idle_time_ms);
+
+        hardware_worker_emulate_start(instance->hw_worker);
+
+        instance->in_emu_phase = true;
+        furi_timer_start(instance->timer, furi_ms_to_ticks(instance->timer_emu_time_ms));
+        return true;
+    }
+    return false;
+}
+
+void fuzzer_worker_start_emulate(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    hardware_worker_emulate_start(instance->hw_worker);
+}
+
+void fuzzer_worker_pause(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    furi_timer_stop(instance->timer);
+
+    hardware_worker_stop(instance->hw_worker);
+}
+
+void fuzzer_worker_stop(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    furi_timer_stop(instance->timer);
+
+    hardware_worker_stop(instance->hw_worker);
+
+    if(instance->attack_type == FuzzerWorkerAttackTypeLoadFileCustomUids) {
+        buffered_file_stream_close(instance->uids_stream);
+        stream_free(instance->uids_stream);
+        instance->attack_type = FuzzerWorkerAttackTypeMax;
+    }
+
+    // TODO anything else
+}
+
+void fuzzer_worker_set_uid_chaged_callback(
+    FuzzerWorker* instance,
+    FuzzerWorkerUidChagedCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->tick_callback = callback;
+    instance->tick_context = context;
+}
+
+void fuzzer_worker_set_end_callback(
+    FuzzerWorker* instance,
+    FuzzerWorkerEndCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->end_callback = callback;
+    instance->end_context = context;
+}

+ 153 - 0
multi_fuzzer/lib/worker/fake_worker.h

@@ -0,0 +1,153 @@
+#pragma once
+
+#include <furi.h>
+
+#include "protocol.h"
+
+typedef enum {
+    FuzzerWorkerAttackTypeDefaultDict = 0,
+    FuzzerWorkerAttackTypeLoadFile,
+    FuzzerWorkerAttackTypeLoadFileCustomUids,
+
+    FuzzerWorkerAttackTypeMax,
+} FuzzerWorkerAttackType;
+
+typedef enum {
+    FuzzerWorkerLoadKeyStateBadFile = -2,
+    FuzzerWorkerLoadKeyStateUnsuportedProto,
+    FuzzerWorkerLoadKeyStateOk = 0,
+    FuzzerWorkerLoadKeyStateDifferentProto,
+
+} FuzzerWorkerLoadKeyState;
+
+typedef void (*FuzzerWorkerUidChagedCallback)(void* context);
+typedef void (*FuzzerWorkerEndCallback)(void* context);
+
+typedef struct FuzzerWorker FuzzerWorker;
+
+/**
+ * Allocate FuzzerWorker
+ * 
+ * @return FuzzerWorker* pointer to FuzzerWorker
+ */
+FuzzerWorker* fuzzer_worker_alloc();
+
+/**
+ * Free FuzzerWorker
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ */
+void fuzzer_worker_free(FuzzerWorker* instance);
+
+/**
+ * Start or continue emulation
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param idle_time Delay between emulations in tenths of a second
+ * @param emu_time Emulation time of one UID in tenths of a second
+ * @return bool True if emulation has started
+ */
+bool fuzzer_worker_start(FuzzerWorker* instance, uint8_t idle_time, uint8_t emu_time);
+
+/**
+ * Stop emulation and deinit worker
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ */
+void fuzzer_worker_stop(FuzzerWorker* instance);
+
+void fuzzer_worker_start_emulate(FuzzerWorker* instance);
+
+/**
+ * Suspend emulation
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ */
+void fuzzer_worker_pause(FuzzerWorker* instance);
+
+/**
+ * Init attack by default dictionary
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param protocol_index index of the selected protocol
+ * @return bool True if initialization is successful
+ */
+bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index);
+
+/**
+ * Init attack by custom dictionary
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param protocol_index index of the selected protocol
+ * @param file_path file path to the dictionary
+ * @return bool True if initialization is successful
+ */
+bool fuzzer_worker_init_attack_file_dict(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    FuriString* file_path);
+
+/**
+ * Init attack brute force one of byte
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param protocol_index index of the selected protocol
+ * @param new_uid Pointer to a FuzzerPayload with UID for brute force
+ * @param chosen index of chusen byte
+ * @return bool True if initialization is successful
+ */
+bool fuzzer_worker_init_attack_bf_byte(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    const FuzzerPayload* new_uid,
+    uint8_t chusen);
+
+/**
+ * Get current UID
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param output_key Pointer to a FuzzerPayload
+ */
+void fuzzer_worker_get_current_key(FuzzerWorker* instance, FuzzerPayload* output_key);
+
+bool fuzzer_worker_next_key(FuzzerWorker* instance);
+bool fuzzer_worker_previous_key(FuzzerWorker* instance);
+
+/**
+ * Load UID from Flipper Format Key file
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param protocol_index index of the selected protocol
+ * @param filename file path to the key file
+ * @return bool True if loading is successful
+ */
+FuzzerWorkerLoadKeyState fuzzer_worker_load_key_from_file(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID* protocol_index,
+    const char* filename);
+
+bool fuzzer_worker_save_key(FuzzerWorker* instance, const char* path);
+
+/**
+ * Set callback for uid changed
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param callback Callback for uid changed
+ * @param context Context for callback
+ */
+void fuzzer_worker_set_uid_chaged_callback(
+    FuzzerWorker* instance,
+    FuzzerWorkerUidChagedCallback callback,
+    void* context);
+
+/**
+ * Set callback for end of emulation
+ * 
+ * @param instance Pointer to a FuzzerWorker
+ * @param callback Callback for end of emulation
+ * @param context Context for callback
+ */
+void fuzzer_worker_set_end_callback(
+    FuzzerWorker* instance,
+    FuzzerWorkerEndCallback callback,
+    void* context);

+ 198 - 0
multi_fuzzer/lib/worker/helpers/hardware_worker.c

@@ -0,0 +1,198 @@
+#include "hardware_worker.h"
+#include "furi.h"
+
+#if defined(RFID_125_PROTOCOL)
+
+#include <lib/lfrfid/lfrfid_dict_file.h>
+#include <lib/lfrfid/lfrfid_worker.h>
+
+#else
+
+#include <lib/ibutton/ibutton_worker.h>
+#include <lib/ibutton/ibutton_key.h>
+
+#endif
+
+#define TAG "Fuzzer HW worker"
+
+struct HardwareWorker {
+#if defined(RFID_125_PROTOCOL)
+    LFRFIDWorker* proto_worker;
+    ProtocolId protocol_id;
+    ProtocolDict* protocols_items;
+#else
+    iButtonWorker* proto_worker;
+    iButtonProtocolId protocol_id;
+    iButtonProtocols* protocols_items;
+    iButtonKey* key;
+#endif
+};
+
+HardwareWorker* hardware_worker_alloc() {
+    HardwareWorker* instance = malloc(sizeof(HardwareWorker));
+#if defined(RFID_125_PROTOCOL)
+    instance->protocols_items = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
+
+    instance->proto_worker = lfrfid_worker_alloc(instance->protocols_items);
+#else
+    instance->protocols_items = ibutton_protocols_alloc();
+    instance->key =
+        ibutton_key_alloc(ibutton_protocols_get_max_data_size(instance->protocols_items));
+
+    instance->proto_worker = ibutton_worker_alloc(instance->protocols_items);
+#endif
+    return instance;
+}
+
+void hardware_worker_free(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    lfrfid_worker_free(instance->proto_worker);
+
+    protocol_dict_free(instance->protocols_items);
+#else
+    ibutton_worker_free(instance->proto_worker);
+
+    ibutton_key_free(instance->key);
+    ibutton_protocols_free(instance->protocols_items);
+#endif
+    free(instance);
+}
+
+void hardware_worker_start_thread(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    lfrfid_worker_start_thread(instance->proto_worker);
+#else
+    ibutton_worker_start_thread(instance->proto_worker);
+#endif
+}
+
+void hardware_worker_stop_thread(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    lfrfid_worker_stop(instance->proto_worker);
+    lfrfid_worker_stop_thread(instance->proto_worker);
+#else
+    ibutton_worker_stop(instance->proto_worker);
+    ibutton_worker_stop_thread(instance->proto_worker);
+#endif
+}
+
+void hardware_worker_emulate_start(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id);
+#else
+    ibutton_worker_emulate_start(instance->proto_worker, instance->key);
+#endif
+}
+
+void hardware_worker_stop(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    lfrfid_worker_stop(instance->proto_worker);
+#else
+    ibutton_worker_stop(instance->proto_worker);
+#endif
+}
+
+void hardware_worker_set_protocol_data(
+    HardwareWorker* instance,
+    uint8_t* payload,
+    uint8_t payload_size) {
+#if defined(RFID_125_PROTOCOL)
+    protocol_dict_set_data(
+        instance->protocols_items, instance->protocol_id, payload, payload_size);
+#else
+    ibutton_key_set_protocol_id(instance->key, instance->protocol_id);
+    iButtonEditableData data;
+    ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data);
+
+    furi_check(payload_size >= data.size);
+    memcpy(data.ptr, payload, data.size);
+#endif
+}
+
+void hardware_worker_get_protocol_data(
+    HardwareWorker* instance,
+    uint8_t* payload,
+    uint8_t payload_size) {
+#if defined(RFID_125_PROTOCOL)
+    protocol_dict_get_data(
+        instance->protocols_items, instance->protocol_id, payload, payload_size);
+#else
+    iButtonEditableData data;
+    ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data);
+    furi_check(payload_size >= data.size);
+    memcpy(payload, data.ptr, data.size);
+#endif
+}
+
+static bool hardware_worker_protocol_is_valid(HardwareWorker* instance) {
+#if defined(RFID_125_PROTOCOL)
+    if(instance->protocol_id != PROTOCOL_NO) {
+        return true;
+    }
+#else
+    if(instance->protocol_id != iButtonProtocolIdInvalid) {
+        return true;
+    }
+#endif
+    return false;
+}
+
+bool hardware_worker_set_protocol_id_by_name(HardwareWorker* instance, const char* protocol_name) {
+#if defined(RFID_125_PROTOCOL)
+    instance->protocol_id =
+        protocol_dict_get_protocol_by_name(instance->protocols_items, protocol_name);
+    return (instance->protocol_id != PROTOCOL_NO);
+#else
+    instance->protocol_id =
+        ibutton_protocols_get_id_by_name(instance->protocols_items, protocol_name);
+    return (instance->protocol_id != iButtonProtocolIdInvalid);
+#endif
+}
+
+HwProtocolID hardware_worker_get_protocol_id(HardwareWorker* instance) {
+    if(hardware_worker_protocol_is_valid(instance)) {
+        return instance->protocol_id;
+    }
+    return -1;
+}
+
+bool hardware_worker_load_key_from_file(HardwareWorker* instance, const char* filename) {
+    bool res = false;
+
+#if defined(RFID_125_PROTOCOL)
+    ProtocolId loaded_proto_id = lfrfid_dict_file_load(instance->protocols_items, filename);
+    if(loaded_proto_id == PROTOCOL_NO) {
+        // Err Cant load file
+        FURI_LOG_W(TAG, "Cant load file");
+    } else {
+        instance->protocol_id = loaded_proto_id;
+        res = true;
+    }
+#else
+
+    if(!ibutton_protocols_load(instance->protocols_items, instance->key, filename)) {
+        // Err Cant load file
+        FURI_LOG_W(TAG, "Cant load file");
+    } else {
+        instance->protocol_id = ibutton_key_get_protocol_id(instance->key);
+        res = true;
+    }
+
+#endif
+
+    return res;
+}
+
+bool hardware_worker_save_key(HardwareWorker* instance, const char* path) {
+    furi_assert(instance);
+    bool res;
+
+#if defined(RFID_125_PROTOCOL)
+    res = lfrfid_dict_file_save(instance->protocols_items, instance->protocol_id, path);
+#else
+
+    res = ibutton_protocols_save(instance->protocols_items, instance->key, path);
+#endif
+
+    return res;
+}

+ 48 - 0
multi_fuzzer/lib/worker/helpers/hardware_worker.h

@@ -0,0 +1,48 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#if defined(RFID_125_PROTOCOL)
+
+#include <lib/lfrfid/lfrfid_dict_file.h>
+typedef ProtocolId HwProtocolID;
+
+#else
+
+#include <lib/ibutton/protocols/protocol_common.h>
+typedef iButtonProtocolId HwProtocolID;
+
+#endif
+
+typedef struct HardwareWorker HardwareWorker;
+
+HardwareWorker* hardware_worker_alloc();
+
+void hardware_worker_free(HardwareWorker* instance);
+
+void hardware_worker_start_thread(HardwareWorker* instance);
+
+void hardware_worker_stop_thread(HardwareWorker* instance);
+
+void hardware_worker_emulate_start(HardwareWorker* instance);
+
+void hardware_worker_stop(HardwareWorker* instance);
+
+void hardware_worker_set_protocol_data(
+    HardwareWorker* instance,
+    uint8_t* payload,
+    uint8_t payload_size);
+
+void hardware_worker_get_protocol_data(
+    HardwareWorker* instance,
+    uint8_t* payload,
+    uint8_t payload_size);
+
+bool hardware_worker_set_protocol_id_by_name(HardwareWorker* instance, const char* protocol_name);
+
+HwProtocolID hardware_worker_get_protocol_id(HardwareWorker* instance);
+
+bool hardware_worker_load_key_from_file(HardwareWorker* instance, const char* filename);
+
+bool hardware_worker_save_key(HardwareWorker* instance, const char* path);

+ 291 - 0
multi_fuzzer/lib/worker/protocol.c

@@ -0,0 +1,291 @@
+#include "protocol_i.h"
+#include "furi.h"
+
+// #######################
+// ## Ibutton Protocols ##
+// #######################
+#define DS1990_DATA_SIZE (8)
+#define Metakom_DATA_SIZE (4)
+#define Cyfral_DATA_SIZE (2)
+
+const uint8_t uid_list_ds1990[][DS1990_DATA_SIZE] = {
+    {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x36, 0x00, 0xE1}, //– код универсального ключа, для Vizit
+    {0x01, 0xBE, 0x40, 0x11, 0x5A, 0x56, 0x00, 0xBB}, //- проверен работает
+    {0x01, 0xBE, 0x40, 0x11, 0x00, 0x00, 0x00, 0x77}, //- проверен работает
+    {0x01, 0xBE, 0x40, 0x11, 0x0A, 0x00, 0x00, 0x1D}, //- проверен работает Визит иногда КЕЙМАНЫ
+    {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x2F}, //- проверен(метаком, цифрал, ВИЗИТ).
+    {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x9B}, //- проверен Визит, Метакомы, КОНДОР
+    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, //???-Открываает 98% Метаком и некоторые Цифрал
+    {0x01, 0x00, 0x00, 0x00, 0x00, 0x90, 0x19, 0xFF}, //???-Отлично работает на старых домофонах
+    {0x01, 0x6F, 0x2E, 0x88, 0x8A, 0x00, 0x00, 0x4D}, //???-Открывать что-то должен
+    {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x7E, 0x88}, //???-Cyfral, Metakom
+    {0x01, 0x53, 0xD4, 0xFE, 0x00, 0x00, 0x00, 0x6F}, //???-домофоны Визит (Vizit) - до 99%
+    {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D}, //???-домофоны Cyfral CCD-20 - до 70%
+    {0x01, 0x00, 0xBE, 0x11, 0xAA, 0x00, 0x00, 0xFB}, //???-домофоны Кейман (KEYMAN)
+    {0x01, 0x76, 0xB8, 0x2E, 0x0F, 0x00, 0x00, 0x5C}, //???-домофоны Форвард
+    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x14}, // Only FF
+    {0x01, 0x78, 0x00, 0x48, 0xFD, 0xFF, 0xFF, 0xD1}, // StarNew Uni5
+    {0x01, 0xA9, 0xE4, 0x3C, 0x09, 0x00, 0x00, 0xE6}, // Eltis Uni
+};
+
+const uint8_t uid_list_metakom[][Metakom_DATA_SIZE] = {
+    {0x00, 0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
+    {0x11, 0x11, 0x11, 0x11}, // Only 11
+    {0x22, 0x22, 0x22, 0x22}, // Only 22
+    {0x33, 0x33, 0x33, 0x33}, // Only 33
+    {0x44, 0x44, 0x44, 0x44}, // Only 44
+    {0x55, 0x55, 0x55, 0x55}, // Only 55
+    {0x66, 0x66, 0x66, 0x66}, // Only 66
+    {0x77, 0x77, 0x77, 0x77}, // Only 77
+    {0x88, 0x88, 0x88, 0x88}, // Only 88
+    {0x99, 0x99, 0x99, 0x99}, // Only 99
+    {0x12, 0x34, 0x56, 0x78}, // Incremental UID
+    {0x9A, 0x78, 0x56, 0x34}, // Decremental UID
+    {0x04, 0xd0, 0x9b, 0x0d}, // ??
+    {0x34, 0x00, 0x29, 0x3d}, // ??
+    {0x04, 0xdf, 0x00, 0x00}, // ??
+    {0xCA, 0xCA, 0xCA, 0xCA}, // ??
+};
+
+const uint8_t uid_list_cyfral[][Cyfral_DATA_SIZE] = {
+    {0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF}, // Only FF
+    {0x11, 0x11}, // Only 11
+    {0x22, 0x22}, // Only 22
+    {0x33, 0x33}, // Only 33
+    {0x44, 0x44}, // Only 44
+    {0x55, 0x55}, // Only 55
+    {0x66, 0x66}, // Only 66
+    {0x77, 0x77}, // Only 77
+    {0x88, 0x88}, // Only 88
+    {0x99, 0x99}, // Only 99
+    {0x12, 0x34}, // Incremental UID
+    {0x56, 0x34}, // Decremental UID
+    {0xCA, 0xCA}, // ??
+    {0x8E, 0xC9}, // Elevator code
+    {0x6A, 0x50}, // VERY fresh code from smartkey
+};
+
+// ###########################
+// ## Rfid_125khz Protocols ##
+// ###########################
+#define EM4100_DATA_SIZE (5)
+#define HIDProx_DATA_SIZE (6)
+#define PAC_DATA_SIZE (4)
+#define H10301_DATA_SIZE (3)
+
+const uint8_t uid_list_em4100[][EM4100_DATA_SIZE] = {
+    {0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
+    {0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11
+    {0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22
+    {0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33
+    {0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44
+    {0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55
+    {0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66
+    {0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77
+    {0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88
+    {0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99
+    {0x12, 0x34, 0x56, 0x78, 0x9A}, // Incremental UID
+    {0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID
+    {0x04, 0xd0, 0x9b, 0x0d, 0x6a}, // From arha
+    {0x34, 0x00, 0x29, 0x3d, 0x9e}, // From arha
+    {0x04, 0xdf, 0x00, 0x00, 0x01}, // From arha
+    {0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha
+};
+
+const uint8_t uid_list_hid[][HIDProx_DATA_SIZE] = {
+    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
+    {0x11, 0x11, 0x11, 0x11, 0x11, 0x11}, // Only 11
+    {0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, // Only 22
+    {0x33, 0x33, 0x33, 0x33, 0x33, 0x33}, // Only 33
+    {0x44, 0x44, 0x44, 0x44, 0x44, 0x44}, // Only 44
+    {0x55, 0x55, 0x55, 0x55, 0x55, 0x55}, // Only 55
+    {0x66, 0x66, 0x66, 0x66, 0x66, 0x66}, // Only 66
+    {0x77, 0x77, 0x77, 0x77, 0x77, 0x77}, // Only 77
+    {0x88, 0x88, 0x88, 0x88, 0x88, 0x88}, // Only 88
+    {0x99, 0x99, 0x99, 0x99, 0x99, 0x99}, // Only 99
+    {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}, // Incremental UID
+    {0xBC, 0x9A, 0x78, 0x56, 0x34, 0x12}, // Decremental UID
+    {0xCA, 0xCA, 0xCA, 0xCA, 0xCA, 0xCA}, // From arha
+};
+
+const uint8_t uid_list_pac[][PAC_DATA_SIZE] = {
+    {0x00, 0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF, 0xFF}, // Only FF
+    {0x11, 0x11, 0x11, 0x11}, // Only 11
+    {0x22, 0x22, 0x22, 0x22}, // Only 22
+    {0x33, 0x33, 0x33, 0x33}, // Only 33
+    {0x44, 0x44, 0x44, 0x44}, // Only 44
+    {0x55, 0x55, 0x55, 0x55}, // Only 55
+    {0x66, 0x66, 0x66, 0x66}, // Only 66
+    {0x77, 0x77, 0x77, 0x77}, // Only 77
+    {0x88, 0x88, 0x88, 0x88}, // Only 88
+    {0x99, 0x99, 0x99, 0x99}, // Only 99
+    {0x12, 0x34, 0x56, 0x78}, // Incremental UID
+    {0x9A, 0x78, 0x56, 0x34}, // Decremental UID
+    {0x04, 0xd0, 0x9b, 0x0d}, // From arha
+    {0x34, 0x00, 0x29, 0x3d}, // From arha
+    {0x04, 0xdf, 0x00, 0x00}, // From arha
+    {0xCA, 0xCA, 0xCA, 0xCA}, // From arha
+};
+
+const uint8_t uid_list_h10301[][H10301_DATA_SIZE] = {
+    {0x00, 0x00, 0x00}, // Null bytes
+    {0xFF, 0xFF, 0xFF}, // Only FF
+    {0x11, 0x11, 0x11}, // Only 11
+    {0x22, 0x22, 0x22}, // Only 22
+    {0x33, 0x33, 0x33}, // Only 33
+    {0x44, 0x44, 0x44}, // Only 44
+    {0x55, 0x55, 0x55}, // Only 55
+    {0x66, 0x66, 0x66}, // Only 66
+    {0x77, 0x77, 0x77}, // Only 77
+    {0x88, 0x88, 0x88}, // Only 88
+    {0x99, 0x99, 0x99}, // Only 99
+    {0x12, 0x34, 0x56}, // Incremental UID
+    {0x56, 0x34, 0x12}, // Decremental UID
+    {0xCA, 0xCA, 0xCA}, // From arha
+};
+
+#if defined(RFID_125_PROTOCOL)
+const FuzzerProtocol fuzzer_proto_items[] = {
+    // EM4100
+    {
+        .name = "EM4100",
+        .data_size = EM4100_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_em4100,
+                .len = COUNT_OF(uid_list_em4100),
+            },
+    },
+    // HIDProx
+    {
+        .name = "HIDProx",
+        .data_size = HIDProx_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_hid,
+                .len = COUNT_OF(uid_list_hid),
+            },
+    },
+    // PAC
+    {
+        .name = "PAC/Stanley",
+        .data_size = PAC_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_pac,
+                .len = COUNT_OF(uid_list_pac),
+            },
+    },
+    // H10301
+    {
+        .name = "H10301",
+        .data_size = H10301_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_h10301,
+                .len = COUNT_OF(uid_list_h10301),
+            },
+    },
+};
+#else
+const FuzzerProtocol fuzzer_proto_items[] = {
+    // DS1990
+    {
+        .name = "DS1990",
+        .data_size = DS1990_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_ds1990,
+                .len = COUNT_OF(uid_list_ds1990),
+            },
+    },
+    // Metakom
+    {
+        .name = "Metakom",
+        .data_size = Metakom_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_metakom,
+                .len = COUNT_OF(uid_list_metakom),
+            },
+    },
+    // Cyfral
+    {
+        .name = "Cyfral",
+        .data_size = Cyfral_DATA_SIZE,
+        .dict =
+            {
+                .val = (const uint8_t*)&uid_list_cyfral,
+                .len = COUNT_OF(uid_list_cyfral),
+            },
+    },
+};
+#endif
+
+typedef struct {
+    const char* menu_label;
+    FuzzerAttackId attack_id;
+} FuzzerMenuItems;
+
+const FuzzerMenuItems fuzzer_menu_items[] = {
+    {"Default Values", FuzzerAttackIdDefaultValues},
+#ifdef RFID_125_PROTOCOL
+    {"BF Customer ID", FuzzerAttackIdBFCustomerID},
+#endif
+    {"Load File", FuzzerAttackIdLoadFile},
+    {"Load UIDs from file", FuzzerAttackIdLoadFileCustomUids},
+};
+
+FuzzerPayload* fuzzer_payload_alloc() {
+    FuzzerPayload* payload = malloc(sizeof(FuzzerPayload));
+    payload->data = malloc(sizeof(payload->data[0]) * MAX_PAYLOAD_SIZE);
+
+    return payload;
+}
+
+void fuzzer_payload_free(FuzzerPayload* payload) {
+    furi_assert(payload);
+
+    if(payload->data) {
+        free(payload->data);
+    }
+    free(payload);
+}
+
+const char* fuzzer_proto_get_name(FuzzerProtocolsID index) {
+    return fuzzer_proto_items[index].name;
+}
+
+uint8_t fuzzer_proto_get_count_of_protocols() {
+    return COUNT_OF(fuzzer_proto_items);
+}
+
+uint8_t fuzzer_proto_get_max_data_size() {
+    return MAX_PAYLOAD_SIZE;
+}
+
+uint8_t fuzzer_proto_get_def_emu_time() {
+    return PROTOCOL_DEF_EMU_TIME;
+}
+
+uint8_t fuzzer_proto_get_def_idle_time() {
+    return PROTOCOL_DEF_IDLE_TIME;
+}
+
+const char* fuzzer_proto_get_menu_label(uint8_t index) {
+    return fuzzer_menu_items[index].menu_label;
+}
+
+FuzzerAttackId fuzzer_proto_get_attack_id_by_index(uint8_t index) {
+    return fuzzer_menu_items[index].attack_id;
+}
+
+uint8_t fuzzer_proto_get_count_of_menu_items() {
+    return COUNT_OF(fuzzer_menu_items);
+}

+ 86 - 0
multi_fuzzer/lib/worker/protocol.h

@@ -0,0 +1,86 @@
+#pragma once
+
+#include <stdint.h>
+
+// #define RFID_125_PROTOCOL
+
+typedef struct FuzzerPayload FuzzerPayload;
+
+typedef uint8_t FuzzerProtocolsID;
+
+typedef enum {
+    FuzzerAttackIdDefaultValues = 0,
+    FuzzerAttackIdLoadFile,
+    FuzzerAttackIdLoadFileCustomUids,
+    FuzzerAttackIdBFCustomerID,
+} FuzzerAttackId;
+
+struct FuzzerPayload {
+    uint8_t* data;
+    uint8_t data_size;
+};
+
+/**
+ * Allocate FuzzerPayload
+ * 
+ * @return FuzzerPayload* pointer to FuzzerPayload
+ */
+FuzzerPayload* fuzzer_payload_alloc();
+
+/**
+ * Free FuzzerPayload
+ * 
+ * @param instance Pointer to a FuzzerPayload
+ */
+void fuzzer_payload_free(FuzzerPayload*);
+
+/**
+ * Get maximum length of UID among all supported protocols
+ * @return Maximum length of UID
+ */
+uint8_t fuzzer_proto_get_max_data_size();
+
+/**
+ * Get recomended/default emulation time
+ * @return Default emulation time
+ */
+uint8_t fuzzer_proto_get_def_emu_time();
+
+/**
+ * Get recomended/default idle time
+ * @return Default idle time
+ */
+uint8_t fuzzer_proto_get_def_idle_time();
+
+/**
+ * Get protocol name based on its index
+ * @param index protocol index
+ * @return pointer to a string containing the name
+ */
+const char* fuzzer_proto_get_name(FuzzerProtocolsID index);
+
+/**
+ * Get number of protocols
+ * @return number of protocols
+ */
+uint8_t fuzzer_proto_get_count_of_protocols();
+
+/**
+ * Get menu label based on its index
+ * @param index menu index
+ * @return pointer to a string containing the menu label
+ */
+const char* fuzzer_proto_get_menu_label(uint8_t index);
+
+/**
+ * Get FuzzerAttackId based on its index
+ * @param index menu index
+ * @return FuzzerAttackId
+ */
+FuzzerAttackId fuzzer_proto_get_attack_id_by_index(uint8_t index);
+
+/**
+ * Get number of menu items
+ * @return number of menu items
+ */
+uint8_t fuzzer_proto_get_count_of_menu_items();

+ 51 - 0
multi_fuzzer/lib/worker/protocol_i.h

@@ -0,0 +1,51 @@
+#pragma once
+
+#include "protocol.h"
+
+#if defined(RFID_125_PROTOCOL)
+#define MAX_PAYLOAD_SIZE (6)
+#define PROTOCOL_DEF_IDLE_TIME (4)
+#define PROTOCOL_DEF_EMU_TIME (5)
+#define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME
+
+#define PROTOCOL_KEY_FOLDER_NAME "lfrfid"
+#define PROTOCOL_KEY_EXTENSION ".rfid"
+
+#else
+
+#define MAX_PAYLOAD_SIZE (8)
+#define PROTOCOL_DEF_IDLE_TIME (2)
+#define PROTOCOL_DEF_EMU_TIME (2)
+#define PROTOCOL_TIME_DELAY_MIN PROTOCOL_DEF_IDLE_TIME + PROTOCOL_DEF_EMU_TIME
+
+#define PROTOCOL_KEY_FOLDER_NAME "ibutton"
+#define PROTOCOL_KEY_EXTENSION ".ibtn"
+#endif
+
+typedef struct ProtoDict ProtoDict;
+typedef struct FuzzerProtocol FuzzerProtocol;
+
+struct ProtoDict {
+    const uint8_t* val;
+    const uint8_t len;
+};
+
+struct FuzzerProtocol {
+    const char* name;
+    const uint8_t data_size;
+    const ProtoDict dict;
+};
+
+// #define MAX_PAYLOAD_SIZE 6
+
+// #define FUZZ_TIME_DELAY_MIN (5)
+// #define FUZZ_TIME_DELAY_DEFAULT (10)
+// #define FUZZ_TIME_DELAY_MAX (70)
+
+// #define MAX_PAYLOAD_SIZE 8
+
+// #define FUZZ_TIME_DELAY_MIN (4)
+// #define FUZZ_TIME_DELAY_DEFAULT (8)
+// #define FUZZ_TIME_DELAY_MAX (80)
+
+extern const FuzzerProtocol fuzzer_proto_items[];

+ 30 - 0
multi_fuzzer/scenes/fuzzer_scene.c

@@ -0,0 +1,30 @@
+#include "fuzzer_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const fuzzer_scene_on_enter_handlers[])(void*) = {
+#include "fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const fuzzer_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const fuzzer_scene_on_exit_handlers[])(void* context) = {
+#include "fuzzer_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers fuzzer_scene_handlers = {
+    .on_enter_handlers = fuzzer_scene_on_enter_handlers,
+    .on_event_handlers = fuzzer_scene_on_event_handlers,
+    .on_exit_handlers = fuzzer_scene_on_exit_handlers,
+    .scene_num = FuzzerSceneNum,
+};

+ 29 - 0
multi_fuzzer/scenes/fuzzer_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) FuzzerScene##id,
+typedef enum {
+#include "fuzzer_scene_config.h"
+    FuzzerSceneNum,
+} FuzzerScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers fuzzer_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "fuzzer_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "fuzzer_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "fuzzer_scene_config.h"
+#undef ADD_SCENE

+ 170 - 0
multi_fuzzer/scenes/fuzzer_scene_attack.c

@@ -0,0 +1,170 @@
+#include "../fuzzer_i.h"
+#include "../helpers/fuzzer_custom_event.h"
+
+const NotificationSequence sequence_one_red_50_on_blink_blue = {
+    &message_red_255,
+    &message_delay_50,
+    &message_red_0,
+    &message_blink_start_10,
+    &message_blink_set_color_blue,
+    &message_do_not_reset,
+    NULL,
+};
+
+static void fuzzer_scene_attack_update_uid(PacsFuzzerApp* app) {
+    furi_assert(app);
+    furi_assert(app->worker);
+    furi_assert(app->attack_view);
+
+    fuzzer_worker_get_current_key(app->worker, app->payload);
+
+    fuzzer_view_attack_set_uid(app->attack_view, app->payload);
+}
+
+static void fuzzer_scene_attack_set_state(PacsFuzzerApp* app, FuzzerAttackState state) {
+    furi_assert(app);
+
+    scene_manager_set_scene_state(app->scene_manager, FuzzerSceneAttack, state);
+    switch(state) {
+    case FuzzerAttackStateIdle:
+        notification_message(app->notifications, &sequence_blink_stop);
+        break;
+
+    case FuzzerAttackStateAttacking:
+        notification_message(app->notifications, &sequence_blink_start_blue);
+        break;
+    case FuzzerAttackStateEmulating:
+        notification_message(app->notifications, &sequence_blink_start_blue);
+        break;
+
+    case FuzzerAttackStateEnd:
+        notification_message(app->notifications, &sequence_blink_stop);
+        notification_message(app->notifications, &sequence_single_vibro);
+        break;
+
+    case FuzzerAttackStateOff:
+        notification_message(app->notifications, &sequence_blink_stop);
+        break;
+
+    case FuzzerAttackStatePause:
+        notification_message(app->notifications, &sequence_blink_stop);
+        break;
+    }
+
+    fuzzer_view_update_state(app->attack_view, state);
+}
+
+void fuzzer_scene_attack_worker_tick_callback(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    notification_message(app->notifications, &sequence_one_red_50_on_blink_blue);
+    fuzzer_scene_attack_update_uid(app);
+}
+
+void fuzzer_scene_attack_worker_end_callback(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewAttackEnd);
+}
+
+void fuzzer_scene_attack_callback(FuzzerCustomEvent event, void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void fuzzer_scene_attack_on_enter(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    fuzzer_view_attack_set_callback(app->attack_view, fuzzer_scene_attack_callback, app);
+
+    fuzzer_worker_set_uid_chaged_callback(
+        app->worker, fuzzer_scene_attack_worker_tick_callback, app);
+
+    fuzzer_worker_set_end_callback(app->worker, fuzzer_scene_attack_worker_end_callback, app);
+
+    fuzzer_view_attack_reset_data(
+        app->attack_view,
+        fuzzer_proto_get_menu_label(app->fuzzer_state.menu_index),
+        fuzzer_proto_get_name(app->fuzzer_state.proto_index));
+
+    fuzzer_scene_attack_update_uid(app);
+
+    scene_manager_set_scene_state(app->scene_manager, FuzzerSceneAttack, FuzzerAttackStateIdle);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDAttack);
+}
+
+bool fuzzer_scene_attack_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == FuzzerCustomEventViewAttackExit) {
+            // Exit
+            fuzzer_worker_stop(app->worker);
+
+            fuzzer_scene_attack_set_state(app, FuzzerAttackStateOff);
+            if(!scene_manager_previous_scene(app->scene_manager)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+        } else if(event.event == FuzzerCustomEventViewAttackRunAttack) {
+            if(fuzzer_worker_start(
+                   app->worker,
+                   fuzzer_view_attack_get_time_delay(app->attack_view),
+                   fuzzer_view_attack_get_emu_time(app->attack_view))) {
+                fuzzer_scene_attack_set_state(app, FuzzerAttackStateAttacking);
+            } else {
+                // Error?
+            }
+        } else if(event.event == FuzzerCustomEventViewAttackEmulateCurrent) {
+            fuzzer_worker_start_emulate(app->worker);
+
+            fuzzer_scene_attack_set_state(app, FuzzerAttackStateEmulating);
+        } else if(event.event == FuzzerCustomEventViewAttackPause) {
+            fuzzer_worker_pause(app->worker);
+
+            fuzzer_scene_attack_set_state(app, FuzzerAttackStatePause);
+        } else if(event.event == FuzzerCustomEventViewAttackIdle) {
+            fuzzer_worker_pause(app->worker);
+
+            fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle);
+        } else if(event.event == FuzzerCustomEventViewAttackNextUid) {
+            if(fuzzer_worker_next_key(app->worker)) {
+                fuzzer_scene_attack_update_uid(app);
+            } else {
+                notification_message(app->notifications, &sequence_blink_red_100);
+            }
+        } else if(event.event == FuzzerCustomEventViewAttackPrevUid) {
+            if(fuzzer_worker_previous_key(app->worker)) {
+                fuzzer_scene_attack_update_uid(app);
+            } else {
+                notification_message(app->notifications, &sequence_blink_red_100);
+            }
+        } else if(event.event == FuzzerCustomEventViewAttackSave) {
+            scene_manager_next_scene(app->scene_manager, FuzzerSceneSaveName);
+        }
+        // Callback from worker
+        else if(event.event == FuzzerCustomEventViewAttackEnd) {
+            fuzzer_scene_attack_set_state(app, FuzzerAttackStateEnd);
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void fuzzer_scene_attack_on_exit(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    // XXX the scene has no descendants, and the return will be processed in on_event
+    // fuzzer_worker_stop();
+
+    fuzzer_worker_set_uid_chaged_callback(app->worker, NULL, NULL);
+    fuzzer_worker_set_end_callback(app->worker, NULL, NULL);
+}

+ 5 - 0
multi_fuzzer/scenes/fuzzer_scene_config.h

@@ -0,0 +1,5 @@
+ADD_SCENE(fuzzer, main, Main)
+ADD_SCENE(fuzzer, attack, Attack)
+ADD_SCENE(fuzzer, field_editor, FieldEditor)
+ADD_SCENE(fuzzer, save_name, SaveName)
+ADD_SCENE(fuzzer, save_success, SaveSuccess)

+ 67 - 0
multi_fuzzer/scenes/fuzzer_scene_field_editor.c

@@ -0,0 +1,67 @@
+#include "../fuzzer_i.h"
+#include "../helpers/fuzzer_custom_event.h"
+
+void fuzzer_scene_field_editor_callback(FuzzerCustomEvent event, void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void fuzzer_scene_field_editor_on_enter(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    fuzzer_view_field_editor_set_callback(
+        app->field_editor_view, fuzzer_scene_field_editor_callback, app);
+
+    fuzzer_worker_get_current_key(app->worker, app->payload);
+
+    switch(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneFieldEditor)) {
+    case FuzzerFieldEditorStateEditingOn:
+        fuzzer_view_field_editor_reset_data(app->field_editor_view, app->payload, true);
+        break;
+
+    case FuzzerFieldEditorStateEditingOff:
+        memset(app->payload->data, 0x00, app->payload->data_size);
+        fuzzer_view_field_editor_reset_data(app->field_editor_view, app->payload, false);
+        break;
+
+    default:
+        break;
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDFieldEditor);
+}
+
+bool fuzzer_scene_field_editor_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == FuzzerCustomEventViewFieldEditorBack) {
+            if(!scene_manager_previous_scene(app->scene_manager)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+        } else if(event.event == FuzzerCustomEventViewFieldEditorOk) {
+            fuzzer_view_field_editor_get_uid(app->field_editor_view, app->payload);
+            if(fuzzer_worker_init_attack_bf_byte(
+                   app->worker,
+                   app->fuzzer_state.proto_index,
+                   app->payload,
+                   fuzzer_view_field_editor_get_index(app->field_editor_view))) {
+                scene_manager_next_scene(app->scene_manager, FuzzerSceneAttack);
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void fuzzer_scene_field_editor_on_exit(void* context) {
+    // furi_assert(context);
+    // PacsFuzzerApp* app = context;
+    UNUSED(context);
+}

+ 200 - 0
multi_fuzzer/scenes/fuzzer_scene_main.c

@@ -0,0 +1,200 @@
+#include "../fuzzer_i.h"
+#include "../helpers/fuzzer_custom_event.h"
+
+#include "../lib/worker/protocol.h"
+#define TAG "Fuzzer main menu"
+
+void fuzzer_scene_main_callback(FuzzerCustomEvent event, void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void fuzzer_scene_main_error_popup_callback(void* context) {
+    PacsFuzzerApp* app = context;
+    notification_message(app->notifications, &sequence_reset_rgb);
+    view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewMainPopupErr);
+}
+
+static bool fuzzer_scene_main_load_custom_dict(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    FuzzerConsts* consts = app->fuzzer_const;
+
+    furi_string_set_str(app->file_path, consts->custom_dict_folder);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, consts->custom_dict_extension, &I_rfid_10px);
+    browser_options.base_path = consts->custom_dict_folder;
+    browser_options.hide_ext = false;
+
+    bool res =
+        dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
+
+    return res;
+}
+
+static bool fuzzer_scene_main_load_key_dialog(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    FuzzerConsts* consts = app->fuzzer_const;
+
+    furi_string_set_str(app->file_path, consts->path_key_folder);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, consts->key_extension, consts->key_icon);
+    browser_options.base_path = consts->path_key_folder;
+    browser_options.hide_ext = true;
+
+    bool res =
+        dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options);
+
+    return res;
+}
+
+static void fuzzer_scene_main_show_error(void* context, const char* erre_str) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    popup_set_header(app->popup, erre_str, 64, 20, AlignCenter, AlignTop);
+    notification_message(app->notifications, &sequence_set_red_255);
+    notification_message(app->notifications, &sequence_double_vibro);
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDPopup);
+}
+
+void fuzzer_scene_main_on_enter(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    fuzzer_view_main_set_callback(app->main_view, fuzzer_scene_main_callback, app);
+
+    fuzzer_view_main_update_data(app->main_view, app->fuzzer_state);
+
+    // Setup view
+    Popup* popup = app->popup;
+    // popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48);
+    popup_set_timeout(popup, 2500);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, fuzzer_scene_main_error_popup_callback);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDMain);
+}
+
+bool fuzzer_scene_main_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == FuzzerCustomEventViewMainBack) {
+            if(!scene_manager_previous_scene(app->scene_manager)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+        } else if(event.event == FuzzerCustomEventViewMainPopupErr) {
+            view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDMain);
+            consumed = true;
+        } else if(event.event == FuzzerCustomEventViewMainOk) {
+            fuzzer_view_main_get_state(app->main_view, &app->fuzzer_state);
+
+            // TODO error logic
+            bool loading_ok = false;
+
+            switch(fuzzer_proto_get_attack_id_by_index(app->fuzzer_state.menu_index)) {
+            case FuzzerAttackIdDefaultValues:
+                loading_ok =
+                    fuzzer_worker_init_attack_dict(app->worker, app->fuzzer_state.proto_index);
+
+                if(!loading_ok) {
+                    // error
+                    fuzzer_scene_main_show_error(app, "Default dictionary\nis empty");
+                }
+                break;
+
+            case FuzzerAttackIdBFCustomerID:
+                // TODO
+                app->payload->data_size = fuzzer_proto_get_max_data_size();
+                memset(app->payload->data, 0x00, app->payload->data_size);
+
+                if(fuzzer_worker_init_attack_bf_byte(
+                       app->worker, app->fuzzer_state.proto_index, app->payload, 0)) {
+                    scene_manager_set_scene_state(
+                        app->scene_manager,
+                        FuzzerSceneFieldEditor,
+                        FuzzerFieldEditorStateEditingOff);
+                    scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor);
+
+                } else {
+                    // error
+                }
+                break;
+
+            case FuzzerAttackIdLoadFile:
+                if(!fuzzer_scene_main_load_key_dialog(app)) {
+                    break;
+                } else {
+                    switch(fuzzer_worker_load_key_from_file(
+                        app->worker,
+                        &app->fuzzer_state.proto_index,
+                        furi_string_get_cstr(app->file_path))) {
+                    case FuzzerWorkerLoadKeyStateOk:
+                    case FuzzerWorkerLoadKeyStateDifferentProto:
+                        scene_manager_set_scene_state(
+                            app->scene_manager,
+                            FuzzerSceneFieldEditor,
+                            FuzzerFieldEditorStateEditingOn);
+                        scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor);
+                        FURI_LOG_I(TAG, "Load ok");
+                        break;
+
+                    case FuzzerWorkerLoadKeyStateBadFile:
+                        fuzzer_scene_main_show_error(app, "Cant load\nor broken file");
+                        FURI_LOG_E(TAG, "Cant load or broken file");
+                        break;
+
+                    case FuzzerWorkerLoadKeyStateUnsuportedProto:
+                        fuzzer_scene_main_show_error(app, "Unsupported protocol");
+                        FURI_LOG_E(TAG, "Unsupported protocol");
+                        break;
+                    }
+                }
+                break;
+
+            case FuzzerAttackIdLoadFileCustomUids:
+                if(!fuzzer_scene_main_load_custom_dict(app)) {
+                    break;
+                } else {
+                    loading_ok = fuzzer_worker_init_attack_file_dict(
+                        app->worker, app->fuzzer_state.proto_index, app->file_path);
+                    if(!loading_ok) {
+                        fuzzer_scene_main_show_error(app, "Incorrect key format\nor length");
+                        // error
+                    }
+                }
+                break;
+
+            default:
+                fuzzer_scene_main_show_error(app, "Unsuported attack");
+                break;
+            }
+
+            if(loading_ok) {
+                scene_manager_next_scene(app->scene_manager, FuzzerSceneAttack);
+            }
+            consumed = true;
+        }
+    }
+
+    return consumed;
+}
+
+void fuzzer_scene_main_on_exit(void* context) {
+    // furi_assert(context);
+    // PacsFuzzerApp* app = context;
+    UNUSED(context);
+}

+ 67 - 0
multi_fuzzer/scenes/fuzzer_scene_save_name.c

@@ -0,0 +1,67 @@
+#include "../fuzzer_i.h"
+
+#include <toolbox/name_generator.h>
+#include <toolbox/path.h>
+
+static void fuzzer_scene_save_name_text_input_callback(void* context) {
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventTextEditResult);
+}
+
+void fuzzer_scene_save_name_on_enter(void* context) {
+    PacsFuzzerApp* app = context;
+    TextInput* text_input = app->text_input;
+
+    name_generator_make_auto(app->key_name, KEY_NAME_SIZE, app->fuzzer_const->file_prefix);
+
+    text_input_set_header_text(text_input, "Name the key");
+    text_input_set_result_callback(
+        text_input,
+        fuzzer_scene_save_name_text_input_callback,
+        app,
+        app->key_name,
+        KEY_NAME_SIZE,
+        true);
+
+    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
+        app->fuzzer_const->path_key_folder, app->fuzzer_const->key_extension, app->key_name);
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDTextInput);
+}
+
+bool fuzzer_scene_save_name_on_event(void* context, SceneManagerEvent event) {
+    PacsFuzzerApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == FuzzerCustomEventTextEditResult) {
+            consumed = true;
+            furi_string_printf(
+                app->file_path,
+                "%s/%s%s",
+                app->fuzzer_const->path_key_folder,
+                app->key_name,
+                app->fuzzer_const->key_extension);
+
+            if(fuzzer_worker_save_key(app->worker, furi_string_get_cstr(app->file_path))) {
+                scene_manager_next_scene(app->scene_manager, FuzzerSceneSaveSuccess);
+            } else {
+                scene_manager_previous_scene(app->scene_manager);
+            }
+        }
+    }
+
+    return consumed;
+}
+
+void fuzzer_scene_save_name_on_exit(void* context) {
+    PacsFuzzerApp* app = context;
+    TextInput* text_input = app->text_input;
+
+    void* validator_context = text_input_get_validator_callback_context(text_input);
+    text_input_set_validator(text_input, NULL, NULL);
+    validator_is_file_free((ValidatorIsFile*)validator_context);
+
+    text_input_reset(text_input);
+}

+ 44 - 0
multi_fuzzer/scenes/fuzzer_scene_save_success.c

@@ -0,0 +1,44 @@
+#include "../fuzzer_i.h"
+
+static void fuzzer_scene_save_popup_timeout_callback(void* context) {
+    PacsFuzzerApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventPopupClosed);
+}
+
+void fuzzer_scene_save_success_on_enter(void* context) {
+    PacsFuzzerApp* app = context;
+    Popup* popup = app->popup;
+
+    popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
+    popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, fuzzer_scene_save_popup_timeout_callback);
+    popup_set_timeout(popup, 1500);
+    popup_enable_timeout(popup);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, FuzzerViewIDPopup);
+}
+
+bool fuzzer_scene_save_success_on_event(void* context, SceneManagerEvent event) {
+    PacsFuzzerApp* app = context;
+    bool consumed = false;
+
+    if((event.type == SceneManagerEventTypeBack) ||
+       ((event.type == SceneManagerEventTypeCustom) &&
+        (event.event == FuzzerCustomEventPopupClosed))) {
+        bool result = scene_manager_search_and_switch_to_previous_scene(
+            app->scene_manager, FuzzerSceneAttack);
+        if(!result) {
+            scene_manager_search_and_switch_to_previous_scene(app->scene_manager, FuzzerSceneMain);
+        }
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void fuzzer_scene_save_success_on_exit(void* context) {
+    PacsFuzzerApp* app = context;
+
+    popup_reset(app->popup);
+}

+ 44 - 0
multi_fuzzer/todo.md

@@ -0,0 +1,44 @@
+## Working Improvement
+
+#### Quality of life
+
+- [x] Make the "Load File" independent of the current protocol
+- [x] Add pause
+    - [x] Switching  UIDs if possible
+- [x] Led and sound Notification
+    - [x] Led
+    - [x] Vibro
+    - [ ] Sound?
+- [x] Error Notification
+    - [x] Custom UIDs dict loading 
+    - [x] Key file loading
+    - [ ] Anything else
+
+#### App functionality
+
+- [x] Add `BFCustomerID` attack
+    - [x] Add the ability to select index
+- [x] Save key logic
+
+## Code Improvement
+
+- [ ] GUI
+    - [x] Rewrite `gui_const` logic
+    - [x] Icon in dialog
+    - [x] Description and buttons in `field_editor` view
+    - [ ] Protocol carousel in `main_menu`
+        - [x] prototype 
+    - [x] Add the ability to edit emulation time and downtime separately
+        - [x] Decide on the display
+- [x] UID
+    - [x] Simplify the storage and exchange of `uids.data` `uid.data_size` in `views`
+    - [x] Using `FuzzerPayload` to store the uid
+    - [x] `UID_MAX_SIZE`
+- [x] Add pause
+    - [x] Fix `Custom dict` attack when ended
+- [x] Pause V2
+    - [x] Save logic
+    - [x] Switching  UIDs if possible
+- [ ] Worker
+    - [ ] Use `prtocol_id` instead of protocol name
+    - [x] this can be simplified `fuzzer_proto_items`

+ 495 - 0
multi_fuzzer/views/attack.c

@@ -0,0 +1,495 @@
+#include "attack.h"
+#include "../fuzzer_i.h"
+
+#include <input/input.h>
+#include <gui/elements.h>
+
+#define ATTACK_SCENE_MAX_UID_LENGTH 25
+#define UID_MAX_DISPLAYED_LEN (8U)
+#define LEFT_RIGHT_OFFSET (3U)
+
+#define LINE_1_Y (12U)
+#define LINE_2_Y (24U)
+#define LINE_3_Y (36U)
+#define LINE_4_Y (48U)
+
+struct FuzzerViewAttack {
+    View* view;
+    FuzzerViewAttackCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t time_delay; // 1 = 100ms
+    uint8_t time_delay_min; // 1 = 100ms
+    uint8_t emu_time; // 1 = 100ms
+    uint8_t emu_time_min; // 1 = 100ms
+    bool td_emt_cursor; // false - time_delay, true - emu_time
+    const char* attack_name;
+    const char* protocol_name;
+    FuzzerAttackState attack_state;
+    FuriString* uid_str;
+} FuzzerViewAttackModel;
+
+void fuzzer_view_attack_reset_data(
+    FuzzerViewAttack* view,
+    const char* attack_name,
+    const char* protocol_name) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        {
+            model->attack_name = attack_name;
+            model->protocol_name = protocol_name;
+            model->attack_state = FuzzerAttackStateIdle;
+            furi_string_set_str(model->uid_str, "Not_set");
+        },
+        true);
+}
+
+void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid) {
+    furi_assert(view);
+    furi_assert(uid->data);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        {
+            furi_string_printf(model->uid_str, "%02X", uid->data[0]);
+            for(uint8_t i = 1; i < uid->data_size; i++) {
+                furi_string_cat_printf(model->uid_str, ":%02X", uid->data[i]);
+            }
+        },
+        true);
+}
+
+void fuzzer_view_update_state(FuzzerViewAttack* view, FuzzerAttackState state) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view, FuzzerViewAttackModel * model, { model->attack_state = state; }, true);
+}
+
+void fuzzer_view_attack_set_callback(
+    FuzzerViewAttack* view_attack,
+    FuzzerViewAttackCallback callback,
+    void* context) {
+    furi_assert(view_attack);
+
+    view_attack->callback = callback;
+    view_attack->context = context;
+}
+
+static void
+    fuzzer_view_attack_draw_time_delays_line(Canvas* canvas, FuzzerViewAttackModel* model) {
+    char temp_str[25];
+    uint16_t crt;
+    const uint16_t y = LINE_2_Y;
+
+    canvas_set_font(canvas, FontPrimary);
+
+    if(!model->td_emt_cursor) {
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(temp_str, sizeof(temp_str), "Time delay:");
+        canvas_draw_str_aligned(canvas, LEFT_RIGHT_OFFSET, y, AlignLeft, AlignBottom, temp_str);
+        crt = canvas_string_width(canvas, temp_str);
+
+        canvas_set_font(canvas, FontPrimary);
+        snprintf(
+            temp_str, sizeof(temp_str), "%d.%d", model->time_delay / 10, model->time_delay % 10);
+        canvas_draw_str_aligned(
+            canvas, crt + LEFT_RIGHT_OFFSET + 3, y, AlignLeft, AlignBottom, temp_str);
+
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            temp_str, sizeof(temp_str), "EmT: %d.%d", model->emu_time / 10, model->emu_time % 10);
+        canvas_draw_str_aligned(
+            canvas, 128 - LEFT_RIGHT_OFFSET, y, AlignRight, AlignBottom, temp_str);
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(
+            temp_str,
+            sizeof(temp_str),
+            "TD: %d.%d",
+            model->time_delay / 10,
+            model->time_delay % 10);
+
+        canvas_draw_str_aligned(canvas, LEFT_RIGHT_OFFSET, y, AlignLeft, AlignBottom, temp_str);
+
+        canvas_set_font(canvas, FontPrimary);
+        snprintf(temp_str, sizeof(temp_str), "%d.%d", model->emu_time / 10, model->emu_time % 10);
+        canvas_draw_str_aligned(
+            canvas, 128 - LEFT_RIGHT_OFFSET, y, AlignRight, AlignBottom, temp_str);
+        crt = canvas_string_width(canvas, temp_str);
+
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(temp_str, sizeof(temp_str), "Emulation time:");
+        canvas_draw_str_aligned(
+            canvas, 128 - LEFT_RIGHT_OFFSET - crt - 3, y, AlignRight, AlignBottom, temp_str);
+    }
+}
+
+static void fuzzer_view_attack_draw_time_delays_str(Canvas* canvas, FuzzerViewAttackModel* model) {
+    char temp_str[20];
+    uint16_t crt;
+    const uint16_t y = LINE_2_Y;
+
+    canvas_set_font(canvas, FontSecondary);
+    snprintf(
+        temp_str,
+        sizeof(temp_str),
+        "TD: %d.%d Emt: %d.%d",
+        model->time_delay / 10,
+        model->time_delay % 10,
+        model->emu_time / 10,
+        model->emu_time % 10);
+
+    crt = canvas_string_width(canvas, temp_str);
+
+    canvas_draw_str_aligned(
+        canvas, 128 - LEFT_RIGHT_OFFSET - crt, y, AlignLeft, AlignBottom, temp_str);
+}
+
+static void fuzzer_view_attack_draw_idle(Canvas* canvas, FuzzerViewAttackModel* model) {
+    if(model->td_emt_cursor) {
+        elements_button_center(canvas, "Start");
+        elements_button_left(canvas, "EmT -");
+        elements_button_right(canvas, "+ EmT");
+    } else {
+        elements_button_center(canvas, "Start");
+        elements_button_left(canvas, "TD -");
+        elements_button_right(canvas, "+ TD");
+    }
+}
+
+static void fuzzer_view_attack_draw_running(Canvas* canvas, FuzzerViewAttackModel* model) {
+    UNUSED(model);
+    elements_button_left(canvas, "Stop");
+    elements_button_center(canvas, "Pause");
+}
+
+static void fuzzer_view_attack_draw_end(Canvas* canvas, FuzzerViewAttackModel* model) {
+    UNUSED(model);
+    // elements_button_center(canvas, "Restart"); // Reset
+    elements_button_left(canvas, "Exit");
+}
+
+void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    // Header - Attack name
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, LINE_1_Y, AlignCenter, AlignBottom, model->attack_name);
+
+    // Time delays line or Status line
+    switch(model->attack_state) {
+    case FuzzerAttackStateIdle:
+        fuzzer_view_attack_draw_time_delays_line(canvas, model);
+        break;
+
+    case FuzzerAttackStateAttacking:
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, LEFT_RIGHT_OFFSET, LINE_2_Y, "Attacking");
+        fuzzer_view_attack_draw_time_delays_str(canvas, model);
+
+        break;
+
+    case FuzzerAttackStateEmulating:
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 64, LINE_2_Y, AlignCenter, AlignBottom, "Emulating:");
+
+        break;
+
+    case FuzzerAttackStatePause:
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, LEFT_RIGHT_OFFSET, LINE_2_Y, "Paused");
+
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_icon_ex(canvas, 62, LINE_2_Y - 9, &I_Pin_arrow_up_7x9, IconRotation180);
+        canvas_draw_icon(canvas, 69, LINE_2_Y - 9, &I_Pin_arrow_up_7x9);
+        canvas_draw_str(canvas, 79, LINE_2_Y, "Change UID");
+        break;
+
+    case FuzzerAttackStateEnd:
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 64, LINE_2_Y, AlignCenter, AlignBottom, "Attack is over");
+
+        break;
+
+    default:
+        break;
+    }
+
+    // Protocol name
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, LINE_3_Y, AlignCenter, AlignBottom, model->protocol_name);
+
+    // Current UID
+    canvas_set_font(canvas, FontPrimary);
+    if(128 < canvas_string_width(canvas, furi_string_get_cstr(model->uid_str))) {
+        canvas_set_font(canvas, FontSecondary);
+    }
+    canvas_draw_str_aligned(
+        canvas, 64, LINE_4_Y, AlignCenter, AlignBottom, furi_string_get_cstr(model->uid_str));
+
+    // Btns
+    canvas_set_font(canvas, FontSecondary);
+    if(model->attack_state == FuzzerAttackStateAttacking ||
+       model->attack_state == FuzzerAttackStateEmulating) {
+        fuzzer_view_attack_draw_running(canvas, model);
+    } else if(model->attack_state == FuzzerAttackStateIdle) {
+        fuzzer_view_attack_draw_idle(canvas, model);
+    } else if(model->attack_state == FuzzerAttackStatePause) {
+        elements_button_left(canvas, "Back");
+        elements_button_right(canvas, "Save");
+        elements_button_center(canvas, "Emu");
+    } else if(model->attack_state == FuzzerAttackStateEnd) {
+        fuzzer_view_attack_draw_end(canvas, model);
+    }
+}
+
+static bool fuzzer_view_attack_input_idle(
+    FuzzerViewAttack* view_attack,
+    InputEvent* event,
+    FuzzerViewAttackModel* model) {
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        view_attack->callback(FuzzerCustomEventViewAttackExit, view_attack->context);
+        return true;
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        view_attack->callback(FuzzerCustomEventViewAttackRunAttack, view_attack->context);
+        return true;
+    } else if(event->key == InputKeyLeft) {
+        if(!model->td_emt_cursor) {
+            // TimeDelay --
+            if(event->type == InputTypeShort) {
+                if(model->time_delay > model->time_delay_min) {
+                    model->time_delay--;
+                }
+            } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) {
+                if((model->time_delay - 10) >= model->time_delay_min) {
+                    model->time_delay -= 10;
+                } else {
+                    model->time_delay = model->time_delay_min;
+                }
+            }
+        } else {
+            // EmuTime --
+            if(event->type == InputTypeShort) {
+                if(model->emu_time > model->emu_time_min) {
+                    model->emu_time--;
+                }
+            } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) {
+                if((model->emu_time - 10) >= model->emu_time_min) {
+                    model->emu_time -= 10;
+                } else {
+                    model->emu_time = model->emu_time_min;
+                }
+            }
+        }
+        return true;
+    } else if(event->key == InputKeyRight) {
+        if(!model->td_emt_cursor) {
+            // TimeDelay ++
+            if(event->type == InputTypeShort) {
+                if(model->time_delay < FUZZ_TIME_DELAY_MAX) {
+                    model->time_delay++;
+                }
+            } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) {
+                model->time_delay += 10;
+                if(model->time_delay > FUZZ_TIME_DELAY_MAX) {
+                    model->time_delay = FUZZ_TIME_DELAY_MAX;
+                }
+            }
+        } else {
+            // EmuTime ++
+            if(event->type == InputTypeShort) {
+                if(model->emu_time < FUZZ_TIME_DELAY_MAX) {
+                    model->emu_time++;
+                }
+            } else if(event->type == InputTypeLong || event->type == InputTypeRepeat) {
+                model->emu_time += 10;
+                if(model->emu_time > FUZZ_TIME_DELAY_MAX) {
+                    model->emu_time = FUZZ_TIME_DELAY_MAX;
+                }
+            }
+        }
+        return true;
+    } else if(
+        (event->key == InputKeyUp || event->key == InputKeyDown) &&
+        event->type == InputTypeShort) {
+        with_view_model(
+            view_attack->view,
+            FuzzerViewAttackModel * model,
+            { model->td_emt_cursor = !model->td_emt_cursor; },
+            true);
+        return true;
+    }
+    return true;
+}
+
+static bool fuzzer_view_attack_input_end(
+    FuzzerViewAttack* view_attack,
+    InputEvent* event,
+    FuzzerViewAttackModel* model) {
+    UNUSED(model);
+    if((event->key == InputKeyBack || event->key == InputKeyLeft) &&
+       event->type == InputTypeShort) {
+        // Exit if Ended
+        view_attack->callback(FuzzerCustomEventViewAttackExit, view_attack->context);
+    }
+    return true;
+}
+
+bool fuzzer_view_attack_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FuzzerViewAttack* view_attack = context;
+
+    // if(event->key == InputKeyBack && event->type == InputTypeShort) {
+    //     view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context);
+    //     return true;
+    // } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+    //     view_attack->callback(FuzzerCustomEventViewAttackOk, view_attack->context);
+    //     return true;
+    // } else
+    // {
+    with_view_model(
+        view_attack->view,
+        FuzzerViewAttackModel * model,
+        {
+            switch(model->attack_state) {
+            case FuzzerAttackStateIdle:
+                fuzzer_view_attack_input_idle(view_attack, event, model);
+                break;
+
+            case FuzzerAttackStateEnd:
+                fuzzer_view_attack_input_end(view_attack, event, model);
+                break;
+
+            case FuzzerAttackStateAttacking:
+            case FuzzerAttackStateEmulating:
+                if((event->key == InputKeyBack || event->key == InputKeyLeft) &&
+                   event->type == InputTypeShort) {
+                    view_attack->callback(FuzzerCustomEventViewAttackIdle, view_attack->context);
+                } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+                    view_attack->callback(FuzzerCustomEventViewAttackPause, view_attack->context);
+                }
+                break;
+
+            case FuzzerAttackStatePause:
+                if((event->key == InputKeyBack || event->key == InputKeyLeft) &&
+                   event->type == InputTypeShort) {
+                    view_attack->callback(FuzzerCustomEventViewAttackIdle, view_attack->context);
+                } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+                    view_attack->callback(FuzzerCustomEventViewAttackSave, view_attack->context);
+                } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+                    view_attack->callback(
+                        FuzzerCustomEventViewAttackEmulateCurrent, view_attack->context);
+                } else if(event->key == InputKeyUp && event->type == InputTypeShort) {
+                    view_attack->callback(
+                        FuzzerCustomEventViewAttackPrevUid, view_attack->context);
+                } else if(event->key == InputKeyDown && event->type == InputTypeShort) {
+                    view_attack->callback(
+                        FuzzerCustomEventViewAttackNextUid, view_attack->context);
+                }
+                break;
+
+            default:
+                break;
+            }
+        },
+        true);
+    // }
+
+    return true;
+}
+
+void fuzzer_view_attack_enter(void* context) {
+    furi_assert(context);
+}
+
+void fuzzer_view_attack_exit(void* context) {
+    furi_assert(context);
+    FuzzerViewAttack* view_attack = context;
+    with_view_model(
+        view_attack->view, FuzzerViewAttackModel * model, { model->td_emt_cursor = false; }, true);
+}
+
+FuzzerViewAttack* fuzzer_view_attack_alloc() {
+    if(fuzzer_proto_get_max_data_size() > UID_MAX_DISPLAYED_LEN) {
+        furi_crash("Maximum of displayed bytes exceeded");
+    }
+
+    FuzzerViewAttack* view_attack = malloc(sizeof(FuzzerViewAttack));
+
+    // View allocation and configuration
+    view_attack->view = view_alloc();
+    view_allocate_model(view_attack->view, ViewModelTypeLocking, sizeof(FuzzerViewAttackModel));
+    view_set_context(view_attack->view, view_attack);
+    view_set_draw_callback(view_attack->view, (ViewDrawCallback)fuzzer_view_attack_draw);
+    view_set_input_callback(view_attack->view, fuzzer_view_attack_input);
+    view_set_enter_callback(view_attack->view, fuzzer_view_attack_enter);
+    view_set_exit_callback(view_attack->view, fuzzer_view_attack_exit);
+
+    with_view_model(
+        view_attack->view,
+        FuzzerViewAttackModel * model,
+        {
+            model->time_delay = fuzzer_proto_get_def_idle_time();
+            model->time_delay_min = 0; // model->time_delay;
+
+            model->emu_time = fuzzer_proto_get_def_emu_time();
+
+            model->emu_time_min = 2; // model->emu_time;
+
+            model->uid_str = furi_string_alloc_set_str("Not_set");
+            // malloc(ATTACK_SCENE_MAX_UID_LENGTH + 1);
+            model->attack_state = FuzzerAttackStateOff;
+            model->td_emt_cursor = false;
+
+            // strcpy(model->uid_str, "Not_set");
+            model->attack_name = "Not_set";
+            model->protocol_name = "Not_set";
+        },
+        true);
+    return view_attack;
+}
+
+void fuzzer_view_attack_free(FuzzerViewAttack* view_attack) {
+    furi_assert(view_attack);
+
+    with_view_model(
+        view_attack->view,
+        FuzzerViewAttackModel * model,
+        { furi_string_free(model->uid_str); },
+        true);
+    view_free(view_attack->view);
+    free(view_attack);
+}
+
+View* fuzzer_view_attack_get_view(FuzzerViewAttack* view_attack) {
+    furi_assert(view_attack);
+    return view_attack->view;
+}
+
+uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view) {
+    furi_assert(view);
+    uint8_t time_delay;
+
+    with_view_model(
+        view->view, FuzzerViewAttackModel * model, { time_delay = model->time_delay; }, false);
+
+    return time_delay;
+}
+
+uint8_t fuzzer_view_attack_get_emu_time(FuzzerViewAttack* view) {
+    furi_assert(view);
+    uint8_t emu_time;
+
+    with_view_model(
+        view->view, FuzzerViewAttackModel * model, { emu_time = model->emu_time; }, false);
+
+    return emu_time;
+}

+ 36 - 0
multi_fuzzer/views/attack.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include <gui/view.h>
+
+#include "../helpers/fuzzer_custom_event.h"
+#include "../helpers/fuzzer_types.h"
+
+#include "../lib/worker/protocol.h"
+
+typedef struct FuzzerViewAttack FuzzerViewAttack;
+
+typedef void (*FuzzerViewAttackCallback)(FuzzerCustomEvent event, void* context);
+
+void fuzzer_view_attack_set_callback(
+    FuzzerViewAttack* view_attack,
+    FuzzerViewAttackCallback callback,
+    void* context);
+
+FuzzerViewAttack* fuzzer_view_attack_alloc();
+
+void fuzzer_view_attack_free(FuzzerViewAttack* view_attack);
+
+View* fuzzer_view_attack_get_view(FuzzerViewAttack* view_attack);
+
+void fuzzer_view_attack_reset_data(
+    FuzzerViewAttack* view,
+    const char* attack_name,
+    const char* protocol_name);
+
+void fuzzer_view_attack_set_uid(FuzzerViewAttack* view, const FuzzerPayload* uid);
+
+void fuzzer_view_update_state(FuzzerViewAttack* view, FuzzerAttackState state);
+
+uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view);
+
+uint8_t fuzzer_view_attack_get_emu_time(FuzzerViewAttack* view);

+ 358 - 0
multi_fuzzer/views/field_editor.c

@@ -0,0 +1,358 @@
+#include "field_editor.h"
+#include "../fuzzer_i.h"
+
+#include <input/input.h>
+#include <gui/elements.h>
+#include <toolbox/hex.h>
+
+#define FIELD_EDITOR_V2
+
+#define GUI_DISPLAY_WIDTH 128
+#define GUI_DISPLAY_HEIGHT 64
+
+#define GUI_DISPLAY_HORIZONTAL_CENTER 64
+#define GUI_DISPLAY_VERTICAL_CENTER 32
+
+#define UID_STR_LENGTH 25
+
+#ifdef FIELD_EDITOR_V2
+#define EDITOR_STRING_Y 38
+#else
+#define EDITOR_STRING_Y 50
+#endif
+
+struct FuzzerViewFieldEditor {
+    View* view;
+    FuzzerViewFieldEditorCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t* uid;
+    uint8_t uid_size;
+
+    FuriString* uid_str;
+
+    uint8_t index;
+    bool lo;
+    bool allow_edit;
+} FuzzerViewFieldEditorModel;
+
+void fuzzer_view_field_editor_set_callback(
+    FuzzerViewFieldEditor* view_edit,
+    FuzzerViewFieldEditorCallback callback,
+    void* context) {
+    furi_assert(view_edit);
+
+    view_edit->callback = callback;
+    view_edit->context = context;
+}
+
+void fuzzer_view_field_editor_reset_data(
+    FuzzerViewFieldEditor* view_edit,
+    const FuzzerPayload* new_uid,
+    bool allow_edit) {
+    furi_assert(view_edit);
+    furi_assert(new_uid->data);
+
+    with_view_model(
+        view_edit->view,
+        FuzzerViewFieldEditorModel * model,
+        {
+            memcpy(model->uid, new_uid->data, new_uid->data_size);
+            model->index = 0;
+            model->lo = false;
+            model->uid_size = new_uid->data_size;
+            model->allow_edit = allow_edit;
+        },
+        true);
+}
+
+void fuzzer_view_field_editor_get_uid(FuzzerViewFieldEditor* view_edit, FuzzerPayload* output_uid) {
+    furi_assert(view_edit);
+    furi_assert(output_uid);
+    with_view_model(
+        view_edit->view,
+        FuzzerViewFieldEditorModel * model,
+        {
+            output_uid->data_size = model->uid_size;
+            memcpy(output_uid->data, model->uid, model->uid_size);
+        },
+        true);
+}
+
+uint8_t fuzzer_view_field_editor_get_index(FuzzerViewFieldEditor* view_edit) {
+    furi_assert(view_edit);
+    uint8_t index;
+    with_view_model(
+        view_edit->view, FuzzerViewFieldEditorModel * model, { index = model->index; }, true);
+    return index;
+}
+
+void fuzzer_view_field_editor_draw(Canvas* canvas, FuzzerViewFieldEditorModel* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+#ifdef FIELD_EDITOR_V2
+
+    canvas_set_font(canvas, FontSecondary);
+    if(model->allow_edit) {
+        canvas_draw_icon(canvas, 2, 4, &I_ButtonLeft_4x7);
+        canvas_draw_icon(canvas, 8, 4, &I_ButtonRight_4x7);
+
+        canvas_draw_icon_ex(canvas, 62, 3, &I_Pin_arrow_up_7x9, IconRotation180);
+        canvas_draw_icon(canvas, 69, 3, &I_Pin_arrow_up_7x9);
+
+        canvas_draw_str(canvas, 14, 10, "select byte");
+        canvas_draw_str(canvas, 79, 10, "adjust byte");
+    } else {
+        canvas_draw_icon(canvas, 35, 4, &I_ButtonLeft_4x7);
+        canvas_draw_icon(canvas, 41, 4, &I_ButtonRight_4x7);
+        canvas_draw_str(canvas, 49, 10, "select byte");
+    }
+
+    char msg_index[18];
+    canvas_set_font(canvas, FontPrimary);
+    snprintf(msg_index, sizeof(msg_index), "Field index : %d", model->index);
+
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_HORIZONTAL_CENTER, 24, AlignCenter, AlignBottom, msg_index);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_icon(canvas, 4, 52, &I_Pin_back_arrow_10x8);
+    canvas_draw_icon(canvas, 85, 52, &I_Ok_btn_9x9);
+
+    canvas_draw_str(canvas, 16, 60, "Back");
+    canvas_draw_str(canvas, 96, 60, "Attack");
+#else
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_HORIZONTAL_CENTER,
+        5,
+        AlignCenter,
+        AlignTop,
+        "Left and right: select byte");
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_HORIZONTAL_CENTER,
+        15,
+        AlignCenter,
+        AlignTop,
+        "Up and down: adjust byte");
+
+    char msg_index[18];
+    canvas_set_font(canvas, FontPrimary);
+    snprintf(msg_index, sizeof(msg_index), "Field index : %d", model->index);
+    canvas_draw_str_aligned(
+        canvas, GUI_DISPLAY_HORIZONTAL_CENTER, 28, AlignCenter, AlignTop, msg_index);
+#endif
+    // ####### Editor #######
+    FuriString* temp_s = model->uid_str;
+    canvas_set_font(canvas, FontSecondary);
+
+    furi_string_reset(temp_s);
+    for(int i = -3; i != 0; i++) {
+        if(0 <= (model->index + i)) {
+            furi_string_cat_printf(temp_s, "%02X ", model->uid[model->index + i]);
+        }
+    }
+    canvas_draw_str_aligned(
+        canvas, 52, EDITOR_STRING_Y, AlignRight, AlignBottom, furi_string_get_cstr(temp_s));
+
+    furi_string_reset(temp_s);
+    for(int i = 1; i != 4; i++) {
+        if((model->index + i) < model->uid_size) {
+            furi_string_cat_printf(temp_s, " %02X", model->uid[model->index + i]);
+        }
+    }
+    canvas_draw_str_aligned(
+        canvas, 77, EDITOR_STRING_Y, AlignLeft, AlignBottom, furi_string_get_cstr(temp_s));
+
+    canvas_set_font(canvas, FontPrimary);
+
+    furi_string_reset(temp_s);
+    furi_string_cat_printf(temp_s, "<%02X>", model->uid[model->index]);
+    canvas_draw_str_aligned(
+        canvas,
+        GUI_DISPLAY_HORIZONTAL_CENTER,
+        EDITOR_STRING_Y,
+        AlignCenter,
+        AlignBottom,
+        furi_string_get_cstr(temp_s));
+
+    uint16_t w = canvas_string_width(canvas, furi_string_get_cstr(temp_s));
+    w -= 11; // '<' & '>'
+    w /= 2;
+
+    if(model->allow_edit) {
+        if(model->lo) {
+            canvas_draw_line(
+                canvas,
+                GUI_DISPLAY_HORIZONTAL_CENTER + 1,
+                EDITOR_STRING_Y + 2,
+                GUI_DISPLAY_HORIZONTAL_CENTER + w,
+                EDITOR_STRING_Y + 2);
+        } else {
+            canvas_draw_line(
+                canvas,
+                GUI_DISPLAY_HORIZONTAL_CENTER - w,
+                EDITOR_STRING_Y + 2,
+                GUI_DISPLAY_HORIZONTAL_CENTER - 1,
+                EDITOR_STRING_Y + 2);
+        }
+    } else {
+        // canvas_draw_line(
+        //     canvas,
+        //     GUI_DISPLAY_HORIZONTAL_CENTER - w,
+        //     EDITOR_STRING_Y + 2,
+        //     GUI_DISPLAY_HORIZONTAL_CENTER + w,
+        //     EDITOR_STRING_Y + 2);
+    }
+    // ####### Editor #######
+}
+
+bool fuzzer_view_field_editor_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FuzzerViewFieldEditor* view_edit = context;
+
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        view_edit->callback(FuzzerCustomEventViewFieldEditorBack, view_edit->context);
+        return true;
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        view_edit->callback(FuzzerCustomEventViewFieldEditorOk, view_edit->context);
+        return true;
+    } else if(event->key == InputKeyLeft) {
+        with_view_model(
+            view_edit->view,
+            FuzzerViewFieldEditorModel * model,
+            {
+                if(event->type == InputTypeShort) {
+                    if(!model->allow_edit) {
+                        model->lo = false;
+                    }
+                    if(model->index > 0 || model->lo) {
+                        if(!model->lo) {
+                            model->index--;
+                        }
+                        model->lo = !model->lo;
+                    }
+                } else if(event->type == InputTypeLong) {
+                    model->index = 0;
+                    model->lo = false;
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyRight) {
+        with_view_model(
+            view_edit->view,
+            FuzzerViewFieldEditorModel * model,
+            {
+                if(event->type == InputTypeShort) {
+                    if(!model->allow_edit) {
+                        model->lo = true;
+                    }
+                    if(model->index < (model->uid_size - 1) || !model->lo) {
+                        if(model->lo) {
+                            model->index++;
+                        }
+                        model->lo = !model->lo;
+                    }
+                } else if(event->type == InputTypeLong) {
+                    model->index = model->uid_size - 1;
+                    model->lo = true;
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyUp) {
+        with_view_model(
+            view_edit->view,
+            FuzzerViewFieldEditorModel * model,
+            {
+                if(event->type == InputTypeShort && model->allow_edit) {
+                    if(model->lo) {
+                        model->uid[model->index] = (model->uid[model->index] & 0xF0) |
+                                                   ((model->uid[model->index] + 1) & 0x0F);
+                    } else {
+                        model->uid[model->index] = ((model->uid[model->index] + 0x10) & 0xF0) |
+                                                   (model->uid[model->index] & 0x0F);
+                    }
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyDown) {
+        with_view_model(
+            view_edit->view,
+            FuzzerViewFieldEditorModel * model,
+            {
+                if(event->type == InputTypeShort && model->allow_edit) {
+                    if(model->lo) {
+                        model->uid[model->index] = (model->uid[model->index] & 0xF0) |
+                                                   ((model->uid[model->index] - 1) & 0x0F);
+                    } else {
+                        model->uid[model->index] = ((model->uid[model->index] - 0x10) & 0xF0) |
+                                                   (model->uid[model->index] & 0x0F);
+                    }
+                }
+            },
+            true);
+        return true;
+    }
+
+    return true;
+}
+
+void fuzzer_view_field_editor_enter(void* context) {
+    furi_assert(context);
+}
+
+void fuzzer_view_field_editor_exit(void* context) {
+    furi_assert(context);
+}
+
+FuzzerViewFieldEditor* fuzzer_view_field_editor_alloc() {
+    FuzzerViewFieldEditor* view_edit = malloc(sizeof(FuzzerViewFieldEditor));
+
+    // View allocation and configuration
+    view_edit->view = view_alloc();
+    view_allocate_model(view_edit->view, ViewModelTypeLocking, sizeof(FuzzerViewFieldEditorModel));
+    view_set_context(view_edit->view, view_edit);
+    view_set_draw_callback(view_edit->view, (ViewDrawCallback)fuzzer_view_field_editor_draw);
+    view_set_input_callback(view_edit->view, fuzzer_view_field_editor_input);
+    view_set_enter_callback(view_edit->view, fuzzer_view_field_editor_enter);
+    view_set_exit_callback(view_edit->view, fuzzer_view_field_editor_exit);
+
+    with_view_model(
+        view_edit->view,
+        FuzzerViewFieldEditorModel * model,
+        {
+            model->uid_str = furi_string_alloc();
+            model->uid = malloc(fuzzer_proto_get_max_data_size());
+        },
+        true);
+
+    return view_edit;
+}
+
+void fuzzer_view_field_editor_free(FuzzerViewFieldEditor* view_edit) {
+    furi_assert(view_edit);
+
+    with_view_model(
+        view_edit->view,
+        FuzzerViewFieldEditorModel * model,
+        {
+            furi_string_free(model->uid_str);
+            free(model->uid);
+        },
+        true);
+    view_free(view_edit->view);
+    free(view_edit);
+}
+
+View* fuzzer_view_field_editor_get_view(FuzzerViewFieldEditor* view_edit) {
+    furi_assert(view_edit);
+    return view_edit->view;
+}

+ 29 - 0
multi_fuzzer/views/field_editor.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/fuzzer_custom_event.h"
+#include "../lib/worker/protocol.h"
+
+typedef struct FuzzerViewFieldEditor FuzzerViewFieldEditor;
+
+typedef void (*FuzzerViewFieldEditorCallback)(FuzzerCustomEvent event, void* context);
+
+void fuzzer_view_field_editor_set_callback(
+    FuzzerViewFieldEditor* view_attack,
+    FuzzerViewFieldEditorCallback callback,
+    void* context);
+
+FuzzerViewFieldEditor* fuzzer_view_field_editor_alloc();
+
+void fuzzer_view_field_editor_free(FuzzerViewFieldEditor* view_attack);
+
+View* fuzzer_view_field_editor_get_view(FuzzerViewFieldEditor* view_attack);
+
+void fuzzer_view_field_editor_reset_data(
+    FuzzerViewFieldEditor* view_edit,
+    const FuzzerPayload* new_uid,
+    bool allow_edit);
+
+void fuzzer_view_field_editor_get_uid(FuzzerViewFieldEditor* view_edit, FuzzerPayload* output_uid);
+
+uint8_t fuzzer_view_field_editor_get_index(FuzzerViewFieldEditor* view_edit);

+ 235 - 0
multi_fuzzer/views/main_menu.c

@@ -0,0 +1,235 @@
+#include "main_menu.h"
+#include "../fuzzer_i.h"
+
+#include <input/input.h>
+
+#include "../lib/worker/protocol.h"
+
+#define PROTOCOL_NAME_Y 12
+// #define PROTOCOL_CAROUSEL
+
+struct FuzzerViewMain {
+    View* view;
+    FuzzerViewMainCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint8_t proto_index;
+    uint8_t menu_index;
+    uint8_t proto_max;
+    uint8_t menu_max;
+} FuzzerViewMainModel;
+
+void fuzzer_view_main_update_data(FuzzerViewMain* view, FuzzerState state) {
+    furi_assert(view);
+    with_view_model(
+        view->view,
+        FuzzerViewMainModel * model,
+        {
+            model->proto_index = state.proto_index;
+            model->menu_index = state.menu_index;
+        },
+        true);
+}
+
+void fuzzer_view_main_get_state(FuzzerViewMain* view, FuzzerState* state) {
+    furi_assert(view);
+    with_view_model(
+        view->view,
+        FuzzerViewMainModel * model,
+        {
+            state->proto_index = model->proto_index;
+            state->menu_index = model->menu_index;
+        },
+        true);
+}
+
+void fuzzer_view_main_set_callback(
+    FuzzerViewMain* view,
+    FuzzerViewMainCallback callback,
+    void* context) {
+    furi_assert(view);
+
+    view->callback = callback;
+    view->context = context;
+}
+
+void fuzzer_view_main_draw(Canvas* canvas, FuzzerViewMainModel* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    if(model->menu_index > 0) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas,
+            64,
+            24,
+            AlignCenter,
+            AlignTop,
+            fuzzer_proto_get_menu_label(model->menu_index - 1));
+    }
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(
+        canvas, 64, 36, AlignCenter, AlignTop, fuzzer_proto_get_menu_label(model->menu_index));
+
+    if(model->menu_index < (model->menu_max - 1)) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas,
+            64,
+            48,
+            AlignCenter,
+            AlignTop,
+            fuzzer_proto_get_menu_label(model->menu_index + 1));
+    }
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 27, PROTOCOL_NAME_Y, AlignCenter, AlignBottom, "<");
+    canvas_draw_str_aligned(
+        canvas,
+        64,
+        PROTOCOL_NAME_Y,
+        AlignCenter,
+        AlignBottom,
+        fuzzer_proto_get_name(model->proto_index));
+    canvas_draw_str_aligned(canvas, 101, PROTOCOL_NAME_Y, AlignCenter, AlignBottom, ">");
+
+#ifdef PROTOCOL_CAROUSEL
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(
+        canvas,
+        20,
+        PROTOCOL_NAME_Y,
+        AlignRight,
+        AlignBottom,
+        (model->proto_index > 0) ? fuzzer_proto_get_name(model->proto_index - 1) :
+                                   fuzzer_proto_get_name((model->proto_max - 1)));
+    canvas_draw_str_aligned(
+        canvas,
+        108,
+        PROTOCOL_NAME_Y,
+        AlignLeft,
+        AlignBottom,
+        (model->proto_index < (model->proto_max - 1)) ?
+            fuzzer_proto_get_name(model->proto_index + 1) :
+            fuzzer_proto_get_name(0));
+#endif
+}
+
+bool fuzzer_view_main_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    FuzzerViewMain* view = context;
+
+    if(event->key == InputKeyBack &&
+       (event->type == InputTypeLong || event->type == InputTypeShort)) {
+        view->callback(FuzzerCustomEventViewMainBack, view->context);
+        return true;
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        view->callback(FuzzerCustomEventViewMainOk, view->context);
+        return true;
+    } else if(event->key == InputKeyDown && event->type == InputTypeShort) {
+        with_view_model(
+            view->view,
+            FuzzerViewMainModel * model,
+            {
+                if(model->menu_index < (model->menu_max - 1)) {
+                    model->menu_index++;
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyUp && event->type == InputTypeShort) {
+        with_view_model(
+            view->view,
+            FuzzerViewMainModel * model,
+            {
+                if(model->menu_index != 0) {
+                    model->menu_index--;
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyLeft && event->type == InputTypeShort) {
+        with_view_model(
+            view->view,
+            FuzzerViewMainModel * model,
+            {
+                if(model->proto_index != 0) {
+                    model->proto_index--;
+                } else {
+                    model->proto_index = (model->proto_max - 1);
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+        with_view_model(
+            view->view,
+            FuzzerViewMainModel * model,
+            {
+                if(model->proto_index == (model->proto_max - 1)) {
+                    model->proto_index = 0;
+                } else {
+                    model->proto_index++;
+                }
+            },
+            true);
+        return true;
+    }
+
+    return true;
+}
+
+void fuzzer_view_main_enter(void* context) {
+    furi_assert(context);
+}
+
+void fuzzer_view_main_exit(void* context) {
+    furi_assert(context);
+}
+
+FuzzerViewMain* fuzzer_view_main_alloc() {
+    FuzzerViewMain* view = malloc(sizeof(FuzzerViewMain));
+
+    // View allocation and configuration
+    view->view = view_alloc();
+    view_allocate_model(view->view, ViewModelTypeLocking, sizeof(FuzzerViewMainModel));
+    view_set_context(view->view, view);
+    view_set_draw_callback(view->view, (ViewDrawCallback)fuzzer_view_main_draw);
+    view_set_input_callback(view->view, fuzzer_view_main_input);
+    view_set_enter_callback(view->view, fuzzer_view_main_enter);
+    view_set_exit_callback(view->view, fuzzer_view_main_exit);
+
+    with_view_model(
+        view->view,
+        FuzzerViewMainModel * model,
+        {
+            model->proto_index = 0;
+            model->proto_max = fuzzer_proto_get_count_of_protocols();
+            model->menu_index = 0;
+            model->menu_max = fuzzer_proto_get_count_of_menu_items();
+        },
+        true);
+    return view;
+}
+
+void fuzzer_view_main_free(FuzzerViewMain* view) {
+    furi_assert(view);
+
+    // with_view_model(
+    //     view->view,
+    //     FuzzerViewMainModel * model,
+    //     {
+
+    //     },
+    //     true);
+    view_free(view->view);
+    free(view);
+}
+
+View* fuzzer_view_main_get_view(FuzzerViewMain* view) {
+    furi_assert(view);
+    return view->view;
+}

+ 23 - 0
multi_fuzzer/views/main_menu.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/fuzzer_custom_event.h"
+#include "../helpers/fuzzer_types.h"
+
+typedef struct FuzzerViewMain FuzzerViewMain;
+
+typedef void (*FuzzerViewMainCallback)(FuzzerCustomEvent event, void* context);
+
+void fuzzer_view_main_set_callback(
+    FuzzerViewMain* fuzzer_view_main,
+    FuzzerViewMainCallback callback,
+    void* context);
+
+FuzzerViewMain* fuzzer_view_main_alloc();
+
+void fuzzer_view_main_free(FuzzerViewMain* view);
+
+View* fuzzer_view_main_get_view(FuzzerViewMain* view);
+
+void fuzzer_view_main_update_data(FuzzerViewMain* view, FuzzerState state);
+void fuzzer_view_main_get_state(FuzzerViewMain* view, FuzzerState* state);