diff --git a/.vscode/example/clangd/extensions.json b/.vscode/example/clangd/extensions.json index daab417cd..4f24dd7b5 100644 --- a/.vscode/example/clangd/extensions.json +++ b/.vscode/example/clangd/extensions.json @@ -8,7 +8,8 @@ "amiralizadeh9480.cpp-helper", "marus25.cortex-debug", "zxh404.vscode-proto3", - "augustocdias.tasks-shell-input" + "augustocdias.tasks-shell-input", + "rioj7.command-variable" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [ @@ -16,4 +17,4 @@ "ms-vscode.cpptools", "ms-vscode.cmake-tools" ] -} +} \ No newline at end of file diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json index 3f8d15a5d..245d44ca1 100644 --- a/.vscode/example/cpptools/c_cpp_properties.json +++ b/.vscode/example/cpptools/c_cpp_properties.json @@ -2,7 +2,7 @@ "configurations": [ { "name": "Win32", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -10,7 +10,7 @@ }, { "name": "Linux", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", @@ -18,7 +18,7 @@ }, { "name": "Mac", - "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", + "compilerPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", "cStandard": "gnu23", diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index f3fd0fff7..79f6791b3 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -17,6 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) -ADD_SCENE(ibutton, read_exit_confirm, ReadExitConfirm) ADD_SCENE(ibutton, view_data, ViewData) ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c deleted file mode 100644 index 4077de9a6..000000000 --- a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../ibutton_i.h" - -static void ibutton_scene_read_exit_confirm_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - iButton* ibutton = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); - } -} - -void ibutton_scene_read_exit_confirm_on_enter(void* context) { - iButton* ibutton = context; - Widget* widget = ibutton->widget; - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Exit", - ibutton_scene_read_exit_confirm_widget_callback, - ibutton); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Stay", - ibutton_scene_read_exit_confirm_widget_callback, - ibutton); - widget_add_string_element( - widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); - widget_add_string_element( - widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); -} - -bool ibutton_scene_read_exit_confirm_on_event(void* context, SceneManagerEvent event) { - iButton* ibutton = context; - SceneManager* scene_manager = ibutton->scene_manager; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - consumed = true; // Ignore Back button presses - } else if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == GuiButtonTypeLeft) { - scene_manager_search_and_switch_to_previous_scene(scene_manager, iButtonSceneRead); - } else if(event.event == GuiButtonTypeRight) { - scene_manager_previous_scene(scene_manager); - } - } - - return consumed; -} - -void ibutton_scene_read_exit_confirm_on_exit(void* context) { - iButton* ibutton = context; - widget_reset(ibutton->widget); -} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 6dd06ca52..a6c4db12a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -44,7 +44,7 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeBack) { consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c index 34de5b877..75ae84a14 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c @@ -15,7 +15,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) { Widget* widget = ibutton->widget; widget_add_button_element( - widget, GuiButtonTypeLeft, "Exit", ibutton_scene_retry_confirm_widget_callback, ibutton); + widget, GuiButtonTypeLeft, "Retry", ibutton_scene_retry_confirm_widget_callback, ibutton); widget_add_button_element( widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton); widget_add_string_element( diff --git a/applications/main/infrared/resources/infrared/assets/ac.ir b/applications/main/infrared/resources/infrared/assets/ac.ir index 4e1735246..68abaf259 100644 --- a/applications/main/infrared/resources/infrared/assets/ac.ir +++ b/applications/main/infrared/resources/infrared/assets/ac.ir @@ -826,3 +826,35 @@ type: raw frequency: 38000 duty_cycle: 0.330000 data: 3302 1640 404 423 407 420 410 1212 437 390 440 1234 405 395 435 392 438 415 415 1207 432 1242 407 420 410 391 439 414 405 1243 406 1241 408 392 438 415 415 386 433 393 437 390 440 414 405 396 434 419 411 389 441 412 407 420 410 390 440 387 432 1242 407 393 437 390 440 414 405 395 435 392 438 389 430 396 434 1240 409 417 413 414 405 395 435 419 411 1237 412 389 430 396 434 393 437 416 414 387 432 394 436 1212 437 389 441 1234 405 1217 432 1241 408 1213 436 1212 437 1210 439 +# +# Model: Toshiba RAS-2518D +# +name: Dh +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4349 4437 549 1615 551 1614 551 1614 551 1617 549 531 550 530 551 1615 550 531 550 532 549 530 551 531 550 530 551 1615 550 1614 551 531 550 1615 551 529 552 531 550 530 551 533 548 530 551 530 551 1616 549 1615 550 1616 550 1615 550 1614 551 1616 550 1615 551 1615 550 531 550 531 550 530 551 529 552 530 551 530 551 529 552 530 551 531 550 1615 550 532 549 1615 550 1616 550 531 550 531 550 530 551 530 551 529 552 532 549 530 551 530 551 531 550 529 552 531 550 1615 551 530 551 530 551 530 551 531 550 530 551 531 550 530 551 531 550 531 550 531 550 1616 550 1618 547 532 549 529 552 530 551 1615 551 1615 550 5379 4350 4436 550 1616 549 1615 551 1614 552 1615 550 529 552 530 551 1614 552 530 551 529 552 531 550 531 550 531 550 1614 552 1614 551 530 551 1615 550 530 551 530 551 530 551 530 551 531 550 532 549 1616 549 1615 551 1614 552 1615 550 1614 551 1616 550 1614 552 1615 550 529 552 530 551 530 551 530 551 531 550 531 550 530 551 530 551 531 550 1615 550 530 551 1615 550 1615 551 530 551 530 551 530 551 530 551 530 551 530 551 529 552 530 551 531 550 532 549 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 531 550 531 550 531 550 530 551 531 550 1615 551 1615 551 532 549 531 550 531 550 1616 549 1614 552 +# +name: Cool_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4438 549 1615 551 1614 552 1616 549 1616 550 530 551 531 550 1615 551 530 551 529 552 531 550 530 551 531 550 1614 551 1616 550 531 550 1616 549 530 551 531 550 530 551 529 552 530 551 531 550 1616 549 1616 550 1616 549 1616 550 1615 551 1614 551 1614 552 1615 551 530 551 531 550 530 551 531 550 531 550 529 552 532 549 531 550 530 551 1613 552 530 551 531 550 529 552 532 549 530 551 530 551 531 550 531 550 530 551 530 551 530 551 531 550 530 551 531 550 531 550 1615 551 529 552 530 551 530 551 530 551 530 551 530 551 530 551 530 551 532 549 531 550 531 550 532 549 531 550 531 550 530 551 530 551 5132 4351 4435 552 1616 550 1615 550 1615 551 1613 553 531 550 530 551 1615 550 530 551 531 550 531 550 530 551 532 549 1616 550 1616 549 530 551 1615 551 530 551 531 550 530 551 530 551 530 551 531 550 1615 551 1615 551 1614 551 1615 550 1615 551 1615 550 1615 550 1616 550 530 551 530 551 531 550 532 549 530 551 530 551 531 550 531 550 531 550 1615 550 530 551 530 551 530 551 529 552 531 550 530 551 531 550 531 550 530 551 530 551 531 550 530 551 530 551 530 551 531 550 1616 550 530 551 529 552 530 551 531 550 532 549 530 551 530 551 529 552 531 550 529 552 530 551 530 551 531 550 531 550 529 552 531 550 +# +name: Cool_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4436 550 1617 549 1615 550 1615 550 1617 548 530 551 531 550 1615 551 531 550 531 550 530 551 530 551 531 550 1614 552 1615 550 530 551 1614 551 531 550 531 550 531 550 529 552 532 549 530 551 1617 549 1616 549 1615 551 1619 547 1615 550 1615 550 1616 549 1616 550 530 551 531 550 530 551 530 551 531 550 530 551 529 552 529 552 530 551 1617 548 533 548 1615 551 1613 552 530 551 531 550 531 550 530 551 530 551 532 549 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 531 550 531 550 532 549 531 550 530 551 531 550 533 548 531 550 530 551 1617 548 1616 549 530 551 531 550 532 549 532 549 532 549 5200 4349 4436 550 1615 551 1615 551 1615 550 1616 550 531 550 530 551 1615 551 531 550 530 551 530 551 530 551 530 551 1616 549 1615 551 530 551 1615 551 531 550 531 550 530 551 531 550 531 550 531 550 1615 551 1616 550 1616 550 1615 550 1617 548 1616 549 1616 550 1615 550 531 550 530 551 531 550 531 550 532 549 530 551 531 550 531 550 532 549 1616 550 531 550 1616 550 1615 550 531 550 530 551 531 550 531 550 531 550 531 550 531 550 532 549 532 549 531 550 532 549 531 550 1616 550 531 550 530 551 532 549 532 549 530 551 532 549 531 550 532 549 531 550 1616 549 1617 549 531 550 530 551 531 550 532 549 532 549 +# +name: Heat_hi +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4350 4437 547 1618 548 1620 546 1620 546 1619 547 534 547 535 546 1619 547 534 547 536 545 536 545 535 546 535 546 1619 547 1620 545 534 523 1644 546 535 522 559 546 535 546 534 547 535 546 535 545 1620 546 1620 546 1620 546 1619 547 1619 546 1619 547 1620 545 1620 546 535 546 534 547 537 520 558 523 558 547 534 547 536 521 559 522 559 522 1644 546 535 546 535 522 560 545 536 521 559 522 559 522 558 523 559 522 560 521 559 522 559 522 560 521 559 522 561 520 1644 521 1645 520 559 522 559 522 559 522 559 522 559 522 560 521 560 521 560 521 561 520 559 522 560 521 559 522 559 522 559 522 1644 522 559 522 5341 4349 4439 520 1645 521 1645 521 1646 519 1645 521 560 521 561 520 1645 521 560 521 560 521 559 522 560 521 561 520 1646 520 1645 521 561 520 1645 521 561 520 560 521 560 521 560 521 560 521 561 520 1644 522 1644 522 1645 520 1645 521 1645 521 1645 520 1646 520 1644 522 561 520 560 521 560 521 561 520 560 521 561 520 561 520 561 520 560 521 1646 520 562 519 561 520 561 520 562 519 560 521 560 521 561 520 561 520 560 521 560 521 561 520 560 521 560 521 562 519 1646 520 1645 521 561 520 561 520 561 520 560 521 560 521 561 520 560 521 559 522 560 521 561 520 561 520 560 521 562 519 559 522 1645 521 561 520 +# +name: Heat_lo +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 4348 4439 520 1646 520 1646 520 1646 519 1646 520 561 520 561 520 1646 519 561 520 561 520 562 519 562 519 561 520 1646 520 1647 518 563 518 1646 519 562 519 561 520 561 520 562 519 562 519 561 520 1648 517 1647 519 1646 519 1647 519 1646 520 1646 520 1645 520 1647 519 561 520 561 520 562 519 562 519 562 519 562 519 561 520 562 519 561 520 1646 520 562 519 1647 518 1646 520 562 519 560 521 561 520 561 520 561 520 562 519 562 519 560 521 562 519 562 519 560 521 1646 520 1646 520 561 520 562 519 561 520 562 519 561 520 561 520 561 520 561 520 561 520 1647 518 1646 520 562 519 562 519 561 520 1646 520 561 520 5409 4348 4440 519 1645 521 1646 519 1645 521 1645 521 561 520 561 520 1644 522 561 520 561 520 561 520 560 521 562 519 1646 520 1646 520 562 519 1644 522 561 520 561 520 561 520 561 520 561 520 561 520 1646 520 1645 520 1646 520 1645 521 1646 520 1646 520 1644 522 1645 521 560 521 560 521 561 520 561 520 560 521 560 521 561 520 561 520 561 520 1645 521 562 519 1645 521 1645 520 561 520 562 519 561 520 561 520 561 520 560 521 560 521 560 521 560 521 561 520 560 521 1646 520 1646 520 561 520 560 521 559 522 560 521 561 520 561 520 560 521 560 521 560 521 1646 520 1645 520 561 520 560 521 560 521 1645 521 561 520 diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 8844ee4af..3138d790b 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -506,7 +506,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) { furi_string_cat(result, "\e#No ticket"); - return true; + return false; } //remaining_trips furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index f8eacd51a..23a1a3b69 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -2,6 +2,8 @@ #include "../iso14443_4a/iso14443_4a_render.h" +#define MF_DESFIRE_RENDER_MAX_RECORD_SIZE (256U) + void nfc_render_mf_desfire_info( const MfDesfireData* data, NfcProtocolFormatType format_type, @@ -212,8 +214,6 @@ void nfc_render_mf_desfire_file_settings_data( uint32_t record_count = 1; uint32_t record_size = 0; - const uint32_t total_size = simple_array_get_count(data->data); - switch(settings->type) { case MfDesfireFileTypeStandard: case MfDesfireFileTypeBackup: @@ -257,17 +257,14 @@ void nfc_render_mf_desfire_file_settings_data( return; } - for(uint32_t rec = 0; rec < record_count; rec++) { - const uint32_t size_offset = rec * record_size; - const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0; + // Limit record size + bool trim_data = record_size > MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + if(trim_data) { + record_size = MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + } - if(size_remaining < record_size) { - furi_string_cat_printf( - str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size); - record_size = size_remaining; - } else { - furi_string_cat_printf(str, "record %lu\n", rec); - } + for(uint32_t rec = 0; rec < record_count; rec++) { + furi_string_cat_printf(str, "record %lu\n", rec); for(uint32_t ch = 0; ch < record_size; ch += 4) { furi_string_cat_printf(str, "%03lx|", ch); @@ -296,6 +293,9 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_push_back(str, '\n'); } + if(trim_data) { + furi_string_cat_str(str, "..."); + } furi_string_push_back(str, '\n'); } diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index cccee769f..64a9ac5dc 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -83,6 +83,20 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; +static void troika_render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -204,18 +218,19 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); - if(result1) { - furi_string_cat_printf( - parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); + if(result1 && !furi_string_empty(metro_result)) { + troika_render_section_header(parsed_data, "Metro", 22, 21); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2) { - furi_string_cat_printf( - parsed_data, "\n\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); + if(result2 && !furi_string_empty(ground_result)) { + troika_render_section_header(parsed_data, "Ediny", 22, 22); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3) { - furi_string_cat_printf(parsed_data, "\n\e#TAT\n%s", furi_string_get_cstr(tat_result)); + if(result3 && !furi_string_empty(tat_result)) { + troika_render_section_header(parsed_data, "TAT", 24, 23); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result)); } furi_string_free(tat_result); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index c5a334a45..748f9a555 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -61,11 +61,11 @@ static void desktop_clock_update(Desktop* desktop) { furi_hal_rtc_get_datetime(&curr_dt); bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; - if(desktop->time_hour != curr_dt.hour || desktop->time_minute != curr_dt.minute || - desktop->time_format_12 != time_format_12) { - desktop->time_format_12 = time_format_12; - desktop->time_hour = curr_dt.hour; - desktop->time_minute = curr_dt.minute; + if(desktop->clock.hour != curr_dt.hour || desktop->clock.minute != curr_dt.minute || + desktop->clock.format_12 != time_format_12) { + desktop->clock.format_12 = time_format_12; + desktop->clock.hour = curr_dt.hour; + desktop->clock.minute = curr_dt.minute; view_port_update(desktop->clock_viewport); } } @@ -92,8 +92,8 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { canvas_set_font(canvas, FontPrimary); - uint8_t hour = desktop->time_hour; - if(desktop->time_format_12) { + uint8_t hour = desktop->clock.hour; + if(desktop->clock.format_12) { if(hour > 12) { hour -= 12; } @@ -103,11 +103,11 @@ static void desktop_clock_draw_callback(Canvas* canvas, void* context) { } char buffer[20]; - snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->time_minute); + snprintf(buffer, sizeof(buffer), "%02u:%02u", hour, desktop->clock.minute); view_port_set_width( desktop->clock_viewport, - canvas_string_width(canvas, buffer) - 1 + (desktop->time_minute % 10 == 1)); + canvas_string_width(canvas, buffer) - 1 + (desktop->clock.minute % 10 == 1)); canvas_draw_str_aligned(canvas, 0, 8, AlignLeft, AlignBottom, buffer); } @@ -139,7 +139,7 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { desktop_auto_lock_arm(desktop); return true; case DesktopGlobalAutoLock: - if(!loader_is_locked(desktop->loader)) { + if(!loader_is_locked(desktop->loader) && !desktop->locked) { desktop_lock(desktop); } return true; @@ -215,6 +215,8 @@ static void desktop_clock_timer_callback(void* context) { } void desktop_lock(Desktop* desktop) { + furi_assert(!desktop->locked); + furi_hal_rtc_set_flag(FuriHalRtcFlagLock); if(desktop->settings.pin_code.length) { @@ -230,9 +232,13 @@ void desktop_lock(Desktop* desktop) { DesktopStatus status = {.locked = true}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = true; } void desktop_unlock(Desktop* desktop) { + furi_assert(desktop->locked); + view_port_enabled_set(desktop->lock_icon_viewport, false); Gui* gui = furi_record_open(RECORD_GUI); gui_set_lockdown(gui, false); @@ -251,6 +257,8 @@ void desktop_unlock(Desktop* desktop) { DesktopStatus status = {.locked = false}; furi_pubsub_publish(desktop->status_pubsub, &status); + + desktop->locked = false; } void desktop_set_dummy_mode_state(Desktop* desktop, bool enabled) { @@ -298,7 +306,7 @@ Desktop* desktop_alloc(void) { desktop->lock_menu = desktop_lock_menu_alloc(); desktop->debug_view = desktop_debug_alloc(); - desktop->hw_mismatch_popup = popup_alloc(); + desktop->popup = popup_alloc(); desktop->locked_view = desktop_view_locked_alloc(); desktop->pin_input_view = desktop_view_pin_input_alloc(); desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); @@ -334,9 +342,7 @@ Desktop* desktop_alloc(void) { view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); view_dispatcher_add_view( - desktop->view_dispatcher, - DesktopViewIdHwMismatch, - popup_get_view(desktop->hw_mismatch_popup)); + desktop->view_dispatcher, DesktopViewIdPopup, popup_get_view(desktop->popup)); view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewIdPinTimeout, @@ -476,6 +482,17 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault); } + uint8_t keys_total, keys_valid; + if(!furi_hal_crypto_enclave_verify(&keys_total, &keys_valid)) { + FURI_LOG_E( + TAG, + "Secure Enclave verification failed: total %hhu, valid %hhu", + keys_total, + keys_valid); + + scene_manager_next_scene(desktop->scene_manager, DesktopSceneSecureEnclave); + } + // Special case: autostart application is already running if(loader_is_locked(desktop->loader) && animation_manager_is_animation_loaded(desktop->animation_manager)) { diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index c0b29f922..634f0ee00 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -28,13 +28,19 @@ typedef enum { DesktopViewIdLockMenu, DesktopViewIdLocked, DesktopViewIdDebug, - DesktopViewIdHwMismatch, + DesktopViewIdPopup, DesktopViewIdPinInput, DesktopViewIdPinTimeout, DesktopViewIdSlideshow, DesktopViewIdTotal, } DesktopViewId; +typedef struct { + uint8_t hour; + uint8_t minute; + bool format_12; // 1 - 12 hour, 0 - 24H +} DesktopClock; + struct Desktop { // Scene FuriThread* scene_thread; @@ -43,7 +49,7 @@ struct Desktop { ViewDispatcher* view_dispatcher; SceneManager* scene_manager; - Popup* hw_mismatch_popup; + Popup* popup; DesktopLockMenuView* lock_menu; DesktopDebugView* debug_view; DesktopViewLocked* locked_view; @@ -75,11 +81,10 @@ struct Desktop { FuriPubSub* status_pubsub; - uint8_t time_hour; - uint8_t time_minute; - bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H + DesktopClock clock; bool in_transition : 1; + bool locked : 1; FuriSemaphore* animation_semaphore; }; diff --git a/applications/services/desktop/scenes/desktop_scene_config.h b/applications/services/desktop/scenes/desktop_scene_config.h index c153972b2..34d000543 100644 --- a/applications/services/desktop/scenes/desktop_scene_config.h +++ b/applications/services/desktop/scenes/desktop_scene_config.h @@ -7,3 +7,4 @@ ADD_SCENE(desktop, locked, Locked) ADD_SCENE(desktop, pin_input, PinInput) ADD_SCENE(desktop, pin_timeout, PinTimeout) ADD_SCENE(desktop, slideshow, Slideshow) +ADD_SCENE(desktop, secure_enclave, SecureEnclave) \ No newline at end of file diff --git a/applications/services/desktop/scenes/desktop_scene_fault.c b/applications/services/desktop/scenes/desktop_scene_fault.c index 36c958af5..16683ba74 100644 --- a/applications/services/desktop/scenes/desktop_scene_fault.c +++ b/applications/services/desktop/scenes/desktop_scene_fault.c @@ -12,20 +12,21 @@ void desktop_scene_fault_callback(void* context) { void desktop_scene_fault_on_enter(void* context) { Desktop* desktop = (Desktop*)context; - Popup* popup = desktop->hw_mismatch_popup; + Popup* popup = desktop->popup; popup_set_context(popup, desktop); popup_set_header( popup, "Flipper crashed\n and was rebooted", - 60, + 64, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); char* message = (char*)furi_hal_rtc_get_fault_data(); - popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); + popup_set_text(popup, message, 64, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_fault_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); } bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { @@ -47,6 +48,11 @@ bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { } void desktop_scene_fault_on_exit(void* context) { - UNUSED(context); + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_reset(popup); + furi_hal_rtc_set_fault_data(0); } diff --git a/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c b/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c index 35c506103..4624b589c 100644 --- a/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c +++ b/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c @@ -4,17 +4,15 @@ #include "desktop_scene.h" #include "../desktop_i.h" -#define HW_MISMATCH_BACK_EVENT (0UL) - void desktop_scene_hw_mismatch_callback(void* context) { Desktop* desktop = (Desktop*)context; - view_dispatcher_send_custom_event(desktop->view_dispatcher, HW_MISMATCH_BACK_EVENT); + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopHwMismatchExit); } void desktop_scene_hw_mismatch_on_enter(void* context) { Desktop* desktop = (Desktop*)context; furi_assert(desktop); - Popup* popup = desktop->hw_mismatch_popup; + Popup* popup = desktop->popup; char* text_buffer = malloc(256); scene_manager_set_scene_state( @@ -28,10 +26,10 @@ void desktop_scene_hw_mismatch_on_enter(void* context) { version_get_target(NULL)); popup_set_context(popup, desktop); popup_set_header( - popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); - popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); + popup, "!!!! HW Mismatch !!!!", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom); + popup_set_text(popup, text_buffer, 64, 33 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_hw_mismatch_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); } bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) { @@ -40,11 +38,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case HW_MISMATCH_BACK_EVENT: + case DesktopHwMismatchExit: scene_manager_previous_scene(desktop->scene_manager); consumed = true; break; - default: break; } @@ -55,11 +52,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) void desktop_scene_hw_mismatch_on_exit(void* context) { Desktop* desktop = (Desktop*)context; furi_assert(desktop); - Popup* popup = desktop->hw_mismatch_popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); + + Popup* popup = desktop->popup; + popup_reset(popup); + char* text_buffer = (char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch); free(text_buffer); diff --git a/applications/services/desktop/scenes/desktop_scene_secure_enclave.c b/applications/services/desktop/scenes/desktop_scene_secure_enclave.c new file mode 100644 index 000000000..c08125c70 --- /dev/null +++ b/applications/services/desktop/scenes/desktop_scene_secure_enclave.c @@ -0,0 +1,57 @@ +#include +#include + +#include "desktop_scene.h" +#include "../desktop_i.h" + +void desktop_scene_secure_enclave_callback(void* context) { + Desktop* desktop = (Desktop*)context; + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopEnclaveExit); +} + +void desktop_scene_secure_enclave_on_enter(void* context) { + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_set_context(popup, desktop); + popup_set_header( + popup, "No Factory Keys Found", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom); + popup_set_text( + popup, + "Secure Enclave is damaged.\n" + "Some apps will not work.", + 64, + 33 + STATUS_BAR_Y_SHIFT, + AlignCenter, + AlignCenter); + popup_set_callback(popup, desktop_scene_secure_enclave_callback); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); +} + +bool desktop_scene_secure_enclave_on_event(void* context, SceneManagerEvent event) { + Desktop* desktop = (Desktop*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopEnclaveExit: + scene_manager_previous_scene(desktop->scene_manager); + consumed = true; + break; + + default: + break; + } + } + return consumed; +} + +void desktop_scene_secure_enclave_on_exit(void* context) { + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_reset(popup); +} diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index bce9c09d1..7749a7e15 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -46,6 +46,10 @@ typedef enum { DesktopSlideshowCompleted, DesktopSlideshowPoweroff, + DesktopHwMismatchExit, + + DesktopEnclaveExit, + // Global events DesktopGlobalBeforeAppStarted, DesktopGlobalAfterAppFinished, diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_0.png b/assets/dolphin/external/L1_Akira_128x64/frame_0.png new file mode 100755 index 000000000..36c1bbd49 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_0.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_1.png b/assets/dolphin/external/L1_Akira_128x64/frame_1.png new file mode 100755 index 000000000..1950347a6 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_1.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_10.png b/assets/dolphin/external/L1_Akira_128x64/frame_10.png new file mode 100755 index 000000000..65a0154d4 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_10.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_11.png b/assets/dolphin/external/L1_Akira_128x64/frame_11.png new file mode 100755 index 000000000..f7c086431 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_11.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_12.png b/assets/dolphin/external/L1_Akira_128x64/frame_12.png new file mode 100755 index 000000000..e3bfd179d Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_12.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_13.png b/assets/dolphin/external/L1_Akira_128x64/frame_13.png new file mode 100755 index 000000000..0094ab5ae Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_13.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_14.png b/assets/dolphin/external/L1_Akira_128x64/frame_14.png new file mode 100755 index 000000000..b36fd051f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_14.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_15.png b/assets/dolphin/external/L1_Akira_128x64/frame_15.png new file mode 100755 index 000000000..33607328a Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_15.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_16.png b/assets/dolphin/external/L1_Akira_128x64/frame_16.png new file mode 100755 index 000000000..e115834ef Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_16.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_17.png b/assets/dolphin/external/L1_Akira_128x64/frame_17.png new file mode 100755 index 000000000..8e5fa20d8 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_17.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_18.png b/assets/dolphin/external/L1_Akira_128x64/frame_18.png new file mode 100755 index 000000000..6a658e0e2 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_18.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_19.png b/assets/dolphin/external/L1_Akira_128x64/frame_19.png new file mode 100755 index 000000000..cba72f16f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_19.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_2.png b/assets/dolphin/external/L1_Akira_128x64/frame_2.png new file mode 100755 index 000000000..9f4cc1fb9 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_2.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_20.png b/assets/dolphin/external/L1_Akira_128x64/frame_20.png new file mode 100755 index 000000000..457fa7a00 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_20.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_21.png b/assets/dolphin/external/L1_Akira_128x64/frame_21.png new file mode 100755 index 000000000..727511100 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_21.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_22.png b/assets/dolphin/external/L1_Akira_128x64/frame_22.png new file mode 100755 index 000000000..213e05771 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_22.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_23.png b/assets/dolphin/external/L1_Akira_128x64/frame_23.png new file mode 100755 index 000000000..5d3571db8 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_23.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_24.png b/assets/dolphin/external/L1_Akira_128x64/frame_24.png new file mode 100755 index 000000000..e54d47e29 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_24.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_25.png b/assets/dolphin/external/L1_Akira_128x64/frame_25.png new file mode 100755 index 000000000..26c944d16 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_25.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_26.png b/assets/dolphin/external/L1_Akira_128x64/frame_26.png new file mode 100755 index 000000000..2972318cd Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_26.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_27.png b/assets/dolphin/external/L1_Akira_128x64/frame_27.png new file mode 100755 index 000000000..397face78 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_27.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_28.png b/assets/dolphin/external/L1_Akira_128x64/frame_28.png new file mode 100755 index 000000000..6ca246f57 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_28.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_29.png b/assets/dolphin/external/L1_Akira_128x64/frame_29.png new file mode 100755 index 000000000..8bcc83a1d Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_29.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_3.png b/assets/dolphin/external/L1_Akira_128x64/frame_3.png new file mode 100755 index 000000000..7a1d2b36e Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_3.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_30.png b/assets/dolphin/external/L1_Akira_128x64/frame_30.png new file mode 100755 index 000000000..9f477f8d7 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_30.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_31.png b/assets/dolphin/external/L1_Akira_128x64/frame_31.png new file mode 100755 index 000000000..4f2deb5c5 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_31.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_32.png b/assets/dolphin/external/L1_Akira_128x64/frame_32.png new file mode 100755 index 000000000..4f59f9955 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_32.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_33.png b/assets/dolphin/external/L1_Akira_128x64/frame_33.png new file mode 100755 index 000000000..1adf3d351 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_33.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_34.png b/assets/dolphin/external/L1_Akira_128x64/frame_34.png new file mode 100755 index 000000000..5ffc55f75 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_34.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_35.png b/assets/dolphin/external/L1_Akira_128x64/frame_35.png new file mode 100755 index 000000000..9a101f0aa Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_35.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_4.png b/assets/dolphin/external/L1_Akira_128x64/frame_4.png new file mode 100755 index 000000000..ea42d75d6 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_4.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_5.png b/assets/dolphin/external/L1_Akira_128x64/frame_5.png new file mode 100755 index 000000000..c347d3035 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_5.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_6.png b/assets/dolphin/external/L1_Akira_128x64/frame_6.png new file mode 100755 index 000000000..07a7c70c0 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_6.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_7.png b/assets/dolphin/external/L1_Akira_128x64/frame_7.png new file mode 100755 index 000000000..275f2a357 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_7.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_8.png b/assets/dolphin/external/L1_Akira_128x64/frame_8.png new file mode 100755 index 000000000..1feb3e875 Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_8.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/frame_9.png b/assets/dolphin/external/L1_Akira_128x64/frame_9.png new file mode 100755 index 000000000..e0ba8250f Binary files /dev/null and b/assets/dolphin/external/L1_Akira_128x64/frame_9.png differ diff --git a/assets/dolphin/external/L1_Akira_128x64/meta.txt b/assets/dolphin/external/L1_Akira_128x64/meta.txt new file mode 100755 index 000000000..4ac9b38b3 --- /dev/null +++ b/assets/dolphin/external/L1_Akira_128x64/meta.txt @@ -0,0 +1,14 @@ +Filetype: Flipper Animation +Version: 1 + +Width: 128 +Height: 64 +Passive frames: 15 +Active frames: 21 +Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 +Active cycles: 1 +Frame rate: 2 +Duration: 3600 +Active cooldown: 7 + +Bubble slots: 0 \ No newline at end of file diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index c0e1741a0..42e18ad3f 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -189,3 +189,10 @@ Max butthurt: 12 Min level: 3 Max level: 3 Weight: 5 + +Name: L1_Akira_128x64 +Min butthurt: 0 +Max butthurt: 8 +Min level: 1 +Max level: 3 +Weight: 5 diff --git a/assets/icons/Dolphin/DolphinWait_61x59.png b/assets/icons/Dolphin/DolphinWait_61x59.png deleted file mode 100644 index 423e07919..000000000 Binary files a/assets/icons/Dolphin/DolphinWait_61x59.png and /dev/null differ diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index 7231fdb19..85fb101b8 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -381,7 +381,7 @@ static GapConfig template_config = { .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/lib/flipper_application/elf/elf_file.c b/lib/flipper_application/elf/elf_file.c index 654316866..398f25209 100644 --- a/lib/flipper_application/elf/elf_file.c +++ b/lib/flipper_application/elf/elf_file.c @@ -202,6 +202,7 @@ __attribute__((unused)) static const char* elf_reloc_type_to_str(int symt) { STRCASE(R_ARM_NONE) STRCASE(R_ARM_TARGET1) STRCASE(R_ARM_ABS32) + STRCASE(R_ARM_REL32) STRCASE(R_ARM_THM_PC22) STRCASE(R_ARM_THM_JUMP24) default: @@ -329,6 +330,10 @@ static bool elf_relocate_symbol(ELFFile* elf, Elf32_Addr relAddr, int type, Elf3 *((uint32_t*)relAddr) += symAddr; FURI_LOG_D(TAG, " R_ARM_ABS32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); break; + case R_ARM_REL32: + *((uint32_t*)relAddr) += symAddr - relAddr; + FURI_LOG_D(TAG, " R_ARM_REL32 relocated is 0x%08X", (unsigned int)*((uint32_t*)relAddr)); + break; case R_ARM_THM_PC22: case R_ARM_CALL: case R_ARM_THM_JUMP24: diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index d60803fc6..6d147d28d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -218,19 +218,15 @@ void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1971ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1971_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 5ddd8ef2c..483d9827f 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -191,19 +191,15 @@ void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1992_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1992_SRAM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 6af61f355..157dc601a 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -217,19 +217,14 @@ void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1996_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1996_SRAM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c index d38865bae..e43d0c6ad 100644 --- a/lib/ibutton/protocols/misc/protocol_cyfral.c +++ b/lib/ibutton/protocols/misc/protocol_cyfral.c @@ -325,7 +325,7 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } -static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) { +static void protocol_cyfral_render_uid(ProtocolCyfral* proto, FuriString* result) { furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); @@ -333,10 +333,7 @@ static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto } static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) { - furi_string_cat_printf(result, "ID: "); - for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { - furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); - } + protocol_cyfral_render_uid(proto, result); } const ProtocolBase ibutton_protocol_misc_cyfral = { diff --git a/lib/ibutton/protocols/misc/protocol_group_misc.c b/lib/ibutton/protocols/misc/protocol_group_misc.c index 20534602d..ddbbf6bd8 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc.c +++ b/lib/ibutton/protocols/misc/protocol_group_misc.c @@ -200,6 +200,16 @@ static bool ibutton_protocol_group_misc_load( } } +static void ibutton_protocol_group_misc_render_uid( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + protocol_dict_set_data(group->dict, id, data, data_size); + protocol_dict_render_uid(group->dict, result, id); +} + static void ibutton_protocol_group_misc_render_data( iButtonProtocolGroupMisc* group, const iButtonProtocolData* data, @@ -283,6 +293,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_misc = { .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_misc_save, .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_misc_load, + .render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_uid, .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_data, .render_brief_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_brief_data, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index a8d0ff280..175d64c2b 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -1,5 +1,6 @@ #include "lfrfid_protocols.h" #include "protocol_em4100.h" +#include "protocol_electra.h" #include "protocol_h10301.h" #include "protocol_idteck.h" #include "protocol_indala26.h" @@ -22,6 +23,7 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, [LFRFIDProtocolEM410032] = &protocol_em4100_32, [LFRFIDProtocolEM410016] = &protocol_em4100_16, + [LFRFIDProtocolElectra] = &protocol_electra, [LFRFIDProtocolH10301] = &protocol_h10301, [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 64a9fcba2..89aa51c25 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -11,6 +11,7 @@ typedef enum { LFRFIDProtocolEM4100, LFRFIDProtocolEM410032, LFRFIDProtocolEM410016, + LFRFIDProtocolElectra, LFRFIDProtocolH10301, LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c new file mode 100644 index 000000000..b94e60fbe --- /dev/null +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -0,0 +1,439 @@ +/* + * Electra intercom rfid protocol (Romania) + * + * Based on EM4100 protocol implementation from https://github.com/flipperdevices/flipperzero-firmware/blob/dev/lib/lfrfid/protocols/protocol_em4100.c + * + * Copyright 2024 Leptoptilos + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * ------------------------------------------------------------------------------------------------------------------------------ + * PROTOCOL DESCRIPTION: + * ------------------------------------------------------------------------------------------------------------------------------ + * Electra intercom 125 kHz protocol based on 64-bit clock EM4100, but includes some extra data after base EM4100 data (epilogue) + * + * Epilogue size is 64 bits, but only first 16 bits matter. Rest 6 bytes - some filler data, + * that arbitrary change is not validated by the Electra intercoms + * + * There are curently three known types of epilogue: + * - 0x7E71AAAAAAAAAAAA (AA filler) + * - 0x7E71000000000000 (00 filler) + * - 0x0030AAAAAAAAAAAA + * + * First two epilogue bytes may be interpreted as EM4100 data continuation + * Nevertheless, these bytes have correct row parity bits, but have not correct collumn parity + + * For example: 0x7E71AAAAAAAAAAAA epilogue: + * + * In binary: | 0b01111110 | 01110001 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | 10101010 | + * In hex: | 0x7E | 71 | AA | AA | AA | AA | AA | AA | + * + * As EM4100 data: + * 0111 1 // 7 + * 1100 0 // C + * 0111 1 // 7 + * 1101 0 // here epilogue filler starts (from second bit) + * 1010 1 // there is no correct raw parity bits anymore + * 0101 0 + * 1010 1 + * 0101 0 + * 1010 // and no correct column parity + */ + +#include "bit_lib/bit_lib.h" +#include +#include +#include +#include +#include "lfrfid_protocols.h" + +#define TAG "ELECTRA" + +typedef uint64_t ElectraDecodedData; + +#define EM_HEADER_POS (55) +#define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) + +#define EM_FIRST_ROW_POS (50) + +#define EM_ROW_COUNT (10) +#define EM_COLUMN_COUNT (4) +#define EM_BITS_PER_ROW_COUNT (EM_COLUMN_COUNT + 1) + +#define EM_COLUMN_POS (4) +#define ELECTRA_STOP_POS (0) +#define ELECTRA_STOP_MASK (0x1LLU << ELECTRA_STOP_POS) + +#define EM_HEADER_AND_STOP_MASK (EM_HEADER_MASK | ELECTRA_STOP_MASK) +#define EM_HEADER_AND_STOP_DATA (EM_HEADER_MASK) + +#define ELECTRA_DECODED_BASE_DATA_SIZE (5) +#define ELECTRA_ENCODED_BASE_DATA_SIZE (sizeof(ElectraDecodedData)) + +#define ELECTRA_DECODED_EPILOGUE_SIZE (3) +#define ELECTRA_ENCODED_EPILOGUE_SIZE (sizeof(ElectraDecodedData)) + +#define ELECTRA_DECODED_DATA_SIZE (ELECTRA_DECODED_BASE_DATA_SIZE + ELECTRA_DECODED_EPILOGUE_SIZE) +#define ELECTRA_ENCODED_DATA_SIZE (ELECTRA_ENCODED_BASE_DATA_SIZE + ELECTRA_ENCODED_EPILOGUE_SIZE) + +#define ELECTRA_DECODED_DATA_EPILOGUE_START_POS (ELECTRA_DECODED_BASE_DATA_SIZE) + +#define ELECTRA_CLOCK_PER_BIT (64) + +#define ELECTRA_READ_SHORT_TIME (256) +#define ELECTRA_READ_LONG_TIME (512) +#define ELECTRA_READ_JITTER_TIME (100) + +#define ELECTRA_READ_SHORT_TIME_LOW (ELECTRA_READ_SHORT_TIME - ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_SHORT_TIME_HIGH (ELECTRA_READ_SHORT_TIME + ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_LONG_TIME_LOW (ELECTRA_READ_LONG_TIME - ELECTRA_READ_JITTER_TIME) +#define ELECTRA_READ_LONG_TIME_HIGH (ELECTRA_READ_LONG_TIME + ELECTRA_READ_JITTER_TIME) + +#define EM_ENCODED_DATA_HEADER (0xFF80000000000000ULL) + +typedef struct { + uint8_t data[ELECTRA_DECODED_DATA_SIZE]; + + ElectraDecodedData encoded_base_data; + ElectraDecodedData encoded_epilogue; + + uint8_t encoded_data_index; + bool encoded_polarity; + + ManchesterState decoder_manchester_state; +} ProtocolElectra; + +ProtocolElectra* protocol_electra_alloc(void) { + ProtocolElectra* proto = malloc(sizeof(ProtocolElectra)); + return (void*)proto; +}; + +void protocol_electra_free(ProtocolElectra* proto) { + free(proto); +}; + +uint8_t* protocol_electra_get_data(ProtocolElectra* proto) { + return proto->data; +}; + +static void electra_decode( + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, + const uint8_t* encoded_epilogue, + const uint8_t encoded_epilogue_size, + uint8_t* decoded_data, + const uint8_t decoded_data_size) { + furi_check(decoded_data_size >= ELECTRA_DECODED_DATA_SIZE); + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); + furi_check(encoded_epilogue_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); + + uint8_t decoded_data_index = 0; + ElectraDecodedData base_data = *((ElectraDecodedData*)(encoded_base_data)); + //ElectraDecodedData epilogue = *((ElectraDecodedData*)(encoded_epilogue)); + + // clean result + memset(decoded_data, 0, decoded_data_size); + + // header + for(uint8_t i = 0; i < 9; i++) { + base_data = base_data << 1; + } + + // nibbles + uint8_t value = 0; + for(uint8_t r = 0; r < EM_ROW_COUNT; r++) { + uint8_t nibble = 0; + for(uint8_t i = 0; i < 5; i++) { + if(i < 4) nibble = (nibble << 1) | (base_data & (1LLU << 63) ? 1 : 0); + base_data = base_data << 1; + } + value = (value << 4) | nibble; + if(r % 2) { + decoded_data[decoded_data_index] |= value; + decoded_data_index++; + value = 0; + } + } + + // copy first 3 bytes of encoded epilogue to decoded data + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 1]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 2]; + decoded_data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2] = + encoded_epilogue[ELECTRA_ENCODED_EPILOGUE_SIZE - 3]; +} + +static bool electra_can_be_decoded( + const uint8_t* encoded_base_data, + const uint8_t encoded_base_data_size, + const uint8_t* encoded_epilogue_data, + const uint8_t encoded_epilogue_data_size) { + furi_check(encoded_base_data_size >= ELECTRA_ENCODED_BASE_DATA_SIZE); + furi_check(encoded_epilogue_data_size >= ELECTRA_ENCODED_EPILOGUE_SIZE); + const ElectraDecodedData* base_data = (ElectraDecodedData*)encoded_base_data; + const ElectraDecodedData* epilogue = (ElectraDecodedData*)encoded_epilogue_data; + + // check electra epilogue. if em4100 header - break + if((*epilogue & EM_ENCODED_DATA_HEADER) == EM_ENCODED_DATA_HEADER) return false; + + // check header and stop bit + if((*base_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; + + // check row parity + for(uint8_t i = 0; i < EM_ROW_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_BITS_PER_ROW_COUNT; j++) { + parity_sum += (*base_data >> (EM_FIRST_ROW_POS - i * EM_BITS_PER_ROW_COUNT + j)) & 1; + } + + if((parity_sum % 2)) { + return false; + } + } + + // check columns parity + for(uint8_t i = 0; i < EM_COLUMN_COUNT; i++) { + uint8_t parity_sum = 0; + + for(uint8_t j = 0; j < EM_ROW_COUNT + 1; j++) { + parity_sum += (*base_data >> (EM_COLUMN_POS - i + j * EM_BITS_PER_ROW_COUNT)) & 1; + } + + if((parity_sum % 2)) { + FURI_LOG_D( + TAG, + "Unexpected column parity found. EM4100 data: %016llX", + bit_lib_bytes_to_num_be(encoded_base_data, encoded_base_data_size)); + return false; + } + } + + // encoded_epilogue_data lsb encoded + uint8_t epilogue_filler = encoded_epilogue_data[(ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2]; + + for(uint8_t i = 0; i < ((ELECTRA_ENCODED_EPILOGUE_SIZE - 1) - 2); i++) + if(encoded_epilogue_data[i] != epilogue_filler) { + FURI_LOG_D(TAG, "Unexpected epilogue filler found: %016llX", *epilogue); + return false; + } + + return true; +} + +void protocol_electra_decoder_start(ProtocolElectra* proto) { + memset(proto->data, 0, ELECTRA_DECODED_DATA_SIZE); + proto->encoded_base_data = 0; + proto->encoded_epilogue = 0; + + manchester_advance( + proto->decoder_manchester_state, + ManchesterEventReset, + &proto->decoder_manchester_state, + NULL); +}; + +bool protocol_electra_decoder_feed(ProtocolElectra* proto, bool level, uint32_t duration) { + bool result = false; + + ManchesterEvent event = ManchesterEventReset; + + if(duration > ELECTRA_READ_SHORT_TIME_LOW && duration < ELECTRA_READ_SHORT_TIME_HIGH) { + if(!level) { + event = ManchesterEventShortHigh; + } else { + event = ManchesterEventShortLow; + } + } else if(duration > ELECTRA_READ_LONG_TIME_LOW && duration < ELECTRA_READ_LONG_TIME_HIGH) { + if(!level) { + event = ManchesterEventLongHigh; + } else { + event = ManchesterEventLongLow; + } + } + + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); + + if(data_ok) { + /* + EM 4100 BASE DATA (64 bit) ELECTRA EPILOGUE (64 bit) + _________________________________ _________________________________ + | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | <- new data bit + --------------------------------- --------------------------------- + <- epilogue msb is carry bit to base data + */ + bool carry = proto->encoded_epilogue >> 63 & 0b1; + + proto->encoded_base_data = (proto->encoded_base_data << 1) | carry; + proto->encoded_epilogue = (proto->encoded_epilogue << 1) | data; + + if(electra_can_be_decoded( + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, + (uint8_t*)&proto->encoded_epilogue, + ELECTRA_ENCODED_EPILOGUE_SIZE)) { + electra_decode( + (uint8_t*)&proto->encoded_base_data, + ELECTRA_ENCODED_BASE_DATA_SIZE, + (uint8_t*)&proto->encoded_epilogue, + ELECTRA_ENCODED_EPILOGUE_SIZE, + proto->data, + ELECTRA_DECODED_DATA_SIZE); + result = true; + } + } + } + + return result; +}; + +static void em_write_nibble(bool low_nibble, uint8_t data, ElectraDecodedData* encoded_base_data) { + uint8_t parity_sum = 0; + uint8_t start = 0; + if(!low_nibble) start = 4; + + for(int8_t i = (start + 3); i >= start; i--) { + parity_sum += (data >> i) & 1; + *encoded_base_data = (*encoded_base_data << 1) | ((data >> i) & 1); + } + + *encoded_base_data = (*encoded_base_data << 1) | ((parity_sum % 2) & 1); +} + +bool protocol_electra_encoder_start(ProtocolElectra* proto) { + // header + proto->encoded_base_data = 0b111111111; + + // data + for(uint8_t i = 0; i < ELECTRA_DECODED_BASE_DATA_SIZE; i++) { + em_write_nibble(false, proto->data[i], &proto->encoded_base_data); + em_write_nibble(true, proto->data[i], &proto->encoded_base_data); + } + + // column parity and stop bit + uint8_t parity_sum; + + for(uint8_t c = 0; c < EM_COLUMN_COUNT; c++) { + parity_sum = 0; + for(uint8_t i = 1; i <= EM_ROW_COUNT; i++) { + uint8_t parity_bit = (proto->encoded_base_data >> (i * EM_BITS_PER_ROW_COUNT - 1)) & 1; + parity_sum += parity_bit; + } + proto->encoded_base_data = (proto->encoded_base_data << 1) | ((parity_sum % 2) & 1); + } + + // stop bit + proto->encoded_base_data = (proto->encoded_base_data << 1) | 0; + + proto->encoded_data_index = 0; + proto->encoded_polarity = true; + + // epilogue + proto->encoded_epilogue = (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS]); + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= (proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 1]); + + //fill bytes 2-7 by epilogue filler + for(uint8_t i = 2; i < ELECTRA_ENCODED_EPILOGUE_SIZE; i++) { + proto->encoded_epilogue <<= 8; + proto->encoded_epilogue |= proto->data[ELECTRA_DECODED_DATA_EPILOGUE_START_POS + 2]; + } + + return true; +}; + +LevelDuration protocol_electra_encoder_yield(ProtocolElectra* proto) { + bool level; + if(proto->encoded_data_index < 64) + level = (proto->encoded_base_data >> (63 - proto->encoded_data_index)) & 1; + else + level = (proto->encoded_epilogue >> (63 - (proto->encoded_data_index - 64))) & 1; + + uint32_t duration = ELECTRA_CLOCK_PER_BIT / 2; + + if(proto->encoded_polarity) { + proto->encoded_polarity = false; + } else { + level = !level; + + proto->encoded_polarity = true; + proto->encoded_data_index++; + if(proto->encoded_data_index >= 128) { + proto->encoded_data_index = 0; + } + } + + return level_duration_make(level, duration); +}; + +bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + // Correct protocol data by redecoding + protocol_electra_encoder_start(protocol); + electra_decode( + (uint8_t*)&protocol->encoded_base_data, + sizeof(ElectraDecodedData), + (uint8_t*)&protocol->encoded_epilogue, + sizeof(ElectraDecodedData), + protocol->data, + ELECTRA_DECODED_DATA_SIZE); + + protocol_electra_encoder_start(protocol); + + if(request->write_type == LFRFIDWriteTypeT5577) { + request->t5577.block[0] = + (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_64 | + (4 << LFRFID_T5577_MAXBLOCK_SHIFT)); + request->t5577.block[1] = protocol->encoded_base_data >> 32; + request->t5577.block[2] = protocol->encoded_base_data & 0xFFFFFFFF; + request->t5577.block[3] = protocol->encoded_epilogue >> 32; + request->t5577.block[4] = protocol->encoded_epilogue & 0xFFFFFFFF; + request->t5577.blocks_to_write = 5; + result = true; + } + return result; +}; + +void protocol_electra_render_data(ProtocolElectra* protocol, FuriString* result) { + furi_string_printf(result, "Epilogue: %016llX", protocol->encoded_epilogue); +}; + +const ProtocolBase protocol_electra = { + .name = "Electra", + .manufacturer = "Electra Group", + .data_size = ELECTRA_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_electra_alloc, + .free = (ProtocolFree)protocol_electra_free, + .get_data = (ProtocolGetData)protocol_electra_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_electra_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_electra_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_electra_encoder_start, + .yield = (ProtocolEncoderYield)protocol_electra_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_electra_render_data, + .render_brief_data = (ProtocolRenderData)protocol_electra_render_data, + .write_data = (ProtocolWriteData)protocol_electra_write_data, +}; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_electra.h b/lib/lfrfid/protocols/protocol_electra.h new file mode 100644 index 000000000..2c9f79285 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_electra.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_electra; \ No newline at end of file diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 055d06d86..a4a403167 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -4,6 +4,7 @@ #include "lfrfid_protocols.h" typedef uint64_t EM4100DecodedData; +typedef uint64_t EM4100Epilogue; #define EM_HEADER_POS (55) #define EM_HEADER_MASK (0x1FFLLU << EM_HEADER_POS) @@ -28,10 +29,13 @@ typedef uint64_t EM4100DecodedData; #define EM_READ_LONG_TIME_BASE (512) #define EM_READ_JITTER_TIME_BASE (100) +#define EM_ENCODED_DATA_HEADER (0xFF80000000000000ULL) + typedef struct { uint8_t data[EM4100_DECODED_DATA_SIZE]; EM4100DecodedData encoded_data; + EM4100Epilogue encoded_epilogue; uint8_t encoded_data_index; bool encoded_polarity; @@ -147,9 +151,16 @@ static void em4100_decode( } } -static bool em4100_can_be_decoded(const uint8_t* encoded_data, const uint8_t encoded_data_size) { +static bool em4100_can_be_decoded( + const uint8_t* encoded_data, + const uint8_t encoded_data_size, + const uint8_t* encoded_epilogue) { furi_check(encoded_data_size >= EM4100_ENCODED_DATA_SIZE); const EM4100DecodedData* card_data = (EM4100DecodedData*)encoded_data; + const EM4100Epilogue* epilogue = (EM4100Epilogue*)encoded_epilogue; + + // check first 9 bytes on epilogue (to prevent conflict with Electra protocol) + if((*epilogue & EM_ENCODED_DATA_HEADER) != EM_ENCODED_DATA_HEADER) return false; // check header and stop bit if((*card_data & EM_HEADER_AND_STOP_MASK) != EM_HEADER_AND_STOP_DATA) return false; @@ -221,9 +232,15 @@ bool protocol_em4100_decoder_feed(ProtocolEM4100* proto, bool level, uint32_t du proto->decoder_manchester_state, event, &proto->decoder_manchester_state, &data); if(data_ok) { - proto->encoded_data = (proto->encoded_data << 1) | data; + bool carry = proto->encoded_epilogue >> 63 & 0b1; - if(em4100_can_be_decoded((uint8_t*)&proto->encoded_data, sizeof(EM4100DecodedData))) { + proto->encoded_data = (proto->encoded_data << 1) | carry; + proto->encoded_epilogue = (proto->encoded_epilogue << 1) | data; + + if(em4100_can_be_decoded( + (uint8_t*)&proto->encoded_data, + sizeof(EM4100DecodedData), + (uint8_t*)&proto->encoded_epilogue)) { em4100_decode( (uint8_t*)&proto->encoded_data, sizeof(EM4100DecodedData), diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 2b8631849..1dd6c50e1 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -75,10 +75,10 @@ MfDesfireError mf_desfire_send_chunks( const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - if(rx_size <= rx_capacity_remaining) { + if(rx_size <= rx_capacity_remaining + 1) { bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size); + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); } } } while(false); @@ -327,36 +327,63 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } -MfDesfireError mf_desfire_poller_read_file_data( +static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, + uint8_t read_cmd, uint32_t offset, size_t size, MfDesfireFileData* data) { furi_check(instance); furi_check(data); - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + MfDesfireError error = MfDesfireErrorNone; + simple_array_init(data->data, size); - MfDesfireError error; + size_t buffer_capacity = bit_buffer_get_capacity_bytes(instance->result_buffer); + uint32_t current_offset = offset; + uint32_t bytes_read = 0; + + while(bytes_read < size) { + size_t bytes_to_read = MIN(buffer_capacity, size - bytes_read); + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, read_cmd); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); - do { error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - if(error != MfDesfireErrorNone) break; - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); + if(bytes_received != bytes_to_read) { + FURI_LOG_W(TAG, "Read %zu out of %zu bytes", bytes_received, bytes_to_read); error = MfDesfireErrorProtocol; + break; } - } while(false); + + uint8_t* file_data = simple_array_get_data(data->data); + bit_buffer_write_bytes(instance->result_buffer, &file_data[current_offset], bytes_to_read); + bytes_read += bytes_to_read; + current_offset += bytes_to_read; + } + + if(error != MfDesfireErrorNone) { + simple_array_reset(data->data); + } return error; } +MfDesfireError mf_desfire_poller_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + return mf_desfire_poller_read_file(instance, id, MF_DESFIRE_CMD_READ_DATA, offset, size, data); +} + MfDesfireError mf_desfire_poller_read_file_value( MfDesfirePoller* instance, MfDesfireFileId id, @@ -389,28 +416,8 @@ MfDesfireError mf_desfire_poller_read_file_records( uint32_t offset, size_t size, MfDesfireFileData* data) { - furi_check(instance); - furi_check(data); - - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); - - MfDesfireError error; - - do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - - if(error != MfDesfireErrorNone) break; - - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; - } - } while(false); - - return error; + return mf_desfire_poller_read_file( + instance, id, MF_DESFIRE_CMD_READ_RECORDS, offset, size, data); } MfDesfireError mf_desfire_poller_read_file_data_multi( diff --git a/lib/toolbox/pretty_format.c b/lib/toolbox/pretty_format.c index 2dcfdb6d9..f8319b69d 100644 --- a/lib/toolbox/pretty_format.c +++ b/lib/toolbox/pretty_format.c @@ -28,8 +28,7 @@ void pretty_format_bytes_hex_canonical( const size_t line_length = (line_prefix ? strlen(line_prefix) : 0) + 4 * num_places + 2; /* Reserve memory in adance in order to avoid unnecessary reallocs */ - furi_string_reset(result); - furi_string_reserve(result, line_count * line_length); + furi_string_reserve(result, furi_string_size(result) + line_count * line_length); for(size_t i = 0; i < data_size; i += num_places) { if(line_prefix) { diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c index e8da4b1cd..71fa4fe28 100644 --- a/lib/toolbox/protocols/protocol_dict.c +++ b/lib/toolbox/protocols/protocol_dict.c @@ -198,12 +198,21 @@ LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_in } } +void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index) { + furi_check(protocol_index < dict->count); + ProtocolRenderData fn = dict->base[protocol_index]->render_uid; + + if(fn) { + fn(dict->data[protocol_index], result); + } +} + void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index) { furi_check(protocol_index < dict->count); ProtocolRenderData fn = dict->base[protocol_index]->render_data; if(fn) { - return fn(dict->data[protocol_index], result); + fn(dict->data[protocol_index], result); } } @@ -212,7 +221,7 @@ void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, siz ProtocolRenderData fn = dict->base[protocol_index]->render_brief_data; if(fn) { - return fn(dict->data[protocol_index], result); + fn(dict->data[protocol_index], result); } } diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h index cd8503952..dd0711732 100644 --- a/lib/toolbox/protocols/protocol_dict.h +++ b/lib/toolbox/protocols/protocol_dict.h @@ -58,6 +58,8 @@ bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index); LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index); +void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index); + void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 13a793ccd..3cee044aa 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -2251,6 +2251,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6cfc939b8..247c2ee2d 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init(); furi_hal_speaker_init(); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index de4e8d5d9..b7d608d0b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2864,6 +2864,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 2e4a74a9e..2235a8da3 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -38,6 +38,8 @@ typedef struct { FuriThread* thread; FuriMessageQueue* command_queue; bool enable_adv; + bool is_secure; + uint8_t negotiation_round; } Gap; typedef enum { @@ -72,17 +74,46 @@ static void gap_verify_connection_parameters(Gap* gap) { // Send connection parameters request update if necessary GapConnectionParamsRequest* params = &gap->config->conn_param; - if(params->conn_int_min > gap->connection_params.conn_interval || - params->conn_int_max < gap->connection_params.conn_interval) { - FURI_LOG_W(TAG, "Unsupported connection interval. Request connection parameters update"); + + // Desired max connection interval depends on how many negotiation rounds we had in the past + // In the first negotiation round we want connection interval to be minimum + // If platform disagree then we request wider range + uint16_t connection_interval_max = gap->negotiation_round ? params->conn_int_max : + params->conn_int_min; + + // We do care about lower connection interval bound a lot: if it's lower than 30ms 2nd core will not allow us to use flash controller + bool negotiation_failed = params->conn_int_min > gap->connection_params.conn_interval; + + // We don't care about upper bound till connection become secure + if(gap->is_secure) { + negotiation_failed |= connection_interval_max < gap->connection_params.conn_interval; + } + + if(negotiation_failed) { + FURI_LOG_W( + TAG, + "Connection interval doesn't suite us. Trying to negotiate, round %u", + gap->negotiation_round + 1); if(aci_l2cap_connection_parameter_update_req( gap->service.connection_handle, params->conn_int_min, - params->conn_int_max, + connection_interval_max, gap->connection_params.slave_latency, gap->connection_params.supervisor_timeout)) { FURI_LOG_E(TAG, "Failed to request connection parameters update"); + // The other side is not in the mood + // But we are open to try it again + gap->negotiation_round = 0; + } else { + gap->negotiation_round++; } + } else { + FURI_LOG_I( + TAG, + "Connection interval suits us. Spent %u rounds to negotiate", + gap->negotiation_round); + // Looks like the other side is open to negotiation + gap->negotiation_round = 0; } } @@ -97,9 +128,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { event_pckt = (hci_event_pckt*)((hci_uart_pckt*)pckt)->data; - if(gap) { - furi_mutex_acquire(gap->state_mutex, FuriWaitForever); - } + furi_check(gap); + furi_mutex_acquire(gap->state_mutex, FuriWaitForever); + switch(event_pckt->evt) { case HCI_DISCONNECTION_COMPLETE_EVT_CODE: { hci_disconnection_complete_event_rp0* disconnection_complete_event = @@ -110,6 +141,8 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { FURI_LOG_I( TAG, "Disconnect from client. Reason: %02X", disconnection_complete_event->Reason); } + gap->is_secure = false; + gap->negotiation_round = 0; // Enterprise sleep furi_delay_us(666 + 666); if(gap->enable_adv) { @@ -211,6 +244,7 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { case ACI_GAP_SLAVE_SECURITY_INITIATED_VSEVT_CODE: FURI_LOG_D(TAG, "Slave security initiated"); + gap->is_secure = true; break; case ACI_GAP_BOND_LOST_VSEVT_CODE: @@ -269,9 +303,9 @@ BleEventFlowStatus ble_event_app_notification(void* pckt) { default: break; } - if(gap) { - furi_mutex_release(gap->state_mutex); - } + + furi_mutex_release(gap->state_mutex); + return BleEventFlowEnable; } @@ -512,6 +546,10 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { gap->thread = furi_thread_alloc_ex("BleGapDriver", 1024, gap_app, gap); furi_thread_start(gap->thread); + // Set initial state + gap->is_secure = false; + gap->negotiation_round = 0; + uint8_t adv_service_uid[2]; gap->service.adv_svc_uuid_len = 1; adv_service_uid[0] = gap->config->adv_service_uuid & 0xff; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index 165b81330..118a76e8c 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -47,7 +47,7 @@ static GapConfig serial_template_config = { .pairing_method = GapPairingPinCodeShow, .conn_param = { .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms - .conn_int_max = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms .slave_latency = 0, .supervisor_timeout = 0, }}; diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 7f3959bc3..3a58fe196 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -17,6 +17,7 @@ void furi_hal_init_early(void) { furi_hal_i2c_init_early(); furi_hal_light_init(); furi_hal_rtc_init_early(); + furi_hal_version_init(); } void furi_hal_deinit_early(void) { @@ -39,7 +40,6 @@ void furi_hal_init(void) { furi_hal_interrupt_init(); furi_hal_flash_init(); furi_hal_resources_init(); - furi_hal_version_init(); furi_hal_region_init(); furi_hal_spi_config_init(); furi_hal_spi_dma_init();