Merge branch 'dev' into ntag-auto-pwd-capture

This commit is contained in:
Yukai Li
2022-10-16 00:00:32 -06:00
committed by GitHub
21 changed files with 370 additions and 77 deletions

View File

@@ -127,7 +127,7 @@ jobs:
**Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:** **Compiled firmware for commit `${{steps.names.outputs.commit_sha}}`:**
- [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz) - [📦 Update package](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz)
- [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu) - [📥 DFU file](https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-full-${{steps.names.outputs.suffix}}.dfu)
- [☁️ Web updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}}) - [☁️ Web/App updater](https://my.flipp.dev/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.branch_name}}/flipper-z-${{steps.names.outputs.default_target}}-update-${{steps.names.outputs.suffix}}.tgz&channel=${{steps.names.outputs.branch_name}}&version=${{steps.names.outputs.commit_sha}})
edit-mode: replace edit-mode: replace
compact: compact:

View File

@@ -3,6 +3,7 @@ App(
name="Applications", name="Applications",
apptype=FlipperAppType.APP, apptype=FlipperAppType.APP,
entry_point="fap_loader_app", entry_point="fap_loader_app",
cdefines=["APP_FAP_LOADER"],
requires=[ requires=[
"gui", "gui",
"storage", "storage",

View File

@@ -101,7 +101,7 @@ static bool fap_loader_run_selected_app(FapLoader* loader) {
} }
FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start)); FURI_LOG_I(TAG, "Loaded in %ums", (size_t)(furi_get_tick() - start));
FURI_LOG_I(TAG, "FAP Loader is staring app"); FURI_LOG_I(TAG, "FAP Loader is starting app");
FuriThread* thread = flipper_application_spawn(loader->app, NULL); FuriThread* thread = flipper_application_spawn(loader->app, NULL);
furi_thread_start(thread); furi_thread_start(thread);
@@ -182,6 +182,7 @@ int32_t fap_loader_app(void* p) {
FapLoader* loader; FapLoader* loader;
if(p) { if(p) {
loader = fap_loader_alloc((const char*)p); loader = fap_loader_alloc((const char*)p);
view_dispatcher_switch_to_view(loader->view_dispatcher, 0);
fap_loader_run_selected_app(loader); fap_loader_run_selected_app(loader);
} else { } else {
loader = fap_loader_alloc(EXT_PATH("apps")); loader = fap_loader_alloc(EXT_PATH("apps"));

View File

@@ -165,6 +165,11 @@ static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context
ret = rpc_session_get_available_size(bt->rpc_session); ret = rpc_session_get_available_size(bt->rpc_session);
} else if(event.event == SerialServiceEventTypeDataSent) { } else if(event.event == SerialServiceEventTypeDataSent) {
furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT);
} else if(event.event == SerialServiceEventTypesBleResetRequest) {
FURI_LOG_I(TAG, "BLE restart request received");
BtMessage message = {.type = BtMessageTypeSetProfile, .data.profile = BtProfileSerial};
furi_check(
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
} }
return ret; return ret;
} }
@@ -226,6 +231,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) {
rpc_session_set_context(bt->rpc_session, bt); rpc_session_set_context(bt->rpc_session, bt);
furi_hal_bt_serial_set_event_callback( furi_hal_bt_serial_set_event_callback(
RPC_BUFFER_SIZE, bt_serial_event_callback, bt); RPC_BUFFER_SIZE, bt_serial_event_callback, bt);
furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusActive);
} else { } else {
FURI_LOG_W(TAG, "RPC is busy, failed to open new session"); FURI_LOG_W(TAG, "RPC is busy, failed to open new session");
} }
@@ -241,6 +247,7 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) {
} else if(event.type == GapEventTypeDisconnected) { } else if(event.type == GapEventTypeDisconnected) {
if(bt->profile == BtProfileSerial && bt->rpc_session) { if(bt->profile == BtProfileSerial && bt->rpc_session) {
FURI_LOG_I(TAG, "Close RPC connection"); FURI_LOG_I(TAG, "Close RPC connection");
furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusNotActive);
furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
rpc_session_close(bt->rpc_session); rpc_session_close(bt->rpc_session);
furi_hal_bt_serial_set_event_callback(0, NULL, NULL); furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
@@ -330,15 +337,21 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
} }
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
bt->profile = message->data.profile; bt->profile = message->data.profile;
if(message->result) {
*message->result = true; *message->result = true;
}
} else { } else {
FURI_LOG_E(TAG, "Failed to start Bt App"); FURI_LOG_E(TAG, "Failed to start Bt App");
if(message->result) {
*message->result = false; *message->result = false;
} }
}
} else { } else {
bt_show_warning(bt, "Radio stack doesn't support this app"); bt_show_warning(bt, "Radio stack doesn't support this app");
if(message->result) {
*message->result = false; *message->result = false;
} }
}
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT); furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
} }

