Просмотр исходного кода

[FL-2527] Updater: Migrating to new manifest path convention (#1213)

* Updater: Migrating to new manifest path convention
* RPC: Added update preparation status to RPC
* RPC: bumped protobuf submodule
* Bumped protobuf_version.h
* FuriCore: add missing include. Lib: make mlib smaller
* Explicitly tell where we have doubles and fix random in animations
* makefile: added -DLFS_NO_DEBUG
* Updater: path len constant dedup
* Updater: checking for hardware version match before parsing manifest
* LD: moved _DRIVER_CONTEXT sections to .bss, where they belong.
* LD: avoiding PROBGITS warning, moved _CONTEXT to data
* Updater: Added version check on update package - refusing to install outdated

Co-authored-by: あく <alleteam@gmail.com>
hedger 3 лет назад
Родитель
Сommit
597ee5b939
32 измененных файлов с 289 добавлено и 216 удалено
  1. 2 4
      applications/bt/bt_cli.c
  2. 1 1
      applications/bt/bt_debug_app/views/bt_test.c
  3. 2 1
      applications/desktop/animations/animation_manager.c
  4. 2 1
      applications/desktop/animations/views/bubble_animation_view.c
  5. 1 1
      applications/infrared/helpers/infrared_parser.cpp
  6. 4 2
      applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp
  7. 12 5
      applications/rpc/rpc_system.c
  8. 1 1
      applications/subghz/subghz_cli.c
  9. 1 1
      applications/unit_tests/test_index.c
  10. 1 1
      applications/updater/cli/updater_cli.c
  11. 21 9
      applications/updater/util/update_task.c
  12. 1 2
      applications/updater/util/update_task_worker_backup.c
  13. 5 1
      assets/compiled/flipper.pb.h
  14. 1 1
      assets/compiled/protobuf_version.h
  15. 4 0
      assets/compiled/system.pb.c
  16. 30 0
      assets/compiled/system.pb.h
  17. 1 1
      assets/protobuf
  18. 1 0
      core/furi/memmgr_heap.c
  19. 1 1
      firmware/targets/f7/Src/main.c
  20. 31 34
      firmware/targets/f7/Src/update.c
  21. 7 1
      firmware/targets/f7/furi_hal/furi_hal_random.c
  22. 3 3
      firmware/targets/f7/furi_hal/furi_hal_subghz.c
  23. 3 2
      firmware/targets/f7/stm32wb55xx_flash.ld
  24. 2 2
      firmware/targets/f7/target.mk
  25. 1 1
      lib/flipper_format/flipper_format_stream.c
  26. 1 1
      lib/lib.mk
  27. 4 1
      lib/subghz/subghz_tx_rx_worker.c
  28. 4 5
      lib/update_util/update_manifest.c
  29. 2 0
      lib/update_util/update_manifest.h
  30. 123 111
      lib/update_util/update_operation.c
  31. 8 16
      lib/update_util/update_operation.h
  32. 8 6
      scripts/update.py

+ 2 - 4
applications/bt/bt_cli.c

@@ -68,7 +68,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
 
 
         while(!cli_cmd_interrupt_received(cli)) {
         while(!cli_cmd_interrupt_received(cli)) {
             osDelay(250);
             osDelay(250);
-            printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi());
+            printf("RSSI: %6.1f dB\r", (double)furi_hal_bt_get_rssi());
             fflush(stdout);
             fflush(stdout);
         }
         }
 
 
@@ -140,11 +140,9 @@ static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
         printf("Press CTRL+C to stop\r\n");
         printf("Press CTRL+C to stop\r\n");
         furi_hal_bt_start_packet_rx(channel, datarate);
         furi_hal_bt_start_packet_rx(channel, datarate);
 
 
-        float rssi_raw = 0;
         while(!cli_cmd_interrupt_received(cli)) {
         while(!cli_cmd_interrupt_received(cli)) {
             osDelay(250);
             osDelay(250);
-            rssi_raw = furi_hal_bt_get_rssi();
-            printf("RSSI: %03.1f dB\r", rssi_raw);
+            printf("RSSI: %03.1f dB\r", (double)furi_hal_bt_get_rssi());
             fflush(stdout);
             fflush(stdout);
         }
         }
         uint16_t packets_received = furi_hal_bt_stop_packet_test();
         uint16_t packets_received = furi_hal_bt_stop_packet_test();

+ 1 - 1
applications/bt/bt_debug_app/views/bt_test.c

@@ -99,7 +99,7 @@ static void bt_test_draw_callback(Canvas* canvas, void* _model) {
     canvas_draw_str(canvas, 6, 60, model->message);
     canvas_draw_str(canvas, 6, 60, model->message);
     if(model->state == BtTestStateStarted) {
     if(model->state == BtTestStateStarted) {
         if(model->rssi != 0.0f) {
         if(model->rssi != 0.0f) {
-            snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", model->rssi);
+            snprintf(info_str, sizeof(info_str), "RSSI:%3.1f dB", (double)model->rssi);
             canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
             canvas_draw_str_aligned(canvas, 124, 60, AlignRight, AlignBottom, info_str);
         }
         }
     } else if(model->state == BtTestStateStopped) {
     } else if(model->state == BtTestStateStopped) {

+ 2 - 1
applications/desktop/animations/animation_manager.c

@@ -1,6 +1,7 @@
 #include <gui/view_stack.h>
 #include <gui/view_stack.h>
 #include <stdint.h>
 #include <stdint.h>
 #include <furi.h>
 #include <furi.h>
+#include <furi_hal.h>
 #include <m-string.h>
 #include <m-string.h>
 #include <portmacro.h>
 #include <portmacro.h>
 #include <dolphin/dolphin.h>
 #include <dolphin/dolphin.h>
@@ -391,7 +392,7 @@ static StorageAnimation*
         }
         }
     }
     }
 
 
-    uint32_t lucky_number = random() % whole_weight;
+    uint32_t lucky_number = furi_hal_random_get() % whole_weight;
     uint32_t weight = 0;
     uint32_t weight = 0;
 
 
     StorageAnimation* selected = NULL;
     StorageAnimation* selected = NULL;

+ 2 - 1
applications/desktop/animations/views/bubble_animation_view.c

@@ -100,7 +100,8 @@ static const FrameBubble*
         return NULL;
         return NULL;
     }
     }
 
 
