Sfoglia il codice sorgente

Start as a uFBT project

gid9798 2 anni fa
parent
commit
aab8be301e

+ 4 - 0
.gitignore

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

+ 55 - 0
application.fam

@@ -0,0 +1,55 @@
+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",
+    fap_version=0.1,
+    fap_description="Fuzzer for ibutton readers",
+    fap_icon="icons/ibutt_10px.png",
+    fap_category="Tools",
+    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",
+    fap_version=0.1,
+    fap_description="Fuzzer for lfrfid readers",
+    fap_icon="icons/rfid_10px.png",
+    fap_category="Tools",
+    fap_private_libs=[
+        Lib(
+            name="worker",
+            cdefines=["RFID_125_PROTOCOL"],
+        ),
+    ],
+    fap_icon_assets="icons",
+    fap_icon_assets_symbol="fuzzer",
+)

+ 160 - 0
fuzzer.c

@@ -0,0 +1,160 @@
+#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));
+
+    // 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);
+
+    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/ibtnfuzzer",
+        .custom_dict_extension = ".txt",
+        .key_extension = ".ibtn",
+        .path_key_folder = "/ext/ibutton",
+        .key_icon = &I_ibutt_10px,
+    };
+    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/rfidfuzzer",
+        .custom_dict_extension = ".txt",
+        .key_extension = ".rfid",
+        .path_key_folder = "/ext/lfrfid",
+        .key_icon = &I_125_10px,
+    };
+    fuzzer_app->fuzzer_const = &app_const;
+
+    view_dispatcher_run(fuzzer_app->view_dispatcher);
+
+    fuzzer_app_free(fuzzer_app);
+    return 0;
+}

+ 55 - 0
fuzzer_i.h

@@ -0,0 +1,55 @@
+#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 <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"
+
+#define FUZZ_TIME_DELAY_MAX (80)
+
+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;
+} FuzzerConsts;
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notifications;
+
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+
+    Popup* popup;
+    DialogsApp* dialogs;
+    FuzzerViewMain* main_view;
+    FuzzerViewAttack* attack_view;
+    FuzzerViewFieldEditor* field_editor_view;
+
+    FuriString* file_path;
+
+    FuzzerState fuzzer_state;
+    FuzzerConsts* fuzzer_const;
+
+    FuzzerWorker* worker;
+    FuzzerPayload* payload;
+} PacsFuzzerApp;

+ 17 - 0
helpers/fuzzer_custom_event.h

@@ -0,0 +1,17 @@
+#pragma once
+
+typedef enum {
+
+    // FuzzerCustomEvent
+    FuzzerCustomEventViewMainBack = 100,
+    FuzzerCustomEventViewMainOk,
+    FuzzerCustomEventViewMainPopupErr,
+
+    FuzzerCustomEventViewAttackBack,
+    FuzzerCustomEventViewAttackOk,
+    // FuzzerCustomEventViewAttackTick, // now not use
+    FuzzerCustomEventViewAttackEnd,
+
+    FuzzerCustomEventViewFieldEditorBack,
+    FuzzerCustomEventViewFieldEditorOk,
+} FuzzerCustomEvent;

+ 30 - 0
helpers/fuzzer_types.h

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

BIN
icons/125_10px.png


BIN
icons/ButtonLeft_4x7.png


BIN
icons/ButtonRight_4x7.png


BIN
icons/Ok_btn_9x9.png


BIN
icons/Pin_arrow_up_7x9.png


BIN
icons/Pin_back_arrow_10x8.png


BIN
icons/ibutt_10px.png


BIN
icons/rfid_10px.png


+ 485 - 0
lib/worker/fake_worker.c