View File

@@ -8,7 +8,7 @@
#include <toolbox/saved_struct.h> #include <toolbox/saved_struct.h>
#include <storage/storage.h> #include <storage/storage.h>
#define DESKTOP_SETTINGS_VER (5) #define DESKTOP_SETTINGS_VER (6)
#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME) #define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
#define DESKTOP_SETTINGS_MAGIC (0x17) #define DESKTOP_SETTINGS_MAGIC (0x17)
@@ -34,6 +34,9 @@
#define MAX_PIN_SIZE 10 #define MAX_PIN_SIZE 10
#define MIN_PIN_SIZE 4 #define MIN_PIN_SIZE 4
#define MAX_APP_LENGTH 128
#define FAP_LOADER_APP_NAME "Applications"
typedef struct { typedef struct {
InputKey data[MAX_PIN_SIZE]; InputKey data[MAX_PIN_SIZE];
@@ -41,8 +44,13 @@ typedef struct {
} PinCode; } PinCode;
typedef struct { typedef struct {
uint16_t favorite_primary; bool is_external;
uint16_t favorite_secondary; char name_or_path[MAX_APP_LENGTH];
} FavoriteApp;
typedef struct {
FavoriteApp favorite_primary;
FavoriteApp favorite_secondary;
PinCode pin_code; PinCode pin_code;
uint8_t is_locked; uint8_t is_locked;
uint32_t auto_lock_delay_ms; uint32_t auto_lock_delay_ms;

View File

@@ -114,29 +114,39 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
case DesktopMainEventOpenFavoritePrimary: case DesktopMainEventOpenFavoritePrimary:
DESKTOP_SETTINGS_LOAD(&desktop->settings); DESKTOP_SETTINGS_LOAD(&desktop->settings);
if(desktop->settings.favorite_primary < FLIPPER_APPS_COUNT) { if(desktop->settings.favorite_primary.is_external) {
LoaderStatus status = loader_start( LoaderStatus status = loader_start(
desktop->loader, FLIPPER_APPS[desktop->settings.favorite_primary].name, NULL); desktop->loader,
FAP_LOADER_APP_NAME,
desktop->settings.favorite_primary.name_or_path);
if(status != LoaderStatusOk) { if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status); FURI_LOG_E(TAG, "loader_start failed: %d", status);
} }
} else { } else {
FURI_LOG_E(TAG, "Can't find primary favorite application"); LoaderStatus status = loader_start(
desktop->loader, desktop->settings.favorite_primary.name_or_path, NULL);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
} }
consumed = true; consumed = true;
break; break;
case DesktopMainEventOpenFavoriteSecondary: case DesktopMainEventOpenFavoriteSecondary:
DESKTOP_SETTINGS_LOAD(&desktop->settings); DESKTOP_SETTINGS_LOAD(&desktop->settings);
if(desktop->settings.favorite_secondary < FLIPPER_APPS_COUNT) { if(desktop->settings.favorite_secondary.is_external) {
LoaderStatus status = loader_start( LoaderStatus status = loader_start(
desktop->loader, desktop->loader,
FLIPPER_APPS[desktop->settings.favorite_secondary].name, FAP_LOADER_APP_NAME,
NULL); desktop->settings.favorite_secondary.name_or_path);
if(status != LoaderStatusOk) { if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status); FURI_LOG_E(TAG, "loader_start failed: %d", status);
} }
} else { } else {
FURI_LOG_E(TAG, "Can't find secondary favorite application"); LoaderStatus status = loader_start(
desktop->loader, desktop->settings.favorite_secondary.name_or_path, NULL);
if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status);
}
} }
consumed = true; consumed = true;
break; break;
@@ -166,7 +176,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
} }
case DesktopMainEventOpenGameMenu: { case DesktopMainEventOpenGameMenu: {
LoaderStatus status = loader_start( LoaderStatus status = loader_start(
desktop->loader, "Applications", EXT_PATH("/apps/Games/snake_game.fap")); desktop->loader, FAP_LOADER_APP_NAME, EXT_PATH("/apps/Games/snake_game.fap"));
if(status != LoaderStatusOk) { if(status != LoaderStatusOk) {
FURI_LOG_E(TAG, "loader_start failed: %d", status); FURI_LOG_E(TAG, "loader_start failed: %d", status);
} }

View File

@@ -35,9 +35,9 @@ static const DolphinDeedWeight dolphin_deed_weights[] = {
{2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd {2, DolphinAppIbutton}, // DolphinDeedIbuttonAdd
{3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript {3, DolphinAppBadusb}, // DolphinDeedBadUsbPlayScript
{3, DolphinAppU2f}, // DolphinDeedU2fAuthorized {3, DolphinAppPlugin}, // DolphinDeedU2fAuthorized
{1, DolphinAppGpio}, // DolphinDeedGpioUartBridge {1, DolphinAppPlugin}, // DolphinDeedGpioUartBridge
{1, DolphinAppPlugin}, // DolphinDeedPluginStart {1, DolphinAppPlugin}, // DolphinDeedPluginStart
{1, DolphinAppPlugin}, // DolphinDeedPluginGameStart {1, DolphinAppPlugin}, // DolphinDeedPluginGameStart
@@ -51,8 +51,6 @@ static uint8_t dolphin_deed_limits[] = {
20, // DolphinAppIr 20, // DolphinAppIr
20, // DolphinAppIbutton 20, // DolphinAppIbutton
20, // DolphinAppBadusb 20, // DolphinAppBadusb
20, // DolphinAppU2f
20, // DolphinAppGpio
20, // DolphinAppPlugin 20, // DolphinAppPlugin
}; };

View File

@@ -13,8 +13,6 @@ typedef enum {
DolphinAppIr, DolphinAppIr,
DolphinAppIbutton, DolphinAppIbutton,
DolphinAppBadusb, DolphinAppBadusb,
DolphinAppU2f,
DolphinAppGpio,
DolphinAppPlugin, DolphinAppPlugin,
DolphinAppMAX, DolphinAppMAX,
} DolphinApp; } DolphinApp;
@@ -55,7 +53,6 @@ typedef enum {
DolphinDeedBadUsbPlayScript, DolphinDeedBadUsbPlayScript,
DolphinDeedU2fAuthorized, DolphinDeedU2fAuthorized,
DolphinDeedGpioUartBridge, DolphinDeedGpioUartBridge,
DolphinDeedPluginStart, DolphinDeedPluginStart,

View File

@@ -22,6 +22,7 @@ DesktopSettingsApp* desktop_settings_app_alloc() {
DesktopSettingsApp* app = malloc(sizeof(DesktopSettingsApp)); DesktopSettingsApp* app = malloc(sizeof(DesktopSettingsApp));
app->gui = furi_record_open(RECORD_GUI); app->gui = furi_record_open(RECORD_GUI);
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->view_dispatcher = view_dispatcher_alloc(); app->view_dispatcher = view_dispatcher_alloc();
app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app); app->scene_manager = scene_manager_alloc(&desktop_settings_scene_handlers, app);
view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_enable_queue(app->view_dispatcher);
@@ -83,6 +84,7 @@ void desktop_settings_app_free(DesktopSettingsApp* app) {
view_dispatcher_free(app->view_dispatcher); view_dispatcher_free(app->view_dispatcher);
scene_manager_free(app->scene_manager); scene_manager_free(app->scene_manager);
// Records // Records
furi_record_close(RECORD_DIALOGS);
furi_record_close(RECORD_GUI); furi_record_close(RECORD_GUI);
free(app); free(app);
} }

View File

@@ -6,6 +6,7 @@
#include <gui/scene_manager.h> #include <gui/scene_manager.h>
#include <gui/modules/submenu.h> #include <gui/modules/submenu.h>
#include <gui/modules/variable_item_list.h> #include <gui/modules/variable_item_list.h>
#include <dialogs/dialogs.h>
#include <desktop/desktop_settings.h> #include <desktop/desktop_settings.h>
#include <desktop/views/desktop_view_pin_input.h> #include <desktop/views/desktop_view_pin_input.h>
@@ -25,6 +26,7 @@ typedef struct {
DesktopSettings settings; DesktopSettings settings;
Gui* gui; Gui* gui;
DialogsApp* dialogs;
SceneManager* scene_manager; SceneManager* scene_manager;
ViewDispatcher* view_dispatcher; ViewDispatcher* view_dispatcher;
VariableItemList* variable_item_list; VariableItemList* variable_item_list;

View File

@@ -1,6 +1,35 @@
#include "../desktop_settings_app.h" #include "../desktop_settings_app.h"
#include "applications.h" #include "applications.h"
#include "desktop_settings_scene.h" #include "desktop_settings_scene.h"
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include <fap_loader/fap_loader_app.h>
static bool favorite_fap_selector_item_callback(
FuriString* file_path,
void* context,
uint8_t** icon_ptr,
FuriString* item_name) {
UNUSED(context);
#ifdef APP_FAP_LOADER
Storage* storage = furi_record_open(RECORD_STORAGE);
bool success = fap_loader_load_name_and_icon(file_path, storage, icon_ptr, item_name);
furi_record_close(RECORD_STORAGE);
#else
UNUSED(file_path);
UNUSED(icon_ptr);
UNUSED(item_name);
bool success = false;
#endif
return success;
}
static bool favorite_fap_selector_file_exists(char* file_path) {
Storage* storage = furi_record_open(RECORD_STORAGE);
bool exists = storage_file_exists(storage, file_path);
furi_record_close(RECORD_STORAGE);
return exists;
}
static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) { static void desktop_settings_scene_favorite_submenu_callback(void* context, uint32_t index) {
DesktopSettingsApp* app = context; DesktopSettingsApp* app = context;
@@ -12,6 +41,10 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
Submenu* submenu = app->submenu; Submenu* submenu = app->submenu;
submenu_reset(submenu); submenu_reset(submenu);
uint32_t primary_favorite =
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
uint32_t pre_select_item = 0;
for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) { for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
submenu_add_item( submenu_add_item(
submenu, submenu,
@@ -19,38 +52,96 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
i, i,
desktop_settings_scene_favorite_submenu_callback, desktop_settings_scene_favorite_submenu_callback,
app); app);
}
uint32_t primary_favorite = if(primary_favorite) { // Select favorite item in submenu
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); if((app->settings.favorite_primary.is_external &&
!strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) ||
(!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_primary.name_or_path))) {
pre_select_item = i;
}
} else {
if((app->settings.favorite_secondary.is_external &&
!strcmp(FLIPPER_APPS[i].name, FAP_LOADER_APP_NAME)) ||
(!strcmp(FLIPPER_APPS[i].name, app->settings.favorite_secondary.name_or_path))) {
pre_select_item = i;
}
}
}
submenu_set_header( submenu_set_header(
app->submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:"); submenu, primary_favorite ? "Primary favorite app:" : "Secondary favorite app:");
submenu_set_selected_item(submenu, pre_select_item); // If set during loop, visual glitch.
if(primary_favorite) {
submenu_set_selected_item(app->submenu, app->settings.favorite_primary);
} else {
submenu_set_selected_item(app->submenu, app->settings.favorite_secondary);
}
view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewMenu);
} }
bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) { bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent event) {
DesktopSettingsApp* app = context; DesktopSettingsApp* app = context;
bool consumed = false; bool consumed = false;
FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps"));
uint32_t primary_favorite = uint32_t primary_favorite =
scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite); scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
if(event.type == SceneManagerEventTypeCustom) { if(event.type == SceneManagerEventTypeCustom) {
if(strcmp(FLIPPER_APPS[event.event].name, FAP_LOADER_APP_NAME)) {
if(primary_favorite) { if(primary_favorite) {
app->settings.favorite_primary = event.event; app->settings.favorite_primary.is_external = false;
strncpy(
app->settings.favorite_primary.name_or_path,
FLIPPER_APPS[event.event].name,
MAX_APP_LENGTH);
} else { } else {
app->settings.favorite_secondary = event.event; app->settings.favorite_secondary.is_external = false;
strncpy(
app->settings.favorite_secondary.name_or_path,
FLIPPER_APPS[event.event].name,
MAX_APP_LENGTH);
}
} else {
const DialogsFileBrowserOptions browser_options = {
.extension = ".fap",
.icon = &I_unknown_10px,
.skip_assets = true,
.hide_ext = true,
.item_loader_callback = favorite_fap_selector_item_callback,
.item_loader_context = app,
};
if(primary_favorite) { // Select favorite fap in file browser
if(favorite_fap_selector_file_exists(
app->settings.favorite_primary.name_or_path)) {
furi_string_set_str(temp_path, app->settings.favorite_primary.name_or_path);
}
} else {
if(favorite_fap_selector_file_exists(
app->settings.favorite_secondary.name_or_path)) {
furi_string_set_str(temp_path, app->settings.favorite_secondary.name_or_path);
}
}
submenu_reset(app->submenu);
if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) {
if(primary_favorite) {
app->settings.favorite_primary.is_external = true;
strncpy(
app->settings.favorite_primary.name_or_path,
furi_string_get_cstr(temp_path),
MAX_APP_LENGTH);
} else {
app->settings.favorite_secondary.is_external = true;
strncpy(
app->settings.favorite_secondary.name_or_path,
furi_string_get_cstr(temp_path),
MAX_APP_LENGTH);
}
}
} }
scene_manager_previous_scene(app->scene_manager); scene_manager_previous_scene(app->scene_manager);
consumed = true; consumed = true;
} }
furi_string_free(temp_path);
return consumed; return consumed;
} }