-    uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles);
+    uint8_t index =
+        furi_hal_random_get() % (active ? model->active_bubbles : model->passive_bubbles);
     const BubbleAnimation* animation = model->current;
     const BubbleAnimation* animation = model->current;
 
 
     for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {
     for(int i = 0; i < animation->frame_bubble_sequences_count; ++i) {

+ 1 - 1
applications/infrared/helpers/infrared_parser.cpp

@@ -145,7 +145,7 @@ bool infrared_parser_is_raw_signal_valid(
             frequency);
             frequency);
         result = false;
         result = false;
     } else if((duty_cycle <= 0) || (duty_cycle > 1)) {
     } else if((duty_cycle <= 0) || (duty_cycle > 1)) {
-        FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", duty_cycle);
+        FURI_LOG_E(TAG, "Duty cycle is out of range (0 - 1): %f", (double)duty_cycle);
         result = false;
         result = false;
     } else if((timings_cnt <= 0) || (timings_cnt > MAX_TIMINGS_AMOUNT)) {
     } else if((timings_cnt <= 0) || (timings_cnt > MAX_TIMINGS_AMOUNT)) {
         FURI_LOG_E(
         FURI_LOG_E(

+ 4 - 2
applications/lfrfid_debug/view_modules/lfrfid_view_tune_vm.cpp

@@ -29,6 +29,8 @@ void LfRfidViewTuneVM::view_draw_callback(Canvas* canvas, void* _model) {
 
 
     constexpr uint8_t buffer_size = 128;
     constexpr uint8_t buffer_size = 128;
     char buffer[buffer_size + 1];
     char buffer[buffer_size + 1];
+    double freq = ((float)SystemCoreClock / ((float)model->ARR + 1));
+    double duty = ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f;
     snprintf(
     snprintf(
         buffer,
         buffer,
         buffer_size,
         buffer_size,
@@ -38,10 +40,10 @@ void LfRfidViewTuneVM::view_draw_callback(Canvas* canvas, void* _model) {
         "duty = %.4f",
         "duty = %.4f",
         model->pos == 0 ? ">" : "",
         model->pos == 0 ? ">" : "",
         model->ARR,
         model->ARR,
-        (float)SystemCoreClock / ((float)model->ARR + 1),
+        freq,
         model->pos == 1 ? ">" : "",
         model->pos == 1 ? ">" : "",
         model->CCR,
         model->CCR,
-        ((float)model->CCR + 1) / ((float)model->ARR + 1) * 100.0f);
+        duty);
     elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer);
     elements_multiline_text_aligned(canvas, 2, 2, AlignLeft, AlignTop, buffer);
 }
 }
 
 

+ 12 - 5
applications/rpc/rpc_system.c

@@ -275,15 +275,22 @@ static void rpc_system_system_update_request_process(const PB_Main* request, voi
     RpcSession* session = (RpcSession*)context;
     RpcSession* session = (RpcSession*)context;
     furi_assert(session);
     furi_assert(session);
 
 
-    bool update_prepare_result =
-        update_operation_prepare(request->content.system_update_request.update_manifest) ==
-        UpdatePrepareResultOK;
+    UpdatePrepareResult update_prepare_result =
+        update_operation_prepare(request->content.system_update_request.update_manifest);
+    /* RPC enum does not have such entry; setting to closest one */
+    if(update_prepare_result == UpdatePrepareResultOutdatedManifestVersion) {
+        update_prepare_result = UpdatePrepareResultManifestInvalid;
+    }
 
 
     PB_Main* response = malloc(sizeof(PB_Main));
     PB_Main* response = malloc(sizeof(PB_Main));
     response->command_id = request->command_id;
     response->command_id = request->command_id;
     response->has_next = false;
     response->has_next = false;
-    response->command_status = update_prepare_result ? PB_CommandStatus_OK :
-                                                       PB_CommandStatus_ERROR_INVALID_PARAMETERS;
+    response->command_status = (update_prepare_result == UpdatePrepareResultOK) ?
+                                   PB_CommandStatus_OK :
+                                   PB_CommandStatus_ERROR_INVALID_PARAMETERS;
+    response->which_content = PB_Main_system_update_response_tag;
+    response->content.system_update_response.code =
+        (PB_System_UpdateResponse_UpdateResultCode)update_prepare_result;
     rpc_send_and_release(session, response);
     rpc_send_and_release(session, response);
     free(response);
     free(response);
 }
 }

+ 1 - 1
applications/subghz/subghz_cli.c

@@ -94,7 +94,7 @@ void subghz_cli_command_rx_carrier(Cli* cli, string_t args, void* context) {
 
 
     while(!cli_cmd_interrupt_received(cli)) {
     while(!cli_cmd_interrupt_received(cli)) {
         osDelay(250);
         osDelay(250);
-        printf("RSSI: %03.1fdbm\r", furi_hal_subghz_get_rssi());
+        printf("RSSI: %03.1fdbm\r", (double)furi_hal_subghz_get_rssi());
         fflush(stdout);
         fflush(stdout);
     }
     }
 
 

+ 1 - 1
applications/unit_tests/test_index.c

@@ -70,7 +70,7 @@ void unit_tests_cli(Cli* cli, string_t args, void* context) {
 
 
         cycle_counter = (furi_hal_get_tick() - cycle_counter);
         cycle_counter = (furi_hal_get_tick() - cycle_counter);
 
 
-        FURI_LOG_I(TAG, "Consumed: %0.2fs", (float)cycle_counter / 1000);
+        FURI_LOG_I(TAG, "Consumed: %u us", cycle_counter);
 
 
         if(test_result == 0) {
         if(test_result == 0) {
             furi_hal_delay_ms(200); /* wait for tested services and apps to deallocate */
             furi_hal_delay_ms(200); /* wait for tested services and apps to deallocate */

+ 1 - 1
applications/updater/cli/updater_cli.c

@@ -52,7 +52,7 @@ static void updater_cli_restore(string_t args) {
 static void updater_cli_help(string_t args) {
 static void updater_cli_help(string_t args) {
     UNUSED(args);
     UNUSED(args);
     printf("Commands:\r\n"
     printf("Commands:\r\n"
-           "\tinstall /ext/update/PACKAGE/update.fuf - verify & apply update package\r\n"
+           "\tinstall /ext/path/to/update.fuf - verify & apply update package\r\n"
            "\tbackup /ext/path/to/backup.tar - create internal storage backup\r\n"
            "\tbackup /ext/path/to/backup.tar - create internal storage backup\r\n"
            "\trestore /ext/path/to/backup.tar - restore internal storage backup\r\n");
            "\trestore /ext/path/to/backup.tar - restore internal storage backup\r\n");
 }
 }

+ 21 - 9
applications/updater/util/update_task.c

@@ -260,16 +260,18 @@ bool update_task_parse_manifest(UpdateTask* update_task) {
     string_init(manifest_path);
     string_init(manifest_path);
 
 
     do {
     do {
-        update_task_set_progress(update_task, UpdateTaskStageProgress, 10);
-        if(!update_operation_get_current_package_path(
-               update_task->storage, update_task->update_path)) {
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 13);
+        if(!furi_hal_version_do_i_belong_here()) {
             break;
             break;
         }
         }
 
 
-        path_concat(
-            string_get_cstr(update_task->update_path),
-            UPDATE_MANIFEST_DEFAULT_NAME,
-            manifest_path);
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 20);
+        if(!update_operation_get_current_package_manifest_path(
+               update_task->storage, manifest_path)) {
+            break;
+        }
+
+        path_extract_dirname(string_get_cstr(manifest_path), update_task->update_path);
         update_task_set_progress(update_task, UpdateTaskStageProgress, 30);
         update_task_set_progress(update_task, UpdateTaskStageProgress, 30);
 
 
         UpdateManifest* manifest = update_task->manifest;
         UpdateManifest* manifest = update_task->manifest;
@@ -277,6 +279,16 @@ bool update_task_parse_manifest(UpdateTask* update_task) {
             break;
             break;
         }
         }
 
 
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 40);
+        if(manifest->manifest_version < UPDATE_OPERATION_MIN_MANIFEST_VERSION) {
+            break;
+        }
+
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 50);
+        if(manifest->target != furi_hal_version_get_hw_target()) {
+            break;
+        }
+
         update_task->state.groups = update_task_get_task_groups(update_task);
         update_task->state.groups = update_task_get_task_groups(update_task);
         for(size_t stage_counter = 0; stage_counter < COUNT_OF(update_task_stage_progress);
         for(size_t stage_counter = 0; stage_counter < COUNT_OF(update_task_stage_progress);
             ++stage_counter) {
             ++stage_counter) {
@@ -286,13 +298,13 @@ bool update_task_parse_manifest(UpdateTask* update_task) {
             }
             }
         }
         }
 
 
-        update_task_set_progress(update_task, UpdateTaskStageProgress, 50);
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 60);
         if((update_task->state.groups & UpdateTaskStageGroupFirmware) &&
         if((update_task->state.groups & UpdateTaskStageGroupFirmware) &&
            !update_task_check_file_exists(update_task, manifest->firmware_dfu_image)) {
            !update_task_check_file_exists(update_task, manifest->firmware_dfu_image)) {
             break;
             break;
         }
         }
 
 
-        update_task_set_progress(update_task, UpdateTaskStageProgress, 70);
+        update_task_set_progress(update_task, UpdateTaskStageProgress, 80);
         if((update_task->state.groups & UpdateTaskStageGroupRadio) &&
         if((update_task->state.groups & UpdateTaskStageGroupRadio) &&
            (!update_task_check_file_exists(update_task, manifest->radio_image) ||
            (!update_task_check_file_exists(update_task, manifest->radio_image) ||
             (manifest->radio_version.version.type == 0))) {
             (manifest->radio_version.version.type == 0))) {

+ 1 - 2
applications/updater/util/update_task_worker_backup.c

@@ -63,7 +63,6 @@ static bool update_task_post_update(UpdateTask* update_task) {
 
 
     TarArchive* archive = tar_archive_alloc(update_task->storage);
     TarArchive* archive = tar_archive_alloc(update_task->storage);
     do {
     do {
-        CHECK_RESULT(update_task_parse_manifest(update_task));
         path_concat(
         path_concat(
             string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path);
             string_get_cstr(update_task->update_path), LFS_BACKUP_DEFAULT_FILENAME, file_path);
 
 
@@ -114,7 +113,7 @@ int32_t update_task_worker_backup_restore(void* context) {
         return UPDATE_TASK_NOERR;
         return UPDATE_TASK_NOERR;
     }
     }
 
 
-    if(!update_operation_get_current_package_path(update_task->storage, update_task->update_path)) {
+    if(!update_task_parse_manifest(update_task)) {
         return UPDATE_TASK_FAILED;
         return UPDATE_TASK_FAILED;
     }
     }
 
 

+ 5 - 1
assets/compiled/flipper.pb.h

@@ -103,6 +103,7 @@ typedef struct _PB_Main {
         PB_Storage_BackupRestoreRequest storage_backup_restore_request;
         PB_Storage_BackupRestoreRequest storage_backup_restore_request;
         PB_System_PowerInfoRequest system_power_info_request;
         PB_System_PowerInfoRequest system_power_info_request;
         PB_System_PowerInfoResponse system_power_info_response;
         PB_System_PowerInfoResponse system_power_info_response;
+        PB_System_UpdateResponse system_update_response;
     } content; 
     } content; 
 } PB_Main;
 } PB_Main;
 
 
@@ -171,6 +172,7 @@ extern "C" {
 #define PB_Main_storage_backup_restore_request_tag 43
 #define PB_Main_storage_backup_restore_request_tag 43
 #define PB_Main_system_power_info_request_tag    44
 #define PB_Main_system_power_info_request_tag    44
 #define PB_Main_system_power_info_response_tag   45
 #define PB_Main_system_power_info_response_tag   45
+#define PB_Main_system_update_response_tag       46
 
 
 /* Struct field encoding specification for nanopb */
 /* Struct field encoding specification for nanopb */
 #define PB_Empty_FIELDLIST(X, a) \
 #define PB_Empty_FIELDLIST(X, a) \
@@ -228,7 +230,8 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_update_request,content.system
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_backup_create_request,content.storage_backup_create_request),  42) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_backup_create_request,content.storage_backup_create_request),  42) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_backup_restore_request,content.storage_backup_restore_request),  43) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,storage_backup_restore_request,content.storage_backup_restore_request),  43) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_power_info_request,content.system_power_info_request),  44) \
 X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_power_info_request,content.system_power_info_request),  44) \
