mirror of
https://github.com/DarkFlippers/unleashed-firmware.git
synced 2024-12-24 05:48:08 +03:00
Merge branch 'fz-dev' into dev
This commit is contained in:
commit
7e049d185f
110
applications/debug/unit_tests/bt/bt_test.c
Normal file
110
applications/debug/unit_tests/bt/bt_test.c
Normal file
@ -0,0 +1,110 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../minunit.h"
|
||||
|
||||
#include <bt/bt_service/bt_keys_storage.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define BT_TEST_KEY_STORAGE_FILE_PATH EXT_PATH("unit_tests/bt_test.keys")
|
||||
#define BT_TEST_NVM_RAM_BUFF_SIZE (507 * 4) // The same as in ble NVM storage
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
BtKeysStorage* bt_keys_storage;
|
||||
uint8_t* nvm_ram_buff_dut;
|
||||
uint8_t* nvm_ram_buff_ref;
|
||||
} BtTest;
|
||||
|
||||
BtTest* bt_test = NULL;
|
||||
|
||||
void bt_test_alloc() {
|
||||
bt_test = malloc(sizeof(BtTest));
|
||||
bt_test->storage = furi_record_open(RECORD_STORAGE);
|
||||
bt_test->nvm_ram_buff_dut = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
|
||||
bt_test->nvm_ram_buff_ref = malloc(BT_TEST_NVM_RAM_BUFF_SIZE);
|
||||
bt_test->bt_keys_storage = bt_keys_storage_alloc(BT_TEST_KEY_STORAGE_FILE_PATH);
|
||||
bt_keys_storage_set_ram_params(
|
||||
bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, BT_TEST_NVM_RAM_BUFF_SIZE);
|
||||
}
|
||||
|
||||
void bt_test_free() {
|
||||
furi_assert(bt_test);
|
||||
free(bt_test->nvm_ram_buff_ref);
|
||||
free(bt_test->nvm_ram_buff_dut);
|
||||
bt_keys_storage_free(bt_test->bt_keys_storage);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
free(bt_test);
|
||||
bt_test = NULL;
|
||||
}
|
||||
|
||||
static void bt_test_keys_storage_profile() {
|
||||
// Emulate nvm change on initial connection
|
||||
const int nvm_change_size_on_connection = 88;
|
||||
for(size_t i = 0; i < nvm_change_size_on_connection; i++) {
|
||||
bt_test->nvm_ram_buff_dut[i] = rand();
|
||||
bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
|
||||
}
|
||||
// Emulate update storage on initial connect
|
||||
mu_assert(
|
||||
bt_keys_storage_update(
|
||||
bt_test->bt_keys_storage, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection),
|
||||
"Failed to update key storage on initial connect");
|
||||
memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
|
||||
mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
|
||||
mu_assert(
|
||||
memcmp(
|
||||
bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_change_size_on_connection) ==
|
||||
0,
|
||||
"Wrong buffer loaded");
|
||||
|
||||
const int nvm_disconnect_update_offset = 84;
|
||||
const int nvm_disconnect_update_size = 324;
|
||||
const int nvm_total_size = nvm_change_size_on_connection -
|
||||
(nvm_change_size_on_connection - nvm_disconnect_update_offset) +
|
||||
nvm_disconnect_update_size;
|
||||
// Emulate update storage on initial disconnect
|
||||
for(size_t i = nvm_disconnect_update_offset;
|
||||
i < nvm_disconnect_update_offset + nvm_disconnect_update_size;
|
||||
i++) {
|
||||
bt_test->nvm_ram_buff_dut[i] = rand();
|
||||
bt_test->nvm_ram_buff_ref[i] = bt_test->nvm_ram_buff_dut[i];
|
||||
}
|
||||
mu_assert(
|
||||
bt_keys_storage_update(
|
||||
bt_test->bt_keys_storage,
|
||||
&bt_test->nvm_ram_buff_dut[nvm_disconnect_update_offset],
|
||||
nvm_disconnect_update_size),
|
||||
"Failed to update key storage on initial disconnect");
|
||||
memset(bt_test->nvm_ram_buff_dut, 0, BT_TEST_NVM_RAM_BUFF_SIZE);
|
||||
mu_assert(bt_keys_storage_load(bt_test->bt_keys_storage), "Failed to load NVM");
|
||||
mu_assert(
|
||||
memcmp(bt_test->nvm_ram_buff_ref, bt_test->nvm_ram_buff_dut, nvm_total_size) == 0,
|
||||
"Wrong buffer loaded");
|
||||
}
|
||||
|
||||
static void bt_test_keys_remove_test_file() {
|
||||
mu_assert(
|
||||
storage_simply_remove(bt_test->storage, BT_TEST_KEY_STORAGE_FILE_PATH),
|
||||
"Can't remove test file");
|
||||
}
|
||||
|
||||
MU_TEST(bt_test_keys_storage_serial_profile) {
|
||||
furi_assert(bt_test);
|
||||
|
||||
bt_test_keys_remove_test_file();
|
||||
bt_test_keys_storage_profile();
|
||||
bt_test_keys_remove_test_file();
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_bt) {
|
||||
bt_test_alloc();
|
||||
|
||||
MU_RUN_TEST(bt_test_keys_storage_serial_profile);
|
||||
|
||||
bt_test_free();
|
||||
}
|
||||
|
||||
int run_minunit_test_bt() {
|
||||
MU_RUN_SUITE(test_bt);
|
||||
return MU_EXIT_CODE;
|
||||
}
|
@ -24,6 +24,7 @@ int run_minunit_test_protocol_dict();
|
||||
int run_minunit_test_lfrfid_protocols();
|
||||
int run_minunit_test_nfc();
|
||||
int run_minunit_test_bit_lib();
|
||||
int run_minunit_test_bt();
|
||||
|
||||
typedef int (*UnitTestEntry)();
|
||||
|
||||
@ -49,6 +50,7 @@ const UnitTest unit_tests[] = {
|
||||
{.name = "protocol_dict", .entry = run_minunit_test_protocol_dict},
|
||||
{.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols},
|
||||
{.name = "bit_lib", .entry = run_minunit_test_bit_lib},
|
||||
{.name = "bt", .entry = run_minunit_test_bt},
|
||||
};
|
||||
|
||||
void minunit_print_progress() {
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "../helpers/archive_browser.h"
|
||||
|
||||
#define TAG "Archive"
|
||||
#define SCROLL_INTERVAL (333)
|
||||
#define SCROLL_DELAY (2)
|
||||
|
||||
static const char* ArchiveTabNames[] = {
|
||||
[ArchiveTabFavorites] = "Favorites",
|
||||
@ -268,13 +270,18 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
|
||||
furi_string_set(str_buf, "---");
|
||||
}
|
||||
|
||||
elements_string_fit_width(
|
||||
canvas, str_buf, (scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset);
|
||||
size_t scroll_counter = model->scroll_counter;
|
||||
|
||||
if(model->item_idx == idx) {
|
||||
archive_draw_frame(canvas, i, scrollbar, model->move_fav);
|
||||
if(scroll_counter < SCROLL_DELAY) {
|
||||
scroll_counter = 0;
|
||||
} else {
|
||||
scroll_counter -= SCROLL_DELAY;
|
||||
}
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
scroll_counter = 0;
|
||||
}
|
||||
|
||||
if(custom_icon_data) {
|
||||
@ -284,8 +291,15 @@ static void draw_list(Canvas* canvas, ArchiveBrowserViewModel* model) {
|
||||
canvas_draw_icon(
|
||||
canvas, 2 + x_offset, 16 + i * FRAME_HEIGHT, ArchiveItemIcons[file_type]);
|
||||
}
|
||||
canvas_draw_str(
|
||||
canvas, 15 + x_offset, 24 + i * FRAME_HEIGHT, furi_string_get_cstr(str_buf));
|
||||
|
||||
elements_scrollable_text_line(
|
||||
canvas,
|
||||
15 + x_offset,
|
||||
24 + i * FRAME_HEIGHT,
|
||||
((scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX) - x_offset),
|
||||
str_buf,
|
||||
scroll_counter,
|
||||
(model->item_idx != idx));
|
||||
|
||||
furi_string_free(str_buf);
|
||||
}
|
||||
@ -454,6 +468,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
|
||||
if(move_fav_mode) {
|
||||
browser->callback(ArchiveBrowserEventFavMoveUp, browser->context);
|
||||
}
|
||||
model->scroll_counter = 0;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->item_idx = (model->item_idx + 1) % model->item_cnt;
|
||||
if(is_file_list_load_required(model)) {
|
||||
@ -463,6 +478,7 @@ static bool archive_view_input(InputEvent* event, void* context) {
|
||||
if(move_fav_mode) {
|
||||
browser->callback(ArchiveBrowserEventFavMoveDown, browser->context);
|
||||
}
|
||||
model->scroll_counter = 0;
|
||||
}
|
||||
},
|
||||
true);
|
||||
@ -502,6 +518,27 @@ static bool archive_view_input(InputEvent* event, void* context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static void browser_scroll_timer(void* context) {
|
||||
furi_assert(context);
|
||||
ArchiveBrowserView* browser = context;
|
||||
with_view_model(
|
||||
browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter++; }, true);
|
||||
}
|
||||
|
||||
static void browser_view_enter(void* context) {
|
||||
furi_assert(context);
|
||||
ArchiveBrowserView* browser = context;
|
||||
with_view_model(
|
||||
browser->view, ArchiveBrowserViewModel * model, { model->scroll_counter = 0; }, true);
|
||||
furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
|
||||
}
|
||||
|
||||
static void browser_view_exit(void* context) {
|
||||
furi_assert(context);
|
||||
ArchiveBrowserView* browser = context;
|
||||
furi_timer_stop(browser->scroll_timer);
|
||||
}
|
||||
|
||||
ArchiveBrowserView* browser_alloc() {
|
||||
ArchiveBrowserView* browser = malloc(sizeof(ArchiveBrowserView));
|
||||
browser->view = view_alloc();
|
||||
@ -509,6 +546,10 @@ ArchiveBrowserView* browser_alloc() {
|
||||
view_set_context(browser->view, browser);
|
||||
view_set_draw_callback(browser->view, archive_view_render);
|
||||
view_set_input_callback(browser->view, archive_view_input);
|
||||
view_set_enter_callback(browser->view, browser_view_enter);
|
||||
view_set_exit_callback(browser->view, browser_view_exit);
|
||||
|
||||
browser->scroll_timer = furi_timer_alloc(browser_scroll_timer, FuriTimerTypePeriodic, browser);
|
||||
|
||||
browser->path = furi_string_alloc_set(archive_get_default_path(TAB_DEFAULT));
|
||||
|
||||
@ -528,6 +569,8 @@ ArchiveBrowserView* browser_alloc() {
|
||||
void browser_free(ArchiveBrowserView* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
furi_timer_free(browser->scroll_timer);
|
||||
|
||||
if(browser->worker_running) {
|
||||
file_browser_worker_free(browser->worker);
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ struct ArchiveBrowserView {
|
||||
FuriString* path;
|
||||
InputKey last_tab_switch_dir;
|
||||
bool is_root;
|
||||
FuriTimer* scroll_timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@ -96,6 +97,7 @@ typedef struct {
|
||||
int32_t item_idx;
|
||||
int32_t array_offset;
|
||||
int32_t list_offset;
|
||||
size_t scroll_counter;
|
||||
} ArchiveBrowserViewModel;
|
||||
|
||||
void archive_browser_set_callback(
|
||||
|
@ -367,9 +367,17 @@ int32_t hid_ble_app(void* p) {
|
||||
Hid* app = hid_alloc(HidTransportBle);
|
||||
app = hid_app_alloc_view(app);
|
||||
|
||||
bt_disconnect(app->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_storage_path(app->bt, HID_BT_KEYS_STORAGE_PATH);
|
||||
|
||||
if(!bt_set_profile(app->bt, BtProfileHidKeyboard)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch profile");
|
||||
FURI_LOG_E(TAG, "Failed to switch to HID profile");
|
||||
}
|
||||
|
||||
furi_hal_bt_start_advertising();
|
||||
bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app);
|
||||
|
||||
@ -378,7 +386,17 @@ int32_t hid_ble_app(void* p) {
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
bt_set_status_changed_callback(app->bt, NULL, NULL);
|
||||
bt_set_profile(app->bt, BtProfileSerial);
|
||||
|
||||
bt_disconnect(app->bt);
|
||||
|
||||
// Wait 2nd core to update nvm storage
|
||||
furi_delay_ms(200);
|
||||
|
||||
bt_keys_storage_set_default_path(app->bt);
|
||||
|
||||
if(!bt_set_profile(app->bt, BtProfileSerial)) {
|
||||
FURI_LOG_E(TAG, "Failed to switch to Serial profile");
|
||||
}
|
||||
|
||||
hid_free(app);
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <notification/notification.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/dialog_ex.h>
|
||||
@ -22,6 +23,8 @@
|
||||
#include "views/hid_mouse_jiggler.h"
|
||||
#include "views/hid_tiktok.h"
|
||||
|
||||
#define HID_BT_KEYS_STORAGE_PATH EXT_PATH("apps/Tools/.bt_hid.keys")
|
||||
|
||||
typedef enum {
|
||||
HidTransportUsb,
|
||||
HidTransportBle,
|
||||
|
@ -117,6 +117,8 @@ Bt* bt_alloc() {
|
||||
if(!bt_settings_load(&bt->bt_settings)) {
|
||||
bt_settings_save(&bt->bt_settings);
|
||||
}
|
||||
// Keys storage
|
||||
bt->keys_storage = bt_keys_storage_alloc(BT_KEYS_STORAGE_PATH);
|
||||
// Alloc queue
|
||||
bt->message_queue = furi_message_queue_alloc(8, sizeof(BtMessage));
|
||||
|
||||
@ -285,8 +287,10 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) {
|
||||
static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void* context) {
|
||||
furi_assert(context);
|
||||
Bt* bt = context;
|
||||
FURI_LOG_I(TAG, "Changed addr start: %p, size changed: %d", addr, size);
|
||||
BtMessage message = {.type = BtMessageTypeKeysStorageUpdated};
|
||||
BtMessage message = {
|
||||
.type = BtMessageTypeKeysStorageUpdated,
|
||||
.data.key_storage_data.start_address = addr,
|
||||
.data.key_storage_data.size = size};
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
@ -331,6 +335,8 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
furi_profile = FuriHalBtProfileSerial;
|
||||
}
|
||||
|
||||
bt_keys_storage_load(bt->keys_storage);
|
||||
|
||||
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
|
||||
FURI_LOG_I(TAG, "Bt App started");
|
||||
if(bt->bt_settings.enabled) {
|
||||
@ -358,6 +364,7 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
|
||||
|
||||
static void bt_close_connection(Bt* bt) {
|
||||
bt_close_rpc_connection(bt);
|
||||
furi_hal_bt_stop_advertising();
|
||||
furi_event_flag_set(bt->api_event, BT_API_UNLOCK_EVENT);
|
||||
}
|
||||
|
||||
@ -372,8 +379,8 @@ int32_t bt_srv(void* p) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read keys
|
||||
if(!bt_keys_storage_load(bt)) {
|
||||
// Load keys
|
||||
if(!bt_keys_storage_load(bt->keys_storage)) {
|
||||
FURI_LOG_W(TAG, "Failed to load bonding keys");
|
||||
}
|
||||
|
||||
@ -418,13 +425,16 @@ int32_t bt_srv(void* p) {
|
||||
// Display PIN code
|
||||
bt_pin_code_show(bt, message.data.pin_code);
|
||||
} else if(message.type == BtMessageTypeKeysStorageUpdated) {
|
||||
bt_keys_storage_save(bt);
|
||||
bt_keys_storage_update(
|
||||
bt->keys_storage,
|
||||
message.data.key_storage_data.start_address,
|
||||
message.data.key_storage_data.size);
|
||||
} else if(message.type == BtMessageTypeSetProfile) {
|
||||
bt_change_profile(bt, &message);
|
||||
} else if(message.type == BtMessageTypeDisconnect) {
|
||||
bt_close_connection(bt);
|
||||
} else if(message.type == BtMessageTypeForgetBondedDevices) {
|
||||
bt_keys_storage_delete(bt);
|
||||
bt_keys_storage_delete(bt->keys_storage);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
@ -56,6 +56,19 @@ void bt_set_status_changed_callback(Bt* bt, BtStatusChangedCallback callback, vo
|
||||
*/
|
||||
void bt_forget_bonded_devices(Bt* bt);
|
||||
|
||||
/** Set keys storage file path
|
||||
*
|
||||
* @param bt Bt instance
|
||||
* @param keys_storage_path Path to file with saved keys
|
||||
*/
|
||||
void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path);
|
||||
|
||||
/** Set default keys storage file path
|
||||
*
|
||||
* @param bt Bt instance
|
||||
*/
|
||||
void bt_keys_storage_set_default_path(Bt* bt);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -39,3 +39,18 @@ void bt_forget_bonded_devices(Bt* bt) {
|
||||
furi_check(
|
||||
furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk);
|
||||
}
|
||||
|
||||
void bt_keys_storage_set_storage_path(Bt* bt, const char* keys_storage_path) {
|
||||
furi_assert(bt);
|
||||
furi_assert(bt->keys_storage);
|
||||
furi_assert(keys_storage_path);
|
||||
|
||||
bt_keys_storage_set_file_path(bt->keys_storage, keys_storage_path);
|
||||
}
|
||||
|
||||
void bt_keys_storage_set_default_path(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
furi_assert(bt->keys_storage);
|
||||
|
||||
bt_keys_storage_set_file_path(bt->keys_storage, BT_KEYS_STORAGE_PATH);
|
||||
}
|
||||
|
@ -13,8 +13,14 @@
|
||||
#include <power/power_service/power.h>
|
||||
#include <rpc/rpc.h>
|
||||
#include <notification/notification.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include <bt/bt_settings.h>
|
||||
#include <bt/bt_service/bt_keys_storage.h>
|
||||
|
||||
#include "bt_keys_filename.h"
|
||||
|
||||
#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
|
||||
|
||||
#define BT_API_UNLOCK_EVENT (1UL << 0)
|
||||
|
||||
@ -29,10 +35,16 @@ typedef enum {
|
||||
BtMessageTypeForgetBondedDevices,
|
||||
} BtMessageType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* start_address;
|
||||
uint16_t size;
|
||||
} BtKeyStorageUpdateData;
|
||||
|
||||
typedef union {
|
||||
uint32_t pin_code;
|
||||
uint8_t battery_level;
|
||||
BtProfile profile;
|
||||
BtKeyStorageUpdateData key_storage_data;
|
||||
} BtMessageData;
|
||||
|
||||
typedef struct {
|
||||
@ -46,6 +58,7 @@ struct Bt {
|
||||
uint16_t bt_keys_size;
|
||||
uint16_t max_packet_size;
|
||||
BtSettings bt_settings;
|
||||
BtKeysStorage* keys_storage;
|
||||
BtStatus status;
|
||||
BtProfile profile;
|
||||
FuriMessageQueue* message_queue;
|
||||
|
@ -1,49 +1,24 @@
|
||||
#include "bt_keys_storage.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal_bt.h>
|
||||
#include <lib/toolbox/saved_struct.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#define BT_KEYS_STORAGE_PATH INT_PATH(BT_KEYS_STORAGE_FILE_NAME)
|
||||
#define BT_KEYS_STORAGE_VERSION (0)
|
||||
#define BT_KEYS_STORAGE_MAGIC (0x18)
|
||||
|
||||
bool bt_keys_storage_load(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
bool file_loaded = false;
|
||||
#define TAG "BtKeyStorage"
|
||||
|
||||
furi_hal_bt_get_key_storage_buff(&bt->bt_keys_addr_start, &bt->bt_keys_size);
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
file_loaded = saved_struct_load(
|
||||
BT_KEYS_STORAGE_PATH,
|
||||
bt->bt_keys_addr_start,
|
||||
bt->bt_keys_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
struct BtKeysStorage {
|
||||
uint8_t* nvm_sram_buff;
|
||||
uint16_t nvm_sram_buff_size;
|
||||
FuriString* file_path;
|
||||
};
|
||||
|
||||
return file_loaded;
|
||||
}
|
||||
bool bt_keys_storage_delete(BtKeysStorage* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool bt_keys_storage_save(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
furi_assert(bt->bt_keys_addr_start);
|
||||
bool file_saved = false;
|
||||
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
file_saved = saved_struct_save(
|
||||
BT_KEYS_STORAGE_PATH,
|
||||
bt->bt_keys_addr_start,
|
||||
bt->bt_keys_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
|
||||
return file_saved;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_delete(Bt* bt) {
|
||||
furi_assert(bt);
|
||||
bool delete_succeed = false;
|
||||
bool bt_is_active = furi_hal_bt_is_active();
|
||||
|
||||
@ -55,3 +30,117 @@ bool bt_keys_storage_delete(Bt* bt) {
|
||||
|
||||
return delete_succeed;
|
||||
}
|
||||
|
||||
BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path) {
|
||||
furi_assert(keys_storage_path);
|
||||
|
||||
BtKeysStorage* instance = malloc(sizeof(BtKeysStorage));
|
||||
// Set default nvm ram parameters
|
||||
furi_hal_bt_get_key_storage_buff(&instance->nvm_sram_buff, &instance->nvm_sram_buff_size);
|
||||
// Set key storage file
|
||||
instance->file_path = furi_string_alloc();
|
||||
furi_string_set_str(instance->file_path, keys_storage_path);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void bt_keys_storage_free(BtKeysStorage* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
furi_string_free(instance->file_path);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path) {
|
||||
furi_assert(instance);
|
||||
furi_assert(path);
|
||||
|
||||
furi_string_set_str(instance->file_path, path);
|
||||
}
|
||||
|
||||
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(buff);
|
||||
|
||||
instance->nvm_sram_buff = buff;
|
||||
instance->nvm_sram_buff_size = size;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_load(BtKeysStorage* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
bool loaded = false;
|
||||
do {
|
||||
// Get payload size
|
||||
size_t payload_size = 0;
|
||||
if(!saved_struct_get_payload_size(
|
||||
furi_string_get_cstr(instance->file_path),
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION,
|
||||
&payload_size)) {
|
||||
FURI_LOG_E(TAG, "Failed to read payload size");
|
||||
break;
|
||||
}
|
||||
|
||||
if(payload_size > instance->nvm_sram_buff_size) {
|
||||
FURI_LOG_E(TAG, "Saved data doesn't fit ram buffer");
|
||||
break;
|
||||
}
|
||||
|
||||
// Load saved data to ram
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
bool data_loaded = saved_struct_load(
|
||||
furi_string_get_cstr(instance->file_path),
|
||||
instance->nvm_sram_buff,
|
||||
payload_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
if(!data_loaded) {
|
||||
FURI_LOG_E(TAG, "Failed to load struct");
|
||||
break;
|
||||
}
|
||||
|
||||
loaded = true;
|
||||
} while(false);
|
||||
|
||||
return loaded;
|
||||
}
|
||||
|
||||
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size) {
|
||||
furi_assert(instance);
|
||||
furi_assert(start_addr);
|
||||
|
||||
bool updated = false;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Base address: %p. Start update address: %p. Size changed: %ld",
|
||||
(void*)instance->nvm_sram_buff,
|
||||
start_addr,
|
||||
size);
|
||||
|
||||
do {
|
||||
size_t new_size = start_addr - instance->nvm_sram_buff + size;
|
||||
if(new_size > instance->nvm_sram_buff_size) {
|
||||
FURI_LOG_E(TAG, "NVM RAM buffer overflow");
|
||||
break;
|
||||
}
|
||||
|
||||
furi_hal_bt_nvm_sram_sem_acquire();
|
||||
bool data_updated = saved_struct_save(
|
||||
furi_string_get_cstr(instance->file_path),
|
||||
instance->nvm_sram_buff,
|
||||
new_size,
|
||||
BT_KEYS_STORAGE_MAGIC,
|
||||
BT_KEYS_STORAGE_VERSION);
|
||||
furi_hal_bt_nvm_sram_sem_release();
|
||||
if(!data_updated) {
|
||||
FURI_LOG_E(TAG, "Failed to update key storage");
|
||||
break;
|
||||
}
|
||||
updated = true;
|
||||
} while(false);
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
@ -1,10 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "bt_i.h"
|
||||
#include "bt_keys_filename.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool bt_keys_storage_load(Bt* bt);
|
||||
typedef struct BtKeysStorage BtKeysStorage;
|
||||
|
||||
bool bt_keys_storage_save(Bt* bt);
|
||||
BtKeysStorage* bt_keys_storage_alloc(const char* keys_storage_path);
|
||||
|
||||
bool bt_keys_storage_delete(Bt* bt);
|
||||
void bt_keys_storage_free(BtKeysStorage* instance);
|
||||
|
||||
void bt_keys_storage_set_file_path(BtKeysStorage* instance, const char* path);
|
||||
|
||||
void bt_keys_storage_set_ram_params(BtKeysStorage* instance, uint8_t* buff, uint16_t size);
|
||||
|
||||
bool bt_keys_storage_load(BtKeysStorage* instance);
|
||||
|
||||
bool bt_keys_storage_update(BtKeysStorage* instance, uint8_t* start_addr, uint32_t size);
|
||||
|
||||
bool bt_keys_storage_delete(BtKeysStorage* instance);
|
||||
|
@ -50,6 +50,10 @@ void view_holder_free(ViewHolder* view_holder) {
|
||||
void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||
furi_assert(view_holder);
|
||||
if(view_holder->view) {
|
||||
if(view_holder->view->exit_callback) {
|
||||
view_holder->view->exit_callback(view_holder->view->context);
|
||||
}
|
||||
|
||||
view_set_update_callback(view_holder->view, NULL);
|
||||
view_set_update_callback_context(view_holder->view, NULL);
|
||||
}
|
||||
@ -59,6 +63,10 @@ void view_holder_set_view(ViewHolder* view_holder, View* view) {
|
||||
if(view_holder->view) {
|
||||
view_set_update_callback(view_holder->view, view_holder_update);
|
||||
view_set_update_callback_context(view_holder->view, view_holder);
|
||||
|
||||
if(view_holder->view->enter_callback) {
|
||||
view_holder->view->enter_callback(view_holder->view->context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -547,6 +547,52 @@ void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width
|
||||
}
|
||||
}
|
||||
|
||||
void elements_scrollable_text_line(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
FuriString* string,
|
||||
size_t scroll,
|
||||
bool ellipsis) {
|
||||
FuriString* line = furi_string_alloc_set(string);
|
||||
|
||||
size_t len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
|
||||
if(len_px > width) {
|
||||
if(ellipsis) {
|
||||
width -= canvas_string_width(canvas, "...");
|
||||
}
|
||||
|
||||
// Calculate scroll size
|
||||
size_t scroll_size = furi_string_size(string);
|
||||
size_t right_width = 0;
|
||||
for(size_t i = scroll_size; i > 0; i--) {
|
||||
right_width += canvas_glyph_width(canvas, furi_string_get_char(line, i));
|
||||
if(right_width > width) break;
|
||||
scroll_size--;
|
||||
if(!scroll_size) break;
|
||||
}
|
||||
// Ensure that we have something to scroll
|
||||
if(scroll_size) {
|
||||
scroll_size += 3;
|
||||
scroll = scroll % scroll_size;
|
||||
furi_string_right(line, scroll);
|
||||
}
|
||||
|
||||
do {
|
||||
furi_string_left(line, furi_string_size(line) - 1);
|
||||
len_px = canvas_string_width(canvas, furi_string_get_cstr(line));
|
||||
} while(len_px > width);
|
||||
|
||||
if(ellipsis) {
|
||||
furi_string_cat(line, "...");
|
||||
}
|
||||
}
|
||||
|
||||
canvas_draw_str(canvas, x, y, furi_string_get_cstr(line));
|
||||
furi_string_free(line);
|
||||
}
|
||||
|
||||
void elements_text_box(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
|
@ -192,6 +192,25 @@ void elements_bubble_str(
|
||||
*/
|
||||
void elements_string_fit_width(Canvas* canvas, FuriString* string, uint8_t width);
|
||||
|
||||
/** Draw scrollable text line
|
||||
*
|
||||
* @param canvas The canvas
|
||||
* @param[in] x X coordinate
|
||||
* @param[in] y Y coordinate
|
||||
* @param[in] width The width
|
||||
* @param string The string
|
||||
* @param[in] scroll The scroll counter: 0 - no scroll, any other number - scroll. Just count up, everything else will be calculated on the inside.
|
||||
* @param[in] ellipsis The ellipsis flag: true to add ellipse
|
||||
*/
|
||||
void elements_scrollable_text_line(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t width,
|
||||
FuriString* string,
|
||||
size_t scroll,
|
||||
bool ellipsis);
|
||||
|
||||
/** Draw text box element
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
#define CUSTOM_ICON_MAX_SIZE 32
|
||||
|
||||
#define SCROLL_INTERVAL (333)
|
||||
#define SCROLL_DELAY (2)
|
||||
|
||||
typedef enum {
|
||||
BrowserItemTypeLoading,
|
||||
BrowserItemTypeBack,
|
||||
@ -95,6 +98,7 @@ struct FileBrowser {
|
||||
void* item_context;
|
||||
|
||||
FuriString* result_path;
|
||||
FuriTimer* scroll_timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@ -110,6 +114,7 @@ typedef struct {
|
||||
|
||||
const Icon* file_icon;
|
||||
bool hide_ext;
|
||||
size_t scroll_counter;
|
||||
} FileBrowserModel;
|
||||
|
||||
static const Icon* BrowserItemIcons[] = {
|
||||
@ -129,6 +134,27 @@ static void
|
||||
browser_list_item_cb(void* context, FuriString* item_path, bool is_folder, bool is_last);
|
||||
static void browser_long_load_cb(void* context);
|
||||
|
||||
static void file_browser_scroll_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = context;
|
||||
with_view_model(
|
||||
browser->view, FileBrowserModel * model, { model->scroll_counter++; }, true);
|
||||
}
|
||||
|
||||
static void file_browser_view_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = context;
|
||||
with_view_model(
|
||||
browser->view, FileBrowserModel * model, { model->scroll_counter = 0; }, true);
|
||||
furi_timer_start(browser->scroll_timer, SCROLL_INTERVAL);
|
||||
}
|
||||
|
||||
static void file_browser_view_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
FileBrowser* browser = context;
|
||||
furi_timer_stop(browser->scroll_timer);
|
||||
}
|
||||
|
||||
FileBrowser* file_browser_alloc(FuriString* result_path) {
|
||||
furi_assert(result_path);
|
||||
FileBrowser* browser = malloc(sizeof(FileBrowser));
|
||||
@ -137,6 +163,11 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
|
||||
view_set_context(browser->view, browser);
|
||||
view_set_draw_callback(browser->view, file_browser_view_draw_callback);
|
||||
view_set_input_callback(browser->view, file_browser_view_input_callback);
|
||||
view_set_enter_callback(browser->view, file_browser_view_enter_callback);
|
||||
view_set_exit_callback(browser->view, file_browser_view_exit_callback);
|
||||
|
||||
browser->scroll_timer =
|
||||
furi_timer_alloc(file_browser_scroll_timer_callback, FuriTimerTypePeriodic, browser);
|
||||
|
||||
browser->result_path = result_path;
|
||||
|
||||
@ -149,6 +180,8 @@ FileBrowser* file_browser_alloc(FuriString* result_path) {
|
||||
void file_browser_free(FileBrowser* browser) {
|
||||
furi_assert(browser);
|
||||
|
||||
furi_timer_free(browser->scroll_timer);
|
||||
|
||||
with_view_model(
|
||||
browser->view, FileBrowserModel * model, { items_array_clear(model->items); }, false);
|
||||
|
||||
@ -468,13 +501,17 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
|
||||
furi_string_set(filename, ". .");
|
||||
}
|
||||
|
||||
elements_string_fit_width(
|
||||
canvas, filename, (show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX));
|
||||
|
||||
size_t scroll_counter = model->scroll_counter;
|
||||
if(model->item_idx == idx) {
|
||||
browser_draw_frame(canvas, i, show_scrollbar);
|
||||
if(scroll_counter < SCROLL_DELAY) {
|
||||
scroll_counter = 0;
|
||||
} else {
|
||||
scroll_counter -= SCROLL_DELAY;
|
||||
}
|
||||
} else {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
scroll_counter = 0;
|
||||
}
|
||||
|
||||
if(custom_icon_data) {
|
||||
@ -487,8 +524,14 @@ static void browser_draw_list(Canvas* canvas, FileBrowserModel* model) {
|
||||
canvas_draw_icon(
|
||||
canvas, 2, Y_OFFSET + 1 + i * FRAME_HEIGHT, BrowserItemIcons[item_type]);
|
||||
}
|
||||
canvas_draw_str(
|
||||
canvas, 15, Y_OFFSET + 9 + i * FRAME_HEIGHT, furi_string_get_cstr(filename));
|
||||
elements_scrollable_text_line(
|
||||
canvas,
|
||||
15,
|
||||
Y_OFFSET + 9 + i * FRAME_HEIGHT,
|
||||
(show_scrollbar ? MAX_LEN_PX - 6 : MAX_LEN_PX),
|
||||
filename,
|
||||
scroll_counter,
|
||||
(model->item_idx != idx));
|
||||
}
|
||||
|
||||
if(show_scrollbar) {
|
||||
@ -543,6 +586,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
|
||||
file_browser_worker_load(
|
||||
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
|
||||
}
|
||||
model->scroll_counter = 0;
|
||||
} else if(event->key == InputKeyDown) {
|
||||
model->item_idx = (model->item_idx + 1) % model->item_cnt;
|
||||
if(browser_is_list_load_required(model)) {
|
||||
@ -554,6 +598,7 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
|
||||
file_browser_worker_load(
|
||||
browser->worker, load_offset, ITEM_LIST_LEN_MAX);
|
||||
}
|
||||
model->scroll_counter = 0;
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
@ -51,6 +51,9 @@ void locale_format_time(
|
||||
} else {
|
||||
am_pm = 1;
|
||||
}
|
||||
if(hours == 0) {
|
||||
hours = 12;
|
||||
}
|
||||
}
|
||||
|
||||
if(show_seconds) {
|
||||
|
255
documentation/file_formats/NfcFileFormats.md
Normal file
255
documentation/file_formats/NfcFileFormats.md
Normal file
@ -0,0 +1,255 @@
|
||||
# NFC Flipper File Formats
|
||||
|
||||
## NFC-A (UID) + Header
|
||||
|
||||
### Example
|
||||
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic, Bank card
|
||||
Device type: UID
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 85 92 8A A0 61 81
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
|
||||
### Description
|
||||
|
||||
This file format is used to store the UID, SAK and ATQA of a NFC-A device. It does not store any internal data, so it can be used for multiple different card types. Also used as a header for other formats.
|
||||
|
||||
Version differences:
|
||||
|
||||
1. Initial version, deprecated
|
||||
2. LSB ATQA (e.g. 4400 instead of 0044)
|
||||
3. MSB ATQA (current version)
|
||||
|
||||
UID can be either 4 or 7 bytes long. ATQA is 2 bytes long. SAK is 1 byte long.
|
||||
|
||||
## Mifare Ultralight/NTAG
|
||||
|
||||
### Example
|
||||
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: NTAG216
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 85 90 54 12 98 23
|
||||
ATQA: 00 44
|
||||
SAK: 00
|
||||
# Mifare Ultralight specific data
|
||||
Data format version: 1
|
||||
Signature: 1B 84 EB 70 BD 4C BD 1B 1D E4 98 0B 18 58 BD 7C 72 85 B4 E4 7B 38 8E 96 CF 88 6B EE A3 43 AD 90
|
||||
Mifare version: 00 04 04 02 01 00 13 03
|
||||
Counter 0: 0
|
||||
Tearing 0: 00
|
||||
Counter 1: 0
|
||||
Tearing 1: 00
|
||||
Counter 2: 0
|
||||
Tearing 2: 00
|
||||
Pages total: 231
|
||||
Pages read: 231
|
||||
Page 0: 04 85 92 9B
|
||||
Page 1: 8A A0 61 81
|
||||
Page 2: CA 48 0F 00
|
||||
...
|
||||
Page 224: 00 00 00 00
|
||||
Page 225: 00 00 00 00
|
||||
Page 226: 00 00 7F BD
|
||||
Page 227: 04 00 00 E2
|
||||
Page 228: 00 05 00 00
|
||||
Page 229: 00 00 00 00
|
||||
Page 230: 00 00 00 00
|
||||
Failed authentication attempts: 0
|
||||
|
||||
### Description
|
||||
|
||||
This file format is used to store the UID, SAK and ATQA of a Mifare Ultralight/NTAG device. It also stores the internal data of the card, the signature, the version, and the counters. The data is stored in pages, just like on the card itself.
|
||||
|
||||
The "Signature" field contains the reply of the tag to the READ_SIG command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 31)
|
||||
|
||||
The "Mifare version" field is not related to the file format version, but to the Mifare Ultralight version. It contains the responce of the tag to the GET_VERSION command. More on that can be found here: <https://www.nxp.com/docs/en/data-sheet/MF0ULX1.pdf> (page 21)
|
||||
|
||||
Other fields are the direct representation of the card's internal state, more on them can be found in the same datasheet.
|
||||
|
||||
Version differences:
|
||||
|
||||
1. Current version
|
||||
|
||||
## Mifare Classic
|
||||
|
||||
### Example
|
||||
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: Mifare Classic
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: BA E2 7C 9D
|
||||
ATQA: 00 02
|
||||
SAK: 18
|
||||
# Mifare Classic specific data
|
||||
Mifare Classic type: 4K
|
||||
Data format version: 2
|
||||
# Mifare Classic blocks, '??' means unknown data
|
||||
Block 0: BA E2 7C 9D B9 18 02 00 46 44 53 37 30 56 30 31
|
||||
Block 1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 3: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
|
||||
Block 4: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 5: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 6: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 7: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
|
||||
...
|
||||
Block 238: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 239: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
|
||||
Block 240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 241: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 242: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 243: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 244: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 245: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 246: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 247: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 248: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 249: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 251: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 252: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 253: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
Block 255: FF FF FF FF FF FF FF 07 80 69 FF FF FF FF FF FF
|
||||
|
||||
### Description
|
||||
|
||||
This file format is used to store the NFC-A and Mifare Classic specific data of a Mifare Classic card. Aside from the NFC-A data, it stores the card type (1K/4K) and the internal data of the card. The data is stored in blocks, there is no sector grouping. If the block's data is unknown, it is represented by '??'. Otherwise, the data is represented as a hex string.
|
||||
|
||||
Version differences:
|
||||
|
||||
1. Initial version, has Key A and Key B masks instead of marking unknown data with '??'.
|
||||
|
||||
Example:
|
||||
|
||||
...
|
||||
Data format version: 1
|
||||
# Key map is the bit mask indicating valid key in each sector
|
||||
Key A map: 000000000000FFFF
|
||||
Key B map: 000000000000FFFF
|
||||
# Mifare Classic blocks
|
||||
...
|
||||
|
||||
2. Current version
|
||||
|
||||
## Mifare DESFire
|
||||
|
||||
### Example
|
||||
|
||||
Filetype: Flipper NFC device
|
||||
Version: 3
|
||||
# Nfc device type can be UID, Mifare Ultralight, Mifare Classic
|
||||
Device type: Mifare DESFire
|
||||
# UID, ATQA and SAK are common for all formats
|
||||
UID: 04 2F 19 0A CD 66 80
|
||||
ATQA: 03 44
|
||||
SAK: 20
|
||||
# Mifare DESFire specific data
|
||||
PICC Version: 04 01 01 12 00 1A 05 04 01 01 02 01 1A 05 04 2F 19 0A CD 66 80 CE ED D4 51 80 31 19
|
||||
PICC Free Memory: 7520
|
||||
PICC Change Key ID: 00
|
||||
PICC Config Changeable: true
|
||||
PICC Free Create Delete: true
|
||||
PICC Free Directory List: true
|
||||
PICC Key Changeable: true
|
||||
PICC Max Keys: 01
|
||||
PICC Key 0 Version: 00
|
||||
Application Count: 1
|
||||
Application IDs: 56 34 12
|
||||
Application 563412 Change Key ID: 00
|
||||
Application 563412 Config Changeable: true
|
||||
Application 563412 Free Create Delete: true
|
||||
Application 563412 Free Directory List: true
|
||||
Application 563412 Key Changeable: true
|
||||
Application 563412 Max Keys: 0E
|
||||
Application 563412 Key 0 Version: 00
|
||||
Application 563412 Key 1 Version: 00
|
||||
Application 563412 Key 2 Version: 00
|
||||
Application 563412 Key 3 Version: 00
|
||||
Application 563412 Key 4 Version: 00
|
||||
Application 563412 Key 5 Version: 00
|
||||
Application 563412 Key 6 Version: 00
|
||||
Application 563412 Key 7 Version: 00
|
||||
Application 563412 Key 8 Version: 00
|
||||
Application 563412 Key 9 Version: 00
|
||||
Application 563412 Key 10 Version: 00
|
||||
Application 563412 Key 11 Version: 00
|
||||
Application 563412 Key 12 Version: 00
|
||||
Application 563412 Key 13 Version: 00
|
||||
Application 563412 File IDs: 01
|
||||
Application 563412 File 1 Type: 00
|
||||
Application 563412 File 1 Communication Settings: 00
|
||||
Application 563412 File 1 Access Rights: EE EE
|
||||
Application 563412 File 1 Size: 256
|
||||
Application 563412 File 1: 13 37 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
|
||||
|
||||
### Description
|
||||
|
||||
This file format is used to store the NFC-A and Mifare DESFire specific data of a Mifare DESFire card. Aside from the NFC-A data, it stores the card type (DESFire) and the internal data of the card. The data is stored per-application, and per-file. Here, the card was written using those pm3 commands:
|
||||
|
||||
hf mfdes createapp --aid 123456 --fid 2345 --dfname astra
|
||||
hf mfdes createfile --aid 123456 --fid 01 --isofid 0001 --size 000100
|
||||
hf mfdes write --aid 123456 --fid 01 -d 1337
|
||||
|
||||
Version differences:
|
||||
None, there are no versions yet.
|
||||
|
||||
## Mifare Classic Dictionary
|
||||
|
||||
### Example
|
||||
|
||||
# Key dictionary from https://github.com/ikarus23/MifareClassicTool.git
|
||||
|
||||
# More well known keys!
|
||||
# Standard keys
|
||||
FFFFFFFFFFFF
|
||||
A0A1A2A3A4A5
|
||||
D3F7D3F7D3F7
|
||||
000000000000
|
||||
|
||||
# Keys from mfoc
|
||||
B0B1B2B3B4B5
|
||||
4D3A99C351DD
|
||||
1A982C7E459A
|
||||
AABBCCDDEEFF
|
||||
714C5C886E97
|
||||
587EE5F9350F
|
||||
A0478CC39091
|
||||
533CB6C723F6
|
||||
8FD0A4F256E9
|
||||
...
|
||||
|
||||
### Description
|
||||
|
||||
This file contains a list of Mifare Classic keys. Each key is represented as a hex string. Lines starting with '#' are ignored as comments. Blank lines are ignored as well.
|
||||
|
||||
## EMV resources
|
||||
|
||||
### Example
|
||||
|
||||
Filetype: Flipper EMV resources
|
||||
Version: 1
|
||||
# EMV currency code: currency name
|
||||
0997: USN
|
||||
0994: XSU
|
||||
0990: CLF
|
||||
0986: BRL
|
||||
0985: PLN
|
||||
0984: BOV
|
||||
...
|
||||
|
||||
### Description
|
||||
|
||||
This file stores a list of EMV currency codes, country codes, or AIDs and their names. Each line contains a hex value and a name separated by a colon and a space.
|
||||
|
||||
Version differences:
|
||||
|
||||
1. Initial version
|
@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,11.1,,
|
||||
Version,+,11.2,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
@ -570,6 +570,8 @@ Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t
|
||||
Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t"
|
||||
Function,+,bt_disconnect,void,Bt*
|
||||
Function,+,bt_forget_bonded_devices,void,Bt*
|
||||
Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*"
|
||||
Function,+,bt_keys_storage_set_default_path,void,Bt*
|
||||
Function,+,bt_set_profile,_Bool,"Bt*, BtProfile"
|
||||
Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*"
|
||||
Function,+,buffered_file_stream_alloc,Stream*,Storage*
|
||||
@ -771,6 +773,7 @@ Function,+,elements_multiline_text,void,"Canvas*, uint8_t, uint8_t, const char*"
|
||||
Function,+,elements_multiline_text_aligned,void,"Canvas*, uint8_t, uint8_t, Align, Align, const char*"
|
||||
Function,+,elements_multiline_text_framed,void,"Canvas*, uint8_t, uint8_t, const char*"
|
||||
Function,+,elements_progress_bar,void,"Canvas*, uint8_t, uint8_t, uint8_t, float"
|
||||
Function,+,elements_scrollable_text_line,void,"Canvas*, uint8_t, uint8_t, uint8_t, FuriString*, size_t, _Bool"
|
||||
Function,+,elements_scrollbar,void,"Canvas*, uint16_t, uint16_t"
|
||||
Function,+,elements_scrollbar_pos,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint16_t, uint16_t"
|
||||
Function,+,elements_slightly_rounded_box,void,"Canvas*, uint8_t, uint8_t, uint8_t, uint8_t"
|
||||
@ -2341,6 +2344,7 @@ Function,+,rpc_system_app_set_data_exchange_callback,void,"RpcAppSystem*, RpcApp
|
||||
Function,+,rpc_system_app_set_error_code,void,"RpcAppSystem*, uint32_t"
|
||||
Function,+,rpc_system_app_set_error_text,void,"RpcAppSystem*, const char*"
|
||||
Function,-,rpmatch,int,const char*
|
||||
Function,+,saved_struct_get_payload_size,_Bool,"const char*, uint8_t, uint8_t, size_t*"
|
||||
Function,+,saved_struct_load,_Bool,"const char*, void*, size_t, uint8_t, uint8_t"
|
||||
Function,+,saved_struct_save,_Bool,"const char*, void*, size_t, uint8_t, uint8_t"
|
||||
Function,-,scalbln,double,"double, long int"
|
||||
|
|
@ -125,3 +125,54 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic,
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool saved_struct_get_payload_size(
|
||||
const char* path,
|
||||
uint8_t magic,
|
||||
uint8_t version,
|
||||
size_t* payload_size) {
|
||||
furi_assert(path);
|
||||
furi_assert(payload_size);
|
||||
|
||||
SavedStructHeader header;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
bool result = false;
|
||||
do {
|
||||
if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
FURI_LOG_E(
|
||||
TAG, "Failed to read \"%s\". Error: %s", path, storage_file_get_error_desc(file));
|
||||
break;
|
||||
}
|
||||
|
||||
uint16_t bytes_count = storage_file_read(file, &header, sizeof(SavedStructHeader));
|
||||
if(bytes_count != sizeof(SavedStructHeader)) {
|
||||
FURI_LOG_E(TAG, "Failed to read header");
|
||||
break;
|
||||
}
|
||||
|
||||
if((header.magic != magic) || (header.version != version)) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Magic(%d != %d) or Version(%d != %d) mismatch of file \"%s\"",
|
||||
header.magic,
|
||||
magic,
|
||||
header.version,
|
||||
version,
|
||||
path);
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t file_size = storage_file_size(file);
|
||||
*payload_size = file_size - sizeof(SavedStructHeader);
|
||||
|
||||
result = true;
|
||||
} while(false);
|
||||
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -12,6 +12,12 @@ bool saved_struct_load(const char* path, void* data, size_t size, uint8_t magic,
|
||||
|
||||
bool saved_struct_save(const char* path, void* data, size_t size, uint8_t magic, uint8_t version);
|
||||
|
||||
bool saved_struct_get_payload_size(
|
||||
const char* path,
|
||||
uint8_t magic,
|
||||
uint8_t version,
|
||||
size_t* payload_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user