View File

@@ -11,6 +11,7 @@ typedef struct {
uint16_t rx_char_handle; uint16_t rx_char_handle;
uint16_t tx_char_handle; uint16_t tx_char_handle;
uint16_t flow_ctrl_char_handle; uint16_t flow_ctrl_char_handle;
uint16_t rpc_status_char_handle;
FuriMutex* buff_size_mtx; FuriMutex* buff_size_mtx;
uint32_t buff_size; uint32_t buff_size;
uint16_t bytes_ready_to_receive; uint16_t bytes_ready_to_receive;
@@ -28,6 +29,8 @@ static const uint8_t char_rx_uuid[] =
{0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; {0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
static const uint8_t flow_ctrl_uuid[] = static const uint8_t flow_ctrl_uuid[] =
{0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19}; {0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
static const uint8_t rpc_status_uuid[] =
{0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19};
static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) {
SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck;
@@ -67,6 +70,17 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) {
furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk);
} }
ret = SVCCTL_EvtAckFlowEnable; ret = SVCCTL_EvtAckFlowEnable;
} else if(attribute_modified->Attr_Handle == serial_svc->rpc_status_char_handle + 1) {
SerialServiceRpcStatus* rpc_status =
(SerialServiceRpcStatus*)attribute_modified->Attr_Data;
if(*rpc_status == SerialServiceRpcStatusNotActive) {
if(serial_svc->callback) {
SerialServiceEvent event = {
.event = SerialServiceEventTypesBleResetRequest,
};
serial_svc->callback(event, serial_svc->context);
}
}
} }
} else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) {
FURI_LOG_T(TAG, "Ack received"); FURI_LOG_T(TAG, "Ack received");
@@ -82,6 +96,18 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) {
return ret; return ret;
} }
static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) {
tBleStatus ble_status = aci_gatt_update_char_value(
serial_svc->svc_handle,
serial_svc->rpc_status_char_handle,
0,
sizeof(SerialServiceRpcStatus),
(uint8_t*)&status);
if(ble_status) {
FURI_LOG_E(TAG, "Failed to update RPC status char: %d", ble_status);
}
}
void serial_svc_start() { void serial_svc_start() {
tBleStatus status; tBleStatus status;
serial_svc = malloc(sizeof(SerialSvc)); serial_svc = malloc(sizeof(SerialSvc));
@@ -90,7 +116,7 @@ void serial_svc_start() {
// Add service // Add service
status = aci_gatt_add_service( status = aci_gatt_add_service(
UUID_TYPE_128, (Service_UUID_t*)service_uuid, PRIMARY_SERVICE, 10, &serial_svc->svc_handle); UUID_TYPE_128, (Service_UUID_t*)service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle);
if(status) { if(status) {
FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); FURI_LOG_E(TAG, "Failed to add Serial service: %d", status);
} }
@@ -141,6 +167,22 @@ void serial_svc_start() {
if(status) { if(status) {
FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status); FURI_LOG_E(TAG, "Failed to add Flow Control characteristic: %d", status);
} }
// Add RPC status characteristic
status = aci_gatt_add_char(
serial_svc->svc_handle,
UUID_TYPE_128,
(const Char_UUID_t*)rpc_status_uuid,
sizeof(SerialServiceRpcStatus),
CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY,
ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE,
GATT_NOTIFY_ATTRIBUTE_WRITE,
10,
CHAR_VALUE_LEN_CONSTANT,
&serial_svc->rpc_status_char_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to add RPC status characteristic: %d", status);
}
serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive);
// Allocate buffer size mutex // Allocate buffer size mutex
serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal); serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal);
} }
@@ -198,6 +240,10 @@ void serial_svc_stop() {
if(status) { if(status) {
FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status); FURI_LOG_E(TAG, "Failed to delete Flow Control characteristic: %d", status);
} }
status = aci_gatt_del_char(serial_svc->svc_handle, serial_svc->rpc_status_char_handle);
if(status) {
FURI_LOG_E(TAG, "Failed to delete RPC Status characteristic: %d", status);
}
// Delete service // Delete service
status = aci_gatt_del_service(serial_svc->svc_handle); status = aci_gatt_del_service(serial_svc->svc_handle);
if(status) { if(status) {
@@ -242,3 +288,8 @@ bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) {
return true; return true;
} }
void serial_svc_set_rpc_status(SerialServiceRpcStatus status) {
furi_assert(serial_svc);
serial_svc_update_rpc_char(status);
}