@@ -0,0 +1,485 @@
+#include "fake_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"
+
+#if defined(RFID_125_PROTOCOL)
+
+#include <lib/lfrfid/lfrfid_dict_file.h>
+#include <lib/lfrfid/lfrfid_worker.h>
+#include <lfrfid/protocols/lfrfid_protocols.h>
+
+#else
+
+#include <lib/ibutton/ibutton_worker.h>
+#include <lib/ibutton/ibutton_key.h>
+
+#endif
+
+#include <toolbox/stream/stream.h>
+
+struct FuzzerWorker {
+#if defined(RFID_125_PROTOCOL)
+    LFRFIDWorker* proto_worker;
+    ProtocolId protocol_id;
+    ProtocolDict* protocols_items;
+#else
+    iButtonWorker* proto_worker;
+    iButtonProtocolId protocol_id; // TODO
+    iButtonProtocols* protocols_items;
+    iButtonKey* key;
+#endif
+
+    const FuzzerProtocol* protocol;
+    FuzzerWorkerAttackType attack_type;
+    uint16_t timer_idle_time_ms;
+    uint16_t timer_emu_time_ms;
+
+    uint8_t payload[MAX_PAYLOAD_SIZE];
+    Stream* uids_stream;
+    uint16_t index;
+    uint8_t chusen_byte;
+
+    bool treead_running;
+    bool in_emu_phase;
+    FuriTimer* timer;
+
+    FuzzerWorkerUidChagedCallback tick_callback;
+    void* tick_context;
+
+    FuzzerWorkerEndCallback end_callback;
+    void* end_context;
+};
+
+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) {
+            instance->index++;
+        }
+        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;
+        }
+    }
+
+    break;
+
+    case FuzzerWorkerAttackTypeLoadFile:
+        if(instance->payload[instance->index] != 0xFF) {
+            instance->payload[instance->index]++;
+            res = true;
+        }
+
+        break;
+
+    default:
+        break;
+    }
+#if defined(RFID_125_PROTOCOL)
+    protocol_dict_set_data(
+        instance->protocols_items, instance->protocol_id, instance->payload, MAX_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);
+
+    //  TODO  check data.size logic
+    data.size = MAX_PAYLOAD_SIZE;
+    memcpy(data.ptr, instance->payload, MAX_PAYLOAD_SIZE); // data.size);
+#endif
+    return res;
+}
+
+static void fuzzer_worker_on_tick_callback(void* context) {
+    furi_assert(context);
+
+    FuzzerWorker* instance = context;
+
+    if(instance->in_emu_phase) {
+        if(instance->treead_running) {
+#if defined(RFID_125_PROTOCOL)
+            lfrfid_worker_stop(instance->proto_worker);
+#else
+            ibutton_worker_stop(instance->proto_worker);
+#endif
+        }
+        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 {
+            if(instance->treead_running) {
+#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
+            }
+            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);
+}
+
+static void fuzzer_worker_set_protocol(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) {
+    instance->protocol = &fuzzer_proto_items[protocol_index];
+
+#if defined(RFID_125_PROTOCOL)
+    instance->protocol_id =
+        protocol_dict_get_protocol_by_name(instance->protocols_items, instance->protocol->name);
+#else
+    // TODO iButtonProtocolIdInvalid check
+    instance->protocol_id =
+        ibutton_protocols_get_id_by_name(instance->protocols_items, instance->protocol->name);
+#endif
+}
+
+bool fuzzer_worker_init_attack_dict(FuzzerWorker* instance, FuzzerProtocolsID protocol_index) {
+    furi_assert(instance);
+
+    bool res = false;
+    fuzzer_worker_set_protocol(instance, protocol_index);
+
+    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;
+    fuzzer_worker_set_protocol(instance, protocol_index);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    instance->uids_stream = buffered_file_stream_alloc(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);
+        furi_record_close(RECORD_STORAGE);
+    } 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;
+    fuzzer_worker_set_protocol(instance, protocol_index);
+
+    instance->attack_type = FuzzerWorkerAttackTypeLoadFile;
+    instance->index = chusen;
+
+    memcpy(instance->payload, new_uid->data, instance->protocol->data_size);
+
+    res = true;
+
+    return res;
+}
+
+// TODO make it protocol independent
+bool fuzzer_worker_load_key_from_file(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    const char* filename) {
+    furi_assert(instance);
+
+    bool res = false;
+    fuzzer_worker_set_protocol(instance, protocol_index);
+
+#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 if(instance->protocol_id != loaded_proto_id) { // Err wrong protocol
+        FURI_LOG_W(TAG, "Wrong protocol");
+        FURI_LOG_W(
+            TAG,
+            "Selected: %s Loaded: %s",
+            instance->protocol->name,
+            protocol_dict_get_name(instance->protocols_items, loaded_proto_id));
+    } else {
+        protocol_dict_get_data(
+            instance->protocols_items, instance->protocol_id, instance->payload, MAX_PAYLOAD_SIZE);
+        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 {
+        if(instance->protocol_id != ibutton_key_get_protocol_id(instance->key)) {
+            // Err wrong protocol
+            FURI_LOG_W(TAG, "Wrong protocol");
+            FURI_LOG_W(
+                TAG,
+                "Selected: %s Loaded: %s",
+                instance->protocol->name,
+                ibutton_protocols_get_name(
+                    instance->protocols_items, ibutton_key_get_protocol_id(instance->key)));
+        } else {
+            iButtonEditableData data;
+            ibutton_protocols_get_editable_data(instance->protocols_items, instance->key, &data);
+            memcpy(instance->payload, data.ptr, data.size);
+            res = true;
+        }
+    }
+#endif
+
+    return res;
+}
+
+FuzzerWorker* fuzzer_worker_alloc() {
+    FuzzerWorker* instance = malloc(sizeof(FuzzerWorker));
+
+#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
+    instance->attack_type = FuzzerWorkerAttackTypeMax;
+    instance->index = 0;
+    instance->treead_running = false;
+    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);
+
+#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);
+}
+
+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);
+
+        if(!instance->treead_running) {
+#if defined(RFID_125_PROTOCOL)
+            lfrfid_worker_start_thread(instance->proto_worker);
+#else
+            ibutton_worker_start_thread(instance->proto_worker);
+#endif
+            FURI_LOG_D(TAG, "Worker Starting");
+            instance->treead_running = true;
+        } else {
+            FURI_LOG_D(TAG, "Worker UnPaused");
+        }
+
+#if defined(RFID_125_PROTOCOL)
+        // lfrfid_worker_start_thread(instance->proto_worker);
+        lfrfid_worker_emulate_start(instance->proto_worker, instance->protocol_id);
+#else
+        // ibutton_worker_start_thread(instance->proto_worker);
+        ibutton_worker_emulate_start(instance->proto_worker, instance->key);
+#endif
+        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_pause(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    furi_timer_stop(instance->timer);
+
+    if(instance->treead_running) {
+#if defined(RFID_125_PROTOCOL)
+        lfrfid_worker_stop(instance->proto_worker);
+#else
+        ibutton_worker_stop(instance->proto_worker);
+#endif
+        FURI_LOG_D(TAG, "Worker Paused");
+    }
+}
+
+void fuzzer_worker_stop(FuzzerWorker* instance) {
+    furi_assert(instance);
+
+    furi_timer_stop(instance->timer);
+
+    if(instance->treead_running) {
+#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
+        FURI_LOG_D(TAG, "Worker Stopping");
+        instance->treead_running = false;
+    }
+
+    if(instance->attack_type == FuzzerWorkerAttackTypeLoadFileCustomUids) {
+        buffered_file_stream_close(instance->uids_stream);
+        furi_record_close(RECORD_STORAGE);
+        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;
+}

+ 138 - 0
lib/worker/fake_worker.h

@@ -0,0 +1,138 @@
+#pragma once
+
+#include <furi.h>
+
+#include "protocol.h"
+
+typedef enum {
+    FuzzerWorkerAttackTypeDefaultDict = 0,
+    FuzzerWorkerAttackTypeLoadFile,
+    FuzzerWorkerAttackTypeLoadFileCustomUids,
+
+    FuzzerWorkerAttackTypeMax,
+} FuzzerWorkerAttackType;
+
+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);
+
+/**
+ * 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);
+
+/**
+ * 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
+ */
+bool fuzzer_worker_load_key_from_file(
+    FuzzerWorker* instance,
+    FuzzerProtocolsID protocol_index,
+    const char* filename);
+
+/**
+ * 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);

+ 291 - 0
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);
+}

+ 89 - 0
lib/worker/protocol.h

@@ -0,0 +1,89 @@
+#pragma once
+
+#include <stdint.h>
+
+// #define RFID_125_PROTOCOL
+
+typedef struct FuzzerPayload FuzzerPayload;
+
+typedef enum {
+#if defined(RFID_125_PROTOCOL)
+    EM4100,
+    HIDProx,
+    PAC,
+    H10301,
+#else
+    DS1990,
+    Metakom,
+    Cyfral,
+#endif
+} 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();
+
+// TODO add description
+uint8_t fuzzer_proto_get_def_emu_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();

+ 43 - 0
lib/worker/protocol_i.h

@@ -0,0 +1,43 @@
+#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
+#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
+#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
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
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

+ 162 - 0
scenes/fuzzer_scene_attack.c

@@ -0,0 +1,162 @@
+#include "../fuzzer_i.h"
+#include "../helpers/fuzzer_custom_event.h"
+
+const NotificationSequence sequence_one_green_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);
+        fuzzer_view_attack_pause(app->attack_view);
+        break;
+
+    case FuzzerAttackStateRunning:
+        notification_message(app->notifications, &sequence_blink_start_blue);
+        fuzzer_view_attack_start(app->attack_view);
+        break;
+
+    case FuzzerAttackStateEnd:
+        notification_message(app->notifications, &sequence_blink_stop);
+        notification_message(app->notifications, &sequence_single_vibro);
+        fuzzer_view_attack_end(app->attack_view);
+        break;
+
+    case FuzzerAttackStateOff:
+        notification_message(app->notifications, &sequence_blink_stop);
+        fuzzer_view_attack_stop(app->attack_view);
+        break;
+    }
+}
+
+void fuzzer_scene_attack_worker_tick_callback(void* context) {
+    furi_assert(context);
+    PacsFuzzerApp* app = context;
+
+    notification_message(app->notifications, &sequence_one_green_50_on_blink_blue);
+    fuzzer_scene_attack_update_uid(app);
+
+    // view_dispatcher_send_custom_event(app->view_dispatcher, FuzzerCustomEventViewAttackTick);
+}
+
+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 == FuzzerCustomEventViewAttackBack) {
+            if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) ==
+               FuzzerAttackStateRunning) {
+                // Pause if attack running
+                fuzzer_worker_pause(app->worker);
+
+                fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle);
+            } else {
+                // 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);
+                }
+            }
+            consumed = true;
+        } else if(event.event == FuzzerCustomEventViewAttackOk) {
+            if(scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) ==
+               FuzzerAttackStateIdle) {
+                // Start or Continue Attack
+                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, FuzzerAttackStateRunning);
+                } else {
+                    // Error?
+                }
+            } else if(
+                scene_manager_get_scene_state(app->scene_manager, FuzzerSceneAttack) ==
+                FuzzerAttackStateRunning) {
+                // Pause if attack running
+                fuzzer_worker_pause(app->worker);
+
+                fuzzer_scene_attack_set_state(app, FuzzerAttackStateIdle);
+            }
+            consumed = true;
+            // } else if(event.event == FuzzerCustomEventViewAttackTick) {
+            //     consumed = true;
+        } 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);
+}

+ 3 - 0
scenes/fuzzer_scene_config.h

@@ -0,0 +1,3 @@
+ADD_SCENE(fuzzer, main, Main)
+ADD_SCENE(fuzzer, attack, Attack)
+ADD_SCENE(fuzzer, field_editor, FieldEditor)

+ 66 - 0
scenes/fuzzer_scene_field_editor.c

@@ -0,0 +1,66 @@
+#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:
+        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);
+}

+ 189 - 0
scenes/fuzzer_scene_main.c

@@ -0,0 +1,189 @@
+#include "../fuzzer_i.h"
+#include "../helpers/fuzzer_custom_event.h"
+
+#include "../lib/worker/protocol.h"
+
+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(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(app)) {
+                    break;
+                } else {
+                    if(fuzzer_worker_load_key_from_file(
+                           app->worker,
+                           app->fuzzer_state.proto_index,
+                           furi_string_get_cstr(app->file_path))) {
+                        scene_manager_set_scene_state(
+                            app->scene_manager,
+                            FuzzerSceneFieldEditor,
+                            FuzzerFieldEditorStateEditingOn);
+                        scene_manager_next_scene(app->scene_manager, FuzzerSceneFieldEditor);
+                        FURI_LOG_I("Scene", "Load ok");
+                    } else {
+                        fuzzer_scene_main_show_error(app, "Unsupported protocol\nor broken file");
+                        FURI_LOG_W("Scene", "Load err");
+                    }
+                }
+                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);
+}

+ 384 - 0
views/attack.c

@@ -0,0 +1,384 @@
+#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 LIFT_RIGHT_OFFSET (3)
+
+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_attack_start(FuzzerViewAttack* view) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        { model->attack_state = FuzzerAttackStateRunning; },
+        true);
+}
+
+void fuzzer_view_attack_stop(FuzzerViewAttack* view) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        { model->attack_state = FuzzerAttackStateOff; },
+        true);
+}
+
+void fuzzer_view_attack_pause(FuzzerViewAttack* view) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        { model->attack_state = FuzzerAttackStateIdle; },
+        true);
+}
+
+void fuzzer_view_attack_end(FuzzerViewAttack* view) {
+    furi_assert(view);
+
+    with_view_model(
+        view->view,
+        FuzzerViewAttackModel * model,
+        { model->attack_state = FuzzerAttackStateEnd; },
+        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;
+}
+
+void fuzzer_view_attack_draw(Canvas* canvas, FuzzerViewAttackModel* model) {
+    char temp_str[50];
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->attack_name);
+
+    uint16_t crt;
+    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, LIFT_RIGHT_OFFSET, 21, 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 + LIFT_RIGHT_OFFSET + 3, 21, 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 - LIFT_RIGHT_OFFSET, 21, 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, LIFT_RIGHT_OFFSET, 21, 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 - LIFT_RIGHT_OFFSET, 21, 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 - LIFT_RIGHT_OFFSET - crt - 3, 21, AlignRight, AlignBottom, temp_str);
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, 26, AlignCenter, AlignTop, model->protocol_name);
+
+    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, 38, AlignCenter, AlignTop, furi_string_get_cstr(model->uid_str));
+
+    canvas_set_font(canvas, FontSecondary);
+    if(model->attack_state == FuzzerAttackStateRunning) {
+        elements_button_center(canvas, "Stop");
+    } else if(model->attack_state == FuzzerAttackStateIdle) {
+        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");
+        }
+
+    } else if(model->attack_state == FuzzerAttackStateEnd) {
+        // elements_button_center(canvas, "Restart"); // Reset
+        elements_button_left(canvas, "Exit");
+    }
+}
+
+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 if(event->key == InputKeyLeft) {
+        with_view_model(
+            view_attack->view,
+            FuzzerViewAttackModel * model,
+            {
+                if(model->attack_state == FuzzerAttackStateIdle) {
+                    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) {
+                            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) {
+                            if((model->emu_time - 10) >= model->emu_time_min) {
+                                model->emu_time -= 10;
+                            } else {
+                                model->emu_time = model->emu_time_min;
+                            }
+                        }
+                    }
+                } else if(
+                    (model->attack_state == FuzzerAttackStateEnd) &&
+                    (event->type == InputTypeShort)) {
+                    // Exit if Ended
+                    view_attack->callback(FuzzerCustomEventViewAttackBack, view_attack->context);
+                }
+            },
+            true);
+        return true;
+    } else if(event->key == InputKeyRight) {
+        with_view_model(
+            view_attack->view,
+            FuzzerViewAttackModel * model,
+            {
+                if(model->attack_state == FuzzerAttackStateIdle) {
+                    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) {
+                            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) {
+                            model->emu_time += 10;
+                            if(model->emu_time > FUZZ_TIME_DELAY_MAX) {
+                                model->emu_time = FUZZ_TIME_DELAY_MAX;
+                            }
+                        }
+                    }
+                } else {
+                    // Nothing
+                }
+            },
+            true);
+        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;
+}
+
+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;
+}

+ 42 - 0
views/attack.h

@@ -0,0 +1,42 @@
+#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_attack_start(FuzzerViewAttack* view);
+
+void fuzzer_view_attack_stop(FuzzerViewAttack* view);
+
+void fuzzer_view_attack_pause(FuzzerViewAttack* view);
+
+void fuzzer_view_attack_end(FuzzerViewAttack* view);
+
+uint8_t fuzzer_view_attack_get_time_delay(FuzzerViewAttack* view);
+
+uint8_t fuzzer_view_attack_get_emu_time(FuzzerViewAttack* view);

+ 358 - 0
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
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
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
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);