-X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_power_info_response,content.system_power_info_response),  45)
+X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_power_info_response,content.system_power_info_response),  45) \
+X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_update_response,content.system_update_response),  46)
 #define PB_Main_CALLBACK NULL
 #define PB_Main_CALLBACK NULL
 #define PB_Main_DEFAULT NULL
 #define PB_Main_DEFAULT NULL
 #define PB_Main_content_empty_MSGTYPE PB_Empty
 #define PB_Main_content_empty_MSGTYPE PB_Empty
@@ -273,6 +276,7 @@ X(a, STATIC,   ONEOF,    MSG_W_CB, (content,system_power_info_response,content.s
 #define PB_Main_content_storage_backup_restore_request_MSGTYPE PB_Storage_BackupRestoreRequest
 #define PB_Main_content_storage_backup_restore_request_MSGTYPE PB_Storage_BackupRestoreRequest
 #define PB_Main_content_system_power_info_request_MSGTYPE PB_System_PowerInfoRequest
 #define PB_Main_content_system_power_info_request_MSGTYPE PB_System_PowerInfoRequest
 #define PB_Main_content_system_power_info_response_MSGTYPE PB_System_PowerInfoResponse
 #define PB_Main_content_system_power_info_response_MSGTYPE PB_System_PowerInfoResponse
+#define PB_Main_content_system_update_response_MSGTYPE PB_System_UpdateResponse
 
 
 extern const pb_msgdesc_t PB_Empty_msg;
 extern const pb_msgdesc_t PB_Empty_msg;
 extern const pb_msgdesc_t PB_StopSession_msg;
 extern const pb_msgdesc_t PB_StopSession_msg;

+ 1 - 1
assets/compiled/protobuf_version.h

@@ -1,3 +1,3 @@
 #pragma once
 #pragma once
 #define PROTOBUF_MAJOR_VERSION 0
 #define PROTOBUF_MAJOR_VERSION 0
-#define PROTOBUF_MINOR_VERSION 6
+#define PROTOBUF_MINOR_VERSION 7

+ 4 - 0
assets/compiled/system.pb.c

@@ -48,6 +48,9 @@ PB_BIND(PB_System_ProtobufVersionResponse, PB_System_ProtobufVersionResponse, AU
 PB_BIND(PB_System_UpdateRequest, PB_System_UpdateRequest, AUTO)
 PB_BIND(PB_System_UpdateRequest, PB_System_UpdateRequest, AUTO)
 
 
 
 
+PB_BIND(PB_System_UpdateResponse, PB_System_UpdateResponse, AUTO)
+
+
 PB_BIND(PB_System_PowerInfoRequest, PB_System_PowerInfoRequest, AUTO)
 PB_BIND(PB_System_PowerInfoRequest, PB_System_PowerInfoRequest, AUTO)
 
 
 
 
@@ -56,3 +59,4 @@ PB_BIND(PB_System_PowerInfoResponse, PB_System_PowerInfoResponse, AUTO)
 
 
 
 
 
 
+

+ 30 - 0
assets/compiled/system.pb.h

@@ -16,6 +16,17 @@ typedef enum _PB_System_RebootRequest_RebootMode {
     PB_System_RebootRequest_RebootMode_UPDATE = 2 
     PB_System_RebootRequest_RebootMode_UPDATE = 2 
 } PB_System_RebootRequest_RebootMode;
 } PB_System_RebootRequest_RebootMode;
 
 
+typedef enum _PB_System_UpdateResponse_UpdateResultCode { 
+    PB_System_UpdateResponse_UpdateResultCode_OK = 0, 
+    PB_System_UpdateResponse_UpdateResultCode_ManifestPathInvalid = 1, 
+    PB_System_UpdateResponse_UpdateResultCode_ManifestFolderNotFound = 2, 
+    PB_System_UpdateResponse_UpdateResultCode_ManifestInvalid = 3, 
+    PB_System_UpdateResponse_UpdateResultCode_StageMissing = 4, 
+    PB_System_UpdateResponse_UpdateResultCode_StageIntegrityError = 5, 
+    PB_System_UpdateResponse_UpdateResultCode_ManifestPointerError = 6, 
+    PB_System_UpdateResponse_UpdateResultCode_TargetMismatch = 7 
+} PB_System_UpdateResponse_UpdateResultCode;
+
 /* Struct definitions */
 /* Struct definitions */
 typedef struct _PB_System_DeviceInfoRequest { 
 typedef struct _PB_System_DeviceInfoRequest { 
     char dummy_field;
     char dummy_field;
@@ -84,6 +95,10 @@ typedef struct _PB_System_RebootRequest {
     PB_System_RebootRequest_RebootMode mode; 
     PB_System_RebootRequest_RebootMode mode; 
 } PB_System_RebootRequest;
 } PB_System_RebootRequest;
 
 
+typedef struct _PB_System_UpdateResponse { 
+    PB_System_UpdateResponse_UpdateResultCode code; 
+} PB_System_UpdateResponse;
+
 typedef struct _PB_System_GetDateTimeResponse { 
 typedef struct _PB_System_GetDateTimeResponse { 
     bool has_datetime;
     bool has_datetime;
     PB_System_DateTime datetime; 
     PB_System_DateTime datetime; 
@@ -100,6 +115,10 @@ typedef struct _PB_System_SetDateTimeRequest {
 #define _PB_System_RebootRequest_RebootMode_MAX PB_System_RebootRequest_RebootMode_UPDATE
 #define _PB_System_RebootRequest_RebootMode_MAX PB_System_RebootRequest_RebootMode_UPDATE
 #define _PB_System_RebootRequest_RebootMode_ARRAYSIZE ((PB_System_RebootRequest_RebootMode)(PB_System_RebootRequest_RebootMode_UPDATE+1))
 #define _PB_System_RebootRequest_RebootMode_ARRAYSIZE ((PB_System_RebootRequest_RebootMode)(PB_System_RebootRequest_RebootMode_UPDATE+1))
 
 
+#define _PB_System_UpdateResponse_UpdateResultCode_MIN PB_System_UpdateResponse_UpdateResultCode_OK
+#define _PB_System_UpdateResponse_UpdateResultCode_MAX PB_System_UpdateResponse_UpdateResultCode_TargetMismatch
+#define _PB_System_UpdateResponse_UpdateResultCode_ARRAYSIZE ((PB_System_UpdateResponse_UpdateResultCode)(PB_System_UpdateResponse_UpdateResultCode_TargetMismatch+1))
+
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 extern "C" {
 extern "C" {
@@ -120,6 +139,7 @@ extern "C" {
 #define PB_System_ProtobufVersionRequest_init_default {0}
 #define PB_System_ProtobufVersionRequest_init_default {0}
 #define PB_System_ProtobufVersionResponse_init_default {0, 0}
 #define PB_System_ProtobufVersionResponse_init_default {0, 0}
 #define PB_System_UpdateRequest_init_default     {NULL}
 #define PB_System_UpdateRequest_init_default     {NULL}
+#define PB_System_UpdateResponse_init_default    {_PB_System_UpdateResponse_UpdateResultCode_MIN}
 #define PB_System_PowerInfoRequest_init_default  {0}
 #define PB_System_PowerInfoRequest_init_default  {0}
 #define PB_System_PowerInfoResponse_init_default {NULL, NULL}
 #define PB_System_PowerInfoResponse_init_default {NULL, NULL}
 #define PB_System_PingRequest_init_zero          {NULL}
 #define PB_System_PingRequest_init_zero          {NULL}
@@ -136,6 +156,7 @@ extern "C" {
 #define PB_System_ProtobufVersionRequest_init_zero {0}
 #define PB_System_ProtobufVersionRequest_init_zero {0}
 #define PB_System_ProtobufVersionResponse_init_zero {0, 0}
 #define PB_System_ProtobufVersionResponse_init_zero {0, 0}
 #define PB_System_UpdateRequest_init_zero        {NULL}
 #define PB_System_UpdateRequest_init_zero        {NULL}
+#define PB_System_UpdateResponse_init_zero       {_PB_System_UpdateResponse_UpdateResultCode_MIN}
 #define PB_System_PowerInfoRequest_init_zero     {0}
 #define PB_System_PowerInfoRequest_init_zero     {0}
 #define PB_System_PowerInfoResponse_init_zero    {NULL, NULL}
 #define PB_System_PowerInfoResponse_init_zero    {NULL, NULL}
 
 
@@ -157,6 +178,7 @@ extern "C" {
 #define PB_System_ProtobufVersionResponse_major_tag 1
 #define PB_System_ProtobufVersionResponse_major_tag 1
 #define PB_System_ProtobufVersionResponse_minor_tag 2
 #define PB_System_ProtobufVersionResponse_minor_tag 2
 #define PB_System_RebootRequest_mode_tag         1
 #define PB_System_RebootRequest_mode_tag         1
+#define PB_System_UpdateResponse_code_tag        1
 #define PB_System_GetDateTimeResponse_datetime_tag 1
 #define PB_System_GetDateTimeResponse_datetime_tag 1
 #define PB_System_SetDateTimeRequest_datetime_tag 1
 #define PB_System_SetDateTimeRequest_datetime_tag 1
 
 
@@ -241,6 +263,11 @@ X(a, POINTER,  SINGULAR, STRING,   update_manifest,   1)
 #define PB_System_UpdateRequest_CALLBACK NULL
 #define PB_System_UpdateRequest_CALLBACK NULL
 #define PB_System_UpdateRequest_DEFAULT NULL
 #define PB_System_UpdateRequest_DEFAULT NULL
 
 
+#define PB_System_UpdateResponse_FIELDLIST(X, a) \
+X(a, STATIC,   SINGULAR, UENUM,    code,              1)
+#define PB_System_UpdateResponse_CALLBACK NULL
+#define PB_System_UpdateResponse_DEFAULT NULL
+
 #define PB_System_PowerInfoRequest_FIELDLIST(X, a) \
 #define PB_System_PowerInfoRequest_FIELDLIST(X, a) \
 
 
 #define PB_System_PowerInfoRequest_CALLBACK NULL
 #define PB_System_PowerInfoRequest_CALLBACK NULL
@@ -266,6 +293,7 @@ extern const pb_msgdesc_t PB_System_PlayAudiovisualAlertRequest_msg;
 extern const pb_msgdesc_t PB_System_ProtobufVersionRequest_msg;
 extern const pb_msgdesc_t PB_System_ProtobufVersionRequest_msg;
 extern const pb_msgdesc_t PB_System_ProtobufVersionResponse_msg;
 extern const pb_msgdesc_t PB_System_ProtobufVersionResponse_msg;
 extern const pb_msgdesc_t PB_System_UpdateRequest_msg;
 extern const pb_msgdesc_t PB_System_UpdateRequest_msg;
+extern const pb_msgdesc_t PB_System_UpdateResponse_msg;
 extern const pb_msgdesc_t PB_System_PowerInfoRequest_msg;
 extern const pb_msgdesc_t PB_System_PowerInfoRequest_msg;
 extern const pb_msgdesc_t PB_System_PowerInfoResponse_msg;
 extern const pb_msgdesc_t PB_System_PowerInfoResponse_msg;
 
 
@@ -284,6 +312,7 @@ extern const pb_msgdesc_t PB_System_PowerInfoResponse_msg;
 #define PB_System_ProtobufVersionRequest_fields &PB_System_ProtobufVersionRequest_msg
 #define PB_System_ProtobufVersionRequest_fields &PB_System_ProtobufVersionRequest_msg
 #define PB_System_ProtobufVersionResponse_fields &PB_System_ProtobufVersionResponse_msg
 #define PB_System_ProtobufVersionResponse_fields &PB_System_ProtobufVersionResponse_msg
 #define PB_System_UpdateRequest_fields &PB_System_UpdateRequest_msg
 #define PB_System_UpdateRequest_fields &PB_System_UpdateRequest_msg
+#define PB_System_UpdateResponse_fields &PB_System_UpdateResponse_msg
 #define PB_System_PowerInfoRequest_fields &PB_System_PowerInfoRequest_msg
 #define PB_System_PowerInfoRequest_fields &PB_System_PowerInfoRequest_msg
 #define PB_System_PowerInfoResponse_fields &PB_System_PowerInfoResponse_msg
 #define PB_System_PowerInfoResponse_fields &PB_System_PowerInfoResponse_msg
 
 
@@ -304,6 +333,7 @@ extern const pb_msgdesc_t PB_System_PowerInfoResponse_msg;
 #define PB_System_ProtobufVersionResponse_size   12
 #define PB_System_ProtobufVersionResponse_size   12
 #define PB_System_RebootRequest_size             2
 #define PB_System_RebootRequest_size             2
 #define PB_System_SetDateTimeRequest_size        24
 #define PB_System_SetDateTimeRequest_size        24
+#define PB_System_UpdateResponse_size            2
 
 
 #ifdef __cplusplus
 #ifdef __cplusplus
 } /* extern "C" */
 } /* extern "C" */

+ 1 - 1
assets/protobuf

@@ -1 +1 @@
-Subproject commit 0ad90705b9434b6f8fb2c4b605069f0d56d8cc70
+Subproject commit ffa62429f3c678537e0e883a3a8c3ae5f1398ed4

+ 1 - 0
core/furi/memmgr_heap.c

@@ -37,6 +37,7 @@
 #include "memmgr_heap.h"
 #include "memmgr_heap.h"
 #include "check.h"
 #include "check.h"
 #include <stdlib.h>
 #include <stdlib.h>
+#include <stdio.h>
 #include <cmsis_os2.h>
 #include <cmsis_os2.h>
 #include <stm32wbxx.h>
 #include <stm32wbxx.h>
 #include <furi_hal_console.h>
 #include <furi_hal_console.h>

+ 1 - 1
firmware/targets/f7/Src/main.c

@@ -48,7 +48,7 @@ int main() {
         flipper_boot_update_exec();
         flipper_boot_update_exec();
         // if things go nice, we shouldn't reach this point.
         // if things go nice, we shouldn't reach this point.
         // But if we do, abandon to avoid bootloops
         // But if we do, abandon to avoid bootloops
-        update_operation_disarm();
+        furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal);
         furi_hal_power_reset();
         furi_hal_power_reset();
     } else {
     } else {
         furi_hal_light_sequence("rgb G");
         furi_hal_light_sequence("rgb G");

+ 31 - 34
firmware/targets/f7/Src/update.c

@@ -11,9 +11,10 @@
 #include <toolbox/path.h>
 #include <toolbox/path.h>
 #include <toolbox/crc32_calc.h>
 #include <toolbox/crc32_calc.h>
 
 
-static FATFS* pfs = NULL;
+#define FS_ROOT_PATH "/"
+#define UPDATE_POINTER_FILE_PATH FS_ROOT_PATH UPDATE_MANIFEST_POINTER_FILE_NAME
 
 
-static const char FS_ROOT_PATH[] = "/";
+static FATFS* pfs = NULL;
 
 
 #define CHECK_FRESULT(result)   \
 #define CHECK_FRESULT(result)   \
     {                           \
     {                           \
@@ -100,42 +101,35 @@ static bool flipper_update_load_stage(const string_t work_dir, UpdateManifest* m
     return false;
     return false;
 }
 }
 
 
-static bool flipper_update_get_work_directory(string_t out_dir) {
-    const uint32_t update_index = furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
-    if(update_index == UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC) {
-        string_set(out_dir, UPDATE_DIR_DEFAULT_REL_PATH);
-        return true;
-    }
-
-    DIR dir;
-    UINT entry_idx = 0;
-    FILINFO fno;
-    CHECK_FRESULT(f_opendir(&dir, UPDATE_DIR_DEFAULT_REL_PATH));
-    string_set(out_dir, UPDATE_DIR_DEFAULT_REL_PATH);
+static bool flipper_update_get_manifest_path(string_t out_path) {
+    FIL file;
+    FILINFO stat;
+    uint16_t size_read = 0;
+    char manifest_name_buf[UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN] = {0};
 
 
-    while(f_readdir(&dir, &fno) == FR_OK) {
-        entry_idx++;
-        if(fno.fname[0] == '\0') {
-            return false;
-        }
-        if(entry_idx == update_index) {
-            path_append(out_dir, fno.fname);
-            return true;
+    string_reset(out_path);
+    CHECK_FRESULT(f_stat(UPDATE_POINTER_FILE_PATH, &stat));
+    CHECK_FRESULT(f_open(&file, UPDATE_POINTER_FILE_PATH, FA_OPEN_EXISTING | FA_READ));
+    do {
+        if(f_read(&file, manifest_name_buf, UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN, &size_read) !=
+           FR_OK) {
+            break;
         }
         }
-    }
 
 
-    string_reset(out_dir);
-    return false;
+        if((size_read == 0) || (size_read == UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN)) {
+            break;
+        }
+        string_set_str(out_path, manifest_name_buf);
+        string_right(out_path, strlen("/ext"));
+    } while(0);
+    f_close(&file);
+    return !string_empty_p(out_path);
 }
 }
 
 
-static UpdateManifest* flipper_update_process_manifest(const string_t work_dir) {
+static UpdateManifest* flipper_update_process_manifest(const string_t manifest_path) {
     FIL file;
     FIL file;
     FILINFO stat;
     FILINFO stat;
 
 
-    string_t manifest_path;
-    string_init_set(manifest_path, work_dir);
-    path_append(manifest_path, UPDATE_MANIFEST_DEFAULT_NAME);
-
     CHECK_FRESULT(f_stat(string_get_cstr(manifest_path), &stat));
     CHECK_FRESULT(f_stat(string_get_cstr(manifest_path), &stat));
     CHECK_FRESULT(f_open(&file, string_get_cstr(manifest_path), FA_OPEN_EXISTING | FA_READ));
     CHECK_FRESULT(f_open(&file, string_get_cstr(manifest_path), FA_OPEN_EXISTING | FA_READ));
 
 
@@ -164,7 +158,7 @@ static UpdateManifest* flipper_update_process_manifest(const string_t work_dir)
         }
         }
     } while(false);
     } while(false);
 
 
-    string_clear(manifest_path);
+    f_close(&file);
     free(manifest_data);
     free(manifest_data);
     return manifest;
     return manifest;
 }
 }
@@ -174,22 +168,25 @@ void flipper_boot_update_exec() {
         return;
         return;
     }
     }
 
 
-    string_t work_dir;
+    string_t work_dir, manifest_path;
     string_init(work_dir);
     string_init(work_dir);
+    string_init(manifest_path);
     do {
     do {
-        if(!flipper_update_get_work_directory(work_dir)) {
+        if(!flipper_update_get_manifest_path(manifest_path)) {
             break;
             break;
         }
         }
 
 
-        UpdateManifest* manifest = flipper_update_process_manifest(work_dir);
+        UpdateManifest* manifest = flipper_update_process_manifest(manifest_path);
         if(!manifest) {
         if(!manifest) {
             break;
             break;
         }
         }
 
 
+        path_extract_dirname(string_get_cstr(manifest_path), work_dir);
         if(!flipper_update_load_stage(work_dir, manifest)) {
         if(!flipper_update_load_stage(work_dir, manifest)) {
             update_manifest_free(manifest);
             update_manifest_free(manifest);
         }
         }
     } while(false);
     } while(false);
+    string_clear(manifest_path);
     string_clear(work_dir);
     string_clear(work_dir);
     free(pfs);
     free(pfs);
 }
 }

+ 7 - 1
firmware/targets/f7/furi_hal/furi_hal_random.c

@@ -7,6 +7,8 @@
 
 
 #include <hw_conf.h>
 #include <hw_conf.h>
 
 
+#define TAG "FuriHalRandom"
+
 uint32_t furi_hal_random_get() {
 uint32_t furi_hal_random_get() {
     while(LL_HSEM_1StepLock(HSEM, CFG_HW_RNG_SEMID))
     while(LL_HSEM_1StepLock(HSEM, CFG_HW_RNG_SEMID))
         ;
         ;
@@ -51,9 +53,13 @@ void furi_hal_random_fill_buf(uint8_t* buf, uint32_t len) {
 }
 }
 
 
 void srand(unsigned seed) {
 void srand(unsigned seed) {
-    UNUSED(seed); // FIXME!
+    UNUSED(seed);
 }
 }
 
 
 int rand() {
 int rand() {
     return (furi_hal_random_get() & RAND_MAX);
     return (furi_hal_random_get() & RAND_MAX);
 }
 }
+
+long random() {
+    return (furi_hal_random_get() & RAND_MAX);
+}

+ 3 - 3
firmware/targets/f7/furi_hal/furi_hal_subghz.c

@@ -972,9 +972,9 @@ void furi_hal_subghz_stop_async_tx() {
     FURI_LOG_D(
     FURI_LOG_D(
         TAG,
         TAG,
         "Async TX Radio stats: on %0.0fus, off %0.0fus, DutyCycle: %0.0f%%",
         "Async TX Radio stats: on %0.0fus, off %0.0fus, DutyCycle: %0.0f%%",
-        (float)furi_hal_subghz_async_tx.duty_high,
-        (float)furi_hal_subghz_async_tx.duty_low,
-        duty_cycle);
+        (double)furi_hal_subghz_async_tx.duty_high,
+        (double)furi_hal_subghz_async_tx.duty_low,
+        (double)duty_cycle);
 
 
     furi_hal_subghz_state = SubGhzStateIdle;
     furi_hal_subghz_state = SubGhzStateIdle;
 }
 }

+ 3 - 2
firmware/targets/f7/stm32wb55xx_flash.ld

@@ -135,6 +135,7 @@ SECTIONS
     _sdata = .;        /* create a global symbol at data start */
     _sdata = .;        /* create a global symbol at data start */
     *(.data)           /* .data sections */
     *(.data)           /* .data sections */
     *(.data*)          /* .data* sections */
     *(.data*)          /* .data* sections */
+    *(*_DRIVER_CONTEXT)
 
 
     . = ALIGN(4);
     . = ALIGN(4);
     _edata = .;        /* define a global symbol at data end */
     _edata = .;        /* define a global symbol at data end */
@@ -158,7 +159,7 @@ SECTIONS
   } >RAM1
   } >RAM1
 
 
   /* User_heap_stack section, used to check that there is enough RAM left */
   /* User_heap_stack section, used to check that there is enough RAM left */
-  ._user_heap_stack :
+  ._user_heap_stack(NOLOAD):
   {
   {
     . = ALIGN(8);
     . = ALIGN(8);
     __heap_start__ = .;
     __heap_start__ = .;
@@ -173,7 +174,7 @@ SECTIONS
   {
   {
     __free_flash_start__ = .;
     __free_flash_start__ = .;
     . = ORIGIN(FLASH) + LENGTH(FLASH);
     . = ORIGIN(FLASH) + LENGTH(FLASH);
-  } >FLASH
+  } >FLASH 
 
 
   /* Remove information from the standard libraries */
   /* Remove information from the standard libraries */
   /DISCARD/ :
   /DISCARD/ :

+ 2 - 2
firmware/targets/f7/target.mk

@@ -19,9 +19,9 @@ endif
 MCU_FLAGS		= -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
 MCU_FLAGS		= -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard
 
 
 # Warnings configuration
 # Warnings configuration
-CFLAGS			+= -Wall -Wextra -Wredundant-decls
+CFLAGS			+= -Wall -Wextra -Wredundant-decls -Wdouble-promotion
 
 
-CFLAGS			+= $(MCU_FLAGS) -DSTM32WB55xx -fdata-sections -ffunction-sections
+CFLAGS			+= $(MCU_FLAGS) -DSTM32WB55xx -fdata-sections -ffunction-sections -fsingle-precision-constant
 LDFLAGS			+= $(MCU_FLAGS) -specs=nosys.specs -specs=nano.specs
 LDFLAGS			+= $(MCU_FLAGS) -specs=nosys.specs -specs=nano.specs
 
 
 CPPFLAGS		+= -fno-rtti -fno-use-cxa-atexit -fno-exceptions
 CPPFLAGS		+= -fno-rtti -fno-use-cxa-atexit -fno-exceptions

+ 1 - 1
lib/flipper_format/flipper_format_stream.c

@@ -276,7 +276,7 @@ bool flipper_format_stream_write_value_line(Stream* stream, FlipperStreamWriteDa
 #ifndef FLIPPER_STREAM_LITE
 #ifndef FLIPPER_STREAM_LITE
                 case FlipperStreamValueFloat: {
                 case FlipperStreamValueFloat: {
                     const float* data = write_data->data;
                     const float* data = write_data->data;
-                    string_printf(value, "%f", data[i]);
+                    string_printf(value, "%f", (double)data[i]);
                 }; break;
                 }; break;
 #endif
 #endif
                 case FlipperStreamValueInt32: {
                 case FlipperStreamValueInt32: {

+ 1 - 1
lib/lib.mk

@@ -4,7 +4,7 @@ LIB_DIR			= $(PROJECT_ROOT)/lib
 CFLAGS			+= -I$(LIB_DIR)
 CFLAGS			+= -I$(LIB_DIR)
 
 
 # Mlib containers
 # Mlib containers
-CFLAGS			+= -I$(LIB_DIR)/mlib
+CFLAGS			+= -I$(LIB_DIR)/mlib -D'M_MEMORY_FULL(x)=abort()'
 
 
 # U8G2 display library
 # U8G2 display library
 U8G2_DIR		= $(LIB_DIR)/u8g2
 U8G2_DIR		= $(LIB_DIR)/u8g2

+ 4 - 1
lib/subghz/subghz_tx_rx_worker.c

@@ -83,7 +83,10 @@ bool subghz_tx_rx_worker_rx(SubGhzTxRxWorker* instance, uint8_t* data, uint8_t*
 
 
     if(furi_hal_subghz_rx_pipe_not_empty()) {
     if(furi_hal_subghz_rx_pipe_not_empty()) {
         FURI_LOG_I(
         FURI_LOG_I(
-            TAG, "RSSI: %03.1fdbm LQI: %d", furi_hal_subghz_get_rssi(), furi_hal_subghz_get_lqi());
+            TAG,
+            "RSSI: %03.1fdbm LQI: %d",
+            (double)furi_hal_subghz_get_rssi(),
+            furi_hal_subghz_get_lqi());
         if(furi_hal_subghz_is_rx_data_crc_valid()) {
         if(furi_hal_subghz_is_rx_data_crc_valid()) {
             furi_hal_subghz_read_packet(data, size);
             furi_hal_subghz_read_packet(data, size);
             ret = true;
             ret = true;

+ 4 - 5
lib/update_util/update_manifest.c

@@ -26,6 +26,7 @@ UpdateManifest* update_manifest_alloc() {
     string_init(update_manifest->staged_loader_file);
     string_init(update_manifest->staged_loader_file);
     string_init(update_manifest->resource_bundle);
     string_init(update_manifest->resource_bundle);
     update_manifest->target = 0;
     update_manifest->target = 0;
+    update_manifest->manifest_version = 0;
     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
     memset(update_manifest->ob_reference.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
     memset(update_manifest->ob_compare_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
     memset(update_manifest->ob_compare_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
     memset(update_manifest->ob_write_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
     memset(update_manifest->ob_write_mask.bytes, 0, FURI_HAL_FLASH_OB_RAW_SIZE_BYTES);
@@ -49,12 +50,11 @@ static bool
     furi_assert(flipper_file);
     furi_assert(flipper_file);
 
 
     string_t filetype;
     string_t filetype;
-    uint32_t version = 0;
 
 
     // TODO: compare filetype?
     // TODO: compare filetype?
     string_init(filetype);
     string_init(filetype);
     update_manifest->valid =
     update_manifest->valid =
-        flipper_format_read_header(flipper_file, filetype, &version) &&
+        flipper_format_read_header(flipper_file, filetype, &update_manifest->manifest_version) &&
         flipper_format_read_string(flipper_file, MANIFEST_KEY_INFO, update_manifest->version) &&
         flipper_format_read_string(flipper_file, MANIFEST_KEY_INFO, update_manifest->version) &&
         flipper_format_read_uint32(
         flipper_format_read_uint32(
             flipper_file, MANIFEST_KEY_TARGET, &update_manifest->target, 1) &&
             flipper_file, MANIFEST_KEY_TARGET, &update_manifest->target, 1) &&
@@ -68,7 +68,7 @@ static bool
     string_clear(filetype);
     string_clear(filetype);
 
 
     if(update_manifest->valid) {
     if(update_manifest->valid) {
-        /* Optional fields - we can have dfu, radio, or both */
+        /* Optional fields - we can have dfu, radio, resources, or any combination */
         flipper_format_read_string(
         flipper_format_read_string(
             flipper_file, MANIFEST_KEY_DFU_FILE, update_manifest->firmware_dfu_image);
             flipper_file, MANIFEST_KEY_DFU_FILE, update_manifest->firmware_dfu_image);
         flipper_format_read_string(
         flipper_format_read_string(
@@ -131,8 +131,7 @@ static bool ob_data_check_masked_values_valid(
     const FuriHalFlashRawOptionByteData* mask) {
     const FuriHalFlashRawOptionByteData* mask) {
     bool valid = true;
     bool valid = true;
     for(size_t idx = 0; valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) {
     for(size_t idx = 0; valid && (idx < FURI_HAL_FLASH_OB_TOTAL_VALUES); ++idx) {
-        valid &= (data->obs[idx]. dword & mask->obs[idx].dword) ==
-                 data->obs[idx].dword;
+        valid &= (data->obs[idx].dword & mask->obs[idx].dword) == data->obs[idx].dword;
     }
     }
     return valid;
     return valid;
 }
 }

+ 2 - 0
lib/update_util/update_manifest.h

@@ -12,6 +12,7 @@ extern "C" {
 /* Paths don't include /ext -- because at startup SD card is mounted as root */
 /* Paths don't include /ext -- because at startup SD card is mounted as root */
 #define UPDATE_DIR_DEFAULT_REL_PATH "/update"
 #define UPDATE_DIR_DEFAULT_REL_PATH "/update"
 #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf"
 #define UPDATE_MANIFEST_DEFAULT_NAME "update.fuf"
+#define UPDATE_MANIFEST_POINTER_FILE_NAME ".fupdate"
 
 
 typedef union {
 typedef union {
     uint8_t raw[6];
     uint8_t raw[6];
@@ -27,6 +28,7 @@ typedef union {
 _Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVersion size error");
 _Static_assert(sizeof(UpdateManifestRadioVersion) == 6, "UpdateManifestRadioVersion size error");
 
 
 typedef struct {
 typedef struct {
+    uint32_t manifest_version;
     string_t version;
     string_t version;
     uint32_t target;
     uint32_t target;
     string_t staged_loader_file;
     string_t staged_loader_file;

+ 123 - 111
lib/update_util/update_operation.c

@@ -12,7 +12,6 @@
 #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH
 #define UPDATE_ROOT_DIR "/ext" UPDATE_DIR_DEFAULT_REL_PATH
 #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"
 #define UPDATE_PREFIX "/ext" UPDATE_DIR_DEFAULT_REL_PATH "/"
 #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME
 #define UPDATE_SUFFIX "/" UPDATE_MANIFEST_DEFAULT_NAME
-#define MAX_DIR_NAME_LEN 250
 
 
 static const char* update_prepare_result_descr[] = {
 static const char* update_prepare_result_descr[] = {
     [UpdatePrepareResultOK] = "OK",
     [UpdatePrepareResultOK] = "OK",
@@ -21,6 +20,8 @@ static const char* update_prepare_result_descr[] = {
     [UpdatePrepareResultManifestInvalid] = "Invalid manifest data",
     [UpdatePrepareResultManifestInvalid] = "Invalid manifest data",
     [UpdatePrepareResultStageMissing] = "Missing Stage2 loader",
     [UpdatePrepareResultStageMissing] = "Missing Stage2 loader",
     [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader",
     [UpdatePrepareResultStageIntegrityError] = "Corrupted Stage2 loader",
+    [UpdatePrepareResultManifestPointerError] = "Failed to create update pointer file",
+    [UpdatePrepareResultOutdatedManifestVersion] = "Update package is too old",
 };
 };
 
 
 const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) {
 const char* update_operation_describe_preparation_result(const UpdatePrepareResult value) {
@@ -31,65 +32,7 @@ const char* update_operation_describe_preparation_result(const UpdatePrepareResu
     }
     }
 }
 }
 
 
-bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir) {
-    bool path_ok = false;
-    string_t full_path_str;
-    string_init_set(full_path_str, full_path);
-    string_reset(out_manifest_dir);
-    bool start_end_ok = string_start_with_str_p(full_path_str, UPDATE_PREFIX) &&
-                        string_end_with_str_p(full_path_str, UPDATE_SUFFIX);
-    int16_t dir_name_len =
-        strlen(full_path) - strlen(UPDATE_PREFIX) - strlen(UPDATE_MANIFEST_DEFAULT_NAME) - 1;
-    if(dir_name_len == -1) {
-        path_ok = true;
-    } else if(start_end_ok && (dir_name_len > 0)) {
-        string_set_n(out_manifest_dir, full_path_str, strlen(UPDATE_PREFIX), dir_name_len);
-        path_ok = true;
-        if(string_search_char(out_manifest_dir, '/') != STRING_FAILURE) {
-            string_reset(out_manifest_dir);
-            path_ok = false;
-        }
-    }
-    string_clear(full_path_str);
-    return path_ok;
-}
-
-int32_t update_operation_get_package_index(Storage* storage, const char* update_package_dir) {
-    furi_assert(storage);
-    furi_assert(update_package_dir);
-
-    if(strlen(update_package_dir) == 0) {
-        return UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC;
-    }
-
-    bool found = false;
-    int32_t index = 0;
-    File* dir = storage_file_alloc(storage);
-    FileInfo fi = {0};
-    char* name_buffer = malloc(MAX_DIR_NAME_LEN);
-    do {
-        if(!storage_dir_open(dir, UPDATE_ROOT_DIR)) {
-            break;
-        }
-
-        while(storage_dir_read(dir, &fi, name_buffer, MAX_DIR_NAME_LEN)) {
-            index++;
-            if(strcmp(name_buffer, update_package_dir)) {
-                continue;
-            } else {
-                found = true;
-                break;
-            }
-        }
-    } while(false);
-
-    free(name_buffer);
-    storage_file_free(dir);
-
-    return found ? index : -1;
-}
-
-bool update_operation_get_current_package_path(Storage* storage, string_t out_path) {
+static bool update_operation_get_current_package_path_rtc(Storage* storage, string_t out_path) {
     const uint32_t update_index = furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
     const uint32_t update_index = furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
     string_set_str(out_path, UPDATE_ROOT_DIR);
     string_set_str(out_path, UPDATE_ROOT_DIR);
     if(update_index == UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC) {
     if(update_index == UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC) {
@@ -100,13 +43,13 @@ bool update_operation_get_current_package_path(Storage* storage, string_t out_pa
     uint32_t iter_index = 0;
     uint32_t iter_index = 0;
     File* dir = storage_file_alloc(storage);
     File* dir = storage_file_alloc(storage);
     FileInfo fi = {0};
     FileInfo fi = {0};
-    char* name_buffer = malloc(MAX_DIR_NAME_LEN);
+    char* name_buffer = malloc(UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN);
     do {
     do {
         if(!storage_dir_open(dir, UPDATE_ROOT_DIR)) {
         if(!storage_dir_open(dir, UPDATE_ROOT_DIR)) {
             break;
             break;
         }
         }
 
 
-        while(storage_dir_read(dir, &fi, name_buffer, MAX_DIR_NAME_LEN)) {
+        while(storage_dir_read(dir, &fi, name_buffer, UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN)) {
             if(++iter_index == update_index) {
             if(++iter_index == update_index) {
                 found = true;
                 found = true;
                 path_append(out_path, name_buffer);
                 path_append(out_path, name_buffer);
@@ -124,79 +67,148 @@ bool update_operation_get_current_package_path(Storage* storage, string_t out_pa
     return found;
     return found;
 }
 }
 
 
-UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) {
-    string_t update_folder;
-    string_init(update_folder);
-    if(!update_operation_get_package_dir_name(manifest_file_path, update_folder)) {
-        string_clear(update_folder);
-        return UpdatePrepareResultManifestPathInvalid;
+#define UPDATE_FILE_POINTER_FN "/ext/" UPDATE_MANIFEST_POINTER_FILE_NAME
+#define UPDATE_MANIFEST_MAX_PATH_LEN 256u
+
+bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path) {
+    string_reset(out_path);
+    if(storage_common_stat(storage, UPDATE_FILE_POINTER_FN, NULL) == FSE_OK) {
+        char* manifest_name_buffer = malloc(UPDATE_MANIFEST_MAX_PATH_LEN);
+        File* upd_file = NULL;
+        do {
+            upd_file = storage_file_alloc(storage);
+            if(!storage_file_open(
+                   upd_file, UPDATE_FILE_POINTER_FN, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                break;
+            }
+            uint16_t bytes_read =
+                storage_file_read(upd_file, manifest_name_buffer, UPDATE_MANIFEST_MAX_PATH_LEN);
+            if((bytes_read == 0) || (bytes_read == UPDATE_MANIFEST_MAX_PATH_LEN)) {
+                break;
+            }
+            if(storage_common_stat(storage, manifest_name_buffer, NULL) != FSE_OK) {
+                break;
+            }
+            string_set_str(out_path, manifest_name_buffer);
+        } while(0);
+        free(manifest_name_buffer);
+        storage_file_free(upd_file);
+    } else {
+        /* legacy, will be deprecated */
+        string_t rtcpath;
+        string_init(rtcpath);
+        do {
+            if(!update_operation_get_current_package_path_rtc(storage, rtcpath)) {
+                break;
+            }
+            path_concat(string_get_cstr(rtcpath), UPDATE_MANIFEST_DEFAULT_NAME, out_path);
+        } while(0);
+        string_clear(rtcpath);
     }
     }
+    return !string_empty_p(out_path);
+}
 
 
-    Storage* storage = furi_record_open("storage");
-    int32_t update_index =
-        update_operation_get_package_index(storage, string_get_cstr(update_folder));
-    string_clear(update_folder);
+static bool update_operation_persist_manifest_path(Storage* storage, const char* manifest_path) {
+    const uint16_t manifest_path_len = strlen(manifest_path);
+    furi_check(manifest_path && manifest_path_len);
+    bool success = false;
+    File* file = storage_file_alloc(storage);
+    do {
+        if(manifest_path_len >= UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN) {
+            break;
+        }
 
 
-    if(update_index < 0) {
-        furi_record_close("storage");
-        return UpdatePrepareResultManifestFolderNotFound;
-    }
+        if(!storage_file_open(file, UPDATE_FILE_POINTER_FN, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+            break;
+        }
+
+        if(storage_file_write(file, manifest_path, manifest_path_len) != manifest_path_len) {
+            break;
+        }
 
 
-    string_t update_dir_path;
-    string_init(update_dir_path);
-    path_extract_dirname(manifest_file_path, update_dir_path);
+        success = true;
+    } while(0);
+    storage_file_free(file);
+    return success;
+}
 
 
-    UpdatePrepareResult result = UpdatePrepareResultManifestInvalid;
+UpdatePrepareResult update_operation_prepare(const char* manifest_file_path) {
+    UpdatePrepareResult result = UpdatePrepareResultManifestFolderNotFound;
+    Storage* storage = furi_record_open("storage");
     UpdateManifest* manifest = update_manifest_alloc();
     UpdateManifest* manifest = update_manifest_alloc();
-    if(update_manifest_init(manifest, manifest_file_path)) {
-        result = UpdatePrepareResultStageMissing;
-        File* file = storage_file_alloc(storage);
+    File* file = storage_file_alloc(storage);
+
+    string_t stage_path;
+    string_init(stage_path);
+    do {
+        if(storage_common_stat(storage, manifest_file_path, NULL) != FSE_OK) {
+            break;
+        }
+
+        if(!update_manifest_init(manifest, manifest_file_path)) {
+            result = UpdatePrepareResultManifestInvalid;
+            break;
+        }
+
+        if(manifest->manifest_version < UPDATE_OPERATION_MIN_MANIFEST_VERSION) {
+            result = UpdatePrepareResultOutdatedManifestVersion;
+            break;
+        }
+
+        if(furi_hal_version_get_hw_target() != manifest->target) {
+            result = UpdatePrepareResultTargetMismatch;
+            break;
+        }
 
 
-        string_t stage_path;
-        string_init(stage_path);
         path_extract_dirname(manifest_file_path, stage_path);
         path_extract_dirname(manifest_file_path, stage_path);
         path_append(stage_path, string_get_cstr(manifest->staged_loader_file));
         path_append(stage_path, string_get_cstr(manifest->staged_loader_file));
 
 
-        uint32_t crc = 0;
-        do {
-            if(!storage_file_open(
-                   file, string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
-                break;
-            }
+        if(!storage_file_open(file, string_get_cstr(stage_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+            result = UpdatePrepareResultStageMissing;
+            break;
+        }
 
 
+        uint32_t crc = crc32_calc_file(file, NULL, NULL);
+        if(crc != manifest->staged_loader_crc) {
             result = UpdatePrepareResultStageIntegrityError;
             result = UpdatePrepareResultStageIntegrityError;
-            crc = crc32_calc_file(file, NULL, NULL);
-        } while(false);
-
-        string_clear(stage_path);
-        storage_file_free(file);
+            break;
+        }
 
 
-        if(crc == manifest->staged_loader_crc) {
-            furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
-            update_operation_persist_package_index(update_index);
-            result = UpdatePrepareResultOK;
+        if(!update_operation_persist_manifest_path(storage, manifest_file_path)) {
+            result = UpdatePrepareResultManifestPointerError;
+            break;
         }
         }
-    }
-    furi_record_close("storage");
+
+        result = UpdatePrepareResultOK;
+        furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePreUpdate);
+    } while(false);
+
+    string_clear(stage_path);
+    storage_file_free(file);
+
     update_manifest_free(manifest);
     update_manifest_free(manifest);
+    furi_record_close("storage");
 
 
     return result;
     return result;
 }
 }
 
 
 bool update_operation_is_armed() {
 bool update_operation_is_armed() {
     FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode();
     FuriHalRtcBootMode boot_mode = furi_hal_rtc_get_boot_mode();
+    const uint32_t rtc_upd_index =
+        furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex);
+    Storage* storage = furi_record_open("storage");
+    const bool upd_fn_ptr_exists =
+        (storage_common_stat(storage, UPDATE_FILE_POINTER_FN, NULL) == FSE_OK);
+    furi_record_close("storage");
     return (boot_mode >= FuriHalRtcBootModePreUpdate) &&
     return (boot_mode >= FuriHalRtcBootModePreUpdate) &&
            (boot_mode <= FuriHalRtcBootModePostUpdate) &&
            (boot_mode <= FuriHalRtcBootModePostUpdate) &&
-           (furi_hal_rtc_get_register(FuriHalRtcRegisterUpdateFolderFSIndex) > 0);
+           ((rtc_upd_index != INT_MAX) || upd_fn_ptr_exists);
 }
 }
 
 
 void update_operation_disarm() {
 void update_operation_disarm() {
     furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal);
     furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal);
-    furi_hal_rtc_set_register(
-        FuriHalRtcRegisterUpdateFolderFSIndex, INT_MAX);
-}
-
-void update_operation_persist_package_index(int32_t index) {
-    furi_check(index >= 0);
-    furi_hal_rtc_set_register(FuriHalRtcRegisterUpdateFolderFSIndex, index);
-}
+    furi_hal_rtc_set_register(FuriHalRtcRegisterUpdateFolderFSIndex, INT_MAX);
+    Storage* storage = furi_record_open("storage");
+    storage_simply_remove(storage, UPDATE_FILE_POINTER_FN);
+    furi_record_close("storage");
+}

+ 8 - 16
lib/update_util/update_operation.h

@@ -9,6 +9,8 @@ extern "C" {
 #endif
 #endif
 
 
 #define UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC 0
 #define UPDATE_OPERATION_ROOT_DIR_PACKAGE_MAGIC 0
+#define UPDATE_OPERATION_MAX_MANIFEST_PATH_LEN 255u
+#define UPDATE_OPERATION_MIN_MANIFEST_VERSION 2
 
 
 /* 
 /* 
  * Checks if supplied full manifest path is valid
  * Checks if supplied full manifest path is valid
@@ -19,6 +21,7 @@ extern "C" {
  */
  */
 bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir);
 bool update_operation_get_package_dir_name(const char* full_path, string_t out_manifest_dir);
 
 
+/* When updating this enum, also update assets/protobuf/system.proto */
 typedef enum {
 typedef enum {
     UpdatePrepareResultOK,
     UpdatePrepareResultOK,
     UpdatePrepareResultManifestPathInvalid,
     UpdatePrepareResultManifestPathInvalid,
@@ -26,6 +29,9 @@ typedef enum {
     UpdatePrepareResultManifestInvalid,
     UpdatePrepareResultManifestInvalid,
     UpdatePrepareResultStageMissing,
     UpdatePrepareResultStageMissing,
     UpdatePrepareResultStageIntegrityError,
     UpdatePrepareResultStageIntegrityError,
+    UpdatePrepareResultManifestPointerError,
+    UpdatePrepareResultTargetMismatch,
+    UpdatePrepareResultOutdatedManifestVersion,
 } UpdatePrepareResult;
 } UpdatePrepareResult;
 
 
 const char* update_operation_describe_preparation_result(const UpdatePrepareResult value);
 const char* update_operation_describe_preparation_result(const UpdatePrepareResult value);
@@ -37,27 +43,13 @@ const char* update_operation_describe_preparation_result(const UpdatePrepareResu
  */
  */
 UpdatePrepareResult update_operation_prepare(const char* manifest_file_path);
 UpdatePrepareResult update_operation_prepare(const char* manifest_file_path);
 
 
-/* 
- * Gets update package index to pass in RTC registers
- * @param storage Storage API
- * @param update_package_dir Package directory name
- * @return int32_t <=0 - error, >0 - update index value
- */
-int32_t update_operation_get_package_index(Storage* storage, const char* update_package_dir);
-
 /* 
 /* 
  * Gets filesystem path for current update package
  * Gets filesystem path for current update package
  * @param storage Storage API
  * @param storage Storage API
- * @param out_path Path to directory with manifest & related files. Must be initialized
+ * @param out_path Path to manifest. Must be initialized
  * @return true if path was restored successfully
  * @return true if path was restored successfully
  */
  */
-bool update_operation_get_current_package_path(Storage* storage, string_t out_path);
-
-/* 
- * Stores given update index in RTC registers
- * @param index Value to store
- */
-void update_operation_persist_package_index(int32_t index);
+bool update_operation_get_current_package_manifest_path(Storage* storage, string_t out_path);
 
 
 /* 
 /* 
  * Checks if an update operation step is pending after reset
  * Checks if an update operation step is pending after reset

+ 8 - 6
scripts/update.py

@@ -13,6 +13,7 @@ import math
 
 
 
 
 class Main(App):
 class Main(App):
+    UPDATE_MANIFEST_VERSION = 2
     UPDATE_MANIFEST_NAME = "update.fuf"
     UPDATE_MANIFEST_NAME = "update.fuf"
 
 
     #  No compression, plain tar
     #  No compression, plain tar
@@ -93,7 +94,9 @@ class Main(App):
             )
             )
 
 
         file = FlipperFormatFile()
         file = FlipperFormatFile()
-        file.setHeader("Flipper firmware upgrade configuration", 1)
+        file.setHeader(
+            "Flipper firmware upgrade configuration", self.UPDATE_MANIFEST_VERSION
+        )
         file.writeKey("Info", self.args.version)
         file.writeKey("Info", self.args.version)
         file.writeKey("Target", self.args.target[1:])  # dirty 'f' strip
         file.writeKey("Target", self.args.target[1:])  # dirty 'f' strip
         file.writeKey("Loader", stage_basename)
         file.writeKey("Loader", stage_basename)
@@ -102,7 +105,7 @@ class Main(App):
         file.writeKey("Firmware", dfu_basename)
         file.writeKey("Firmware", dfu_basename)
         file.writeKey("Radio", radiobin_basename or "")
         file.writeKey("Radio", radiobin_basename or "")
         file.writeKey("Radio address", self.int2ffhex(radio_addr))
         file.writeKey("Radio address", self.int2ffhex(radio_addr))
-        file.writeKey("Radio version", self.int2ffhex(radio_version))
+        file.writeKey("Radio version", self.int2ffhex(radio_version, 12))
         if radiobin_basename:
         if radiobin_basename:
             file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin)))
             file.writeKey("Radio CRC", self.int2ffhex(self.crc(self.args.radiobin)))
         else:
         else:
@@ -149,11 +152,10 @@ class Main(App):
         return " ".join(f"{b:02X}" for b in value)
         return " ".join(f"{b:02X}" for b in value)
 
 
     @staticmethod
     @staticmethod
-    def int2ffhex(value: int):
-        n_hex_bytes = 4
+    def int2ffhex(value: int, n_hex_syms=8):
         if value:
         if value:
-            n_hex_bytes = math.ceil(math.ceil(math.log2(value)) / 8) * 2
-        fmtstr = f"%0{n_hex_bytes}X"
+            n_hex_syms = math.ceil(math.ceil(math.log2(value)) / 8) * 2
+        fmtstr = f"%0{n_hex_syms}X"
         hexstr = fmtstr % value
         hexstr = fmtstr % value
         return " ".join(list(Main.batch(hexstr, 2))[::-1])
         return " ".join(list(Main.batch(hexstr, 2))[::-1])