View File

@@ -10,9 +10,15 @@
extern "C" { extern "C" {
#endif #endif
typedef enum {
SerialServiceRpcStatusNotActive = 0UL,
SerialServiceRpcStatusActive = 1UL,
} SerialServiceRpcStatus;
typedef enum { typedef enum {
SerialServiceEventTypeDataReceived, SerialServiceEventTypeDataReceived,
SerialServiceEventTypeDataSent, SerialServiceEventTypeDataSent,
SerialServiceEventTypesBleResetRequest,
} SerialServiceEventType; } SerialServiceEventType;
typedef struct { typedef struct {
@@ -34,6 +40,8 @@ void serial_svc_set_callbacks(
SerialServiceEventCallback callback, SerialServiceEventCallback callback,
void* context); void* context);
void serial_svc_set_rpc_status(SerialServiceRpcStatus status);
void serial_svc_notify_buffer_is_empty(); void serial_svc_notify_buffer_is_empty();
void serial_svc_stop(); void serial_svc_stop();

View File

@@ -31,6 +31,16 @@ void furi_hal_bt_serial_notify_buffer_is_empty() {
serial_svc_notify_buffer_is_empty(); serial_svc_notify_buffer_is_empty();
} }
void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status) {
SerialServiceRpcStatus st;
if(status == FuriHalBtSerialRpcStatusActive) {
st = SerialServiceRpcStatusActive;
} else {
st = SerialServiceRpcStatusNotActive;
}
serial_svc_set_rpc_status(st);
}
bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) {
if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) {
return false; return false;

View File

@@ -8,6 +8,11 @@ extern "C" {
#define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX #define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX
typedef enum {
FuriHalBtSerialRpcStatusNotActive,
FuriHalBtSerialRpcStatusActive,
} FuriHalBtSerialRpcStatus;
/** Serial service callback type */ /** Serial service callback type */
typedef SerialServiceEventCallback FuriHalBtSerialCallback; typedef SerialServiceEventCallback FuriHalBtSerialCallback;
@@ -30,6 +35,12 @@ void furi_hal_bt_serial_set_event_callback(
FuriHalBtSerialCallback callback, FuriHalBtSerialCallback callback,
void* context); void* context);
/** Set BLE RPC status
*
* @param status FuriHalBtSerialRpcStatus instance
*/
void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status);
/** Notify that application buffer is empty /** Notify that application buffer is empty
*/ */
void furi_hal_bt_serial_notify_buffer_is_empty(); void furi_hal_bt_serial_notify_buffer_is_empty();

