From cd837576ea0e66954b9f3bf0953ffdc304332f35 Mon Sep 17 00:00:00 2001 From: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Date: Sat, 9 Mar 2024 05:04:09 +0000 Subject: [PATCH] FindMy: Add OpenHaystack import from .keys file --- applications/system/findmy/application.fam | 1 + applications/system/findmy/findmy.c | 10 ++ applications/system/findmy/findmy_i.h | 11 ++ applications/system/findmy/helpers/base64.c | 141 +++++++++++++++++ applications/system/findmy/helpers/base64.h | 21 +++ .../system/findmy/icons/text_10px.png | Bin 0 -> 158 bytes .../findmy/scenes/findmy_scene_config.c | 13 +- .../scenes/findmy_scene_config_import.c | 145 ++++++++++++++++++ .../findmy_scene_config_import_result.c | 58 +++++++ .../system/findmy/scenes/findmy_scenes.h | 2 + 10 files changed, 399 insertions(+), 3 deletions(-) create mode 100644 applications/system/findmy/helpers/base64.c create mode 100644 applications/system/findmy/helpers/base64.h create mode 100644 applications/system/findmy/icons/text_10px.png create mode 100644 applications/system/findmy/scenes/findmy_scene_config_import.c create mode 100644 applications/system/findmy/scenes/findmy_scene_config_import_result.c diff --git a/applications/system/findmy/application.fam b/applications/system/findmy/application.fam index 9a7b2df94..2ef49310f 100644 --- a/applications/system/findmy/application.fam +++ b/applications/system/findmy/application.fam @@ -6,6 +6,7 @@ App( requires=["gui"], stack_size=2 * 1024, fap_icon="location_icon.png", + fap_icon_assets="icons", fap_category="Bluetooth", fap_author="@MatthewKuKanich", fap_weburl="https://github.com/MatthewKuKanich/FindMyFlipper", diff --git a/applications/system/findmy/findmy.c b/applications/system/findmy/findmy.c index 921818103..b51343ce1 100644 --- a/applications/system/findmy/findmy.c +++ b/applications/system/findmy/findmy.c @@ -16,6 +16,8 @@ static FindMy* findmy_app_alloc() { FindMy* app = malloc(sizeof(FindMy)); app->gui = furi_record_open(RECORD_GUI); + app->storage = furi_record_open(RECORD_STORAGE); + app->dialogs = furi_record_open(RECORD_DIALOGS); app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); @@ -41,6 +43,9 @@ static FindMy* findmy_app_alloc() { FindMyViewVarItemList, variable_item_list_get_view(app->var_item_list)); + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, FindMyViewPopup, popup_get_view(app->popup)); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); findmy_state_load(&app->state); @@ -56,6 +61,9 @@ static FindMy* findmy_app_alloc() { static void findmy_app_free(FindMy* app) { furi_assert(app); + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewPopup); + popup_free(app->popup); + view_dispatcher_remove_view(app->view_dispatcher, FindMyViewVarItemList); variable_item_list_free(app->var_item_list); @@ -68,6 +76,8 @@ static void findmy_app_free(FindMy* app) { view_dispatcher_free(app->view_dispatcher); scene_manager_free(app->scene_manager); + furi_record_close(RECORD_DIALOGS); + furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_GUI); free(app); diff --git a/applications/system/findmy/findmy_i.h b/applications/system/findmy/findmy_i.h index c5991b5f4..c7ff965ef 100644 --- a/applications/system/findmy/findmy_i.h +++ b/applications/system/findmy/findmy_i.h @@ -5,22 +5,32 @@ #include #include #include +#include "findmy_icons.h" +#include +#include #include +#include +#include #include #include #include "views/findmy_main.h" #include #include +#include #include "scenes/findmy_scene.h" +#include "helpers/base64.h" struct FindMy { Gui* gui; + Storage* storage; + DialogsApp* dialogs; SceneManager* scene_manager; ViewDispatcher* view_dispatcher; FindMyMain* findmy_main; ByteInput* byte_input; VariableItemList* var_item_list; + Popup* popup; uint8_t mac_buf[EXTRA_BEACON_MAC_ADDR_SIZE]; uint8_t packet_buf[EXTRA_BEACON_MAX_DATA_SIZE]; @@ -32,6 +42,7 @@ typedef enum { FindMyViewMain, FindMyViewByteInput, FindMyViewVarItemList, + FindMyViewPopup, } FindMyView; enum FindMyType { diff --git a/applications/system/findmy/helpers/base64.c b/applications/system/findmy/helpers/base64.c new file mode 100644 index 000000000..f1fb71870 --- /dev/null +++ b/applications/system/findmy/helpers/base64.c @@ -0,0 +1,141 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005-2011, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c + +#include "base64.h" + +#define os_malloc malloc +#define os_free free +#define os_memset memset + +static const unsigned char base64_table[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * base64_encode - Base64 encode + * @src: Data to be encoded + * @len: Length of the data to be encoded + * @out_len: Pointer to output length variable, or %NULL if not used + * Returns: Allocated buffer of out_len bytes of encoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. Returned buffer is + * nul terminated to make it easier to use as a C string. The nul terminator is + * not included in out_len. + */ +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char *out, *pos; + const unsigned char *end, *in; + size_t olen; + int line_len; + + olen = len * 4 / 3 + 4; /* 3-byte blocks to 4-byte */ + olen += olen / 72; /* line feeds */ + olen++; /* nul termination */ + if(olen < len) return NULL; /* integer overflow */ + out = os_malloc(olen); + if(out == NULL) return NULL; + + end = src + len; + in = src; + pos = out; + line_len = 0; + while(end - in >= 3) { + *pos++ = base64_table[in[0] >> 2]; + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = base64_table[in[2] & 0x3f]; + in += 3; + line_len += 4; + if(line_len >= 72) { + *pos++ = '\n'; + line_len = 0; + } + } + + if(end - in) { + *pos++ = base64_table[in[0] >> 2]; + if(end - in == 1) { + *pos++ = base64_table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = base64_table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = base64_table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + line_len += 4; + } + + if(line_len) *pos++ = '\n'; + + *pos = '\0'; + if(out_len) *out_len = pos - out; + return out; +} + +/** + * base64_decode - Base64 decode + * @src: Data to be decoded + * @len: Length of the data to be decoded + * @out_len: Pointer to output length variable + * Returns: Allocated buffer of out_len bytes of decoded data, + * or %NULL on failure + * + * Caller is responsible for freeing the returned buffer. + */ +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len) { + unsigned char dtable[256], *out, *pos, block[4], tmp; + size_t i, count, olen; + int pad = 0; + + os_memset(dtable, 0x80, 256); + for(i = 0; i < sizeof(base64_table) - 1; i++) dtable[base64_table[i]] = (unsigned char)i; + dtable['='] = 0; + + count = 0; + for(i = 0; i < len; i++) { + if(dtable[src[i]] != 0x80) count++; + } + + if(count == 0 || count % 4) return NULL; + + olen = count / 4 * 3; + pos = out = os_malloc(olen); + if(out == NULL) return NULL; + + count = 0; + for(i = 0; i < len; i++) { + tmp = dtable[src[i]]; + if(tmp == 0x80) continue; + + if(src[i] == '=') pad++; + block[count] = tmp; + count++; + if(count == 4) { + *pos++ = (block[0] << 2) | (block[1] >> 4); + *pos++ = (block[1] << 4) | (block[2] >> 2); + *pos++ = (block[2] << 6) | block[3]; + count = 0; + if(pad) { + if(pad == 1) + pos--; + else if(pad == 2) + pos -= 2; + else { + /* Invalid padding */ + os_free(out); + return NULL; + } + break; + } + } + } + + *out_len = pos - out; + return out; +} diff --git a/applications/system/findmy/helpers/base64.h b/applications/system/findmy/helpers/base64.h new file mode 100644 index 000000000..333f82dc1 --- /dev/null +++ b/applications/system/findmy/helpers/base64.h @@ -0,0 +1,21 @@ +/* + * Base64 encoding/decoding (RFC1341) + * Copyright (c) 2005, Jouni Malinen + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.h + +#ifndef BASE64_H +#define BASE64_H + +#include +#include +#include +#include + +unsigned char* base64_encode(const unsigned char* src, size_t len, size_t* out_len); +unsigned char* base64_decode(const unsigned char* src, size_t len, size_t* out_len); + +#endif /* BASE64_H */ \ No newline at end of file diff --git a/applications/system/findmy/icons/text_10px.png b/applications/system/findmy/icons/text_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..8e8a6183dd50535729dc9c9b4f220a12dd4c600f GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2VGmzZ%#=aj&u?6^qxB}__|Nk$&IsYz@rRC}3 z7*a7OIiZ2U&CSi=;0cBn1vTatM&Z;3u7g(^G9`qQn09G2aWeNXaKC0S=Q~tg57Z@F z;u=vBoS#-wo>-L1;E+?AmspUPnOCA;ke9BToS%}K{MA`f4ycg9)78&qol`;+00Iau A9smFU literal 0 HcmV?d00001 diff --git a/applications/system/findmy/scenes/findmy_scene_config.c b/applications/system/findmy/scenes/findmy_scene_config.c index 415bda945..eba1a85a2 100644 --- a/applications/system/findmy/scenes/findmy_scene_config.c +++ b/applications/system/findmy/scenes/findmy_scene_config.c @@ -3,7 +3,8 @@ enum VarItemListIndex { VarItemListIndexBroadcastInterval, VarItemListIndexTransmitPower, - VarItemListIndexRegisterTag, + VarItemListIndexImportTagFromFile, + VarItemListIndexRegisterTagManually, VarItemListIndexAbout, }; @@ -57,7 +58,10 @@ void findmy_scene_config_on_enter(void* context) { snprintf(power_str, sizeof(power_str), "%ddBm", app->state.transmit_power); variable_item_set_current_value_text(item, power_str); - item = variable_item_list_add(var_item_list, "Register Tag", 0, NULL, NULL); + item = variable_item_list_add(var_item_list, "Import Tag From File", 0, NULL, NULL); + + item = variable_item_list_add(var_item_list, "Register Tag Manually", 0, NULL, NULL); + item = variable_item_list_add( var_item_list, "Matthew KuKanich, Thanks to Chapoly1305, WillyJL, OpenHaystack, Testers", @@ -82,7 +86,10 @@ bool findmy_scene_config_on_event(void* context, SceneManagerEvent event) { scene_manager_set_scene_state(app->scene_manager, FindMySceneConfig, event.event); consumed = true; switch(event.event) { - case VarItemListIndexRegisterTag: + case VarItemListIndexImportTagFromFile: + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImport); + break; + case VarItemListIndexRegisterTagManually: scene_manager_next_scene(app->scene_manager, FindMySceneConfigMac); break; case VarItemListIndexAbout: diff --git a/applications/system/findmy/scenes/findmy_scene_config_import.c b/applications/system/findmy/scenes/findmy_scene_config_import.c new file mode 100644 index 000000000..8dc9a07e7 --- /dev/null +++ b/applications/system/findmy/scenes/findmy_scene_config_import.c @@ -0,0 +1,145 @@ +#include "../findmy_i.h" + +enum VarItemListIndex { + VarItemListIndexOpenHaystack, +}; + +static const char* parse_open_haystack(FindMy* app, const char* path) { + const char* error = NULL; + + Stream* stream = file_stream_alloc(app->storage); + FuriString* line = furi_string_alloc(); + do { + error = "Can't open file"; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) break; + + error = "Wrong file format"; + while(stream_read_line(stream, line)) { + if(furi_string_start_with(line, "Public key: ") || + furi_string_start_with(line, "Advertisement key: ")) { + error = NULL; + break; + } + } + if(error) break; + + furi_string_right(line, furi_string_search_char(line, ':') + 2); + furi_string_trim(line); + + error = "Base64 failed"; + size_t decoded_len; + uint8_t* public_key = base64_decode( + (uint8_t*)furi_string_get_cstr(line), furi_string_size(line), &decoded_len); + if(decoded_len != 28) { + free(public_key); + break; + } + + memcpy(app->state.mac, public_key, sizeof(app->state.mac)); + app->state.mac[0] |= 0b11000000; + furi_hal_bt_reverse_mac_addr(app->state.mac); + + uint8_t advertisement_template[EXTRA_BEACON_MAX_DATA_SIZE] = { + 0x1e, // length (30) + 0xff, // manufacturer specific data + 0x4c, 0x00, // company ID (Apple) + 0x12, 0x19, // offline finding type and length + 0x00, //state + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // first two bits of key[0] + 0x00, // hint + }; + memcpy(app->state.data, advertisement_template, sizeof(app->state.data)); + memcpy(&app->state.data[7], &public_key[6], decoded_len - 6); + app->state.data[29] = public_key[0] >> 6; + findmy_state_sync_config(&app->state); + findmy_state_save(&app->state); + + free(public_key); + error = NULL; + + } while(false); + furi_string_free(line); + file_stream_close(stream); + stream_free(stream); + + return error; +} + +void findmy_scene_config_import_callback(void* context, uint32_t index) { + furi_assert(context); + FindMy* app = context; + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void findmy_scene_config_import_on_enter(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + VariableItem* item; + + item = variable_item_list_add(var_item_list, "OpenHaystack .keys", 0, NULL, NULL); + + // This scene acts more like a submenu than a var item list tbh + UNUSED(item); + + variable_item_list_set_enter_callback(var_item_list, findmy_scene_config_import_callback, app); + + variable_item_list_set_selected_item( + var_item_list, scene_manager_get_scene_state(app->scene_manager, FindMySceneConfigImport)); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewVarItemList); +} + +bool findmy_scene_config_import_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, FindMySceneConfigImport, event.event); + consumed = true; + + const char* extension = NULL; + switch(event.event) { + case VarItemListIndexOpenHaystack: + extension = ".keys"; + break; + default: + break; + } + if(!extension) { + return consumed; + } + + const DialogsFileBrowserOptions browser_options = { + .extension = extension, + .icon = &I_text_10px, + .base_path = FINDMY_STATE_DIR, + }; + storage_simply_mkdir(app->storage, browser_options.base_path); + FuriString* path = furi_string_alloc_set_str(browser_options.base_path); + if(dialog_file_browser_show(app->dialogs, path, path, &browser_options)) { + // The parse functions return the error text, or NULL for success + // Used in result to show success or error message + const char* error = NULL; + switch(event.event) { + case VarItemListIndexOpenHaystack: + error = parse_open_haystack(app, furi_string_get_cstr(path)); + break; + } + scene_manager_set_scene_state( + app->scene_manager, FindMySceneConfigImportResult, (uint32_t)error); + scene_manager_next_scene(app->scene_manager, FindMySceneConfigImportResult); + } + furi_string_free(path); + } + + return consumed; +} + +void findmy_scene_config_import_on_exit(void* context) { + FindMy* app = context; + VariableItemList* var_item_list = app->var_item_list; + + variable_item_list_reset(var_item_list); +} \ No newline at end of file diff --git a/applications/system/findmy/scenes/findmy_scene_config_import_result.c b/applications/system/findmy/scenes/findmy_scene_config_import_result.c new file mode 100644 index 000000000..62056e78a --- /dev/null +++ b/applications/system/findmy/scenes/findmy_scene_config_import_result.c @@ -0,0 +1,58 @@ +#include "../findmy_i.h" + +enum PopupEvent { + PopupEventExit, +}; + +static void findmy_scene_config_import_result_callback(void* context) { + FindMy* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, PopupEventExit); +} + +void findmy_scene_config_import_result_on_enter(void* context) { + FindMy* app = context; + Popup* popup = app->popup; + + const char* error = (const char*)scene_manager_get_scene_state( + app->scene_manager, FindMySceneConfigImportResult); + if(error) { + popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); + popup_set_header(popup, "Error!", 13, 22, AlignLeft, AlignBottom); + popup_set_text(popup, error, 6, 26, AlignLeft, AlignTop); + popup_disable_timeout(popup); + } else { + popup_set_icon(popup, 36, 5, &I_DolphinDone_80x58); + popup_set_header(popup, "Imported!", 13, 22, AlignLeft, AlignBottom); + popup_enable_timeout(popup); + } + popup_set_timeout(popup, 1500); + popup_set_context(popup, app); + popup_set_callback(popup, findmy_scene_config_import_result_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, FindMyViewPopup); +} + +bool findmy_scene_config_import_result_on_event(void* context, SceneManagerEvent event) { + FindMy* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + switch(event.event) { + case PopupEventExit: + scene_manager_search_and_switch_to_previous_scene( + app->scene_manager, FindMySceneConfig); + break; + default: + break; + } + } + + return consumed; +} + +void findmy_scene_config_import_result_on_exit(void* context) { + FindMy* app = context; + popup_reset(app->popup); +} diff --git a/applications/system/findmy/scenes/findmy_scenes.h b/applications/system/findmy/scenes/findmy_scenes.h index 683c52c5b..9a35c519d 100644 --- a/applications/system/findmy/scenes/findmy_scenes.h +++ b/applications/system/findmy/scenes/findmy_scenes.h @@ -1,4 +1,6 @@ ADD_SCENE(findmy, main, Main) ADD_SCENE(findmy, config, Config) +ADD_SCENE(findmy, config_import, ConfigImport) +ADD_SCENE(findmy, config_import_result, ConfigImportResult) ADD_SCENE(findmy, config_mac, ConfigMac) ADD_SCENE(findmy, config_packet, ConfigPacket)