Ver Fonte

Returned to Worker model

DerSkythe há 3 anos atrás
pai
commit
eb45d6daff

+ 362 - 0
applications/plugins/subbrute/helpers/subbrute_worker.c

@@ -0,0 +1,362 @@
+#include "subbrute_worker_private.h"
+#include <string.h>
+#include <toolbox/stream/stream.h>
+#include <flipper_format.h>
+#include <flipper_format_i.h>
+
+#define TAG "SubBruteWorker"
+#define SUBBRUTE_TX_TIMEOUT 5
+#define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 400
+
+SubBruteWorker* subbrute_worker_alloc() {
+    SubBruteWorker* instance = malloc(sizeof(SubBruteWorker));
+
+    instance->state = SubBruteWorkerStateIDLE;
+    instance->key_index = 0;
+    instance->worker_running = false;
+    instance->initiated = false;
+    instance->last_time_tx_data = 0;
+    instance->load_index = 0;
+
+    instance->thread = furi_thread_alloc();
+    furi_thread_set_name(instance->thread, "SubBruteAttackWorker");
+    furi_thread_set_stack_size(instance->thread, 2048);
+    furi_thread_set_context(instance->thread, instance);
+    furi_thread_set_callback(instance->thread, subbrute_worker_thread);
+
+    instance->context = NULL;
+    instance->callback = NULL;
+
+    instance->decoder_result = NULL;
+    instance->transmitter = NULL;
+    instance->environment = subghz_environment_alloc();
+
+    return instance;
+}
+
+void subbrute_worker_free(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    // I don't know how to free this
+    instance->decoder_result = NULL;
+
+    if(instance->transmitter != NULL) {
+        subghz_transmitter_free(instance->transmitter);
+        instance->transmitter = NULL;
+    }
+
+    subghz_environment_free(instance->environment);
+    instance->environment = NULL;
+
+    furi_thread_free(instance->thread);
+
+    free(instance);
+}
+
+uint64_t subbrute_worker_get_step(SubBruteWorker* instance) {
+    return instance->key_index;
+}
+
+bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step) {
+    furi_assert(instance);
+    if(!subbrute_worker_can_manual_transmit(instance)) {
+        FURI_LOG_W(TAG, "Cannot set step during running mode");
+        return false;
+    }
+
+    instance->key_index = step;
+
+    return true;
+}
+
+bool subbrute_worker_init_default_attack(
+    SubBruteWorker* instance,
+    SubBruteAttacks attack_type,
+    uint64_t step,
+    const SubBruteProtocol* protocol) {
+    furi_assert(instance);
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Init Worker when it's running");
+        subbrute_worker_stop(instance);
+    }
+
+    instance->attack = attack_type;
+    instance->frequency = protocol->frequency;
+    instance->preset = protocol->preset;
+    instance->file = protocol->file;
+    instance->key_index = step;
+    instance->bits = protocol->bits;
+    instance->te = protocol->te;
+    instance->load_index = 0;
+    instance->file_key = NULL;
+    instance->max_value = subbrute_protocol_calc_max_value(instance->attack, instance->bits);
+
+    return true;
+}
+
+bool subbrute_worker_init_file_attack(
+    SubBruteWorker* instance,
+    uint64_t step,
+    uint8_t load_index,
+    const char* file_key,
+    SubBruteProtocol* protocol) {
+    furi_assert(instance);
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Init Worker when it's running");
+        subbrute_worker_stop(instance);
+    }
+
+    instance->attack = SubBruteAttackLoadFile;
+    instance->frequency = protocol->frequency;
+    instance->preset = protocol->preset;
+    instance->file = protocol->file;
+    instance->key_index = step;
+    instance->bits = protocol->bits;
+    instance->te = protocol->te;
+    instance->load_index = load_index;
+    instance->file_key = file_key;
+    instance->max_value = subbrute_protocol_calc_max_value(instance->attack, instance->bits);
+
+    return true;
+}
+
+bool subbrute_worker_start(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker is already running!");
+        return false;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Worker cannot start, invalid device state: %d", instance->state);
+        return false;
+    }
+
+    instance->worker_running = true;
+    furi_thread_start(instance->thread);
+
+    return true;
+}
+
+void subbrute_worker_stop(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    instance->worker_running = false;
+
+    furi_thread_join(instance->thread);
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+}
+
+bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+    if(instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker in running state!");
+        return false;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
+        return false;
+    }
+
+    uint32_t ticks = furi_get_tick();
+    if((ticks - instance->last_time_tx_data) < SUBBRUTE_MANUAL_TRANSMIT_INTERVAL) {
+#if FURI_DEBUG
+        FURI_LOG_D(TAG, "Need to wait, current: %ld", ticks - instance->last_time_tx_data);
+#endif
+        return false;
+    }
+
+    instance->last_time_tx_data = ticks;
+    instance->key_index = step;
+
+    bool result;
+    FlipperFormat* flipper_format = flipper_format_string_alloc();
+    Stream* stream = flipper_format_get_raw_stream(flipper_format);
+
+    FuriString* payload = furi_string_alloc();
+    stream_clean(stream);
+
+    if(instance->attack == SubBruteAttackLoadFile) {
+        payload = subbrute_protocol_file_payload(
+            step,
+            instance->bits,
+            instance->te,
+            instance->repeat,
+            instance->load_index,
+            instance->file_key);
+    } else {
+        payload = subbrute_protocol_default_payload(
+            step, instance->bits, instance->te, instance->repeat);
+    }
+
+    size_t written = stream_write_string(stream, payload);
+    if(written <= 0) {
+        FURI_LOG_W(TAG, "Error creating packet! EXIT");
+        result = false;
+    } else {
+        subbrute_worker_subghz_transmit(instance, flipper_format);
+
+        result = true;
+#if FURI_DEBUG
+        FURI_LOG_D(TAG, "Manual transmit done");
+#endif
+    }
+
+    flipper_format_free(flipper_format);
+    furi_string_free(payload);
+
+    return result;
+}
+
+bool subbrute_worker_is_running(SubBruteWorker* instance) {
+    return instance->worker_running;
+}
+
+bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance) {
+    furi_assert(instance);
+
+    if(!instance->initiated) {
+        FURI_LOG_W(TAG, "Worker not init!");
+        return false;
+    }
+
+    return !instance->worker_running && instance->state != SubBruteWorkerStateIDLE &&
+           instance->state != SubBruteWorkerStateTx &&
+           ((furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_MANUAL_TRANSMIT_INTERVAL);
+}
+
+void subbrute_worker_set_callback(
+    SubBruteWorker* instance,
+    SubBruteWorkerCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format) {
+    instance->transmitter = subghz_transmitter_alloc_init(
+        instance->environment, subbrute_protocol_name(instance->attack));
+    subghz_transmitter_deserialize(instance->transmitter, flipper_format);
+    furi_hal_subghz_reset();
+    furi_hal_subghz_load_preset(instance->preset);
+    furi_hal_subghz_set_frequency_and_path(instance->frequency);
+    furi_hal_subghz_start_async_tx(subghz_transmitter_yield, instance->transmitter);
+
+    while(!furi_hal_subghz_is_async_tx_complete()) {
+        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
+    }
+    furi_hal_subghz_stop_async_tx();
+
+    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
+    furi_hal_subghz_sleep();
+    subghz_transmitter_free(instance->transmitter);
+    instance->transmitter = NULL;
+}
+
+void subbrute_worker_send_callback(SubBruteWorker* instance) {
+    if(instance->callback != NULL) {
+        instance->callback(instance->context, instance->state);
+    }
+}
+
+/**
+ * Entrypoint for worker
+ *
+ * @param context SubBruteWorker*
+ * @return 0 if ok
+ */
+int32_t subbrute_worker_thread(void* context) {
+    furi_assert(context);
+    SubBruteWorker* instance = (SubBruteWorker*)context;
+
+    if(!instance->worker_running) {
+        FURI_LOG_W(TAG, "Worker is not set to running state!");
+        return -1;
+    }
+    if(instance->state != SubBruteWorkerStateReady &&
+       instance->state != SubBruteWorkerStateFinished) {
+        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
+        return -2;
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker start");
+#endif
+
+    SubBruteWorkerState local_state = instance->state = SubBruteWorkerStateTx;
+    subbrute_worker_send_callback(instance);
+
+    FlipperFormat* flipper_format = flipper_format_string_alloc();
+    Stream* stream = flipper_format_get_raw_stream(flipper_format);
+
+    while(instance->worker_running) {
+        FuriString* payload = furi_string_alloc();
+        stream_clean(stream);
+
+        if(instance->attack == SubBruteAttackLoadFile) {
+            payload = subbrute_protocol_file_payload(
+                instance->key_index,
+                instance->bits,
+                instance->te,
+                instance->repeat,
+                instance->load_index,
+                instance->file_key);
+        } else {
+            payload = subbrute_protocol_default_payload(
+                instance->key_index, instance->bits, instance->te, instance->repeat);
+        }
+
+        size_t written = stream_write_string(stream, payload);
+        if(written <= 0) {
+            FURI_LOG_W(TAG, "Error creating packet! BREAK");
+            instance->worker_running = false;
+            local_state = SubBruteWorkerStateIDLE;
+            furi_string_free(payload);
+            break;
+        }
+
+        subbrute_worker_subghz_transmit(instance, flipper_format);
+
+        if(instance->key_index + 1 > instance->max_value) {
+#ifdef FURI_DEBUG
+            FURI_LOG_I(TAG, "Worker finished to end");
+#endif
+            local_state = SubBruteWorkerStateFinished;
+            furi_string_free(payload);
+            break;
+        }
+        instance->key_index++;
+
+        furi_string_free(payload);
+        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
+    }
+
+    flipper_format_free(flipper_format);
+
+    instance->worker_running = false; // Because we have error states
+    instance->state = local_state == SubBruteWorkerStateTx ? SubBruteWorkerStateReady :
+                                                             local_state;
+    subbrute_worker_send_callback(instance);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_I(TAG, "Worker stop");
+#endif
+    return 0;
+}

+ 39 - 0
applications/plugins/subbrute/helpers/subbrute_worker.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include "../subbrute_protocols.h"
+
+typedef enum {
+    SubBruteWorkerStateIDLE,
+    SubBruteWorkerStateReady,
+    SubBruteWorkerStateTx,
+    SubBruteWorkerStateFinished
+} SubBruteWorkerState;
+
+typedef void (*SubBruteWorkerCallback)(void* context, SubBruteWorkerState state);
+
+typedef struct SubBruteWorker SubBruteWorker;
+
+SubBruteWorker* subbrute_worker_alloc();
+void subbrute_worker_free(SubBruteWorker* instance);
+uint64_t subbrute_worker_get_step(SubBruteWorker* instance);
+bool subbrute_worker_set_step(SubBruteWorker* instance, uint64_t step);
+bool subbrute_worker_is_running(SubBruteWorker* instance);
+bool subbrute_worker_init_default_attack(
+    SubBruteWorker* instance,
+    SubBruteAttacks attack_type,
+    uint64_t step,
+    const SubBruteProtocol* protocol);
+bool subbrute_worker_init_file_attack(
+    SubBruteWorker* instance,
+    uint64_t step,
+    uint8_t load_index,
+    const char* file_key,
+    SubBruteProtocol* protocol);
+bool subbrute_worker_start(SubBruteWorker* instance);
+void subbrute_worker_stop(SubBruteWorker* instance);
+bool subbrute_worker_transmit_current_key(SubBruteWorker* instance, uint64_t step);
+bool subbrute_worker_can_manual_transmit(SubBruteWorker* instance);
+void subbrute_worker_set_callback(
+    SubBruteWorker* instance,
+    SubBruteWorkerCallback callback,
+    void* context);

+ 45 - 0
applications/plugins/subbrute/helpers/subbrute_worker_private.h

@@ -0,0 +1,45 @@
+#pragma once
+
+#include "subbrute_worker.h"
+#include <lib/subghz/protocols/base.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/environment.h>
+
+struct SubBruteWorker {
+    SubBruteWorkerState state;
+    volatile bool worker_running;
+    volatile bool initiated;
+
+    // Current step
+    uint64_t key_index;
+
+    // SubGhz
+    FuriThread* thread;
+    SubGhzProtocolDecoderBase* decoder_result;
+    SubGhzEnvironment* environment;
+    SubGhzTransmitter* transmitter;
+
+    // Initiated values
+    SubBruteAttacks attack; // Attack state
+    uint32_t frequency;
+    FuriHalSubGhzPreset preset;
+    SubBruteFileProtocol file;
+    uint8_t bits;
+    uint8_t te;
+    uint8_t repeat;
+    uint8_t load_index; // Index of group to bruteforce in loaded file
+    const char* file_key;
+    uint64_t max_value; // Max step
+
+    // Manual transmit
+    uint32_t last_time_tx_data;
+
+    // Callback for changed states
+    SubBruteWorkerCallback callback;
+    void* context;
+};
+
+int32_t subbrute_worker_thread(void* context);
+void subbrute_worker_subghz_transmit(SubBruteWorker* instance, FlipperFormat* flipper_format);
+void subbrute_worker_send_callback(SubBruteWorker* instance);

+ 10 - 1
applications/plugins/subbrute/scenes/subbrute_scene_load_file.c

@@ -39,6 +39,14 @@ void subbrute_scene_load_file_on_enter(void* context) {
         if(load_result == SubBruteFileResultOk) {
             load_result = subbrute_device_attack_set(instance->device, SubBruteAttackLoadFile);
             if(load_result == SubBruteFileResultOk) {
+                if(!subbrute_worker_init_file_attack(
+                       instance->worker,
+                       instance->device->key_index,
+                       instance->device->load_index,
+                       instance->device->file_key,
+                       instance->device->file_protocol_info)) {
+                    furi_crash("Invalid attack set!");
+                }
                 // Ready to run!
                 FURI_LOG_I(TAG, "Ready to run");
                 res = true;
@@ -52,7 +60,8 @@ void subbrute_scene_load_file_on_enter(void* context) {
 
             FuriString* dialog_msg;
             dialog_msg = furi_string_alloc();
-            furi_string_cat_printf(dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
+            furi_string_cat_printf(
+                dialog_msg, "Cannot parse\nfile: %s", subbrute_device_error_get_desc(load_result));
             dialog_message_show_storage_error(instance->dialogs, furi_string_get_cstr(dialog_msg));
             furi_string_free(dialog_msg);
             scene_manager_search_and_switch_to_previous_scene(

+ 10 - 3
applications/plugins/subbrute/scenes/subbrute_scene_load_select.c

@@ -20,7 +20,7 @@ void subbrute_scene_load_select_on_enter(void* context) {
 
     instance->current_view = SubBruteViewMain;
     subbrute_main_view_set_callback(view, subbrute_scene_load_select_callback, instance);
-    subbrute_main_view_set_index(view, 7, true, subbrute_device_get_file_key(instance->device));
+    subbrute_main_view_set_index(view, 7, true, instance->device->file_key);
 
     view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
 }
@@ -38,8 +38,15 @@ bool subbrute_scene_load_select_on_event(void* context, SceneManagerEvent event)
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubBruteCustomEventTypeIndexSelected) {
-            subbrute_device_set_load_index(
-                instance->device, subbrute_main_view_get_index(instance->view_main));
+            instance->device->load_index = subbrute_main_view_get_index(instance->view_main);
+            if(!subbrute_worker_init_file_attack(
+                   instance->worker,
+                   instance->device->key_index,
+                   instance->device->load_index,
+                   instance->device->file_key,
+                   instance->device->file_protocol_info)) {
+                furi_crash("Invalid attack set!");
+            }
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
             consumed = true;
         }

+ 10 - 10
applications/plugins/subbrute/scenes/subbrute_scene_run_attack.c

@@ -11,15 +11,15 @@ static void subbrute_scene_run_attack_callback(SubBruteCustomEvent event, void*
 }
 
 static void
-    subbrute_scene_run_attack_device_state_changed(void* context, SubBruteDeviceState state) {
+    subbrute_scene_run_attack_device_state_changed(void* context, SubBruteWorkerState state) {
     furi_assert(context);
 
     SubBruteState* instance = (SubBruteState*)context;
 
-    if(state == SubBruteDeviceStateIDLE) {
+    if(state == SubBruteWorkerStateIDLE) {
         // Can't be IDLE on this step!
         view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
-    } else if(state == SubBruteDeviceStateFinished) {
+    } else if(state == SubBruteWorkerStateFinished) {
         view_dispatcher_send_custom_event(
             instance->view_dispatcher, SubBruteCustomEventTypeTransmitFinished);
     }
@@ -28,7 +28,7 @@ void subbrute_scene_run_attack_on_exit(void* context) {
     furi_assert(context);
     SubBruteState* instance = (SubBruteState*)context;
 
-    subbrute_worker_stop(instance->device);
+    subbrute_worker_stop(instance->worker);
 
     notification_message(instance->notifications, &sequence_blink_stop);
 }
@@ -42,11 +42,11 @@ void subbrute_scene_run_attack_on_enter(void* context) {
     subbrute_attack_view_set_callback(view, subbrute_scene_run_attack_callback, instance);
     view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
 
-    subbrute_device_set_callback(
-        instance->device, subbrute_scene_run_attack_device_state_changed, instance);
+    subbrute_worker_set_callback(
+        instance->worker, subbrute_scene_run_attack_device_state_changed, instance);
 
-    if(!subbrute_device_is_worker_running(instance->device)) {
-        subbrute_worker_start(instance->device);
+    if(!subbrute_worker_is_running(instance->worker)) {
+        subbrute_worker_start(instance->worker);
     }
 }
 
@@ -57,7 +57,7 @@ bool subbrute_scene_run_attack_on_event(void* context, SceneManagerEvent event)
     bool consumed = false;
 
     if(event.type == SceneManagerEventTypeCustom) {
-        subbrute_attack_view_set_current_step(view, subbrute_get_step(instance));
+        subbrute_attack_view_set_current_step(view, subbrute_worker_get_step(instance->worker));
 
         if(event.event == SubBruteCustomEventTypeTransmitFinished) {
             notification_message(instance->notifications, &sequence_display_backlight_on);
@@ -77,7 +77,7 @@ bool subbrute_scene_run_attack_on_event(void* context, SceneManagerEvent event)
         }
         consumed = true;
     } else if(event.type == SceneManagerEventTypeTick) {
-        subbrute_attack_view_set_current_step(view, subbrute_get_step(instance));
+        subbrute_attack_view_set_current_step(view, subbrute_worker_get_step(instance->worker));
 
         consumed = true;
     }

+ 33 - 18
applications/plugins/subbrute/scenes/subbrute_scene_setup_attack.c

@@ -11,12 +11,12 @@ static void subbrute_scene_setup_attack_callback(SubBruteCustomEvent event, void
 }
 
 static void
-    subbrute_scene_setup_attack_device_state_changed(void* context, SubBruteDeviceState state) {
+    subbrute_scene_setup_attack_device_state_changed(void* context, SubBruteWorkerState state) {
     furi_assert(context);
 
     SubBruteState* instance = (SubBruteState*)context;
 
-    if(state == SubBruteDeviceStateIDLE) {
+    if(state == SubBruteWorkerStateIDLE) {
         // Can't be IDLE on this step!
         view_dispatcher_send_custom_event(instance->view_dispatcher, SubBruteCustomEventTypeError);
     }
@@ -28,16 +28,24 @@ void subbrute_scene_setup_attack_on_enter(void* context) {
     SubBruteAttackView* view = instance->view_attack;
 
 #ifdef FURI_DEBUG
-    FURI_LOG_D(TAG, "Enter Attack: %d", subbrute_device_get_attack(instance->device));
+    FURI_LOG_D(TAG, "Enter Attack: %d", instance->device->attack);
 #endif
 
-    subbrute_device_set_callback(
-        instance->device, subbrute_scene_setup_attack_device_state_changed, context);
+    subbrute_worker_set_callback(
+        instance->worker, subbrute_scene_setup_attack_device_state_changed, context);
 
-    if(subbrute_device_is_worker_running(instance->device)) {
-        subbrute_worker_stop(instance->device);
+    if(subbrute_worker_is_running(instance->worker)) {
+        instance->device->key_index = subbrute_worker_get_step(instance->worker);
+        subbrute_worker_stop(instance->worker);
     }
 
+    subbrute_attack_view_init_values(
+        view,
+        instance->device->attack,
+        instance->device->max_value,
+        instance->device->key_index,
+        false);
+
     instance->current_view = SubBruteViewAttack;
     subbrute_attack_view_set_callback(view, subbrute_scene_setup_attack_callback, instance);
     view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
@@ -49,7 +57,7 @@ void subbrute_scene_setup_attack_on_exit(void* context) {
     FURI_LOG_D(TAG, "subbrute_scene_setup_attack_on_exit");
 #endif
     SubBruteState* instance = (SubBruteState*)context;
-    subbrute_worker_stop(instance->device);
+    subbrute_worker_stop(instance->worker);
     notification_message(instance->notifications, &sequence_blink_stop);
 }
 
@@ -65,53 +73,60 @@ bool subbrute_scene_setup_attack_on_event(void* context, SceneManagerEvent event
         } else if(event.event == SubBruteCustomEventTypeSaveFile) {
             subbrute_attack_view_init_values(
                 view,
-                subbrute_device_get_attack(instance->device),
-                subbrute_device_get_max_value(instance->device),
-                subbrute_device_get_step(instance->device),
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->key_index,
                 false);
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneSaveName);
         } else if(event.event == SubBruteCustomEventTypeBackPressed) {
-            subbrute_device_reset_step(instance->device);
             subbrute_attack_view_init_values(
                 view,
-                subbrute_device_get_attack(instance->device),
-                subbrute_device_get_max_value(instance->device),
-                subbrute_device_get_step(instance->device),
+                instance->device->attack,
+                instance->device->max_value,
+                instance->device->key_index,
                 false);
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneStart);
         } else if(event.event == SubBruteCustomEventTypeError) {
             notification_message(instance->notifications, &sequence_error);
         } else if(event.event == SubBruteCustomEventTypeTransmitCustom) {
             // We can transmit only in not working states
-            if(subbrute_device_can_manual_transmit(instance->device)) {
+            if(subbrute_worker_can_manual_transmit(instance->worker)) {
                 // MANUAL Transmit!
                 // Blink
                 notification_message(instance->notifications, &sequence_blink_green_100);
-                subbrute_device_transmit_current_key(instance->device);
+                subbrute_worker_transmit_current_key(
+                    instance->worker, instance->device->key_index);
                 // Stop
                 notification_message(instance->notifications, &sequence_blink_stop);
             }
         } else if(event.event == SubBruteCustomEventTypeChangeStepUp) {
             // +1
             uint64_t step = subbrute_device_add_step(instance->device, 1);
+            subbrute_worker_set_step(instance->worker, step);
             subbrute_attack_view_set_current_step(view, step);
         } else if(event.event == SubBruteCustomEventTypeChangeStepUpMore) {
             // +50
             uint64_t step = subbrute_device_add_step(instance->device, 50);
+            subbrute_worker_set_step(instance->worker, step);
             subbrute_attack_view_set_current_step(view, step);
         } else if(event.event == SubBruteCustomEventTypeChangeStepDown) {
             // -1
             uint64_t step = subbrute_device_add_step(instance->device, -1);
+            subbrute_worker_set_step(instance->worker, step);
             subbrute_attack_view_set_current_step(view, step);
         } else if(event.event == SubBruteCustomEventTypeChangeStepDownMore) {
             // -50
             uint64_t step = subbrute_device_add_step(instance->device, -50);
+            subbrute_worker_set_step(instance->worker, step);
             subbrute_attack_view_set_current_step(view, step);
         }
 
         consumed = true;
     } else if(event.type == SceneManagerEventTypeTick) {
-        subbrute_attack_view_set_current_step(view, subbrute_device_get_step(instance->device));
+        if(subbrute_worker_is_running(instance->worker)) {
+            instance->device->key_index = subbrute_worker_get_step(instance->worker);
+        }
+        subbrute_attack_view_set_current_step(view, instance->device->key_index);
         consumed = true;
     }
 

+ 9 - 2
applications/plugins/subbrute/scenes/subbrute_scene_start.c

@@ -23,7 +23,7 @@ void subbrute_scene_start_on_enter(void* context) {
 
     instance->current_view = SubBruteViewMain;
     subbrute_main_view_set_callback(view, subbrute_scene_start_callback, instance);
-    subbrute_main_view_set_index(view, subbrute_device_get_attack(instance->device), false, NULL);
+    subbrute_main_view_set_index(view, instance->device->attack, false, NULL);
 
     view_dispatcher_switch_to_view(instance->view_dispatcher, instance->current_view);
 }
@@ -46,7 +46,14 @@ bool subbrute_scene_start_on_event(void* context, SceneManagerEvent event) {
         if(event.event == SubBruteCustomEventTypeMenuSelected) {
             SubBruteAttacks attack = subbrute_main_view_get_index(instance->view_main);
 
-            subbrute_device_attack_set(instance->device, attack);
+            if(subbrute_device_attack_set(instance->device, attack) != SubBruteFileResultOk ||
+               !subbrute_worker_init_default_attack(
+                   instance->worker,
+                   attack,
+                   instance->device->key_index,
+                   instance->device->protocol_info)) {
+                furi_crash("Invalid attack set!");
+            }
             scene_manager_next_scene(instance->scene_manager, SubBruteSceneSetupAttack);
 
             consumed = true;

+ 7 - 8
applications/plugins/subbrute/subbrute.c

@@ -51,6 +51,9 @@ SubBruteState* subbrute_alloc() {
     // Devices
     instance->device = subbrute_device_alloc();
 
+    // SubBruteWorker
+    instance->worker = subbrute_worker_alloc();
+
     // TextInput
     instance->text_input = text_input_alloc();
     view_dispatcher_add_view(
@@ -96,8 +99,11 @@ SubBruteState* subbrute_alloc() {
 void subbrute_free(SubBruteState* instance) {
     furi_assert(instance);
 
+    // SubBruteWorker
+    subbrute_worker_stop(instance->worker);
+    subbrute_worker_free(instance->worker);
+
     // SubBruteDevice
-    subbrute_worker_stop(instance->device);
     subbrute_device_free(instance->device);
 
     // Notifications
@@ -163,13 +169,6 @@ void subbrute_popup_closed_callback(void* context) {
         instance->view_dispatcher, SubBruteCustomEventTypePopupClosed);
 }
 
-uint64_t subbrute_get_step(void* context) {
-    furi_assert(context);
-    SubBruteState* instance = context;
-
-    return subbrute_device_get_step(instance->device);
-}
-
 // ENTRYPOINT
 int32_t subbrute_app(void* p) {
     UNUSED(p);

+ 38 - 438
applications/plugins/subbrute/subbrute_device.c

@@ -8,40 +8,14 @@
 
 #define TAG "SubBruteDevice"
 
-#define SUBBRUTE_TX_TIMEOUT 5
-#define SUBBRUTE_MANUAL_TRANSMIT_INTERVAL 400
-
-/**
- * Values to not use less memory for packet parse operations
- */
-static const char* subbrute_key_file_start =
-    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d";
-static const char* subbrute_key_file_key = "%s\nKey: %s\nRepeat: %d\n";
-static const char* subbrute_key_file_key_with_tail = "%s\nKey: %s\nTE: %d\nRepeat: %d\n";
-static const char* subbrute_key_small_no_tail = "Bit: %d\nKey: %s\nRepeat: %d\nRepeat: %d\n";
-static const char* subbrute_key_small_with_tail = "Bit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
-
 SubBruteDevice* subbrute_device_alloc() {
     SubBruteDevice* instance = malloc(sizeof(SubBruteDevice));
 
-    instance->state = SubBruteDeviceStateIDLE;
     instance->key_index = 0;
-    instance->worker_running = false;
-    instance->last_time_tx_data = 0;
-
-    instance->thread = furi_thread_alloc();
-    furi_thread_set_name(instance->thread, "SubBruteAttackWorker");
-    furi_thread_set_stack_size(instance->thread, 2048);
-    furi_thread_set_context(instance->thread, instance);
-    furi_thread_set_callback(instance->thread, subbrute_worker_thread);
-
-    instance->context = NULL;
-    instance->callback = NULL;
 
     instance->protocol_info = NULL;
     instance->file_protocol_info = NULL;
     instance->decoder_result = NULL;
-    instance->transmitter = NULL;
     instance->receiver = NULL;
     instance->environment = subghz_environment_alloc();
 
@@ -61,137 +35,15 @@ void subbrute_device_free(SubBruteDevice* instance) {
         instance->receiver = NULL;
     }
 
-    if(instance->transmitter != NULL) {
-        subghz_transmitter_free(instance->transmitter);
-        instance->transmitter = NULL;
-    }
-
     subghz_environment_free(instance->environment);
     instance->environment = NULL;
 
-    furi_thread_free(instance->thread);
     subbrute_device_free_protocol_info(instance);
 
     free(instance);
 }
 
-/**
- * Entrypoint for worker
- *
- * @param context SubBruteWorker*
- * @return 0 if ok
- */
-int32_t subbrute_worker_thread(void* context) {
-    furi_assert(context);
-    SubBruteDevice* instance = (SubBruteDevice*)context;
-
-    if(!instance->worker_running) {
-        FURI_LOG_W(TAG, "Worker is not set to running state!");
-        return -1;
-    }
-    if(instance->state != SubBruteDeviceStateReady &&
-       instance->state != SubBruteDeviceStateFinished) {
-        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
-        return -2;
-    }
-#ifdef FURI_DEBUG
-    FURI_LOG_I(TAG, "Worker start");
-#endif
-
-    SubBruteDeviceState local_state = instance->state = SubBruteDeviceStateTx;
-    subbrute_device_send_callback(instance);
-
-    FlipperFormat* flipper_format = flipper_format_string_alloc();
-
-    while(instance->worker_running) {
-        if(!subbrute_device_create_packet_parsed(
-               instance, flipper_format, instance->key_index, true)) {
-            FURI_LOG_W(TAG, "Error creating packet! BREAK");
-            instance->worker_running = false;
-            local_state = SubBruteDeviceStateIDLE;
-            break;
-        }
-        subbrute_device_subghz_transmit(instance, flipper_format);
-
-        if(instance->key_index + 1 > instance->max_value) {
-#ifdef FURI_DEBUG
-            FURI_LOG_I(TAG, "Worker finished to end");
-#endif
-            local_state = SubBruteDeviceStateFinished;
-            break;
-        }
-        instance->key_index++;
-
-        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
-    }
-
-    flipper_format_free(flipper_format);
-
-    instance->worker_running = false; // Because we have error states
-    instance->state = local_state == SubBruteDeviceStateTx ? SubBruteDeviceStateReady :
-                                                             local_state;
-    subbrute_device_send_callback(instance);
-
-#ifdef FURI_DEBUG
-    FURI_LOG_I(TAG, "Worker stop");
-#endif
-    return 0;
-}
-
-bool subbrute_worker_start(SubBruteDevice* instance) {
-    furi_assert(instance);
-
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Worker is already running!");
-        return false;
-    }
-    if(instance->state != SubBruteDeviceStateReady &&
-       instance->state != SubBruteDeviceStateFinished) {
-        FURI_LOG_W(TAG, "Worker cannot start, invalid device state: %d", instance->state);
-        return false;
-    }
-    if((instance->protocol_info == NULL && instance->attack != SubBruteAttackLoadFile) ||
-       (instance->attack == SubBruteAttackLoadFile && instance->file_protocol_info == NULL)) {
-        FURI_LOG_W(TAG, "Worker cannot start, protocol_info is NULL!");
-        return false;
-    }
-
-    instance->worker_running = true;
-    furi_thread_start(instance->thread);
-
-    return true;
-}
-
-void subbrute_worker_stop(SubBruteDevice* instance) {
-    furi_assert(instance);
-
-    instance->worker_running = false;
-
-    furi_thread_join(instance->thread);
-
-    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
-    furi_hal_subghz_sleep();
-}
-
-SubBruteAttacks subbrute_device_get_attack(SubBruteDevice* instance) {
-    return instance->attack;
-}
-bool subbrute_device_is_worker_running(SubBruteDevice* instance) {
-    return instance->worker_running;
-}
-uint64_t subbrute_device_get_max_value(SubBruteDevice* instance) {
-    return instance->max_value;
-}
-uint64_t subbrute_device_get_step(SubBruteDevice* instance) {
-    return instance->key_index;
-}
-const char* subbrute_device_get_file_key(SubBruteDevice* instance) {
-    return instance->file_key;
-}
 uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step) {
-    if(!subbrute_device_can_manual_transmit(instance)) {
-        return instance->key_index;
-    }
     if(step > 0) {
         if((instance->key_index + step) - instance->max_value == 1) {
             instance->key_index = 0x00;
@@ -220,122 +72,17 @@ uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step) {
 
     return instance->key_index;
 }
-void subbrute_device_set_load_index(SubBruteDevice* instance, uint64_t load_index) {
-    instance->load_index = load_index;
-}
-void subbrute_device_reset_step(SubBruteDevice* instance) {
-    instance->key_index = 0x00;
-}
-void subbrute_device_subghz_transmit(SubBruteDevice* instance, FlipperFormat* flipper_format) {
-    instance->transmitter = subghz_transmitter_alloc_init(
-        instance->environment, subbrute_protocol_name(instance->attack));
-    subghz_transmitter_deserialize(instance->transmitter, flipper_format);
-    furi_hal_subghz_reset();
-    if(instance->attack == SubBruteAttackLoadFile) {
-        furi_hal_subghz_load_preset(instance->file_protocol_info->preset);
-        furi_hal_subghz_set_frequency_and_path(instance->file_protocol_info->preset);
-    } else {
-        furi_hal_subghz_load_preset(instance->protocol_info->preset);
-        furi_hal_subghz_set_frequency_and_path(instance->protocol_info->preset);
-    }
-    furi_hal_subghz_start_async_tx(subghz_transmitter_yield, instance->transmitter);
-
-    while(!furi_hal_subghz_is_async_tx_complete()) {
-        furi_delay_ms(SUBBRUTE_TX_TIMEOUT);
-    }
-    furi_hal_subghz_stop_async_tx();
-
-    furi_hal_subghz_set_path(FuriHalSubGhzPathIsolate);
-    furi_hal_subghz_sleep();
-    subghz_transmitter_free(instance->transmitter);
-    instance->transmitter = NULL;
-}
-
-bool subbrute_device_transmit_current_key(SubBruteDevice* instance) {
-    furi_assert(instance);
-
-    if(instance->worker_running) {
-        FURI_LOG_W(TAG, "Worker in running state!");
-        return false;
-    }
-    if(instance->state != SubBruteDeviceStateReady &&
-       instance->state != SubBruteDeviceStateFinished) {
-        FURI_LOG_W(TAG, "Invalid state for running worker! State: %d", instance->state);
-        return false;
-    }
-
-    uint32_t ticks = furi_get_tick();
-    if((ticks - instance->last_time_tx_data) < SUBBRUTE_MANUAL_TRANSMIT_INTERVAL) {
-#if FURI_DEBUG
-        FURI_LOG_D(TAG, "Need to wait, current: %ld", ticks - instance->last_time_tx_data);
-#endif
-        return false;
-    }
-
-    instance->last_time_tx_data = ticks;
-
-#ifdef FURI_DEBUG
-    if(instance->attack == SubBruteAttackLoadFile) {
-        FURI_LOG_D(
-            TAG,
-            "Protocol: %d, Frequency: %ld",
-            instance->file_protocol_info->file,
-            instance->file_protocol_info->frequency);
-    } else {
-        FURI_LOG_D(
-            TAG,
-            "Protocol: %d, Frequency: %ld",
-            instance->protocol_info->file,
-            instance->protocol_info->frequency);
-    }
-#endif
-
-    FlipperFormat* flipper_format = flipper_format_string_alloc();
-
-    if(!subbrute_device_create_packet_parsed(instance, flipper_format, instance->key_index, true)) {
-        FURI_LOG_W(TAG, "Error creating packet! EXIT");
-        return false;
-    }
-    subbrute_device_subghz_transmit(instance, flipper_format);
-
-    flipper_format_free(flipper_format);
-
-    return true;
-}
-
-void subbrute_device_set_callback(
-    SubBruteDevice* instance,
-    SubBruteDeviceWorkerCallback callback,
-    void* context) {
-    furi_assert(instance);
-
-    instance->callback = callback;
-    instance->context = context;
-}
-
-bool subbrute_device_can_manual_transmit(SubBruteDevice* instance) {
-    furi_assert(instance);
-
-    return !instance->worker_running && instance->state != SubBruteDeviceStateIDLE &&
-           instance->state != SubBruteDeviceStateTx &&
-           ((furi_get_tick() - instance->last_time_tx_data) > SUBBRUTE_MANUAL_TRANSMIT_INTERVAL);
-}
 
 bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_name) {
     furi_assert(instance);
 
-    if(instance->state != SubBruteDeviceStateReady &&
-       instance->state != SubBruteDeviceStateFinished) {
-        FURI_LOG_W(TAG, "Worker is not set to running state!");
-        return false;
-    }
-
 #ifdef FURI_DEBUG
     FURI_LOG_D(TAG, "subbrute_device_save_file: %s", dev_file_name);
 #endif
 
     Storage* storage = furi_record_open(RECORD_STORAGE);
     FlipperFormat* file = flipper_format_file_alloc(storage);
+    FuriString* file_content = furi_string_alloc();
 
     bool result = false;
     do {
@@ -343,7 +90,32 @@ bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_na
             break;
         }
 
-        if(!subbrute_device_create_packet_parsed(instance, file, instance->key_index, false)) {
+        if(instance->attack == SubBruteAttackLoadFile) {
+            file_content = subbrute_protocol_file_generate_file(
+                instance->file_protocol_info->frequency,
+                instance->file_protocol_info->preset,
+                instance->file_protocol_info->file,
+                instance->key_index,
+                instance->file_protocol_info->bits,
+                instance->file_protocol_info->te,
+                instance->file_protocol_info->repeat,
+                instance->load_index,
+                instance->file_key);
+        } else {
+            file_content = subbrute_protocol_default_generate_file(
+                instance->protocol_info->frequency,
+                instance->protocol_info->preset,
+                instance->protocol_info->file,
+                instance->key_index,
+                instance->protocol_info->bits,
+                instance->protocol_info->te,
+                instance->protocol_info->repeat);
+        }
+
+        Stream* stream = flipper_format_get_raw_stream(file);
+        stream_clean(stream);
+        size_t written = stream_write_string(stream, file_content);
+        if(written <= 0) {
             FURI_LOG_E(TAG, "create_packet_parsed failed!");
             break;
         }
@@ -352,140 +124,17 @@ bool subbrute_device_save_file(SubBruteDevice* instance, const char* dev_file_na
     } while(false);
 
     if(!result) {
-        FURI_LOG_E(TAG, "flipper_format_file_open_always failed!");
+        FURI_LOG_E(TAG, "subbrute_device_save_file failed!");
     }
 
+    flipper_format_file_close(file);
     flipper_format_free(file);
     furi_record_close(RECORD_STORAGE);
+    furi_string_free(file_content);
 
     return result;
 }
 
-bool subbrute_device_create_packet_parsed(
-    SubBruteDevice* instance,
-    FlipperFormat* flipper_format,
-    uint64_t step,
-    bool small) {
-    furi_assert(instance);
-
-    FuriString* candidate = furi_string_alloc();
-
-    Stream* stream = flipper_format_get_raw_stream(flipper_format);
-    stream_clean(stream);
-
-    if(instance->attack == SubBruteAttackLoadFile) {
-        if(step >= sizeof(instance->file_key)) {
-            return false;
-        }
-        char subbrute_payload_byte[4];
-        furi_string_set_str(candidate, instance->file_key);
-        snprintf(subbrute_payload_byte, 4, "%02X ", (uint8_t)step);
-        furi_string_replace_at(candidate, instance->load_index * 3, 3, subbrute_payload_byte);
-        //snprintf(step_payload, sizeof(step_payload), "%02X", (uint8_t)instance->file_key[step]);
-
-        if(small) {
-            if(instance->file_protocol_info->te) {
-                stream_write_format(
-                    stream,
-                    subbrute_key_small_with_tail,
-                    instance->file_protocol_info->bits,
-                    furi_string_get_cstr(candidate),
-                    instance->file_protocol_info->te,
-                    instance->file_protocol_info->repeat);
-            } else {
-                stream_write_format(
-                    stream,
-                    subbrute_key_small_no_tail,
-                    instance->file_protocol_info->bits,
-                    furi_string_get_cstr(candidate),
-                    instance->file_protocol_info->repeat);
-            }
-        } else {
-            if(instance->file_protocol_info->te) {
-                stream_write_format(
-                    stream,
-                    subbrute_key_file_key_with_tail,
-                    instance->file_template,
-                    furi_string_get_cstr(candidate),
-                    instance->file_protocol_info->te,
-                    instance->file_protocol_info->repeat);
-            } else {
-                stream_write_format(
-                    stream,
-                    subbrute_key_file_key,
-                    instance->file_template,
-                    furi_string_get_cstr(candidate),
-                    instance->file_protocol_info->repeat);
-            }
-        }
-    } else {
-        //snprintf(step_payload, sizeof(step_payload), "%16X", step);
-        //snprintf(step_payload, sizeof(step_payload), "%016llX", step);
-        FuriString* buffer = furi_string_alloc();
-        furi_string_printf(buffer, "%16llX", step);
-        int j = 0;
-        furi_string_set_str(candidate, "                       ");
-        for(uint8_t i = 0; i < 16; i++) {
-            if(furi_string_get_char(buffer, i) != ' ') {
-                furi_string_set_char(candidate, i + j, furi_string_get_char(buffer, i));
-            } else {
-                furi_string_set_char(candidate, i + j, '0');
-            }
-            if(i % 2 != 0) {
-                j++;
-            }
-        }
-        furi_string_free(buffer);
-
-#ifdef FURI_DEBUG
-        FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
-#endif
-
-        if(small) {
-            if(instance->protocol_info->te) {
-                stream_write_format(
-                    stream,
-                    subbrute_key_small_with_tail,
-                    instance->protocol_info->bits,
-                    furi_string_get_cstr(candidate),
-                    instance->protocol_info->te,
-                    instance->protocol_info->repeat);
-            } else {
-                stream_write_format(
-                    stream,
-                    subbrute_key_small_no_tail,
-                    instance->protocol_info->bits,
-                    furi_string_get_cstr(candidate),
-                    instance->protocol_info->repeat);
-            }
-        } else {
-            if(instance->protocol_info->te) {
-                stream_write_format(
-                    stream,
-                    subbrute_key_file_key_with_tail,
-                    instance->file_template,
-                    furi_string_get_cstr(candidate),
-                    instance->protocol_info->te,
-                    instance->protocol_info->repeat);
-            } else {
-                stream_write_format(
-                    stream,
-                    subbrute_key_file_key,
-                    instance->file_template,
-                    furi_string_get_cstr(candidate),
-                    instance->protocol_info->repeat);
-            }
-        }
-#ifdef FURI_DEBUG
-        FURI_LOG_D(TAG, "candidate: %s", furi_string_get_cstr(candidate));
-#endif
-    }
-
-    furi_string_free(candidate);
-
-    return true;
-}
-
 SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBruteAttacks type) {
     furi_assert(instance);
 #ifdef FURI_DEBUG
@@ -513,10 +162,18 @@ SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBrute
             FURI_LOG_E(TAG, "Can't load SubGhzProtocolDecoderBase in phase non-file decoder set");
         } else {
             protocol_check_result = SubBruteFileResultOk;
+
+            // Calc max value
+            instance->max_value =
+                subbrute_protocol_calc_max_value(instance->attack, instance->protocol_info->bits);
         }
     } else {
         // And here we need to set preset enum
         protocol_check_result = SubBruteFileResultOk;
+
+        // Calc max value
+        instance->max_value =
+            subbrute_protocol_calc_max_value(instance->attack, instance->file_protocol_info->bits);
     }
 
     subghz_receiver_free(instance->receiver);
@@ -526,56 +183,6 @@ SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* instance, SubBrute
         return SubBruteFileResultProtocolNotFound;
     }
 
-    // Calc max value
-    if(instance->attack == SubBruteAttackLoadFile) {
-        instance->max_value = 0x3F;
-
-        // Now we are ready to set file template for using in the future with snprintf
-        // for sending attack payload ONLY for files!
-        snprintf(
-            instance->file_template,
-            sizeof(instance->file_template),
-            subbrute_key_file_start,
-            instance->file_protocol_info->frequency,
-            subbrute_protocol_preset(instance->file_protocol_info->preset),
-            subbrute_protocol_file(instance->file_protocol_info->file),
-            instance->file_protocol_info->bits);
-    } else {
-        FuriString* max_value_s;
-        max_value_s = furi_string_alloc();
-        for(uint8_t i = 0; i < instance->protocol_info->bits; i++) {
-            furi_string_cat_printf(max_value_s, "1");
-        }
-        instance->max_value = (uint64_t)strtol(furi_string_get_cstr(max_value_s), NULL, 2);
-        furi_string_free(max_value_s);
-
-        // Now we are ready to set file template for using in the future with snprintf
-        // for sending attack payload
-        snprintf(
-            instance->file_template,
-            sizeof(instance->file_template),
-            subbrute_key_file_start,
-            instance->protocol_info->frequency,
-            subbrute_protocol_preset(instance->protocol_info->preset),
-            subbrute_protocol_file(instance->protocol_info->file),
-            instance->protocol_info->bits);
-#ifdef FURI_DEBUG
-        FURI_LOG_D(
-            TAG,
-            "tail: %d, file_template: %s",
-            instance->protocol_info->te,
-            instance->file_template);
-#endif
-    }
-
-    // Init payload
-    FlipperFormat* flipper_format = flipper_format_string_alloc();
-    if(subbrute_device_create_packet_parsed(instance, flipper_format, instance->key_index, false)) {
-        instance->state = SubBruteDeviceStateReady;
-        subbrute_device_send_callback(instance);
-    }
-    flipper_format_free(flipper_format);
-
     return SubBruteFileResultOk;
 }
 
@@ -751,7 +358,6 @@ void subbrute_device_attack_set_default_values(
     instance->attack = default_attack;
     instance->key_index = 0x00;
     instance->load_index = 0x00;
-    memset(instance->file_template, 0, sizeof(instance->file_template));
     memset(instance->current_key, 0, sizeof(instance->current_key));
 
     if(default_attack != SubBruteAttackLoadFile) {
@@ -761,12 +367,6 @@ void subbrute_device_attack_set_default_values(
     }
 }
 
-void subbrute_device_send_callback(SubBruteDevice* instance) {
-    if(instance->callback != NULL) {
-        instance->callback(instance->context, instance->state);
-    }
-}
-
 const char* subbrute_device_error_get_desc(SubBruteFileResult error_id) {
     const char* result;
     switch(error_id) {

+ 1 - 46
applications/plugins/subbrute/subbrute_device.h

@@ -31,19 +31,9 @@ typedef enum {
     SubBruteFileResultMissingOrIncorrectTe,
 } SubBruteFileResult;
 
-typedef enum {
-    SubBruteDeviceStateIDLE,
-    SubBruteDeviceStateReady,
-    SubBruteDeviceStateTx,
-    SubBruteDeviceStateFinished
-} SubBruteDeviceState;
-
-typedef void (*SubBruteDeviceWorkerCallback)(void* context, SubBruteDeviceState state);
 typedef struct {
-    SubBruteDeviceState state;
     const SubBruteProtocol* protocol_info;
     SubBruteProtocol* file_protocol_info;
-    volatile bool worker_running;
 
     // Current step
     uint64_t key_index;
@@ -51,28 +41,17 @@ typedef struct {
     uint8_t load_index;
 
     // SubGhz
-    FuriThread* thread;
     SubGhzReceiver* receiver;
     SubGhzProtocolDecoderBase* decoder_result;
     SubGhzEnvironment* environment;
-    SubGhzTransmitter* transmitter;
 
     // Attack state
     SubBruteAttacks attack;
-    char file_template[SUBBRUTE_TEXT_STORE_SIZE];
-
     uint64_t max_value;
 
     // Loaded info for attack type
     char current_key[SUBBRUTE_PAYLOAD_SIZE];
     char file_key[SUBBRUTE_MAX_LEN_NAME];
-
-    // Manual transmit
-    uint32_t last_time_tx_data;
-
-    // Callback for changed states
-    SubBruteDeviceWorkerCallback callback;
-    void* context;
 } SubBruteDevice;
 
 SubBruteDevice* subbrute_device_alloc();
@@ -83,33 +62,9 @@ const char* subbrute_device_error_get_desc(SubBruteFileResult error_id);
 SubBruteFileResult subbrute_device_attack_set(SubBruteDevice* context, SubBruteAttacks type);
 uint8_t subbrute_device_load_from_file(SubBruteDevice* context, const char* file_path);
 
-bool subbrute_device_is_worker_running(SubBruteDevice* instance);
-SubBruteAttacks subbrute_device_get_attack(SubBruteDevice* instance);
-uint64_t subbrute_device_get_max_value(SubBruteDevice* instance);
-uint64_t subbrute_device_get_step(SubBruteDevice* instance);
 uint64_t subbrute_device_add_step(SubBruteDevice* instance, int8_t step);
-void subbrute_device_set_load_index(SubBruteDevice* instance, uint64_t load_index);
-void subbrute_device_reset_step(SubBruteDevice* instance);
-const char* subbrute_device_get_file_key(SubBruteDevice* instance);
-
-bool subbrute_worker_start(SubBruteDevice* instance);
-void subbrute_worker_stop(SubBruteDevice* instance);
-bool subbrute_device_transmit_current_key(SubBruteDevice* instance);
-bool subbrute_device_can_manual_transmit(SubBruteDevice* instance);
-void subbrute_device_set_callback(
-    SubBruteDevice* instance,
-    SubBruteDeviceWorkerCallback callback,
-    void* context);
 
 void subbrute_device_free_protocol_info(SubBruteDevice* instance);
-int32_t subbrute_worker_thread(void* context);
 void subbrute_device_attack_set_default_values(
     SubBruteDevice* context,
-    SubBruteAttacks default_attack);
-bool subbrute_device_create_packet_parsed(
-    SubBruteDevice* instance,
-    FlipperFormat* flipper_format,
-    uint64_t step,
-    bool small);
-void subbrute_device_send_callback(SubBruteDevice* instance);
-void subbrute_device_subghz_transmit(SubBruteDevice* instance, FlipperFormat* flipper_format);
+    SubBruteAttacks default_attack);

+ 4 - 2
applications/plugins/subbrute/subbrute_i.h

@@ -23,6 +23,7 @@
 
 #include "subbrute.h"
 #include "subbrute_device.h"
+#include "helpers/subbrute_worker.h"
 #include "views/subbrute_attack_view.h"
 #include "views/subbrute_main_view.h"
 
@@ -62,9 +63,10 @@ struct SubBruteState {
 
     // SubBruteDevice
     SubBruteDevice* device;
+    // SubBruteWorker
+    SubBruteWorker* worker;
 };
 
 void subbrute_show_loading_popup(void* context, bool show);
 void subbrute_text_input_callback(void* context);
-void subbrute_popup_closed_callback(void* context);
-uint64_t subbrute_get_step(void* context);
+void subbrute_popup_closed_callback(void* context);

+ 218 - 0
applications/plugins/subbrute/subbrute_protocols.c

@@ -1,5 +1,7 @@
 #include "subbrute_protocols.h"
 
+#define TAG "SubBruteProtocols"
+
 /**
  * CAME 12bit 303MHz
  */
@@ -201,6 +203,16 @@ static const char* subbrute_protocol_file_types[] = {
     [PrincetonFileProtocol] = "Princeton",
     [RAWFileProtocol] = "RAW"};
 
+/**
+ * Values to not use less memory for packet parse operations
+ */
+static const char* subbrute_key_file_start_no_tail =
+    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nRepeat: %d\n";
+static const char* subbrute_key_file_start_with_tail =
+    "Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: %u\nPreset: %s\nProtocol: %s\nBit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
+static const char* subbrute_key_small_no_tail = "Bit: %d\nKey: %s\nRepeat: %d\nRepeat: %d\n";
+static const char* subbrute_key_small_with_tail = "Bit: %d\nKey: %s\nTE: %d\nRepeat: %d\n";
+
 const char* subbrute_protocol_name(SubBruteAttacks index) {
     return subbrute_protocol_names[index];
 }
@@ -235,4 +247,210 @@ SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name) {
     }
 
     return RAWFileProtocol;
+}
+
+FuriString*
+    subbrute_protocol_default_payload(uint64_t step, uint8_t bits, uint8_t te, uint8_t repeat) {
+    FuriString* candidate = furi_string_alloc_set_str("                       ");
+
+    FuriString* buffer = furi_string_alloc_printf("%16llX", step);
+    int j = 0;
+    for(uint8_t i = 0; i < 16; i++) {
+        if(furi_string_get_char(buffer, i) != ' ') {
+            furi_string_set_char(candidate, i + j, furi_string_get_char(buffer, i));
+        } else {
+            furi_string_set_char(candidate, i + j, '0');
+        }
+        if(i % 2 != 0) {
+            j++;
+        }
+    }
+    furi_string_free(buffer);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+    FuriString* result;
+
+    if(te) {
+        result = furi_string_alloc_printf(
+            subbrute_key_small_with_tail, bits, furi_string_get_cstr(candidate), te, repeat);
+    } else {
+        result = furi_string_alloc_printf(
+            subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "result: %s", furi_string_get_cstr(result));
+#endif
+
+    furi_string_free(candidate);
+
+    return result;
+}
+
+FuriString* subbrute_protocol_file_payload(
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat,
+    uint8_t load_index,
+    const char* file_key) {
+    FuriString* candidate = furi_string_alloc();
+    if(step >= sizeof(file_key)) {
+        return false;
+    }
+    char subbrute_payload_byte[4];
+    furi_string_set_str(candidate, file_key);
+    snprintf(subbrute_payload_byte, 4, "%02X ", (uint8_t)step);
+    furi_string_replace_at(candidate, load_index * 3, 3, subbrute_payload_byte);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+    FuriString* result;
+
+    if(te) {
+        result = furi_string_alloc_printf(
+            subbrute_key_small_with_tail, bits, furi_string_get_cstr(candidate), te, repeat);
+    } else {
+        result = furi_string_alloc_printf(
+            subbrute_key_small_no_tail, bits, furi_string_get_cstr(candidate), repeat);
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "result: %s", furi_string_get_cstr(result));
+#endif
+
+    furi_string_free(candidate);
+
+    return result;
+}
+
+FuriString* subbrute_protocol_default_generate_file(
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat) {
+    FuriString* candidate = furi_string_alloc_set_str("                       ");
+
+    FuriString* buffer = furi_string_alloc_printf("%16llX", step);
+    int j = 0;
+    for(uint8_t i = 0; i < 16; i++) {
+        if(furi_string_get_char(buffer, i) != ' ') {
+            furi_string_set_char(candidate, i + j, furi_string_get_char(buffer, i));
+        } else {
+            furi_string_set_char(candidate, i + j, '0');
+        }
+        if(i % 2 != 0) {
+            j++;
+        }
+    }
+    furi_string_free(buffer);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+    FuriString* result;
+
+    if(te) {
+        result = furi_string_alloc_printf(
+            subbrute_key_file_start_with_tail,
+            frequency,
+            preset,
+            file,
+            bits,
+            furi_string_get_cstr(candidate),
+            te,
+            repeat);
+    } else {
+        result = furi_string_alloc_printf(
+            subbrute_key_file_start_no_tail,
+            frequency,
+            preset,
+            file,
+            bits,
+            furi_string_get_cstr(candidate),
+            repeat);
+    }
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "result: %s", furi_string_get_cstr(result));
+#endif
+
+    furi_string_free(candidate);
+
+    return result;
+}
+
+FuriString* subbrute_protocol_file_generate_file(
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat,
+    uint8_t load_index,
+    const char* file_key) {
+    FuriString* candidate = furi_string_alloc();
+    if(step >= sizeof(file_key)) {
+        return false;
+    }
+    char subbrute_payload_byte[4];
+    furi_string_set_str(candidate, file_key);
+    snprintf(subbrute_payload_byte, 4, "%02X ", (uint8_t)step);
+    furi_string_replace_at(candidate, load_index * 3, 3, subbrute_payload_byte);
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "candidate: %s, step: %lld", furi_string_get_cstr(candidate), step);
+#endif
+    FuriString* result;
+
+    if(te) {
+        result = furi_string_alloc_printf(
+            subbrute_key_file_start_with_tail,
+            frequency,
+            preset,
+            file,
+            bits,
+            furi_string_get_cstr(candidate),
+            te,
+            repeat);
+    } else {
+        result = furi_string_alloc_printf(
+            subbrute_key_file_start_no_tail,
+            frequency,
+            preset,
+            file,
+            bits,
+            furi_string_get_cstr(candidate),
+            repeat);
+    }
+
+#ifdef FURI_DEBUG
+    FURI_LOG_D(TAG, "result: %s", furi_string_get_cstr(result));
+#endif
+
+    furi_string_free(candidate);
+
+    return result;
+}
+
+uint64_t subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits) {
+    uint64_t max_value;
+    if(attack_type == SubBruteAttackLoadFile) {
+        max_value = 0x3F;
+    } else {
+        FuriString* max_value_s;
+        max_value_s = furi_string_alloc();
+        for(uint8_t i = 0; i < bits; i++) {
+            furi_string_cat_printf(max_value_s, "1");
+        }
+        max_value = (uint64_t)strtol(furi_string_get_cstr(max_value_s), NULL, 2);
+        furi_string_free(max_value_s);
+    }
+
+    return max_value;
 }

+ 30 - 1
applications/plugins/subbrute/subbrute_protocols.h

@@ -54,4 +54,33 @@ const char* subbrute_protocol_preset(FuriHalSubGhzPreset preset);
 const char* subbrute_protocol_file(SubBruteFileProtocol protocol);
 FuriHalSubGhzPreset subbrute_protocol_convert_preset(FuriString* preset_name);
 SubBruteFileProtocol subbrute_protocol_file_protocol_name(FuriString* name);
-const char* subbrute_protocol_name(SubBruteAttacks index);
+const char* subbrute_protocol_name(SubBruteAttacks index);
+
+FuriString*
+    subbrute_protocol_default_payload(uint64_t step, uint8_t bits, uint8_t te, uint8_t repeat);
+FuriString* subbrute_protocol_file_payload(
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat,
+    uint8_t load_index,
+    const char* file_key);
+FuriString* subbrute_protocol_default_generate_file(
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat);
+FuriString* subbrute_protocol_file_generate_file(
+    uint32_t frequency,
+    FuriHalSubGhzPreset preset,
+    SubBruteFileProtocol file,
+    uint64_t step,
+    uint8_t bits,
+    uint8_t te,
+    uint8_t repeat,
+    uint8_t load_index,
+    const char* file_key);
+uint64_t subbrute_protocol_calc_max_value(SubBruteAttacks attack_type, uint8_t bits);

+ 2 - 8
applications/plugins/subbrute/views/subbrute_main_view.c

@@ -227,10 +227,7 @@ bool subbrute_main_view_input(InputEvent* event, void* context) {
 
 #ifdef FURI_DEBUG
         with_view_model(
-            instance->view, (SubBruteMainViewModel * model) {
-                index = model->index;
-                return false;
-            });
+            instance->view, SubBruteMainViewModel * model, { index = model->index; }, false);
         FURI_LOG_I(TAG, "Index: %d", index);
 #endif
 
@@ -265,10 +262,7 @@ bool subbrute_main_view_input(InputEvent* event, void* context) {
 
 #ifdef FURI_DEBUG
         with_view_model(
-            instance->view, (SubBruteMainViewModel * model) {
-                index = model->index;
-                return false;
-            });
+            instance->view, SubBruteMainViewModel * model, { index = model->index; }, false);
         FURI_LOG_I(TAG, "Index: %d", index);
 #endif