View File

@@ -191,7 +191,7 @@ static bool nfc_worker_read_mf_classic(NfcWorker* nfc_worker, FuriHalNfcTxRxCont
uint8_t sectors_total = uint8_t sectors_total =
mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type); mf_classic_get_total_sectors_num(nfc_worker->dev_data->mf_classic_data.type);
FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total); FURI_LOG_I(TAG, "Read %d sectors out of %d total", sectors_read, sectors_total);
read_success = (sectors_read == sectors_total); read_success = mf_classic_is_card_read(&nfc_worker->dev_data->mf_classic_data);
} }
} while(false); } while(false);
@@ -488,6 +488,9 @@ static void nfc_worker_mf_classic_key_attack(
uint16_t start_sector) { uint16_t start_sector) {
furi_assert(nfc_worker); furi_assert(nfc_worker);
bool card_found_notified = true;
bool card_removed_notified = false;
MfClassicData* data = &nfc_worker->dev_data->mf_classic_data; MfClassicData* data = &nfc_worker->dev_data->mf_classic_data;
uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type);
@@ -495,6 +498,14 @@ static void nfc_worker_mf_classic_key_attack(
// Check every sector's A and B keys with the given key // Check every sector's A and B keys with the given key
for(size_t i = start_sector; i < total_sectors; i++) { for(size_t i = start_sector; i < total_sectors; i++) {
furi_hal_nfc_sleep();
if(furi_hal_nfc_activate_nfca(200, NULL)) {
furi_hal_nfc_sleep();
if(!card_found_notified) {
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
card_found_notified = true;
card_removed_notified = false;
}
uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i); uint8_t block_num = mf_classic_get_sector_trailer_block_num_by_sector(i);
if(mf_classic_is_sector_read(data, i)) continue; if(mf_classic_is_sector_read(data, i)) continue;
if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) { if(!mf_classic_is_key_found(data, i, MfClassicKeyA)) {
@@ -523,8 +534,16 @@ static void nfc_worker_mf_classic_key_attack(
nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context); nfc_worker->callback(NfcWorkerEventFoundKeyB, nfc_worker->context);
} }
} }
if(mf_classic_is_sector_read(data, i)) continue; if(mf_classic_is_sector_read(data, i)) continue;
mf_classic_read_sector(tx_rx, data, i); mf_classic_read_sector(tx_rx, data, i);
} else {
if(!card_removed_notified) {
nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context);
card_removed_notified = true;
card_found_notified = false;
}
}
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
} }
} }
@@ -538,6 +557,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
&nfc_worker->dev_data->mf_classic_dict_attack_data; &nfc_worker->dev_data->mf_classic_dict_attack_data;
uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type); uint32_t total_sectors = mf_classic_get_total_sectors_num(data->type);
uint64_t key = 0; uint64_t key = 0;
uint64_t prev_key = 0;
FuriHalNfcTxRxContext tx_rx = {}; FuriHalNfcTxRxContext tx_rx = {};
bool card_found_notified = true; bool card_found_notified = true;
bool card_removed_notified = false; bool card_removed_notified = false;
@@ -572,6 +592,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context);
card_found_notified = true; card_found_notified = true;
card_removed_notified = false; card_removed_notified = false;
nfc_worker_mf_classic_key_attack(nfc_worker, prev_key, &tx_rx, i);
} }
FURI_LOG_D( FURI_LOG_D(
TAG, TAG,
@@ -608,6 +629,7 @@ void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker) {
} }
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
} }
memcpy(&prev_key, &key, sizeof(key));
} }
if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break; if(nfc_worker->state != NfcWorkerStateMfClassicDictAttack) break;
mf_classic_read_sector(&tx_rx, data, i); mf_classic_read_sector(&tx_rx, data, i);

View File

@@ -155,6 +155,16 @@ void mf_classic_set_key_found(
} }
} }
void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) {
furi_assert(data);
if(key_type == MfClassicKeyA) {
FURI_BIT_CLEAR(data->key_a_mask, sector_num);
} else if(key_type == MfClassicKeyB) {
FURI_BIT_CLEAR(data->key_b_mask, sector_num);
}
}
bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) { bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num) {
furi_assert(data); furi_assert(data);
@@ -203,6 +213,18 @@ void mf_classic_get_read_sectors_and_keys(
} }
} }
bool mf_classic_is_card_read(MfClassicData* data) {
furi_assert(data);
uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
uint8_t sectors_read = 0;
uint8_t keys_found = 0;
mf_classic_get_read_sectors_and_keys(data, &sectors_read, &keys_found);
bool card_read = (sectors_read == sectors_total) && (keys_found == sectors_total * 2);
return card_read;
}
static bool mf_classic_is_allowed_access_sector_trailer( static bool mf_classic_is_allowed_access_sector_trailer(
MfClassicEmulator* emulator, MfClassicEmulator* emulator,
uint8_t block_num, uint8_t block_num,
@@ -612,7 +634,15 @@ static bool mf_classic_read_sector_with_reader(
} }
// Auth to first block in sector // Auth to first block in sector
if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) break; if(!mf_classic_auth(tx_rx, first_block, key, key_type, crypto)) {
// Set key to MF_CLASSIC_NO_KEY to prevent further attempts
if(key_type == MfClassicKeyA) {
sector_reader->key_a = MF_CLASSIC_NO_KEY;
} else {
sector_reader->key_b = MF_CLASSIC_NO_KEY;
}
break;
}
sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num); sector->total_blocks = mf_classic_get_blocks_num_in_sector(sector_reader->sector_num);
// Read blocks // Read blocks
@@ -711,6 +741,13 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data
mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]); mf_classic_set_block_read(data, first_block + j, &temp_sector.block[j]);
} }
sectors_read++; sectors_read++;
} else {
// Invalid key, set it to not found
if(key_a != MF_CLASSIC_NO_KEY) {
mf_classic_set_key_not_found(data, i, MfClassicKeyA);
} else {
mf_classic_set_key_not_found(data, i, MfClassicKeyB);
}
} }
} }
} }

View File

@@ -98,12 +98,16 @@ void mf_classic_set_key_found(
MfClassicKey key_type, MfClassicKey key_type,
uint64_t key); uint64_t key);
void mf_classic_set_key_not_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type);
bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num);
void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data);
bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num);
bool mf_classic_is_card_read(MfClassicData* data);
void mf_classic_get_read_sectors_and_keys( void mf_classic_get_read_sectors_and_keys(
MfClassicData* data, MfClassicData* data,
uint8_t* sectors_read, uint8_t* sectors_read,

View File

@@ -60,7 +60,7 @@ class Copro:
array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs}) array.append({"name": filename, "sha256": file_sha256(source_file), **kwargs})
def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None): def bundle(self, output_file, stack_file_name, stack_type, stack_addr=None):
self.output_tar = tarfile.open(output_file, "w:gz") self.output_tar = tarfile.open(output_file, "w:gz", format=tarfile.USTAR_FORMAT)
stack_file = os.path.join(self.mcu_copro, stack_file_name) stack_file = os.path.join(self.mcu_copro, stack_file_name)
# Form Manifest # Form Manifest

View File

@@ -20,6 +20,7 @@ class ProjectDir:
class Main(App): class Main(App):
DIST_FILE_PREFIX = "flipper-z-" DIST_FILE_PREFIX = "flipper-z-"
DIST_FOLDER_MAX_NAME_LENGTH = 80
def init(self): def init(self):
self.subparsers = self.parser.add_subparsers(help="sub-command help") self.subparsers = self.parser.add_subparsers(help="sub-command help")
@@ -129,7 +130,9 @@ class Main(App):
) )
if self.args.version: if self.args.version:
bundle_dir_name = f"{self.target}-update-{self.args.suffix}" bundle_dir_name = f"{self.target}-update-{self.args.suffix}"[
: self.DIST_FOLDER_MAX_NAME_LENGTH
]
bundle_dir = join(self.output_dir_path, bundle_dir_name) bundle_dir = join(self.output_dir_path, bundle_dir_name)
bundle_args = [ bundle_args = [
"generate", "generate",
@@ -170,6 +173,7 @@ class Main(App):
), ),
"w:gz", "w:gz",
compresslevel=9, compresslevel=9,
format=tarfile.USTAR_FORMAT,
) as tar: ) as tar:
tar.add(bundle_dir, arcname=bundle_dir_name) tar.add(bundle_dir, arcname=bundle_dir_name)

View File

@@ -22,6 +22,7 @@ class Main(App):
RESOURCE_TAR_MODE = "w:" RESOURCE_TAR_MODE = "w:"
RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT RESOURCE_TAR_FORMAT = tarfile.USTAR_FORMAT
RESOURCE_FILE_NAME = "resources.tar" RESOURCE_FILE_NAME = "resources.tar"
RESOURCE_ENTRY_NAME_MAX_LENGTH = 100
WHITELISTED_STACK_TYPES = set( WHITELISTED_STACK_TYPES = set(
map( map(
@@ -76,9 +77,13 @@ class Main(App):
self.parser_generate.set_defaults(func=self.generate) self.parser_generate.set_defaults(func=self.generate)
def generate(self): def generate(self):
stage_basename = basename(self.args.stage) stage_basename = "updater.bin" # used to be basename(self.args.stage)
dfu_basename = basename(self.args.dfu) dfu_basename = (
radiobin_basename = basename(self.args.radiobin) "firmware.dfu" if self.args.dfu else ""
) # used to be basename(self.args.dfu)
radiobin_basename = (
"radio.bin" if self.args.radiobin else ""
) # used to be basename(self.args.radiobin)
resources_basename = "" resources_basename = ""
radio_version = 0 radio_version = 0
@@ -120,9 +125,10 @@ class Main(App):
) )
if self.args.resources: if self.args.resources:
resources_basename = self.RESOURCE_FILE_NAME resources_basename = self.RESOURCE_FILE_NAME
self.package_resources( if not self.package_resources(
self.args.resources, join(self.args.directory, resources_basename) self.args.resources, join(self.args.directory, resources_basename)
) ):
return 3
if not self.layout_check(dfu_size, radio_addr): if not self.layout_check(dfu_size, radio_addr):
self.logger.warn("Memory layout looks suspicious") self.logger.warn("Memory layout looks suspicious")
@@ -199,11 +205,28 @@ class Main(App):
"Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes" "Please confirm that you REALLY want to do that with --I-understand-what-I-am-doing=yes"
) )
def _tar_filter(self, tarinfo: tarfile.TarInfo):
if len(tarinfo.name) > self.RESOURCE_ENTRY_NAME_MAX_LENGTH:
self.logger.error(
f"Cannot package resource: name '{tarinfo.name}' too long"
)
raise ValueError("Resource name too long")
return tarinfo
def package_resources(self, srcdir: str, dst_name: str): def package_resources(self, srcdir: str, dst_name: str):
try:
with tarfile.open( with tarfile.open(
dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT dst_name, self.RESOURCE_TAR_MODE, format=self.RESOURCE_TAR_FORMAT
) as tarball: ) as tarball:
tarball.add(srcdir, arcname="") tarball.add(
srcdir,
arcname="",
filter=self._tar_filter,
)
return True
except ValueError as e:
self.logger.error(f"Cannot package resources: {e}")
return False
@staticmethod @staticmethod
def copro_version_as_int(coprometa, stacktype): def copro_version_as_int(coprometa, stacktype):