From 5793d5271c22a4de58f894b349afa6ce8169f57c Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Thu, 4 Apr 2024 03:01:41 -0700 Subject: [PATCH 01/22] Hide unlock with reader for MFU-C (#3553) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc_scene_mf_ultralight_unlock_menu.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c index 4d97040ee20..a23e4b306a0 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_unlock_menu.c @@ -20,12 +20,17 @@ void nfc_scene_mf_ultralight_unlock_menu_on_enter(void* context) { uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); if(nfc_device_get_protocol(nfc->nfc_device) == NfcProtocolMfUltralight) { - submenu_add_item( - submenu, - "Unlock With Reader", - SubmenuIndexMfUlUnlockMenuReader, - nfc_scene_mf_ultralight_unlock_menu_submenu_callback, - nfc); + const MfUltralightData* mfu_data = + nfc_device_get_data(nfc->nfc_device, NfcProtocolMfUltralight); + // Hide for MFU-C since it uses 3DES + if(mfu_data->type != MfUltralightTypeMfulC) { + submenu_add_item( + submenu, + "Unlock With Reader", + SubmenuIndexMfUlUnlockMenuReader, + nfc_scene_mf_ultralight_unlock_menu_submenu_callback, + nfc); + } } submenu_add_item( submenu, From 54312574703dd90641f22c9812a1e6a9831921a0 Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Thu, 4 Apr 2024 22:42:58 +1000 Subject: [PATCH 02/22] [FL-3765][FL-3737] Desktop: ensure that animation is unloaded before app start (#3569) * Desktop: ensure that animation is unloaded before app start * Desktop: unload animation only if it is loaded --- applications/services/desktop/desktop.c | 8 +++++++- applications/services/desktop/desktop_i.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 74979d12842..49aa04e35e5 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -34,10 +34,12 @@ static void desktop_loader_callback(const void* message, void* context) { if(event->type == LoaderEventTypeApplicationStarted) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); + furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); } else if(event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } + static void desktop_lock_icon_draw_callback(Canvas* canvas, void* context) { UNUSED(context); furi_assert(canvas); @@ -120,8 +122,11 @@ static bool desktop_custom_event_callback(void* context, uint32_t event) { switch(event) { case DesktopGlobalBeforeAppStarted: - animation_manager_unload_and_stall_animation(desktop->animation_manager); + if(animation_manager_is_animation_loaded(desktop->animation_manager)) { + animation_manager_unload_and_stall_animation(desktop->animation_manager); + } desktop_auto_lock_inhibit(desktop); + furi_semaphore_release(desktop->animation_semaphore); return true; case DesktopGlobalAfterAppFinished: animation_manager_load_and_continue_animation(desktop->animation_manager); @@ -270,6 +275,7 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { Desktop* desktop_alloc(void) { Desktop* desktop = malloc(sizeof(Desktop)); + desktop->animation_semaphore = furi_semaphore_alloc(1, 0); desktop->animation_manager = animation_manager_alloc(); desktop->gui = furi_record_open(RECORD_GUI); desktop->scene_thread = furi_thread_alloc(); diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index f6eeef6b1b1..c0b29f922db 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -80,6 +80,8 @@ struct Desktop { bool time_format_12 : 1; // 1 - 12 hour, 0 - 24H bool in_transition : 1; + + FuriSemaphore* animation_semaphore; }; Desktop* desktop_alloc(void); From c31b60c7b713b9f42eb06552a424c1291fc4fb5e Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:21:25 +0100 Subject: [PATCH 03/22] IR: Fix crash on duty_cycle=1 (#3568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * IR: Fix crash on duty_cycle=1 * Infrared: use float around duty_cycle Co-authored-by: あく --- lib/infrared/worker/infrared_worker.c | 2 +- targets/f7/furi_hal/furi_hal_infrared.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index a867542e013..89f351eb92d 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -612,7 +612,7 @@ void infrared_worker_set_raw_signal( furi_check(timings); furi_check(timings_cnt > 0); furi_check((frequency <= INFRARED_MAX_FREQUENCY) && (frequency >= INFRARED_MIN_FREQUENCY)); - furi_check((duty_cycle < 1.0f) && (duty_cycle > 0.0f)); + furi_check((duty_cycle <= 1.0f) && (duty_cycle > 0.0f)); size_t max_copy_num = COUNT_OF(instance->signal.raw.timings) - 1; furi_check(timings_cnt <= max_copy_num); diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index 6f2210cc1db..a0b166fad4a 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -357,7 +357,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc if(infrared_tx_output == FuriHalInfraredTxPinInternal) { LL_TIM_OC_SetCompareCH3( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH3, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -368,7 +368,7 @@ static void furi_hal_infrared_configure_tim_pwm_tx(uint32_t freq, float duty_cyc } else if(infrared_tx_output == FuriHalInfraredTxPinExtPA7) { LL_TIM_OC_SetCompareCH1( INFRARED_DMA_TIMER, - ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1 - duty_cycle))); + ((LL_TIM_GetAutoReload(INFRARED_DMA_TIMER) + 1) * (1.0f - duty_cycle))); LL_TIM_OC_EnablePreload(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1); /* LL_TIM_OCMODE_PWM2 set by DMA */ LL_TIM_OC_SetMode(INFRARED_DMA_TIMER, LL_TIM_CHANNEL_CH1, LL_TIM_OCMODE_FORCED_INACTIVE); @@ -609,7 +609,7 @@ static void furi_hal_infrared_async_tx_free_resources(void) { } void furi_hal_infrared_async_tx_start(uint32_t freq, float duty_cycle) { - if((duty_cycle > 1) || (duty_cycle <= 0) || (freq > INFRARED_MAX_FREQUENCY) || + if((duty_cycle > 1.0f) || (duty_cycle <= 0.0f) || (freq > INFRARED_MAX_FREQUENCY) || (freq < INFRARED_MIN_FREQUENCY) || (infrared_tim_tx.data_callback == NULL)) { furi_crash(); } From 6b120a3b09c08cb4c4f7976ab756e0fb67e9cb3b Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 14:49:00 +0100 Subject: [PATCH 04/22] Furi: Add "out of memory" and "malloc(0)" crash messages (#3574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- furi/core/memmgr_heap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/furi/core/memmgr_heap.c b/furi/core/memmgr_heap.c index 24bd327fd2b..3f62b518c2e 100644 --- a/furi/core/memmgr_heap.c +++ b/furi/core/memmgr_heap.c @@ -486,7 +486,7 @@ void* pvPortMalloc(size_t xWantedSize) { configASSERT((((size_t)pvReturn) & (size_t)portBYTE_ALIGNMENT_MASK) == 0); - furi_check(pvReturn); + furi_check(pvReturn, xWantedSize ? "out of memory" : "malloc(0)"); pvReturn = memset(pvReturn, 0, to_wipe); return pvReturn; } From 16b34c6e4de4d983ca938b0803b04339067fc767 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Sun, 7 Apr 2024 15:11:23 +0100 Subject: [PATCH 05/22] Explain RNG differences, add FURI_HAL_RANDOM_MAX (#3565) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Explain RNG differences, add FURI_HAL_RANDOM_MAX * Mark FURI_HAL_RANDOM_MAX unsigned Co-authored-by: あく --- targets/furi_hal_include/furi_hal_random.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/targets/furi_hal_include/furi_hal_random.h b/targets/furi_hal_include/furi_hal_random.h index 051b6f928d6..fab62083f15 100644 --- a/targets/furi_hal_include/furi_hal_random.h +++ b/targets/furi_hal_include/furi_hal_random.h @@ -6,12 +6,16 @@ extern "C" { #endif +#define FURI_HAL_RANDOM_MAX 0xFFFFFFFFU + /** Initialize random subsystem */ void furi_hal_random_init(void); /** Get random value + * furi_hal_random_get() gives up to FURI_HAL_RANDOM_MAX + * rand() and random() give up to RAND_MAX * - * @return random value + * @return 32 bit random value (up to FURI_HAL_RANDOM_MAX) */ uint32_t furi_hal_random_get(void); From 27e61eb80826baf367a8826b5296b9c5d5bbca3b Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sun, 7 Apr 2024 23:47:48 +0900 Subject: [PATCH 06/22] Move crypto1 to helpers, add it to the public API (#3567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move crypto1 to helpers, add it to the public API * F18 API version bump Co-authored-by: あく --- lib/nfc/SConscript | 1 + .../{protocols/mf_classic => helpers}/crypto1.c | 0 .../{protocols/mf_classic => helpers}/crypto1.h | 0 .../protocols/mf_classic/mf_classic_listener_i.h | 2 +- lib/nfc/protocols/mf_classic/mf_classic_poller_i.h | 2 +- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 14 +++++++++++++- 7 files changed, 17 insertions(+), 4 deletions(-) rename lib/nfc/{protocols/mf_classic => helpers}/crypto1.c (100%) rename lib/nfc/{protocols/mf_classic => helpers}/crypto1.h (100%) diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 41332362c80..82317918bdb 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -48,6 +48,7 @@ env.Append( File("helpers/iso14443_crc.h"), File("helpers/iso13239_crc.h"), File("helpers/nfc_data_generator.h"), + File("helpers/crypto1.h"), ], ) diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/helpers/crypto1.c similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.c rename to lib/nfc/helpers/crypto1.c diff --git a/lib/nfc/protocols/mf_classic/crypto1.h b/lib/nfc/helpers/crypto1.h similarity index 100% rename from lib/nfc/protocols/mf_classic/crypto1.h rename to lib/nfc/helpers/crypto1.h diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h index 5269743b5c4..af22b5234fb 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener_i.h @@ -3,7 +3,7 @@ #include "mf_classic_listener.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index a5af3153077..14a7c61fd47 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -3,7 +3,7 @@ #include "mf_classic_poller.h" #include #include -#include "crypto1.h" +#include #ifdef __cplusplus extern "C" { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index f6199445d19..72116ccfdfe 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6e65b947198..4ddff921bc7 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.4,, +Version,+,60.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -123,6 +123,7 @@ Header,+,lib/music_worker/music_worker.h,, Header,+,lib/nanopb/pb.h,, Header,+,lib/nanopb/pb_decode.h,, Header,+,lib/nanopb/pb_encode.h,, +Header,+,lib/nfc/helpers/crypto1.h,, Header,+,lib/nfc/helpers/iso13239_crc.h,, Header,+,lib/nfc/helpers/iso14443_crc.h,, Header,+,lib/nfc/helpers/nfc_data_generator.h,, @@ -853,6 +854,16 @@ Function,-,coshl,long double,long double Function,-,cosl,long double,long double Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" +Function,+,crypto1_alloc,Crypto1*, +Function,+,crypto1_bit,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_byte,uint8_t,"Crypto1*, uint8_t, int" +Function,+,crypto1_decrypt,void,"Crypto1*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt,void,"Crypto1*, uint8_t*, const BitBuffer*, BitBuffer*" +Function,+,crypto1_encrypt_reader_nonce,void,"Crypto1*, uint64_t, uint32_t, uint8_t*, uint8_t*, BitBuffer*, _Bool" +Function,+,crypto1_free,void,Crypto1* +Function,+,crypto1_init,void,"Crypto1*, uint64_t" +Function,+,crypto1_reset,void,Crypto1* +Function,+,crypto1_word,uint32_t,"Crypto1*, uint32_t, int" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* @@ -2782,6 +2793,7 @@ Function,+,powf,float,"float, float" Function,-,powl,long double,"long double, long double" Function,+,pretty_format_bytes_hex_canonical,void,"FuriString*, size_t, const char*, const uint8_t*, size_t" Function,-,printf,int,"const char*, ..." +Function,+,prng_successor,uint32_t,"uint32_t, uint32_t" Function,+,property_value_out,void,"PropertyValueContext*, const char*, unsigned int, ..." Function,+,protocol_dict_alloc,ProtocolDict*,"const ProtocolBase**, size_t" Function,+,protocol_dict_decoders_feed,ProtocolId,"ProtocolDict*, _Bool, uint32_t" From 88a686091398971850e831047dd08791c9763ad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Tue, 9 Apr 2024 16:01:55 +0900 Subject: [PATCH 07/22] Update radio stack to v1.19.0 (#3545) * Update radio stack to v1.19.0 * Ble: set max connection interal same as min, kinda speedups everything --- fbt_options.py | 2 +- lib/ble_profile/extra_profiles/hid_profile.c | 4 ++-- lib/stm32wb_copro | 2 +- targets/f7/ble_glue/ble_app.c | 4 ++-- targets/f7/ble_glue/extra_beacon.c | 3 ++- targets/f7/ble_glue/profiles/serial_profile.c | 4 ++-- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fbt_options.py b/fbt_options.py index 9e4d821a454..c4b84105816 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -22,7 +22,7 @@ COPRO_OB_DATA = "scripts/ob.data" # Must match lib/stm32wb_copro version -COPRO_CUBE_VERSION = "1.17.3" +COPRO_CUBE_VERSION = "1.19.0" COPRO_CUBE_DIR = "lib/stm32wb_copro" diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c index aaa66d96075..7231fdb1957 100644 --- a/lib/ble_profile/extra_profiles/hid_profile.c +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -380,8 +380,8 @@ static GapConfig template_config = { .pairing_method = GapPairingPinCodeVerifyYesNo, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }, diff --git a/lib/stm32wb_copro b/lib/stm32wb_copro index d8a6f1feb0e..64a060d91f5 160000 --- a/lib/stm32wb_copro +++ b/lib/stm32wb_copro @@ -1 +1 @@ -Subproject commit d8a6f1feb0ebb6798c44162c6ae5ea743f90f3df +Subproject commit 64a060d91f5cbf25d765cf23231876add006bcf4 diff --git a/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c index 1f392529deb..fd4e64c09cb 100644 --- a/targets/f7/ble_glue/ble_app.c +++ b/targets/f7/ble_glue/ble_app.c @@ -54,8 +54,8 @@ static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { .PrWriteListSize = CFG_BLE_PREPARE_WRITE_LIST_SIZE, .MblockCount = CFG_BLE_MBLOCK_COUNT, .AttMtu = CFG_BLE_MAX_ATT_MTU, - .SlaveSca = CFG_BLE_SLAVE_SCA, - .MasterSca = CFG_BLE_MASTER_SCA, + .PeripheralSca = CFG_BLE_SLAVE_SCA, + .CentralSca = CFG_BLE_MASTER_SCA, .LsSource = CFG_BLE_LSE_SOURCE, .MaxConnEventLength = CFG_BLE_MAX_CONN_EVENT_LENGTH, .HsStartupTime = CFG_BLE_HSE_STARTUP_TIME, diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c index 2ef3e056fb7..60338cb76bd 100644 --- a/targets/f7/ble_glue/extra_beacon.c +++ b/targets/f7/ble_glue/extra_beacon.c @@ -9,7 +9,8 @@ #define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) // Also used as an indicator of whether the beacon had ever been configured -#define GAP_MIN_ADV_INTERVAL_MS (20) +// AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms +#define GAP_MIN_ADV_INTERVAL_MS (30u) typedef struct { GapExtraBeaconConfig last_config; diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c index a3949abfc88..165b8133076 100644 --- a/targets/f7/ble_glue/profiles/serial_profile.c +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -46,8 +46,8 @@ static GapConfig serial_template_config = { .bonding_mode = true, .pairing_method = GapPairingPinCodeShow, .conn_param = { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms + .conn_int_min = 0x18, // AN5289: 4.7, we need at least 25ms + advertisement, which is 30 ms + .conn_int_max = 0x18, // 30 ms .slave_latency = 0, .supervisor_timeout = 0, }}; From 58da27fa912422d0a903c24f25549f15f6c20a3c Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 9 Apr 2024 12:06:37 +0300 Subject: [PATCH 08/22] JS Documentation (#3535) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial JS documentation * Spelling fix Co-authored-by: あく --- documentation/doxygen/index.dox | 1 + documentation/doxygen/js.dox | 18 ++++ documentation/js/js_badusb.md | 144 ++++++++++++++++++++++++++++ documentation/js/js_builtin.md | 56 +++++++++++ documentation/js/js_data_types.md | 13 +++ documentation/js/js_dialog.md | 49 ++++++++++ documentation/js/js_notification.md | 36 +++++++ documentation/js/js_serial.md | 107 +++++++++++++++++++++ 8 files changed, 424 insertions(+) create mode 100644 documentation/doxygen/js.dox create mode 100644 documentation/js/js_badusb.md create mode 100644 documentation/js/js_builtin.md create mode 100644 documentation/js/js_data_types.md create mode 100644 documentation/js/js_dialog.md create mode 100644 documentation/js/js_notification.md create mode 100644 documentation/js/js_serial.md diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox index 6fb4d5a7177..78055caad04 100644 --- a/documentation/doxygen/index.dox +++ b/documentation/doxygen/index.dox @@ -15,6 +15,7 @@ The documentation is divided into several sections, with all of them accessible - @ref dev_tools - Hardware and software tools for all kinds of programming - @ref expansion - Additional modules to expand Flipper's consciousness - @ref misc - Various useful pieces of information +- @ref js - JS-based scripting engine documentation Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: diff --git a/documentation/doxygen/js.dox b/documentation/doxygen/js.dox new file mode 100644 index 00000000000..1c81a083503 --- /dev/null +++ b/documentation/doxygen/js.dox @@ -0,0 +1,18 @@ +/** +@page js JavaScript + +This page contains some information on the Flipper Zero scripting engine, which is based on a modified mJS library + +- [Brief mJS description](https://github.com/cesanta/mjs/blob/master/README.md) +- @subpage js_data_types +- @subpage js_builtin + +JavaScript Modules +JS modules use the Flipper app plugin system. Each module is compiled into a .fal library file and is located on a microSD card. Here is a list of implemented modules: + +- @subpage js_badusb - BadUSB module +- @subpage js_serial - Serial module +- @subpage js_dialog - Dialog module +- @subpage js_notification - Notifications module + +*/ diff --git a/documentation/js/js_badusb.md b/documentation/js/js_badusb.md new file mode 100644 index 00000000000..28372e56a44 --- /dev/null +++ b/documentation/js/js_badusb.md @@ -0,0 +1,144 @@ +# js_badusb {#js_badusb} + +# BadUSB module +```js +let badusb = require("badusb"); +``` +# Methods +## setup +Start USB HID with optional parameters. Should be called before all other methods. + +### Parameters +Configuration object (optional): +- vid, pid (number): VID and PID values, both are mandatory +- mfr_name (string): Manufacturer name (32 ASCII characters max), optional +- prod_name (string): Product name (32 ASCII characters max), optional + +### Examples: +```js +// Start USB HID with default parameters +badusb.setup(); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer and product strings not defined +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB }); +// Start USB HID with custom vid:pid = AAAA:BBBB, manufacturer string = "Flipper Devices", product string = "Flipper Zero" +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper Devices", prod_name: "Flipper Zero" }); +``` + +## isConnected +Returns USB connection state. + +### Example: +```js +if (badusb.isConnected()) { + // Do something +} else { + // Show an error +} +``` + +## press +Press and release a key. + +### Parameters +Key or modifier name, key code. + +See a list of key names below. + +### Examples: +```js +badusb.press("a"); // Press "a" key +badusb.press("A"); // SHIFT + "a" +badusb.press("CTRL", "a"); // CTRL + "a" +badusb.press("CTRL", "SHIFT", "ESC"); // CTRL + SHIFT + ESC combo +badusb.press(98); // Press key with HID code (dec) 98 (Numpad 0 / Insert) +badusb.press(0x47); // Press key with HID code (hex) 0x47 (Scroll lock) +``` + +## hold +Hold a key. Up to 5 keys (excluding modifiers) can be held simultaneously. + +### Parameters +Same as `press` + +### Examples: +```js +badusb.hold("a"); // Press and hold "a" key +badusb.hold("CTRL", "v"); // Press and hold CTRL + "v" combo +``` + +## release +Release a previously hold key. + +### Parameters +Same as `press` + +Release all keys if called without parameters + +### Examples: +```js +badusb.release(); // Release all keys +badusb.release("a"); // Release "a" key +``` + +## print +Print a string. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.print("Hello, world!"); // print "Hello, world!" +badusb.print("Hello, world!", 100); // Add 100ms delay between key presses +``` + +## println +Same as `print` but ended with "ENTER" press. + +### Parameters +- A string to print +- (optional) delay between key presses + +### Examples: +```js +badusb.println("Hello, world!"); // print "Hello, world!" and press "ENTER" +``` + +# Key names list + +## Modifier keys + +| Name | +| ------------- | +| CTRL | +| SHIFT | +| ALT | +| GUI | + +## Special keys + +| Name | Notes | +| ------------------ | ---------------- | +| DOWN | Down arrow | +| LEFT | Left arrow | +| RIGHT | Right arrow | +| UP | Up arrow | +| ENTER | | +| DELETE | | +| BACKSPACE | | +| END | | +| HOME | | +| ESC | | +| INSERT | | +| PAGEUP | | +| PAGEDOWN | | +| CAPSLOCK | | +| NUMLOCK | | +| SCROLLLOCK | | +| PRINTSCREEN | | +| PAUSE | Pause/Break key | +| SPACE | | +| TAB | | +| MENU | Context menu key | +| Fx | F1-F24 keys | diff --git a/documentation/js/js_builtin.md b/documentation/js/js_builtin.md new file mode 100644 index 00000000000..3d113807b07 --- /dev/null +++ b/documentation/js/js_builtin.md @@ -0,0 +1,56 @@ +# Built-in methods {#js_builtin} + +## require +Load a module plugin. + +### Parameters +- Module name + +### Examples: +```js +let serial = require("serial"); // Load "serial" module +``` + +## delay +### Parameters +- Delay value in ms + +### Examples: +```js +delay(500); // Delay for 500ms +``` +## print +Print a message on a screen console. + +### Parameters +The following argument types are supported: +- String +- Number +- Bool +- undefined + +### Examples: +```js +print("string1", "string2", 123); +``` + +## console.log +## console.warn +## console.error +## console.debug +Same as `print`, but output to serial console only, with corresponding log level. + +## to_string +Convert a number to string. + +### Examples: +```js +to_string(123) +``` +## to_hex_string +Convert a number to string(hex format). + +### Examples: +```js +to_hex_string(0xFF) +``` diff --git a/documentation/js/js_data_types.md b/documentation/js/js_data_types.md new file mode 100644 index 00000000000..de1c896dce3 --- /dev/null +++ b/documentation/js/js_data_types.md @@ -0,0 +1,13 @@ +# Data types {#js_data_types} + +Here is a list of common data types used by mJS. +- string - sequence of single byte characters, no UTF8 support +- number +- boolean +- foreign - C function or data pointer +- undefined +- null +- object - a data structure with named fields +- array - special type of object, all items have indexes and equal types +- ArrayBuffer - raw data buffer +- DataView - provides interface for accessing ArrayBuffer contents diff --git a/documentation/js/js_dialog.md b/documentation/js/js_dialog.md new file mode 100644 index 00000000000..5804b075eb4 --- /dev/null +++ b/documentation/js/js_dialog.md @@ -0,0 +1,49 @@ +# js_dialog {#js_dialog} + +# Dialog module +```js +let dialog = require("dialog"); +``` +# Methods + +## message +Show a simple message dialog with header, text and "OK" button. + +### Parameters +- Dialog header text +- Dialog text + +### Retuns +true if central button was pressed, false if the dialog was closed by back key press + +### Examples: +```js +dialog.message("Dialog demo", "Press OK to start"); +``` + +## custom +More complex dialog with configurable buttons + +### Parameters +Configuration object with the following fileds: +- header: Dialog header text +- text: Dialog text +- button_left: (optional) left button name +- button_right: (optional) right button name +- button_center: (optional) central button name + +### Retuns +Name of pressed button or empty string if the dialog was closed by back key press + +### Examples: +```js +let dialog_params = ({ + header: "Dialog header", + text: "Dialog text", + button_left: "Left", + button_right: "Right", + button_center: "OK" +}); + +dialog.custom(dialog_params); +``` diff --git a/documentation/js/js_notification.md b/documentation/js/js_notification.md new file mode 100644 index 00000000000..100da441464 --- /dev/null +++ b/documentation/js/js_notification.md @@ -0,0 +1,36 @@ +# js_notification {#js_notification} + +# Notification module +```js +let notify = require("notification"); +``` +# Methods + +## success +"Success" flipper notification message + +### Examples: +```js +notify.success(); +``` + +## error +"Error" flipper notification message + +### Examples: +```js +notify.error(); +``` + +## blink +Blink notification LED + +### Parameters +- Blink color (blue/red/green/yellow/cyan/magenta) +- Blink type (short/long) + +### Examples: +```js +notify.blink("red", "short"); // Short blink of red LED +notify.blink("green", "short"); // Long blink of green LED +``` \ No newline at end of file diff --git a/documentation/js/js_serial.md b/documentation/js/js_serial.md new file mode 100644 index 00000000000..cd9993a1810 --- /dev/null +++ b/documentation/js/js_serial.md @@ -0,0 +1,107 @@ +# js_serial {#js_serial} + +# Serial module +```js +let serial = require("serial"); +``` +# Methods + +## setup +Configure serial port. Should be called before all other methods. + +### Parameters +- Serial port name (usart, lpuart) +- Baudrate + +### Examples: +```js +// Configure LPUART port with baudrate = 115200 +serial.setup("lpuart", 115200); +``` + +## write +Write data to serial port + +### Parameters +One or more arguments of the following types: +- A string +- Single number, each number is interpreted as a byte +- Array of numbers, each number is interpreted as a byte +- ArrayBuffer or DataView + +### Examples: +```js +serial.write(0x0a); // Write a single byte 0x0A +serial.write("Hello, world!"); // Write a string +serial.write("Hello, world!", [0x0d, 0x0a]); // Write a string followed by two bytes +``` + +## read +Read a fixed number of characters from serial port. + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.read(1); // Read a single byte, without timeout +serial.read(10, 5000); // Read 10 bytes, with 5s timeout +``` + +## readln +Read from serial port untill line break character + +### Parameters +(optional) Timeout value in ms + +### Returns +A sting of received characters or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readln(); // Read without timeout +serial.readln(5000); // Read with 5s timeout +``` + +## readBytes +Read from serial port untill line break character + +### Parameters +- Number of bytes to read +- (optional) Timeout value in ms + +### Returns +ArrayBuffer with received data or undefined if nothing was received before timeout. + +### Examples: +```js +serial.readBytes(4); // Read 4 bytes, without timeout + +// Read one byte from receive buffer with zero timeout, returns UNDEFINED if Rx bufer is empty +serial.readBytes(1, 0); +``` + +## expect +Search for a string pattern in received data stream + +### Parameters +- Single argument or array of the following types: + - A string + - Array of numbers, each number is interpreted as a byte +- (optional) Timeout value in ms + +### Returns +Index of matched pattern in input patterns list, undefined if nothing was found. + +### Examples: +```js +// Wait for root shell prompt with 1s timeout, returns 0 if it was received before timeout, undefined if not +serial.expect("# ", 1000); + +// Infinitely wait for one of two strings, should return 0 if the first string got matched, 1 if the second one +serial.expect([": not found", "Usage: "]); +``` \ No newline at end of file From e3ca293eee588c155eac7b389ab7f7e2768d43e0 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:42:03 +0100 Subject: [PATCH 09/22] Desktop/Loader: Unload animations before loading FAPs (#3573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Desktop: Unload animations before FAP is loaded * Loader: Add API to start detached (returns instantly, queues event) * Desktop: Fix early animation unload deadlocks * Loader: remove redundant event * Bump api symbols Co-authored-by: あく Co-authored-by: SG --- applications/services/desktop/desktop.c | 6 ++- .../desktop/scenes/desktop_scene_main.c | 17 +++--- applications/services/loader/loader.c | 53 ++++++++++++++----- applications/services/loader/loader.h | 17 ++++-- applications/services/loader/loader_i.h | 1 + targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- 7 files changed, 69 insertions(+), 31 deletions(-) diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 49aa04e35e5..c5a334a4508 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -32,10 +32,12 @@ static void desktop_loader_callback(const void* message, void* context) { Desktop* desktop = context; const LoaderEvent* event = message; - if(event->type == LoaderEventTypeApplicationStarted) { + if(event->type == LoaderEventTypeApplicationBeforeLoad) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalBeforeAppStarted); furi_check(furi_semaphore_acquire(desktop->animation_semaphore, 3000) == FuriStatusOk); - } else if(event->type == LoaderEventTypeApplicationStopped) { + } else if( + event->type == LoaderEventTypeApplicationLoadFailed || + event->type == LoaderEventTypeApplicationStopped) { view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopGlobalAfterAppFinished); } } diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index a659ff4e358..41b1c3e7511 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -62,23 +62,18 @@ static void #endif static void desktop_scene_main_open_app_or_profile(Desktop* desktop, FavoriteApp* application) { - bool load_ok = false; if(strlen(application->name_or_path) > 0) { - if(loader_start(desktop->loader, application->name_or_path, NULL, NULL) == - LoaderStatusOk) { - load_ok = true; - } - } - if(!load_ok) { - loader_start(desktop->loader, "Passport", NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); + } else { + loader_start_detached_with_gui_error(desktop->loader, "Passport", NULL); } } static void desktop_scene_main_start_favorite(Desktop* desktop, FavoriteApp* application) { if(strlen(application->name_or_path) > 0) { - loader_start_with_gui_error(desktop->loader, application->name_or_path, NULL); + loader_start_detached_with_gui_error(desktop->loader, application->name_or_path, NULL); } else { - loader_start(desktop->loader, LOADER_APPLICATIONS_NAME, NULL, NULL); + loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } } @@ -141,7 +136,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { break; case DesktopMainEventOpenPowerOff: { - loader_start(desktop->loader, "Power", "off", NULL); + loader_start_detached_with_gui_error(desktop->loader, "Power", "off"); consumed = true; break; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 5daf99d1de5..1527807ccc6 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -47,13 +47,8 @@ LoaderStatus return result.value; } -LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { - furi_check(loader); - furi_check(name); - - FuriString* error_message = furi_string_alloc(); - LoaderStatus status = loader_start(loader, name, args, error_message); - +static void + loader_show_gui_error(LoaderStatus status, const char* name, FuriString* error_message) { if(status == LoaderStatusErrorUnknownApp && loader_find_external_application_by_name(name) != NULL) { // Special case for external apps @@ -86,11 +81,31 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const dialog_message_free(message); furi_record_close(RECORD_DIALOGS); } +} +LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_start(loader, name, args, error_message); + loader_show_gui_error(status, name, error_message); furi_string_free(error_message); return status; } +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args) { + furi_check(loader); + furi_check(name); + + LoaderMessage message = { + .type = LoaderMessageTypeStartByNameDetachedWithGuiError, + .start.name = name ? strdup(name) : NULL, + .start.args = args ? strdup(args) : NULL, + }; + furi_message_queue_put(loader->queue, &message, FuriWaitForever); +} + bool loader_lock(Loader* loader) { furi_check(loader); @@ -166,11 +181,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con Loader* loader = context; - if(thread_state == FuriThreadStateRunning) { - LoaderEvent event; - event.type = LoaderEventTypeApplicationStarted; - furi_pubsub_publish(loader->pubsub, &event); - } else if(thread_state == FuriThreadStateStopped) { + if(thread_state == FuriThreadStateStopped) { LoaderMessage message; message.type = LoaderMessageTypeAppClosed; furi_message_queue_put(loader->queue, &message, FuriWaitForever); @@ -255,6 +266,9 @@ static void loader_start_internal_app( const FlipperInternalApplication* app, const char* args) { FURI_LOG_I(TAG, "Starting %s", app->name); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); // store args furi_assert(loader->app.args == NULL); @@ -309,6 +323,9 @@ static LoaderStatus loader_start_external_app( const char* args, FuriString* error_message) { LoaderStatus status = loader_make_success_status(error_message); + LoaderEvent event; + event.type = LoaderEventTypeApplicationBeforeLoad; + furi_pubsub_publish(loader->pubsub, &event); do { loader->app.fap = flipper_application_alloc(storage, firmware_api_interface); @@ -356,6 +373,8 @@ static LoaderStatus loader_start_external_app( if(status != LoaderStatusOk) { flipper_application_free(loader->app.fap); loader->app.fap = NULL; + event.type = LoaderEventTypeApplicationLoadFailed; + furi_pubsub_publish(loader->pubsub, &event); } return status; @@ -528,6 +547,16 @@ int32_t loader_srv(void* p) { loader, message.start.name, message.start.args, message.start.error_message); api_lock_unlock(message.api_lock); break; + case LoaderMessageTypeStartByNameDetachedWithGuiError: { + FuriString* error_message = furi_string_alloc(); + LoaderStatus status = loader_do_start_by_name( + loader, message.start.name, message.start.args, error_message); + loader_show_gui_error(status, message.start.name, error_message); + if(message.start.name) free((void*)message.start.name); + if(message.start.args) free((void*)message.start.args); + furi_string_free(error_message); + break; + } case LoaderMessageTypeShowMenu: loader_do_menu_show(loader); break; diff --git a/applications/services/loader/loader.h b/applications/services/loader/loader.h index cca65628f88..ae914117d92 100644 --- a/applications/services/loader/loader.h +++ b/applications/services/loader/loader.h @@ -18,7 +18,8 @@ typedef enum { } LoaderStatus; typedef enum { - LoaderEventTypeApplicationStarted, + LoaderEventTypeApplicationBeforeLoad, + LoaderEventTypeApplicationLoadFailed, LoaderEventTypeApplicationStopped } LoaderEventType; @@ -32,7 +33,7 @@ typedef struct { * @param[in] name application name or id * @param[in] args application arguments * @param[out] error_message detailed error message, can be NULL - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start(Loader* instance, const char* name, const char* args, FuriString* error_message); @@ -42,11 +43,19 @@ LoaderStatus * @param[in] instance loader instance * @param[in] name application name or id * @param[in] args application arguments - * @return LoaderStatus + * @return LoaderStatus */ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const char* args); -/** +/** + * @brief Start application detached with GUI error message + * @param[in] instance loader instance + * @param[in] name application name or id + * @param[in] args application arguments + */ +void loader_start_detached_with_gui_error(Loader* loader, const char* name, const char* args); + +/** * @brief Lock application start * @param[in] instance loader instance * @return true on success diff --git a/applications/services/loader/loader_i.h b/applications/services/loader/loader_i.h index 688b8fb665c..daa5484d335 100644 --- a/applications/services/loader/loader_i.h +++ b/applications/services/loader/loader_i.h @@ -30,6 +30,7 @@ typedef enum { LoaderMessageTypeLock, LoaderMessageTypeUnlock, LoaderMessageTypeIsLocked, + LoaderMessageTypeStartByNameDetachedWithGuiError, } LoaderMessageType; typedef struct { diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 72116ccfdfe..7944bc58fa5 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,60.6,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1748,6 +1748,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 4ddff921bc7..8fd9f4c9b46 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.5,, +Version,+,60.6,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2139,6 +2139,7 @@ Function,+,loader_is_locked,_Bool,Loader* Function,+,loader_lock,_Bool,Loader* Function,+,loader_show_menu,void,Loader* Function,+,loader_start,LoaderStatus,"Loader*, const char*, const char*, FuriString*" +Function,+,loader_start_detached_with_gui_error,void,"Loader*, const char*, const char*" Function,+,loader_start_with_gui_error,LoaderStatus,"Loader*, const char*, const char*" Function,+,loader_unlock,void,Loader* Function,+,loading_alloc,Loading*, From db14ca964bd4456f8f86e79a4b1a28469890952e Mon Sep 17 00:00:00 2001 From: Victor Nikitchuk Date: Tue, 9 Apr 2024 18:05:30 +0300 Subject: [PATCH 10/22] Status output !TX/RX on the GDO2 CC1101 pin (#3571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Status output !TX/RX on the GDO2 CC1101 pin\ * Fix PVS warnings Co-authored-by: あく --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 20 +++++++++++++++++++ applications/services/loader/loader.c | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 8be60088a94..429a4b3a41a 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -177,6 +177,14 @@ static bool subghz_device_cc1101_ext_check_init(void) { furi_hal_gpio_init( subghz_device_cc1101_ext->g0_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + // Reset GDO2 (!TX/RX) to floating state + cc1101_status = cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); + if(cc1101_status.CHIP_RDYn != 0) { + //timeout or error + break; + } + // Go to sleep cc1101_status = cc1101_shutdown(subghz_device_cc1101_ext->spi_bus_handle); if(cc1101_status.CHIP_RDYn != 0) { @@ -385,6 +393,9 @@ void subghz_device_cc1101_ext_reset(void) { // Warning: push pull cc1101 clock output on GD0 cc1101_write_reg( subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG0, CC1101IocfgHighImpedance); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -394,6 +405,9 @@ void subghz_device_cc1101_ext_idle(void) { //waiting for the chip to switch to IDLE mode furi_check(cc1101_wait_status_state( subghz_device_cc1101_ext->spi_bus_handle, CC1101StateIDLE, 10000)); + // Reset GDO2 (!TX/RX) to floating state + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHighImpedance); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -403,6 +417,10 @@ void subghz_device_cc1101_ext_rx(void) { //waiting for the chip to switch to Rx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateRX, 10000)); + // Go GDO2 (!TX/RX) to high (RX state) + cc1101_write_reg( + subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW | CC1101_IOCFG_INV); + furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); } @@ -413,6 +431,8 @@ bool subghz_device_cc1101_ext_tx(void) { //waiting for the chip to switch to Tx mode furi_check( cc1101_wait_status_state(subghz_device_cc1101_ext->spi_bus_handle, CC1101StateTX, 10000)); + // Go GDO2 (!TX/RX) to low (TX state) + cc1101_write_reg(subghz_device_cc1101_ext->spi_bus_handle, CC1101_IOCFG2, CC1101IocfgHW); furi_hal_spi_release(subghz_device_cc1101_ext->spi_bus_handle); return true; } diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 1527807ccc6..cedc2e83ed7 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -100,7 +100,7 @@ void loader_start_detached_with_gui_error(Loader* loader, const char* name, cons LoaderMessage message = { .type = LoaderMessageTypeStartByNameDetachedWithGuiError, - .start.name = name ? strdup(name) : NULL, + .start.name = strdup(name), .start.args = args ? strdup(args) : NULL, }; furi_message_queue_put(loader->queue, &message, FuriWaitForever); From fb9728d5701ef023ae33723195c6186109442fee Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Wed, 10 Apr 2024 12:51:36 +0300 Subject: [PATCH 11/22] [FL-3772] Felica poller (#3570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New types for felica poller * New functions for felica data transmissions * Felica memory map extended with new fields * Init/deinit of mbedtls context added for felica encryption * Functions for session key and mac calculations added * Raw felica_poller implementation added * Removed MAC type parameter from check_mac function * Replaced all data fields needed for auth with context structure * Clean up felica_poller.c * Now RC block is filled with random numbers * New parameter for counting well-read blocks * Some cleanups * Felica file save and load logic added * Now we use card key from context for session key calculation * Copying card key to card block from auth context when both authentications succeeded, otherwise decrement blocks count by 1 * New felica poller event added * Moved some data structions to public namespace * FelicaAuthenticationContext struct moved to felica.h * Field type and name changed for better ones * Helper functions for felica_auth added to the app * New scene for felica card key input added * Logic for felica key input added * Auth context request processing added * Added block index definitions and replaced all index numbers with them * More macro defines * Replace nesting with do while block * New function for write operations mac calculation added * Replace nesting with do while block * Make functions static for now because they are used internally * Wrote some comments * Raw felica render implementation * New felica scenes * Adjusted felica dump rendering according design requirements * New felica scene added * Helper for switching scene during unlock added * Added warning scene and transfer to it * Moved unlock scene logic to separate files * Magic number changed * New felica render logic * Felica scenes adjusted according to design requirements * Felica poller cleanups * Some asserts added and some fixed * Replcaed asserts to checks in public api * Fixed pvs warnings in felica_poller * New event for felica_poller added for incomplete read actions * Handling of new poller event added * Update SConscript with felica files * Update api_symbols.csv with felica functions * Sync API versions Co-authored-by: あく --- applications/main/nfc/helpers/felica_auth.c | 21 ++ applications/main/nfc/helpers/felica_auth.h | 17 ++ .../helpers/protocol_support/felica/felica.c | 123 ++++++++- .../protocol_support/felica/felica_render.c | 98 +++++++- .../protocol_support/felica/felica_render.h | 12 + .../mf_ultralight/mf_ultralight.c | 38 +-- .../nfc_protocol_support_unlock_helper.c | 38 +++ .../nfc_protocol_support_unlock_helper.h | 9 + applications/main/nfc/nfc_app.c | 2 + applications/main/nfc/nfc_app_i.h | 2 + .../main/nfc/scenes/nfc_scene_config.h | 2 + .../nfc/scenes/nfc_scene_felica_key_input.c | 46 ++++ .../nfc/scenes/nfc_scene_felica_unlock_warn.c | 59 +++++ lib/nfc/SConscript | 2 + lib/nfc/protocols/felica/felica.c | 211 +++++++++++++++- lib/nfc/protocols/felica/felica.h | 101 ++++++++ lib/nfc/protocols/felica/felica_poller.c | 233 ++++++++++++++++-- lib/nfc/protocols/felica/felica_poller.h | 21 +- lib/nfc/protocols/felica/felica_poller_i.c | 107 +++++++- lib/nfc/protocols/felica/felica_poller_i.h | 104 +++++++- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 21 +- 22 files changed, 1185 insertions(+), 84 deletions(-) create mode 100644 applications/main/nfc/helpers/felica_auth.c create mode 100644 applications/main/nfc/helpers/felica_auth.h create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c create mode 100644 applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c diff --git a/applications/main/nfc/helpers/felica_auth.c b/applications/main/nfc/helpers/felica_auth.c new file mode 100644 index 00000000000..a8cd0929bae --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.c @@ -0,0 +1,21 @@ +#include "felica_auth.h" + +FelicaAuthenticationContext* felica_auth_alloc() { + FelicaAuthenticationContext* instance = malloc(sizeof(FelicaAuthenticationContext)); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + return instance; +} + +void felica_auth_free(FelicaAuthenticationContext* instance) { + furi_assert(instance); + free(instance); +} + +void felica_auth_reset(FelicaAuthenticationContext* instance) { + furi_assert(instance); + memset(instance->card_key.data, 0, FELICA_DATA_BLOCK_SIZE); + instance->skip_auth = true; + instance->auth_status.external = 0; + instance->auth_status.internal = 0; +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/felica_auth.h b/applications/main/nfc/helpers/felica_auth.h new file mode 100644 index 00000000000..3d99f1f9cd3 --- /dev/null +++ b/applications/main/nfc/helpers/felica_auth.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +FelicaAuthenticationContext* felica_auth_alloc(); + +void felica_auth_free(FelicaAuthenticationContext* instance); + +void felica_auth_reset(FelicaAuthenticationContext* instance); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index affa33b8651..b0660f3e629 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -7,6 +7,11 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" + +enum { + SubmenuIndexUnlock = SubmenuIndexCommonMax, +}; static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; @@ -18,6 +23,35 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(temp_str)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "More", + nfc_protocol_support_common_widget_callback, + instance); + furi_string_free(temp_str); +} + +static bool nfc_scene_info_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + return true; + } + + return false; +} + +static void nfc_scene_more_info_on_enter_felica(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); + + FuriString* temp_str = furi_string_alloc(); + + nfc_render_felica_dump(data, temp_str); + widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); @@ -29,29 +63,75 @@ static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, v NfcApp* instance = context; const FelicaPollerEvent* felica_event = event.event_data; + NfcCommand command = NfcCommandContinue; if(felica_event->type == FelicaPollerEventTypeReady) { nfc_device_set_data( instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if( + felica_event->type == FelicaPollerEventTypeError || + felica_event->type == FelicaPollerEventTypeIncomplete) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcCustomEventPollerIncomplete); + command = NfcCommandStop; + } else if(felica_event->type == FelicaPollerEventTypeRequestAuthContext) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected); + FelicaAuthenticationContext* ctx = felica_event->data->auth_context; + ctx->skip_auth = instance->felica_auth->skip_auth; + memcpy(ctx->card_key.data, instance->felica_auth->card_key.data, FELICA_DATA_BLOCK_SIZE); } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_felica(NfcApp* instance) { + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance); } +bool nfc_scene_read_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + nfc_unlock_helper_card_detected_handler(instance); + } else if(event.event == NfcCustomEventPollerIncomplete) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } + } + return true; +} + static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); - furi_string_cat_printf( - temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); - nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + + if(!scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn)) { + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + nfc_render_felica_info(data, NfcProtocolFormatTypeShort, temp_str); + } else { + bool all_unlocked = data->blocks_read == data->blocks_total; + furi_string_cat_printf( + temp_str, + "\e#%s\n", + all_unlocked ? "All Blocks Are Unlocked" : "Some Blocks Are Locked"); + nfc_render_felica_idm(data, NfcProtocolFormatTypeShort, temp_str); + uint8_t* ck_data = instance->felica_auth->card_key.data; + furi_string_cat_printf(temp_str, "Key:"); + for(uint8_t i = 0; i < 7; i++) { + furi_string_cat_printf(temp_str, " %02X", ck_data[i]); + if(i == 6) furi_string_cat_printf(temp_str, "..."); + } + nfc_render_felica_blocks_count(data, temp_str, false); + } + felica_auth_reset(instance->felica_auth); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); @@ -74,23 +154,50 @@ static void nfc_scene_emulate_on_enter_felica(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } +static void nfc_scene_read_menu_on_enter_felica(NfcApp* instance) { + const FelicaData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolFelica); + if(data->blocks_read != data->blocks_total) { + submenu_add_item( + instance->submenu, + "Unlock", + SubmenuIndexUnlock, + nfc_protocol_support_common_submenu_callback, + instance); + } +} + +static bool nfc_scene_read_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneFelicaKeyInput); + return true; + } + } + return false; +} + const NfcProtocolSupportBase nfc_protocol_support_felica = { .features = NfcProtocolFeatureEmulateUid, .scene_info = { .on_enter = nfc_scene_info_on_enter_felica, + .on_event = nfc_scene_info_on_event_felica, + }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_felica, .on_event = nfc_protocol_support_common_on_event_empty, }, .scene_read = { .on_enter = nfc_scene_read_on_enter_felica, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_read_on_event_felica, }, .scene_read_menu = { - .on_enter = nfc_protocol_support_common_on_enter_empty, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_enter = nfc_scene_read_menu_on_enter_felica, + .on_event = nfc_scene_read_menu_on_event_felica, }, .scene_read_success = { diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c index 3142b2c6dbf..6c57fb24b64 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.c @@ -1,19 +1,107 @@ #include "felica_render.h" -void nfc_render_felica_info( +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification) { + furi_string_cat_printf(str, "\nBlocks Read: %u/%u", data->blocks_read, data->blocks_total); + if(render_auth_notification && data->blocks_read != data->blocks_total) { + furi_string_cat_printf(str, "\nAuth-protected blocks!"); + } +} + +void nfc_render_felica_idm( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str) { - furi_string_cat_printf(str, "IDm:"); + furi_string_cat_printf(str, (format_type == NfcProtocolFormatTypeFull) ? "IDm:\n" : "IDm:"); for(size_t i = 0; i < FELICA_IDM_SIZE; i++) { - furi_string_cat_printf(str, " %02X", data->idm.data[i]); + furi_string_cat_printf( + str, + (format_type == NfcProtocolFormatTypeFull) ? "%02X " : " %02X", + data->idm.data[i]); + } +} + +void nfc_render_felica_info( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + if(format_type == NfcProtocolFormatTypeFull) { + furi_string_cat_printf(str, "Tech: JIS X 6319-4,\nISO 18092 [NFC-F]\n"); } + nfc_render_felica_idm(data, format_type, str); + if(format_type == NfcProtocolFormatTypeFull) { - furi_string_cat_printf(str, "\nPMm:"); + furi_string_cat_printf(str, "\nPMm:\n"); for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) { - furi_string_cat_printf(str, " %02X", data->pmm.data[i]); + furi_string_cat_printf(str, "%02X ", data->pmm.data[i]); + } + } + nfc_render_felica_blocks_count(data, str, true); +} + +static void nfc_render_felica_block_name( + const char* name, + FuriString* str, + 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 void nfc_render_felica_block_data(const FelicaBlock* block, FuriString* str) { + furi_string_cat_printf(str, "\nSF1=%02X; SF2=%02X\n", block->SF1, block->SF2); + for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) { + if((j != 0) && (j % 8 == 0)) furi_string_cat_printf(str, "\n"); + furi_string_cat_printf(str, "%02X ", block->data[j]); + } + furi_string_cat_printf(str, "\n"); +} + +static void nfc_render_felica_block( + const FelicaBlock* block, + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + nfc_render_felica_block_name(name, str, prefix_separator_cnt, suffix_separator_cnt); + nfc_render_felica_block_data(block, str); +} + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str) { + FuriString* name = furi_string_alloc(); + for(size_t i = 0; i < 14; i++) { + furi_string_printf(name, "S_PAD%d", i); + uint8_t suf_cnt = 18; + if(i == 1) { + suf_cnt = 19; + } else if((i == 10) || (i == 12) || (i == 13)) { + suf_cnt = 16; } + nfc_render_felica_block( + &data->data.fs.spad[i], str, furi_string_get_cstr(name), 20, suf_cnt); } + furi_string_free(name); + nfc_render_felica_block(&data->data.fs.reg, str, "REG", 23, 23); + nfc_render_felica_block(&data->data.fs.rc, str, "RC", 25, 25); + nfc_render_felica_block(&data->data.fs.mac, str, "MAC", 23, 23); + nfc_render_felica_block(&data->data.fs.id, str, "ID", 25, 25); + nfc_render_felica_block(&data->data.fs.d_id, str, "D_ID", 22, 24); + nfc_render_felica_block(&data->data.fs.ser_c, str, "SER_C", 20, 21); + nfc_render_felica_block(&data->data.fs.sys_c, str, "SYS_C", 20, 21); + nfc_render_felica_block(&data->data.fs.ckv, str, "CKV", 23, 23); + nfc_render_felica_block(&data->data.fs.ck, str, "CK", 25, 25); + nfc_render_felica_block(&data->data.fs.mc, str, "MC", 25, 24); + nfc_render_felica_block(&data->data.fs.wcnt, str, "WCNT", 22, 20); + nfc_render_felica_block(&data->data.fs.mac_a, str, "MAC_A", 20, 20); + nfc_render_felica_block(&data->data.fs.state, str, "STATE", 20, 21); + nfc_render_felica_block(&data->data.fs.crc_check, str, "CRC_CHCK", 15, 17); } diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h index 6d9816fc663..3d32e8d14e2 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica_render.h +++ b/applications/main/nfc/helpers/protocol_support/felica/felica_render.h @@ -4,7 +4,19 @@ #include "../nfc_protocol_support_render_common.h" +void nfc_render_felica_blocks_count( + const FelicaData* data, + FuriString* str, + bool render_auth_notification); + void nfc_render_felica_info( const FelicaData* data, NfcProtocolFormatType format_type, FuriString* str); + +void nfc_render_felica_dump(const FelicaData* data, FuriString* str); + +void nfc_render_felica_idm( + const FelicaData* data, + NfcProtocolFormatType format_type, + FuriString* str); \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index bd2015889d7..cd16374bc86 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -8,6 +8,7 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include "../nfc_protocol_support_unlock_helper.h" enum { SubmenuIndexUnlock = SubmenuIndexCommonMax, @@ -157,48 +158,15 @@ static NfcCommand return NfcCommandContinue; } -enum { - NfcSceneMfUltralightReadMenuStateCardSearch, - NfcSceneMfUltralightReadMenuStateCardFound, -}; - -static void nfc_scene_read_setup_view(NfcApp* instance) { - Popup* popup = instance->popup; - popup_reset(popup); - uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); - - if(state == NfcSceneMfUltralightReadMenuStateCardSearch) { - popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); - popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); - popup_set_text( - instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); - } else { - popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); - popup_set_icon(instance->popup, 12, 20, &A_Loading_24); - } - - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); -} - static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { - bool unlocking = - scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn); - - uint32_t state = unlocking ? NfcSceneMfUltralightReadMenuStateCardSearch : - NfcSceneMfUltralightReadMenuStateCardFound; - - scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); - - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_setup_from_state(instance); nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); - nfc_scene_read_setup_view(instance); + nfc_unlock_helper_card_detected_handler(instance); } else if((event.event == NfcCustomEventPollerIncomplete)) { notification_message(instance->notifications, &sequence_semi_success); scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c new file mode 100644 index 00000000000..f1d504d2486 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.c @@ -0,0 +1,38 @@ +#include "nfc_protocol_support_unlock_helper.h" + +static void nfc_scene_read_setup_view(NfcApp* instance) { + Popup* popup = instance->popup; + popup_reset(popup); + uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneRead); + + if(state == NfcSceneReadMenuStateCardSearch) { + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Hold card next\nto Flipper's back", 94, 27, AlignCenter, AlignTop); + } else { + popup_set_header(instance->popup, "Don't move", 85, 27, AlignCenter, AlignTop); + popup_set_icon(instance->popup, 12, 20, &A_Loading_24); + } + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); +} + +void nfc_unlock_helper_setup_from_state(NfcApp* instance) { + bool unlocking = + scene_manager_has_previous_scene( + instance->scene_manager, NfcSceneMfUltralightUnlockWarn) || + scene_manager_has_previous_scene(instance->scene_manager, NfcSceneFelicaUnlockWarn); + + uint32_t state = unlocking ? NfcSceneReadMenuStateCardSearch : NfcSceneReadMenuStateCardFound; + + scene_manager_set_scene_state(instance->scene_manager, NfcSceneRead, state); + + nfc_scene_read_setup_view(instance); +} + +void nfc_unlock_helper_card_detected_handler(NfcApp* instance) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); +} \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h new file mode 100644 index 00000000000..65da3324022 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_unlock_helper.h @@ -0,0 +1,9 @@ +#include "nfc/nfc_app_i.h" + +typedef enum { + NfcSceneReadMenuStateCardSearch, + NfcSceneReadMenuStateCardFound, +} NfcSceneUnlockReadState; + +void nfc_unlock_helper_setup_from_state(NfcApp* instance); +void nfc_unlock_helper_card_detected_handler(NfcApp* instance); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 02f852e3460..d0557de2afb 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -50,6 +50,7 @@ NfcApp* nfc_app_alloc(void) { instance->nfc = nfc_alloc(); + instance->felica_auth = felica_auth_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); + felica_auth_free(instance->felica_auth); mf_ultralight_auth_free(instance->mf_ul_auth); slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 706815e623e..64f7fc6e7b9 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/felica_auth.h" #include "helpers/slix_unlock.h" #include @@ -129,6 +130,7 @@ struct NfcApp { NfcScanner* scanner; NfcListener* listener; + FelicaAuthenticationContext* felica_auth; MfUltralightAuth* mf_ul_auth; SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 8a5e29ff394..3017d16a406 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -33,6 +33,8 @@ ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu) ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn) ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput) ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass) +ADD_SCENE(nfc, felica_key_input, FelicaKeyInput) +ADD_SCENE(nfc, felica_unlock_warn, FelicaUnlockWarn) ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo) ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp) diff --git a/applications/main/nfc/scenes/nfc_scene_felica_key_input.c b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c new file mode 100644 index 00000000000..b04f12dae85 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_key_input.c @@ -0,0 +1,46 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_key_input_byte_input_callback(void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_felica_key_input_on_enter(void* context) { + NfcApp* nfc = context; + + // Setup view + ByteInput* byte_input = nfc->byte_input; + byte_input_set_header_text(byte_input, "Enter key in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_felica_key_input_byte_input_callback, + NULL, + nfc, + nfc->felica_auth->card_key.data, + FELICA_DATA_BLOCK_SIZE); + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_felica_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneFelicaUnlockWarn); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_key_input_on_exit(void* context) { + NfcApp* nfc = context; + + // Clear view + byte_input_set_result_callback(nfc->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(nfc->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c new file mode 100644 index 00000000000..15b61dfa510 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_felica_unlock_warn.c @@ -0,0 +1,59 @@ +#include "../nfc_app_i.h" + +void nfc_scene_felica_unlock_warn_dialog_callback(DialogExResult result, void* context) { + NfcApp* nfc = context; + + view_dispatcher_send_custom_event(nfc->view_dispatcher, result); +} + +void nfc_scene_felica_unlock_warn_on_enter(void* context) { + NfcApp* nfc = context; + + const char* message = "Risky Action!"; + DialogEx* dialog_ex = nfc->dialog_ex; + dialog_ex_set_context(dialog_ex, nfc); + dialog_ex_set_result_callback(dialog_ex, nfc_scene_felica_unlock_warn_dialog_callback); + + dialog_ex_set_header(dialog_ex, message, 64, 0, AlignCenter, AlignTop); + + FuriString* str = furi_string_alloc(); + furi_string_cat_printf(str, "Unlock with key: "); + for(uint8_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) + furi_string_cat_printf(str, "%02X ", nfc->felica_auth->card_key.data[i]); + furi_string_cat_printf(str, "?"); + + nfc_text_store_set(nfc, furi_string_get_cstr(str)); + furi_string_free(str); + + dialog_ex_set_text(dialog_ex, nfc->text_store, 0, 12, AlignLeft, AlignTop); + + dialog_ex_set_left_button_text(dialog_ex, "Cancel"); + dialog_ex_set_right_button_text(dialog_ex, "Unlock"); + + view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDialogEx); +} + +bool nfc_scene_felica_unlock_warn_on_event(void* context, SceneManagerEvent event) { + NfcApp* nfc = context; + UNUSED(event); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultRight) { + nfc->felica_auth->skip_auth = false; + scene_manager_next_scene(nfc->scene_manager, NfcSceneRead); + consumed = true; + } else if(event.event == DialogExResultLeft) { + scene_manager_previous_scene(nfc->scene_manager); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_felica_unlock_warn_on_exit(void* context) { + NfcApp* nfc = context; + + dialog_ex_reset(nfc->dialog_ex); + nfc_text_store_clear(nfc); +} diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index 82317918bdb..f047101e6cb 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -24,6 +24,7 @@ env.Append( File("protocols/mf_desfire/mf_desfire.h"), File("protocols/slix/slix.h"), File("protocols/st25tb/st25tb.h"), + File("protocols/felica/felica.h"), # Pollers File("protocols/iso14443_3a/iso14443_3a_poller.h"), File("protocols/iso14443_3b/iso14443_3b_poller.h"), @@ -33,6 +34,7 @@ env.Append( File("protocols/mf_classic/mf_classic_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/st25tb/st25tb_poller.h"), + File("protocols/felica/felica_poller.h"), # Listeners File("protocols/iso14443_3a/iso14443_3a_listener.h"), File("protocols/iso14443_4a/iso14443_4a_listener.h"), diff --git a/lib/nfc/protocols/felica/felica.c b/lib/nfc/protocols/felica/felica.c index 7de1310bf90..bc47e064226 100644 --- a/lib/nfc/protocols/felica/felica.c +++ b/lib/nfc/protocols/felica/felica.c @@ -13,6 +13,14 @@ static const uint32_t felica_data_format_version = 1; +/** @brief This is used in felica_prepare_first_block to define which + * type of block needs to be prepared. +*/ +typedef enum { + FelicaMACTypeRead, + FelicaMACTypeWrite, +} FelicaMACType; + const NfcDeviceBase nfc_device_felica = { .protocol_name = FELICA_PROTOCOL_NAME, .alloc = (NfcDeviceAlloc)felica_alloc, @@ -35,18 +43,18 @@ FelicaData* felica_alloc(void) { } void felica_free(FelicaData* data) { - furi_assert(data); - + furi_check(data); free(data); } void felica_reset(FelicaData* data) { + furi_check(data); memset(data, 0, sizeof(FelicaData)); } void felica_copy(FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); *data = *other; } @@ -59,7 +67,7 @@ bool felica_verify(FelicaData* data, const FuriString* device_type) { } bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { - furi_assert(data); + furi_check(data); bool parsed = false; @@ -77,13 +85,32 @@ bool felica_load(FelicaData* data, FlipperFormat* ff, uint32_t version) { break; parsed = true; + uint32_t blocks_total = 0; + uint32_t blocks_read = 0; + if(!flipper_format_read_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_read_uint32(ff, "Blocks read", &blocks_read, 1)) break; + data->blocks_total = (uint8_t)blocks_total; + data->blocks_read = (uint8_t)blocks_read; + + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < data->blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + parsed = false; + break; + } + } } while(false); return parsed; } bool felica_save(const FelicaData* data, FlipperFormat* ff) { - furi_assert(data); + furi_check(data); bool saved = false; @@ -98,15 +125,33 @@ bool felica_save(const FelicaData* data, FlipperFormat* ff) { ff, FELICA_MANUFACTURE_PARAMETER, data->pmm.data, FELICA_PMM_SIZE)) break; + uint32_t blocks_total = data->blocks_total; + uint32_t blocks_read = data->blocks_read; + if(!flipper_format_write_uint32(ff, "Blocks total", &blocks_total, 1)) break; + if(!flipper_format_write_uint32(ff, "Blocks read", &blocks_read, 1)) break; + saved = true; + FuriString* temp_str = furi_string_alloc(); + for(uint8_t i = 0; i < blocks_total; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_write_hex( + ff, + furi_string_get_cstr(temp_str), + (&data->data.dump[i * sizeof(FelicaBlock)]), + sizeof(FelicaBlock))) { + saved = false; + break; + } + } + furi_string_free(temp_str); } while(false); return saved; } bool felica_is_equal(const FelicaData* data, const FelicaData* other) { - furi_assert(data); - furi_assert(other); + furi_check(data); + furi_check(other); return memcmp(data, other, sizeof(FelicaData)) == 0; } @@ -119,7 +164,7 @@ const char* felica_get_device_name(const FelicaData* data, NfcDeviceNameType nam } const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID if(uid_len) { @@ -130,7 +175,7 @@ const uint8_t* felica_get_uid(const FelicaData* data, size_t* uid_len) { } bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len) { - furi_assert(data); + furi_check(data); // Consider Manufacturer ID as UID const bool uid_valid = uid_len == FELICA_IDM_SIZE; @@ -145,3 +190,149 @@ FelicaData* felica_get_base_data(const FelicaData* data) { UNUSED(data); furi_crash("No base data"); } + +static void felica_reverse_copy_block(const uint8_t* array, uint8_t* reverse_array) { + furi_assert(array); + furi_assert(reverse_array); + + for(int i = 0; i < 8; i++) { + reverse_array[i] = array[7 - i]; + } +} + +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out) { + furi_check(ctx); + furi_check(ck); + furi_check(rc); + furi_check(out); + + uint8_t iv[8]; + memset(iv, 0, 8); + + uint8_t ck_reversed[16]; + felica_reverse_copy_block(ck, ck_reversed); + felica_reverse_copy_block(ck + 8, ck_reversed + 8); + + uint8_t rc_reversed[16]; + felica_reverse_copy_block(rc, rc_reversed); + felica_reverse_copy_block(rc + 8, rc_reversed + 8); + + mbedtls_des3_set2key_enc(ctx, ck_reversed); + mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, FELICA_DATA_BLOCK_SIZE, iv, rc_reversed, out); +} + +static bool felica_calculate_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* first_block, + const uint8_t* data, + const size_t length, + uint8_t* mac) { + furi_check((length % 8) == 0); + + uint8_t reverse_data[8]; + uint8_t iv[8]; + uint8_t out[8]; + mbedtls_des3_set2key_enc(ctx, session_key); + + felica_reverse_copy_block(rc, iv); + felica_reverse_copy_block(first_block, reverse_data); + uint8_t i = 0; + bool error = false; + do { + if(mbedtls_des3_crypt_cbc(ctx, MBEDTLS_DES_ENCRYPT, 8, iv, reverse_data, out) == 0) { + memcpy(iv, out, sizeof(iv)); + felica_reverse_copy_block(data + i, reverse_data); + i += 8; + } else { + error = true; + break; + } + } while(i <= length); + + if(!error) { + felica_reverse_copy_block(out, mac); + } + return !error; +} + +static void felica_prepare_first_block( + FelicaMACType operation_type, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* out) { + furi_check(blocks); + furi_check(out); + if(operation_type == FelicaMACTypeRead) { + memset(out, 0xFF, 8); + for(uint8_t i = 0, j = 0; i < block_count; i++, j += 2) { + out[j] = blocks[i]; + out[j + 1] = 0; + } + } else { + furi_check(block_count == 4); + memset(out, 0, 8); + out[0] = blocks[0]; + out[1] = blocks[1]; + out[2] = blocks[2]; + out[4] = blocks[3]; + out[6] = FELICA_BLOCK_INDEX_MAC_A; + } +} + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(blocks); + furi_check(data); + + uint8_t first_block[8]; + uint8_t mac[8]; + felica_prepare_first_block(FelicaMACTypeRead, blocks, block_count, first_block); + + uint8_t data_size_without_mac = FELICA_DATA_BLOCK_SIZE * (block_count - 1); + felica_calculate_mac(ctx, session_key, rc, first_block, data, data_size_without_mac, mac); + + uint8_t* mac_ptr = data + data_size_without_mac; + return !memcmp(mac, mac_ptr, 8); +} + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac) { + furi_check(ctx); + furi_check(session_key); + furi_check(rc); + furi_check(wcnt); + furi_check(data); + furi_check(mac); + + const uint8_t WCNT_length = 3; + uint8_t block_data[WCNT_length + 1]; + uint8_t first_block[8]; + + memcpy(block_data, wcnt, WCNT_length); + block_data[3] = FELICA_BLOCK_INDEX_STATE; + felica_prepare_first_block(FelicaMACTypeWrite, block_data, WCNT_length + 1, first_block); + + uint8_t session_swapped[FELICA_DATA_BLOCK_SIZE]; + memcpy(session_swapped, session_key + 8, 8); + memcpy(session_swapped + 8, session_key, 8); + felica_calculate_mac(ctx, session_swapped, rc, first_block, data, FELICA_DATA_BLOCK_SIZE, mac); +} \ No newline at end of file diff --git a/lib/nfc/protocols/felica/felica.h b/lib/nfc/protocols/felica/felica.h index e1820d4dc52..d032943d35d 100644 --- a/lib/nfc/protocols/felica/felica.h +++ b/lib/nfc/protocols/felica/felica.h @@ -2,6 +2,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -9,6 +10,23 @@ extern "C" { #define FELICA_IDM_SIZE (8U) #define FELICA_PMM_SIZE (8U) +#define FELICA_DATA_BLOCK_SIZE (16U) + +#define FELICA_BLOCKS_TOTAL_COUNT (27U) +#define FELICA_BLOCK_INDEX_REG (0x0EU) +#define FELICA_BLOCK_INDEX_RC (0x80U) +#define FELICA_BLOCK_INDEX_MAC (0x81U) +#define FELICA_BLOCK_INDEX_ID (0x82U) +#define FELICA_BLOCK_INDEX_D_ID (0x83U) +#define FELICA_BLOCK_INDEX_SER_C (0x84U) +#define FELICA_BLOCK_INDEX_SYS_C (0x85U) +#define FELICA_BLOCK_INDEX_CKV (0x86U) +#define FELICA_BLOCK_INDEX_CK (0x87U) +#define FELICA_BLOCK_INDEX_MC (0x88U) +#define FELICA_BLOCK_INDEX_WCNT (0x90U) +#define FELICA_BLOCK_INDEX_MAC_A (0x91U) +#define FELICA_BLOCK_INDEX_STATE (0x92U) +#define FELICA_BLOCK_INDEX_CRC_CHECK (0xA0U) #define FELICA_GUARD_TIME_US (20000U) #define FELICA_FDT_POLL_FC (10000U) @@ -23,6 +41,7 @@ extern "C" { #define FELICA_TIME_SLOT_8 (0x07U) #define FELICA_TIME_SLOT_16 (0x0FU) +/** @brief Type of possible Felica errors */ typedef enum { FelicaErrorNone, FelicaErrorNotPresent, @@ -35,17 +54,78 @@ typedef enum { FelicaErrorTimeout, } FelicaError; +/** @brief Separate type for card key block. Used in authentication process */ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaCardKey; + +/** @brief In Felica there two types of auth. Internal is the first one, after + * which external became possible. Here are two flags representing which one + * was passed */ +typedef struct { + bool internal : 1; + bool external : 1; +} FelicaAuthenticationStatus; + +/** @brief Struct which controls the process of authentication and can be passed as + * a parameter to the application level. In order to force user to fill card key block data. */ +typedef struct { + bool skip_auth; /**< By default it is true, so auth is skipped. By setting this to false several auth steps will be performed in order to pass auth*/ + FelicaCardKey + card_key; /**< User must fill this field with known card key in order to pass auth*/ + FelicaAuthenticationStatus auth_status; /**< Authentication status*/ +} FelicaAuthenticationContext; + +/** @brief Felica ID block */ typedef struct { uint8_t data[FELICA_IDM_SIZE]; } FelicaIDm; +/** @brief Felica PMm block */ typedef struct { uint8_t data[FELICA_PMM_SIZE]; } FelicaPMm; +/** @brief Felica block with status flags indicating last operation with it. + * See Felica manual for more details on status codes. */ +typedef struct { + uint8_t SF1; /**< Status flag 1, equals to 0 when success*/ + uint8_t SF2; /**< Status flag 2, equals to 0 when success*/ + uint8_t data[FELICA_DATA_BLOCK_SIZE]; /**< Block data */ +} FelicaBlock; + +/** @brief Felica filesystem structure */ +typedef struct { + FelicaBlock spad[14]; + FelicaBlock reg; + FelicaBlock rc; + FelicaBlock mac; + FelicaBlock id; + FelicaBlock d_id; + FelicaBlock ser_c; + FelicaBlock sys_c; + FelicaBlock ckv; + FelicaBlock ck; + FelicaBlock mc; + FelicaBlock wcnt; + FelicaBlock mac_a; + FelicaBlock state; + FelicaBlock crc_check; +} FelicaFileSystem; + +/** @brief Union which represents filesystem in junction with plain data dump */ +typedef union { + FelicaFileSystem fs; + uint8_t dump[sizeof(FelicaFileSystem)]; +} FelicaFSUnion; + +/** @brief Structure used to store Felica data and additional values about reading */ typedef struct { FelicaIDm idm; FelicaPMm pmm; + uint8_t blocks_total; + uint8_t blocks_read; + FelicaFSUnion data; } FelicaData; extern const NfcDeviceBase nfc_device_felica; @@ -74,6 +154,27 @@ bool felica_set_uid(FelicaData* data, const uint8_t* uid, size_t uid_len); FelicaData* felica_get_base_data(const FelicaData* data); +void felica_calculate_session_key( + mbedtls_des3_context* ctx, + const uint8_t* ck, + const uint8_t* rc, + uint8_t* out); + +bool felica_check_mac( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* blocks, + const uint8_t block_count, + uint8_t* data); + +void felica_calculate_mac_write( + mbedtls_des3_context* ctx, + const uint8_t* session_key, + const uint8_t* rc, + const uint8_t* wcnt, + const uint8_t* data, + uint8_t* mac); #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/felica/felica_poller.c b/lib/nfc/protocols/felica/felica_poller.c index 23b1604e19b..720daf2384b 100644 --- a/lib/nfc/protocols/felica/felica_poller.c +++ b/lib/nfc/protocols/felica/felica_poller.c @@ -3,6 +3,11 @@ #include #include +#include + +#define TAG "FelicaPoller" + +typedef NfcCommand (*FelicaPollerReadHandler)(FelicaPoller* instance); const FelicaData* felica_poller_get_data(FelicaPoller* instance) { furi_assert(instance); @@ -23,6 +28,8 @@ static FelicaPoller* felica_poller_alloc(Nfc* nfc) { nfc_set_guard_time_us(instance->nfc, FELICA_GUARD_TIME_US); nfc_set_fdt_poll_fc(instance->nfc, FELICA_FDT_POLL_FC); nfc_set_fdt_poll_poll_us(instance->nfc, FELICA_POLL_POLL_MIN_US); + + mbedtls_des3_init(&instance->auth.des_context); instance->data = felica_alloc(); instance->felica_event.data = &instance->felica_event_data; @@ -40,6 +47,7 @@ static void felica_poller_free(FelicaPoller* instance) { furi_assert(instance->rx_buffer); furi_assert(instance->data); + mbedtls_des3_free(&instance->auth.des_context); bit_buffer_free(instance->tx_buffer); bit_buffer_free(instance->rx_buffer); felica_free(instance->data); @@ -55,6 +63,212 @@ static void instance->context = context; } +NfcCommand felica_poller_state_handler_idle(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Idle"); + felica_reset(instance->data); + instance->state = FelicaPollerStateActivated; + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_activate(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Activate"); + + NfcCommand command = NfcCommandContinue; + + FelicaError error = felica_poller_activate(instance, instance->data); + if(error == FelicaErrorNone) { + furi_hal_random_fill_buf(instance->data->data.fs.rc.data, FELICA_DATA_BLOCK_SIZE); + + instance->felica_event.type = FelicaPollerEventTypeRequestAuthContext; + instance->felica_event_data.auth_context = &instance->auth.context; + + instance->callback(instance->general_event, instance->context); + + bool skip_auth = instance->auth.context.skip_auth; + instance->state = skip_auth ? FelicaPollerStateReadBlocks : + FelicaPollerStateAuthenticateInternal; + } else if(error != FelicaErrorTimeout) { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + return command; +} + +NfcCommand felica_poller_state_handler_auth_internal(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth Internal"); + + felica_calculate_session_key( + &instance->auth.des_context, + instance->auth.context.card_key.data, + instance->data->data.fs.rc.data, + instance->auth.session_key.data); + + instance->state = FelicaPollerStateReadBlocks; + + uint8_t blocks[3] = {FELICA_BLOCK_INDEX_RC, 0, 0}; + FelicaPollerWriteCommandResponse* tx_resp; + do { + FelicaError error = felica_poller_write_blocks( + instance, 1, blocks, instance->data->data.fs.rc.data, &tx_resp); + if((error != FelicaErrorNone) || (tx_resp->SF1 != 0) || (tx_resp->SF2 != 0)) break; + + blocks[0] = FELICA_BLOCK_INDEX_ID; + blocks[1] = FELICA_BLOCK_INDEX_WCNT; + blocks[2] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, sizeof(blocks), blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + if(felica_check_mac( + &instance->auth.des_context, + instance->auth.session_key.data, + instance->data->data.fs.rc.data, + blocks, + rx_resp->block_count, + rx_resp->data)) { + instance->auth.context.auth_status.internal = true; + instance->data->data.fs.wcnt.SF1 = 0; + instance->data->data.fs.wcnt.SF2 = 0; + memcpy( + instance->data->data.fs.wcnt.data, + rx_resp->data + FELICA_DATA_BLOCK_SIZE, + FELICA_DATA_BLOCK_SIZE); + instance->state = FelicaPollerStateAuthenticateExternal; + } + } while(false); + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_auth_external(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Auth External"); + instance->state = FelicaPollerStateReadBlocks; + uint8_t blocks[2]; + + instance->data->data.fs.state.data[0] = 1; + FelicaAuthentication* auth = &instance->auth; + felica_calculate_mac_write( + &auth->des_context, + auth->session_key.data, + instance->data->data.fs.rc.data, + instance->data->data.fs.wcnt.data, + instance->data->data.fs.state.data, + instance->data->data.fs.mac_a.data); + + memcpy(instance->data->data.fs.mac_a.data + 8, instance->data->data.fs.wcnt.data, 3); //-V1086 + + uint8_t tx_data[FELICA_DATA_BLOCK_SIZE * 2]; + memcpy(tx_data, instance->data->data.fs.state.data, FELICA_DATA_BLOCK_SIZE); + memcpy( + tx_data + FELICA_DATA_BLOCK_SIZE, + instance->data->data.fs.mac_a.data, + FELICA_DATA_BLOCK_SIZE); + + do { + blocks[0] = FELICA_BLOCK_INDEX_STATE; + blocks[1] = FELICA_BLOCK_INDEX_MAC_A; + FelicaPollerWriteCommandResponse* tx_resp; + FelicaError error = felica_poller_write_blocks(instance, 2, blocks, tx_data, &tx_resp); + if(error != FelicaErrorNone || tx_resp->SF1 != 0 || tx_resp->SF2 != 0) break; + + FelicaPollerReadCommandResponse* rx_resp; + error = felica_poller_read_blocks(instance, 1, blocks, &rx_resp); + if(error != FelicaErrorNone || rx_resp->SF1 != 0 || rx_resp->SF2 != 0) break; + + instance->data->data.fs.state.SF1 = 0; + instance->data->data.fs.state.SF2 = 0; + memcpy(instance->data->data.fs.state.data, rx_resp->data, FELICA_DATA_BLOCK_SIZE); + instance->auth.context.auth_status.external = instance->data->data.fs.state.data[0]; + } while(false); + instance->state = FelicaPollerStateReadBlocks; + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_blocks(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Blocks"); + + uint8_t block_count = 1; + uint8_t block_list[4] = {0, 0, 0, 0}; + block_list[0] = instance->block_index; + + instance->block_index++; + if(instance->block_index == FELICA_BLOCK_INDEX_REG + 1) { + instance->block_index = FELICA_BLOCK_INDEX_RC; + } else if(instance->block_index == FELICA_BLOCK_INDEX_MC + 1) { + instance->block_index = FELICA_BLOCK_INDEX_WCNT; + } else if(instance->block_index == FELICA_BLOCK_INDEX_STATE + 1) { + instance->block_index = FELICA_BLOCK_INDEX_CRC_CHECK; + } + + FelicaPollerReadCommandResponse* response; + FelicaError error = felica_poller_read_blocks(instance, block_count, block_list, &response); + if(error == FelicaErrorNone) { + block_count = (response->SF1 == 0) ? response->block_count : block_count; + uint8_t* data_ptr = + instance->data->data.dump + instance->data->blocks_total * sizeof(FelicaBlock); + + *data_ptr++ = response->SF1; + *data_ptr++ = response->SF2; + + if(response->SF1 == 0) { + uint8_t* response_data_ptr = response->data; + instance->data->blocks_read++; + memcpy(data_ptr, response_data_ptr, FELICA_DATA_BLOCK_SIZE); + } else { + memset(data_ptr, 0, FELICA_DATA_BLOCK_SIZE); + } + instance->data->blocks_total++; + + if(instance->data->blocks_total == FELICA_BLOCKS_TOTAL_COUNT) { + instance->state = FelicaPollerStateReadSuccess; + } + } else { + instance->felica_event.type = FelicaPollerEventTypeError; + instance->felica_event_data.error = error; + instance->state = FelicaPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +NfcCommand felica_poller_state_handler_read_success(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Success"); + + if(!instance->auth.context.auth_status.internal || + !instance->auth.context.auth_status.external) { + instance->data->blocks_read--; + instance->felica_event.type = FelicaPollerEventTypeIncomplete; + } else { + memcpy( + instance->data->data.fs.ck.data, + instance->auth.context.card_key.data, + FELICA_DATA_BLOCK_SIZE); + instance->felica_event.type = FelicaPollerEventTypeReady; + } + + instance->felica_event_data.error = FelicaErrorNone; + return instance->callback(instance->general_event, instance->context); +} + +NfcCommand felica_poller_state_handler_read_failed(FelicaPoller* instance) { + FURI_LOG_D(TAG, "Read Fail"); + instance->callback(instance->general_event, instance->context); + + return NfcCommandStop; +} + +static const FelicaPollerReadHandler felica_poller_handler[FelicaPollerStateNum] = { + [FelicaPollerStateIdle] = felica_poller_state_handler_idle, + [FelicaPollerStateActivated] = felica_poller_state_handler_activate, + [FelicaPollerStateAuthenticateInternal] = felica_poller_state_handler_auth_internal, + [FelicaPollerStateAuthenticateExternal] = felica_poller_state_handler_auth_external, + [FelicaPollerStateReadBlocks] = felica_poller_state_handler_read_blocks, + [FelicaPollerStateReadSuccess] = felica_poller_state_handler_read_success, + [FelicaPollerStateReadFailed] = felica_poller_state_handler_read_failed, +}; + static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { furi_assert(context); furi_assert(event.protocol == NfcProtocolInvalid); @@ -65,24 +279,7 @@ static NfcCommand felica_poller_run(NfcGenericEvent event, void* context) { NfcCommand command = NfcCommandContinue; if(nfc_event->type == NfcEventTypePollerReady) { - if(instance->state != FelicaPollerStateActivated) { - FelicaError error = felica_poller_activate(instance, instance->data); - if(error == FelicaErrorNone) { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - } else { - instance->felica_event.type = FelicaPollerEventTypeError; - instance->felica_event_data.error = error; - command = instance->callback(instance->general_event, instance->context); - // Add delay to switch context - furi_delay_ms(100); - } - } else { - instance->felica_event.type = FelicaPollerEventTypeReady; - instance->felica_event_data.error = FelicaErrorNone; - command = instance->callback(instance->general_event, instance->context); - } + command = felica_poller_handler[instance->state](instance); } return command; diff --git a/lib/nfc/protocols/felica/felica_poller.h b/lib/nfc/protocols/felica/felica_poller.h index b0e6778a0f5..d4366e7672a 100644 --- a/lib/nfc/protocols/felica/felica_poller.h +++ b/lib/nfc/protocols/felica/felica_poller.h @@ -19,14 +19,33 @@ typedef struct FelicaPoller FelicaPoller; */ typedef enum { FelicaPollerEventTypeError, /**< An error occured during activation procedure. */ - FelicaPollerEventTypeReady, /**< The card was activated by the poller. */ + FelicaPollerEventTypeReady, /**< The card was activated and fully read by the poller. */ + FelicaPollerEventTypeIncomplete, /**< The card was activated and partly read by the poller. */ + FelicaPollerEventTypeRequestAuthContext, /**< Authentication context was requested by poller. */ } FelicaPollerEventType; +/** + * @brief Stucture for holding Felica session key which is calculated from rc and ck. +*/ +typedef struct { + uint8_t data[FELICA_DATA_BLOCK_SIZE]; +} FelicaSessionKey; + +/** + * @brief Structure used to hold authentication related fields. +*/ +typedef struct { + mbedtls_des3_context des_context; /**< Context for mbedtls des functions. */ + FelicaSessionKey session_key; /**< Calculated session key. */ + FelicaAuthenticationContext context; /**< Public auth context provided to upper levels. */ +} FelicaAuthentication; + /** * @brief Felica poller event data. */ typedef union { FelicaError error; /**< Error code indicating card activation fail reason. */ + FelicaAuthenticationContext* auth_context; /**< Authentication context to be filled by user. */ } FelicaPollerEventData; /** diff --git a/lib/nfc/protocols/felica/felica_poller_i.c b/lib/nfc/protocols/felica/felica_poller_i.c index bfbf150ef98..f7726be32c9 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.c +++ b/lib/nfc/protocols/felica/felica_poller_i.c @@ -3,6 +3,11 @@ #include #define TAG "FelicaPoller" +#define FELICA_CMD_READ_WITHOUT_ENCRYPTION (0x06U) +#define FELICA_CMD_WRITE_WITHOUT_ENCRYPTION (0x08U) + +#define FELICA_SERVICE_RW_ACCESS (0x0009U) +#define FELICA_SERVICE_RO_ACCESS (0x000BU) static FelicaError felica_poller_process_error(NfcError error) { switch(error) { @@ -15,8 +20,8 @@ static FelicaError felica_poller_process_error(NfcError error) { } } -static FelicaError felica_poller_frame_exchange( - FelicaPoller* instance, +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) { @@ -93,10 +98,104 @@ FelicaError felica_poller_polling( return error; } -FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { +static void felica_poller_prepare_tx_buffer( + const FelicaPoller* instance, + const uint8_t command, + const uint16_t service_code, + const uint8_t block_count, + const uint8_t* const blocks, + const uint8_t data_block_count, + const uint8_t* data) { + FelicaCommandHeader cmd = { + .code = command, + .idm = instance->data->idm, + .service_num = 1, + .service_code = service_code, + .block_count = block_count, + }; + + FelicaBlockListElement block_list[4] = {{0}, {0}, {0}, {0}}; + for(uint8_t i = 0; i < block_count; i++) { + block_list[i].length = 1; + block_list[i].block_number = blocks[i]; + } + + uint8_t block_list_count = block_count; + uint8_t block_list_size = block_list_count * sizeof(FelicaBlockListElement); + uint8_t total_size = sizeof(FelicaCommandHeader) + 1 + block_list_size + + data_block_count * FELICA_DATA_BLOCK_SIZE; + bit_buffer_reset(instance->tx_buffer); + bit_buffer_append_byte(instance->tx_buffer, total_size); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&cmd, sizeof(FelicaCommandHeader)); + bit_buffer_append_bytes(instance->tx_buffer, (uint8_t*)&block_list, block_list_size); + + if(data_block_count != 0) { + bit_buffer_append_bytes( + instance->tx_buffer, data, data_block_count * FELICA_DATA_BLOCK_SIZE); + } +} + +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr) { furi_assert(instance); + furi_assert(block_count <= 4); + furi_assert(block_numbers); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_READ_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RO_ACCESS, + block_count, + block_numbers, + 0, + NULL); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = (FelicaPollerReadCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} + +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr) { + furi_assert(instance); + furi_assert(block_count <= 2); + furi_assert(block_numbers); + furi_assert(data); + furi_assert(response_ptr); + + felica_poller_prepare_tx_buffer( + instance, + FELICA_CMD_WRITE_WITHOUT_ENCRYPTION, + FELICA_SERVICE_RW_ACCESS, + block_count, + block_numbers, + block_count, + data); + bit_buffer_reset(instance->rx_buffer); + + FelicaError error = felica_poller_frame_exchange( + instance, instance->tx_buffer, instance->rx_buffer, FELICA_POLLER_POLLING_FWT); + if(error == FelicaErrorNone) { + *response_ptr = + (FelicaPollerWriteCommandResponse*)bit_buffer_get_data(instance->rx_buffer); + } + return error; +} - felica_reset(data); +FelicaError felica_poller_activate(FelicaPoller* instance, FelicaData* data) { + furi_assert(instance); FelicaError ret; diff --git a/lib/nfc/protocols/felica/felica_poller_i.h b/lib/nfc/protocols/felica/felica_poller_i.h index 3bd4d91f9f9..f7df4c84502 100644 --- a/lib/nfc/protocols/felica/felica_poller_i.h +++ b/lib/nfc/protocols/felica/felica_poller_i.h @@ -1,7 +1,6 @@ #pragma once #include "felica_poller.h" - #include #ifdef __cplusplus @@ -18,11 +17,20 @@ extern "C" { typedef enum { FelicaPollerStateIdle, FelicaPollerStateActivated, + FelicaPollerStateAuthenticateInternal, + FelicaPollerStateAuthenticateExternal, + FelicaPollerStateReadBlocks, + FelicaPollerStateReadSuccess, + FelicaPollerStateReadFailed, + + FelicaPollerStateNum } FelicaPollerState; struct FelicaPoller { Nfc* nfc; FelicaPollerState state; + FelicaAuthentication auth; + FelicaData* data; BitBuffer* tx_buffer; BitBuffer* rx_buffer; @@ -31,6 +39,7 @@ struct FelicaPoller { FelicaPollerEvent felica_event; FelicaPollerEventData felica_event_data; NfcGenericCallback callback; + uint8_t block_index; void* context; }; @@ -46,13 +55,106 @@ typedef struct { uint8_t request_data[2]; } FelicaPollerPollingResponse; +typedef struct { + uint8_t service_code : 4; + uint8_t access_mode : 3; + uint8_t length : 1; + uint8_t block_number; +} FelicaBlockListElement; + +#pragma pack(push, 1) +typedef struct { + uint8_t code; + FelicaIDm idm; + uint8_t service_num; + uint16_t service_code; + uint8_t block_count; +} FelicaCommandHeader; +#pragma pack(pop) + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; + uint8_t block_count; + uint8_t data[]; +} FelicaPollerReadCommandResponse; + +typedef struct { + uint8_t length; + uint8_t response_code; + FelicaIDm idm; + uint8_t SF1; + uint8_t SF2; +} FelicaPollerWriteCommandResponse; + const FelicaData* felica_poller_get_data(FelicaPoller* instance); +/** + * @brief Performs felica polling operation as part of the activation process + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] cmd Pointer to polling command structure + * @param[out] resp Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure +*/ FelicaError felica_poller_polling( FelicaPoller* instance, const FelicaPollerPollingCommand* cmd, FelicaPollerPollingResponse* resp); +/** + * @brief Performs felica read operation for blocks provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in reading procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_read_blocks( + FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + FelicaPollerReadCommandResponse** const response_ptr); + +/** + * @brief Performs felica write operation with data provided as parameters + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] block_count Amount of blocks involved in writing procedure + * @param[in] block_numbers Array with block indexes according to felica docs + * @param[in] data Data of blocks provided in block_numbers + * @param[out] response_ptr Pointer to the response structure + * @return FelicaErrorNone on success, an error code on failure. +*/ +FelicaError felica_poller_write_blocks( + const FelicaPoller* instance, + const uint8_t block_count, + const uint8_t* const block_numbers, + const uint8_t* data, + FelicaPollerWriteCommandResponse** const response_ptr); + +/** + * @brief Perform frame exchange procedure. + * + * Prepares data for sending by adding crc, after that performs + * low level calls to send package data to the card + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer with data to be transmitted + * @param[out] rx_buffer pointer to the buffer with received data from card + * @param[in] fwt timeout window + * @return FelicaErrorNone on success, an error code on failure. + */ +FelicaError felica_poller_frame_exchange( + const FelicaPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt); + #ifdef __cplusplus } #endif diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7944bc58fa5..6e597e7d05b 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 8fd9f4c9b46..09b8b9ee821 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.6,, +Version,+,60.7,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -133,6 +133,8 @@ Header,+,lib/nfc/nfc_device.h,, Header,+,lib/nfc/nfc_listener.h,, Header,+,lib/nfc/nfc_poller.h,, Header,+,lib/nfc/nfc_scanner.h,, +Header,+,lib/nfc/protocols/felica/felica.h,, +Header,+,lib/nfc/protocols/felica/felica_poller.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h,, Header,+,lib/nfc/protocols/iso14443_3a/iso14443_3a_poller.h,, @@ -987,6 +989,22 @@ Function,-,fdim,double,"double, double" Function,-,fdimf,float,"float, float" Function,-,fdiml,long double,"long double, long double" Function,-,fdopen,FILE*,"int, const char*" +Function,+,felica_alloc,FelicaData*, +Function,+,felica_calculate_mac_write,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_calculate_session_key,void,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, uint8_t*" +Function,+,felica_check_mac,_Bool,"mbedtls_des3_context*, const uint8_t*, const uint8_t*, const uint8_t*, const uint8_t, uint8_t*" +Function,+,felica_copy,void,"FelicaData*, const FelicaData*" +Function,+,felica_free,void,FelicaData* +Function,+,felica_get_base_data,FelicaData*,const FelicaData* +Function,+,felica_get_device_name,const char*,"const FelicaData*, NfcDeviceNameType" +Function,+,felica_get_uid,const uint8_t*,"const FelicaData*, size_t*" +Function,+,felica_is_equal,_Bool,"const FelicaData*, const FelicaData*" +Function,+,felica_load,_Bool,"FelicaData*, FlipperFormat*, uint32_t" +Function,+,felica_poller_activate,FelicaError,"FelicaPoller*, FelicaData*" +Function,+,felica_reset,void,FelicaData* +Function,+,felica_save,_Bool,"const FelicaData*, FlipperFormat*" +Function,+,felica_set_uid,_Bool,"FelicaData*, const uint8_t*, size_t" +Function,+,felica_verify,_Bool,"FelicaData*, const FuriString*" Function,-,feof,int,FILE* Function,-,feof_unlocked,int,FILE* Function,-,ferror,int,FILE* @@ -3739,6 +3757,7 @@ Variable,+,message_red_255,const NotificationMessage, Variable,+,message_sound_off,const NotificationMessage, Variable,+,message_vibro_off,const NotificationMessage, Variable,+,message_vibro_on,const NotificationMessage, +Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, From 1a40fae003d1f37ee0ec4fedfac0d38b78b98535 Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 16 Apr 2024 06:55:24 +0100 Subject: [PATCH 12/22] [FL-3750] Mf Desfire multiple file rights support (#3576) * mf desfire: remove unused type * mf desfire: continue reading after failed get free mem cmd * mf desfire: fix processing read master key settings command * mf desfire: don't read applications if they are auth protected * mf desfire: handle multiple rights * mf desfire: fix PVS warnings * mf desfire: fix print format * mf desfire: fix logs * mf classic: add send frame functions to poller * unit tests: add test from mfc crypto frame exchange * mf classic: add documentation * mf classic: fix incorrect name * target: fix api version --- applications/debug/unit_tests/nfc/nfc_test.c | 127 +++++++++++++++- .../protocol_support/mf_desfire/mf_desfire.c | 8 +- .../mf_desfire/mf_desfire_render.c | 94 ++++++++---- .../scenes/nfc_scene_mf_desfire_more_info.c | 2 +- .../protocols/mf_classic/mf_classic_poller.h | 83 +++++++++++ .../mf_classic/mf_classic_poller_i.c | 74 ++++++++++ lib/nfc/protocols/mf_desfire/mf_desfire.h | 11 +- lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 137 +++++++++++------- lib/nfc/protocols/mf_desfire/mf_desfire_i.h | 46 ++++++ .../protocols/mf_desfire/mf_desfire_poller.c | 22 ++- .../mf_desfire/mf_desfire_poller_i.c | 65 +++++++-- .../mf_desfire/mf_desfire_poller_i.h | 2 + targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 6 +- 14 files changed, 571 insertions(+), 108 deletions(-) diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index 4b6503b7289..c6304d53ca6 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -7,10 +7,13 @@ #include #include #include +#include #include #include #include #include +#include +#include #include #include @@ -22,6 +25,23 @@ #define NFC_TEST_NFC_DEV_PATH EXT_PATH("unit_tests/nfc/nfc_device_test.nfc") #define NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH EXT_PATH("unit_tests/mf_dict.nfc") +#define NFC_TEST_FLAG_WORKER_DONE (1) + +typedef enum { + NfcTestMfClassicSendFrameTestStateAuth, + NfcTestMfClassicSendFrameTestStateReadBlock, + + NfcTestMfClassicSendFrameTestStateFail, + NfcTestMfClassicSendFrameTestStateSuccess, +} NfcTestMfClassicSendFrameTestState; + +typedef struct { + NfcTestMfClassicSendFrameTestState state; + BitBuffer* tx_buf; + BitBuffer* rx_buf; + FuriThreadId thread_id; +} NfcTestMfClassicSendFrameTest; + typedef struct { Storage* storage; } NfcTest; @@ -435,6 +455,109 @@ static void mf_classic_value_block(void) { nfc_free(poller); } +NfcCommand mf_classic_poller_send_frame_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + MfClassicPoller* instance = event.poller; + NfcTestMfClassicSendFrameTest* frame_test = context; + Iso14443_3aPollerEvent* iso3_event = event.parent_event_data; + + MfClassicError error = MfClassicErrorNone; + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + if(frame_test->state == NfcTestMfClassicSendFrameTestStateAuth) { + MfClassicKey key = { + .data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, + }; + error = mf_classic_poller_auth(instance, 0, &key, MfClassicKeyTypeA, NULL); + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateReadBlock : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateReadBlock) { + do { + const uint8_t read_block_cmd[] = { + 0x30, + 0x01, + 0x8b, + 0xb9, + }; + bit_buffer_copy_bytes(frame_test->tx_buf, read_block_cmd, sizeof(read_block_cmd)); + + error = mf_classic_poller_send_encrypted_frame( + instance, frame_test->tx_buf, frame_test->rx_buf, 200000); + if(error != MfClassicErrorNone) break; + if(bit_buffer_get_size_bytes(frame_test->rx_buf) != 18) { + error = MfClassicErrorProtocol; + break; + } + + const uint8_t* rx_data = bit_buffer_get_data(frame_test->rx_buf); + const uint8_t rx_data_ref[16] = {0}; + if(memcmp(rx_data, rx_data_ref, sizeof(rx_data_ref)) != 0) { + error = MfClassicErrorProtocol; + break; + } + } while(false); + + frame_test->state = (error == MfClassicErrorNone) ? + NfcTestMfClassicSendFrameTestStateSuccess : + NfcTestMfClassicSendFrameTestStateFail; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateSuccess) { + command = NfcCommandStop; + } else if(frame_test->state == NfcTestMfClassicSendFrameTestStateFail) { + command = NfcCommandStop; + } + } else { + frame_test->state = NfcTestMfClassicSendFrameTestStateFail; + command = NfcCommandStop; + } + + if(command == NfcCommandStop) { + furi_thread_flags_set(frame_test->thread_id, NFC_TEST_FLAG_WORKER_DONE); + } + + return command; +} + +MU_TEST(mf_classic_send_frame_test) { + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + nfc_data_generator_fill_data(NfcDataGeneratorTypeMfClassic4k_7b, nfc_device); + NfcListener* mfc_listener = nfc_listener_alloc( + listener, NfcProtocolMfClassic, nfc_device_get_data(nfc_device, NfcProtocolMfClassic)); + nfc_listener_start(mfc_listener, NULL, NULL); + + NfcPoller* mfc_poller = nfc_poller_alloc(poller, NfcProtocolMfClassic); + NfcTestMfClassicSendFrameTest context = { + .state = NfcTestMfClassicSendFrameTestStateAuth, + .thread_id = furi_thread_get_current_id(), + .tx_buf = bit_buffer_alloc(32), + .rx_buf = bit_buffer_alloc(32), + }; + nfc_poller_start_ex(mfc_poller, mf_classic_poller_send_frame_callback, &context); + + uint32_t flag = + furi_thread_flags_wait(NFC_TEST_FLAG_WORKER_DONE, FuriFlagWaitAny, FuriWaitForever); + mu_assert(flag == NFC_TEST_FLAG_WORKER_DONE, "Wrong thread flag"); + nfc_poller_stop(mfc_poller); + nfc_poller_free(mfc_poller); + + mu_assert( + context.state == NfcTestMfClassicSendFrameTestStateSuccess, "Wrong test state at the end"); + + bit_buffer_free(context.tx_buf); + bit_buffer_free(context.rx_buf); + nfc_listener_stop(mfc_listener); + nfc_listener_free(mfc_listener); + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + MU_TEST(mf_classic_dict_test) { Storage* storage = furi_record_open(RECORD_STORAGE); if(storage_common_stat(storage, NFC_APP_MF_CLASSIC_DICT_UNIT_TEST_PATH, NULL) == FSE_OK) { @@ -538,11 +661,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_1k_7b_file_test); MU_RUN_TEST(mf_classic_4k_4b_file_test); MU_RUN_TEST(mf_classic_4k_7b_file_test); - MU_RUN_TEST(mf_classic_reader); + MU_RUN_TEST(mf_classic_reader); MU_RUN_TEST(mf_classic_write); MU_RUN_TEST(mf_classic_value_block); - + MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); nfc_test_free(); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index ef51d98e0b3..deba1bca284 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -34,6 +34,8 @@ static void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) { static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolMfDesfire); + NfcCommand command = NfcCommandContinue; + NfcApp* instance = context; const MfDesfirePollerEvent* mf_desfire_event = event.event_data; @@ -41,10 +43,12 @@ static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent even nfc_device_set_data( instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller)); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); - return NfcCommandStop; + command = NfcCommandStop; + } else if(mf_desfire_event->type == MfDesfirePollerEventTypeReadFailed) { + command = NfcCommandReset; } - return NfcCommandContinue; + return command; } static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) { 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 94b333f5523..f8eacd51a22 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 @@ -11,24 +11,29 @@ void nfc_render_mf_desfire_info( const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1); const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0; - furi_string_cat_printf(str, "\n%lu", bytes_total); + if(data->master_key_settings.is_free_directory_list) { + const uint32_t app_count = simple_array_get_count(data->applications); + uint32_t file_count = 0; + + for(uint32_t i = 0; i < app_count; ++i) { + const MfDesfireApplication* app = simple_array_cget(data->applications, i); + if(app->key_settings.is_free_directory_list) { + file_count += simple_array_get_count(app->file_ids); + } + } - if(data->version.sw_storage & 1) { - furi_string_push_back(str, '+'); + furi_string_cat_printf(str, "\n%lu Application%s", app_count, app_count != 1 ? "s" : ""); + furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + } else { + furi_string_cat_printf(str, "\nAuth required to read apps!"); } - furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free); - - const uint32_t app_count = simple_array_get_count(data->applications); - uint32_t file_count = 0; + furi_string_cat_printf(str, "\n%lu", bytes_total); - for(uint32_t i = 0; i < app_count; ++i) { - const MfDesfireApplication* app = simple_array_cget(data->applications, i); - file_count += simple_array_get_count(app->file_ids); + if(data->version.sw_storage & 1) { + furi_string_push_back(str, '+'); } - - furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : ""); - furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : ""); + furi_string_cat_printf(str, " bytes, %lu bytes free", bytes_free); if(format_type != NfcProtocolFormatTypeFull) return; @@ -101,17 +106,29 @@ void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriStri } void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) { - furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); - furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); - furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); - furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); - furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id); + furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable); + furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete); + furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list); + furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable); + } else { + furi_string_cat_printf(str, "changeKeyID ??\n"); + furi_string_cat_printf(str, "configChangeable ??\n"); + furi_string_cat_printf(str, "freeCreateDelete ??\n"); + furi_string_cat_printf(str, "freeDirectoryList 0\n"); + furi_string_cat_printf(str, "masterChangeable ??\n"); + } if(data->flags) { furi_string_cat_printf(str, "flags %d\n", data->flags); } - furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + if(data->is_free_directory_list) { + furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys); + } else { + furi_string_cat_printf(str, "maxKeys ??\n"); + } } void nfc_render_mf_desfire_key_version( @@ -123,14 +140,16 @@ void nfc_render_mf_desfire_key_version( void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) { const uint8_t* app_id = data->data; - furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]); + furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[2], app_id[1], app_id[0]); } void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) { nfc_render_mf_desfire_key_settings(&data->key_settings, str); - for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { - nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + if(data->key_settings.is_free_directory_list) { + for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) { + nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str); + } } } @@ -179,13 +198,16 @@ void nfc_render_mf_desfire_file_settings_data( } furi_string_cat_printf(str, "%s %s\n", type, comm); - furi_string_cat_printf( - str, - "r %d w %d rw %d c %d\n", - settings->access_rights >> 12 & 0xF, - settings->access_rights >> 8 & 0xF, - settings->access_rights >> 4 & 0xF, - settings->access_rights & 0xF); + + for(size_t i = 0; i < settings->access_rights_len; i++) { + furi_string_cat_printf( + str, + "r %d w %d rw %d c %d\n", + settings->access_rights[i] >> 12 & 0xF, + settings->access_rights[i] >> 8 & 0xF, + settings->access_rights[i] >> 4 & 0xF, + settings->access_rights[i] & 0xF); + } uint32_t record_count = 1; uint32_t record_size = 0; @@ -217,6 +239,20 @@ void nfc_render_mf_desfire_file_settings_data( break; } + bool is_auth_required = true; + for(size_t i = 0; i < settings->access_rights_len; i++) { + uint8_t read_rights = (settings->access_rights[i] >> 12) & 0x0f; + uint8_t read_write_rights = (settings->access_rights[i] >> 4) & 0x0f; + if((read_rights == 0x0e) || (read_write_rights == 0x0e)) { + is_auth_required = false; + break; + } + } + if(is_auth_required) { + furi_string_cat_printf(str, "Auth required to read file data\n"); + return; + } + if(simple_array_get_count(data->data) == 0) { return; } diff --git a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c index 76834e3f4f7..3e111c723cd 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_desfire_more_info.c @@ -35,7 +35,7 @@ void nfc_scene_mf_desfire_more_info_on_enter(void* context) { for(uint32_t i = 0; i < simple_array_get_count(data->application_ids); ++i) { const MfDesfireApplicationId* app_id = simple_array_cget(data->application_ids, i); furi_string_printf( - label, "App %02x%02x%02x", app_id->data[0], app_id->data[1], app_id->data[2]); + label, "App %02x%02x%02x", app_id->data[2], app_id->data[1], app_id->data[0]); submenu_add_item( submenu, furi_string_get_cstr(label), diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.h b/lib/nfc/protocols/mf_classic/mf_classic_poller.h index 19e52570175..518d029d078 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.h @@ -315,6 +315,89 @@ MfClassicError mf_classic_poller_value_cmd( */ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8_t block_num); +/** + * @brief Transmit and receive Iso14443_3a standard frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Iso14443_3a frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * Custom parity bits must be set in the tx_buffer. The rx_buffer will contain + * the received data with the parity bits. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + +/** + * @brief Transmit and receive Mifare Classic encrypted frames with custom parity bits in poller mode. + * + * Must ONLY be used inside the callback function. + * + * The rx_buffer will be filled with any data received as a response to data + * sent from tx_buffer, with a timeout defined by the fwt parameter. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] tx_buffer pointer to the buffer containing the plain data to be transmitted. + * @param[out] rx_buffer pointer to the buffer to be filled with decyphered received data. + * @param[in] fwt frame wait time (response timeout), in carrier cycles. + * @return MfClassicErrorNone on success, an error code on failure. + */ +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 2b01e74eedb..949ef8e66ee 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -465,3 +465,77 @@ MfClassicError mf_classic_poller_value_transfer(MfClassicPoller* instance, uint8 return ret; } + +MfClassicError mf_classic_poller_send_standard_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = + iso14443_3a_poller_txrx(instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_custom_parity_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, tx_buffer, rx_buffer, fwt_fc); + + return mf_classic_process_error(error); +} + +MfClassicError mf_classic_poller_send_encrypted_frame( + MfClassicPoller* instance, + const BitBuffer* tx_buffer, + BitBuffer* rx_buffer, + uint32_t fwt_fc) { + furi_check(instance); + furi_check(tx_buffer); + furi_check(rx_buffer); + + MfClassicError ret = MfClassicErrorNone; + do { + crypto1_encrypt(instance->crypto, NULL, tx_buffer, instance->tx_encrypted_buffer); + + Iso14443_3aError error = iso14443_3a_poller_txrx_custom_parity( + instance->iso14443_3a_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + fwt_fc); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + + crypto1_decrypt(instance->crypto, instance->rx_encrypted_buffer, rx_buffer); + } while(false); + + return ret; +} diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire.h b/lib/nfc/protocols/mf_desfire/mf_desfire.h index 4c307538606..2818e9a24eb 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire.h @@ -21,8 +21,6 @@ extern "C" { #define MF_DESFIRE_CMD_GET_VALUE (0x6C) #define MF_DESFIRE_CMD_READ_RECORDS (0xBB) -#define MF_DESFIRE_FLAG_HAS_NEXT (0xAF) - #define MF_DESFIRE_MAX_KEYS (14) #define MF_DESFIRE_MAX_FILES (32) @@ -71,11 +69,6 @@ typedef struct { typedef uint8_t MfDesfireKeyVersion; -typedef struct { - MfDesfireKeySettings key_settings; - SimpleArray* key_versions; -} MfDesfireKeyConfiguration; - typedef enum { MfDesfireFileTypeStandard = 0, MfDesfireFileTypeBackup = 1, @@ -96,7 +89,8 @@ typedef uint16_t MfDesfireFileAccessRights; typedef struct { MfDesfireFileType type; MfDesfireFileCommunicationSettings comm; - MfDesfireFileAccessRights access_rights; + MfDesfireFileAccessRights access_rights[MF_DESFIRE_MAX_KEYS]; + uint8_t access_rights_len; union { struct { uint32_t size; @@ -136,6 +130,7 @@ typedef enum { MfDesfireErrorNotPresent, MfDesfireErrorProtocol, MfDesfireErrorTimeout, + MfDesfireErrorAuthentication, } MfDesfireError; typedef struct { diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index 646803e75b6..bfbbadadff1 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -1,5 +1,7 @@ #include "mf_desfire_i.h" +#define TAG "MfDesfire" + #define BITS_IN_BYTE (8U) #define MF_DESFIRE_FFF_VERSION_KEY \ @@ -175,59 +177,88 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer }; } MfDesfireFileSettingsLayout; + MfDesfireFileSettings file_settings_temp = {}; do { const size_t data_size = bit_buffer_get_size_bytes(buf); + const uint8_t* data_ptr = bit_buffer_get_data(buf); const size_t min_data_size = sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsData); - const size_t max_data_size = - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue); - - if(data_size < min_data_size) break; - if(data_size <= max_data_size) { - MfDesfireFileSettingsLayout layout; - bit_buffer_write_bytes(buf, &layout, sizeof(MfDesfireFileSettingsLayout)); - - data->type = layout.header.type; - data->comm = layout.header.comm; - data->access_rights = layout.header.access_rights; - - if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { - if(data_size != min_data_size) break; - - data->data.size = layout.data.size; - } else if(data->type == MfDesfireFileTypeValue) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsValue)) - break; - data->value.lo_limit = layout.value.lo_limit; - data->value.hi_limit = layout.value.hi_limit; - data->value.limited_credit_value = layout.value.limited_credit_value; - data->value.limited_credit_enabled = layout.value.limited_credit_enabled; - - } else if( - data->type == MfDesfireFileTypeLinearRecord || - data->type == MfDesfireFileTypeCyclicRecord) { - if(data_size != - sizeof(MfDesfireFileSettingsHeader) + sizeof(MfDesfireFileSettingsRecord)) - break; + if(data_size < min_data_size) { + FURI_LOG_E( + TAG, "File settings size %zu less than minimum %zu", data_size, min_data_size); + break; + } - data->record.size = layout.record.size; - data->record.max = layout.record.max; - data->record.cur = layout.record.cur; + size_t bytes_processed = sizeof(MfDesfireFileSettingsHeader); + MfDesfireFileSettingsLayout layout = {}; + memcpy(&layout.header, data_ptr, sizeof(MfDesfireFileSettingsHeader)); + bool has_additional_access_rights = (layout.header.comm & 0x80) != 0; + + file_settings_temp.type = layout.header.type; + file_settings_temp.comm = layout.header.comm & 0x03; + file_settings_temp.access_rights_len = 1; + file_settings_temp.access_rights[0] = layout.header.access_rights; + + if(file_settings_temp.type == MfDesfireFileTypeStandard || + file_settings_temp.type == MfDesfireFileTypeBackup) { + memcpy( + &layout.data, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsData)); + file_settings_temp.data.size = layout.data.size; + bytes_processed += sizeof(MfDesfireFileSettingsData); + } else if(file_settings_temp.type == MfDesfireFileTypeValue) { + memcpy( + &layout.value, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsValue)); + file_settings_temp.value.lo_limit = layout.value.lo_limit; + file_settings_temp.value.hi_limit = layout.value.hi_limit; + file_settings_temp.value.limited_credit_value = layout.value.limited_credit_value; + file_settings_temp.value.limited_credit_enabled = layout.value.limited_credit_enabled; + bytes_processed += sizeof(MfDesfireFileSettingsValue); + } else if( + file_settings_temp.type == MfDesfireFileTypeLinearRecord || + file_settings_temp.type == MfDesfireFileTypeCyclicRecord) { + memcpy( + &layout.record, + &data_ptr[sizeof(MfDesfireFileSettingsHeader)], + sizeof(MfDesfireFileSettingsRecord)); + file_settings_temp.record.size = layout.record.size; + file_settings_temp.record.max = layout.record.max; + file_settings_temp.record.cur = layout.record.cur; + bytes_processed += sizeof(MfDesfireFileSettingsRecord); + } else { + FURI_LOG_W(TAG, "Unknown file type: %02x", file_settings_temp.type); + break; + } - } else { + if(has_additional_access_rights) { + uint8_t additional_access_rights_len = bit_buffer_get_byte(buf, bytes_processed); + FURI_LOG_D(TAG, "Has additional rights: %d", additional_access_rights_len); + if(data_size != bytes_processed + + additional_access_rights_len * sizeof(MfDesfireFileAccessRights) + + 1) { + FURI_LOG_W(TAG, "Unexpected command length: %zu", data_size); + for(size_t i = 0; i < bit_buffer_get_size_bytes(buf); i++) { + printf("%02X ", bit_buffer_get_byte(buf, i)); + } + printf("\r\n"); break; } - } else { - // TODO FL-3750: process HID Desfire command response here - // Set default fields for now - data->type = 0; - data->comm = 0; - data->access_rights = 0; - data->data.size = 0; + if(additional_access_rights_len > + MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights)) + break; + + memcpy( + &file_settings_temp.access_rights[1], + &data_ptr[bytes_processed], + additional_access_rights_len * sizeof(MfDesfireFileAccessRights)); + file_settings_temp.access_rights_len += additional_access_rights_len; } + *data = file_settings_temp; parsed = true; } while(false); @@ -392,18 +423,19 @@ bool mf_desfire_file_settings_load( break; furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_ACCESS_RIGHTS_KEY); + uint32_t access_rights_len = 0; + if(!flipper_format_get_value_count(ff, furi_string_get_cstr(key), &access_rights_len)) + break; + if((access_rights_len == 0) || ((access_rights_len % 2) != 0)) break; if(!flipper_format_read_hex( - ff, - furi_string_get_cstr(key), - (uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + ff, furi_string_get_cstr(key), (uint8_t*)&data->access_rights, access_rights_len)) break; + data->access_rights_len = access_rights_len / sizeof(MfDesfireFileAccessRights); if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_SIZE_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->data.size, 1)) break; - } else if(data->type == MfDesfireFileTypeValue) { furi_string_printf(key, "%s %s", prefix, MF_DESFIRE_FFF_FILE_HI_LIMIT_KEY); if(!flipper_format_read_uint32(ff, furi_string_get_cstr(key), &data->value.hi_limit, 1)) @@ -641,8 +673,8 @@ bool mf_desfire_file_settings_save( if(!flipper_format_write_hex( ff, furi_string_get_cstr(key), - (const uint8_t*)&data->access_rights, - sizeof(MfDesfireFileAccessRights))) + (const uint8_t*)data->access_rights, + data->access_rights_len * sizeof(MfDesfireFileAccessRights))) break; if(data->type == MfDesfireFileTypeStandard || data->type == MfDesfireFileTypeBackup) { @@ -737,8 +769,11 @@ bool mf_desfire_application_save( if(i != key_version_count) break; const uint32_t file_count = simple_array_get_count(data->file_ids); - if(!mf_desfire_file_ids_save(simple_array_get_data(data->file_ids), file_count, prefix, ff)) - break; + if(file_count > 0) { + if(!mf_desfire_file_ids_save( + simple_array_get_data(data->file_ids), file_count, prefix, ff)) + break; + } for(i = 0; i < file_count; ++i) { const MfDesfireFileId* file_id = simple_array_cget(data->file_ids, i); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h index 05381096d16..21250baace3 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.h @@ -5,6 +5,52 @@ #define MF_DESFIRE_FFF_PICC_PREFIX "PICC" #define MF_DESFIRE_FFF_APP_PREFIX "Application" +// Successful operation +#define MF_DESFIRE_STATUS_OPERATION_OK (0x00) +// No changes done to backup files, CommitTransaction / AbortTransaction not necessary +#define MF_DESFIRE_STATUS_NO_CHANGES (0x0C) +// Insufficient NV-Memory to complete command +#define MF_DESFIRE_STATUS_OUT_OF_EEPROM_ERROR (0x0E) +// Command code not supported +#define MF_DESFIRE_STATUS_ILLEGAL_COMMAND_CODE (0x1C) +// CRC or MAC does not match data Padding bytes not valid +#define MF_DESFIRE_STATUS_INTEGRITY_ERROR (0x1E) +// Invalid key number specified +#define MF_DESFIRE_STATUS_NO_SUCH_KEY (0x40) +// Length of command string invalid +#define MF_DESFIRE_STATUS_LENGTH_ERROR (0x7E) +// Current configuration / status does not allow the requested command +#define MF_DESFIRE_STATUS_PERMISSION_DENIED (0x9D) +// Value of the parameter(s) invalid +#define MF_DESFIRE_STATUS_PARAMETER_ERROR (0x9E) +// Requested AID not present on PICC +#define MF_DESFIRE_STATUS_APPLICATION_NOT_FOUND (0xA0) +// Unrecoverable error within application, application will be disabled +#define MF_DESFIRE_STATUS_APPL_INTEGRITY_ERROR (0xA1) +// Current authentication status does not allow the requested command +#define MF_DESFIRE_STATUS_AUTHENTICATION_ERROR (0xAE) +// Additional data frame is expected to be sent +#define MF_DESFIRE_STATUS_ADDITIONAL_FRAME (0xAF) +// Attempt to read/write data from/to beyond the file's/record's limits +// Attempt to exceed the limits of a value file. +#define MF_DESFIRE_STATUS_BOUNDARY_ERROR (0xBE) +// Unrecoverable error within PICC, PICC will be disabled +#define MF_DESFIRE_STATUS_PICC_INTEGRITY_ERROR (0xC1) +// Previous Command was not fully completed. Not all Frames were requested or provided by the PCD +#define MF_DESFIRE_STATUS_COMMAND_ABORTED (0xCA) +// PICC was disabled by an unrecoverable error +#define MF_DESFIRE_STATUS_PICC_DISABLED_ERROR (0xCD) +// Number of Applications limited to 28, no additional CreateApplication possible +#define MF_DESFIRE_STATUS_COUNT_ERROR (0xCE) +// Creation of file/application failed because file/application with same number already exists +#define MF_DESFIRE_STATUS_DUBLICATE_ERROR (0xDE) +// Could not complete NV-write operation due to loss of power, internal backup/rollback mechanism activated +#define MF_DESFIRE_STATUS_EEPROM_ERROR (0xEE) +// Specified file number does not exist +#define MF_DESFIRE_STATUS_FILE_NOT_FOUND (0xF0) +// Unrecoverable error within file, file will be disabled +#define MF_DESFIRE_STATUS_FILE_INTEGRITY_ERROR (0xF1) + // SimpleArray configurations extern const SimpleArrayConfig mf_desfire_key_version_array_config; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c index c9d8bbab603..fa8a7ae9b90 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller.c @@ -75,17 +75,23 @@ static NfcCommand mf_desfire_poller_handler_read_version(MfDesfirePoller* instan } static NfcCommand mf_desfire_poller_handler_read_free_memory(MfDesfirePoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->error = mf_desfire_poller_read_free_memory(instance, &instance->data->free_memory); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read free memory success"); instance->state = MfDesfirePollerStateReadMasterKeySettings; + } else if(instance->error == MfDesfireErrorNotPresent) { + FURI_LOG_D(TAG, "Read free memoty is unsupported"); + instance->state = MfDesfirePollerStateReadMasterKeySettings; + command = NfcCommandReset; } else { FURI_LOG_E(TAG, "Failed to read free memory"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); instance->state = MfDesfirePollerStateReadFailed; } - return NfcCommandContinue; + return command; } static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePoller* instance) { @@ -94,6 +100,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_settings(MfDesfirePo if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key settings success"); instance->state = MfDesfirePollerStateReadMasterKeyVersion; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + instance->data->master_key_settings.is_free_directory_list = false; + instance->data->master_key_settings.max_keys = 1; + instance->state = MfDesfirePollerStateReadMasterKeyVersion; } else { FURI_LOG_E(TAG, "Failed to read master key settings"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -110,7 +121,11 @@ static NfcCommand mf_desfire_poller_handler_read_master_key_version(MfDesfirePol instance->data->master_key_settings.max_keys); if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read master key version success"); - instance->state = MfDesfirePollerStateReadApplicationIds; + if(instance->data->master_key_settings.is_free_directory_list) { + instance->state = MfDesfirePollerStateReadApplicationIds; + } else { + instance->state = MfDesfirePollerStateReadSuccess; + } } else { FURI_LOG_E(TAG, "Failed to read master key version"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); @@ -126,6 +141,9 @@ static NfcCommand mf_desfire_poller_handler_read_application_ids(MfDesfirePoller if(instance->error == MfDesfireErrorNone) { FURI_LOG_D(TAG, "Read application ids success"); instance->state = MfDesfirePollerStateReadApplications; + } else if(instance->error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Read application ids impossible without authentication"); + instance->state = MfDesfirePollerStateReadSuccess; } else { FURI_LOG_E(TAG, "Failed to read application ids"); iso14443_4a_poller_halt(instance->iso14443_4a_poller); 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 790f1171573..2b86318491f 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -19,6 +19,17 @@ MfDesfireError mf_desfire_process_error(Iso14443_4aError error) { } } +MfDesfireError mf_desfire_process_status_code(uint8_t status_code) { + switch(status_code) { + case MF_DESFIRE_STATUS_OPERATION_OK: + return MfDesfireErrorNone; + case MF_DESFIRE_STATUS_AUTHENTICATION_ERROR: + return MfDesfireErrorAuthentication; + default: + return MfDesfireErrorProtocol; + } +} + MfDesfireError mf_desfire_send_chunks( MfDesfirePoller* instance, const BitBuffer* tx_buffer, @@ -42,7 +53,7 @@ MfDesfireError mf_desfire_send_chunks( } bit_buffer_reset(instance->tx_buffer); - bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_FLAG_HAS_NEXT); + bit_buffer_append_byte(instance->tx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME); if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); @@ -50,7 +61,8 @@ MfDesfireError mf_desfire_send_chunks( bit_buffer_reset(rx_buffer); } - while(bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_FLAG_HAS_NEXT)) { + while( + bit_buffer_starts_with_byte(instance->rx_buffer, MF_DESFIRE_STATUS_ADDITIONAL_FRAME)) { Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( instance->iso14443_4a_poller, instance->tx_buffer, instance->rx_buffer); @@ -71,6 +83,11 @@ MfDesfireError mf_desfire_send_chunks( } } while(false); + if(error == MfDesfireErrorNone) { + uint8_t err_code = bit_buffer_get_byte(instance->rx_buffer, 0); + error = mf_desfire_process_status_code(err_code); + } + return error; } @@ -110,7 +127,7 @@ MfDesfireError if(error != MfDesfireErrorNone) break; if(!mf_desfire_free_memory_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; + error = MfDesfireErrorNotPresent; } } while(false); @@ -414,13 +431,25 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( simple_array_init(data, file_id_count); } - for(uint32_t i = 0; i < file_id_count; ++i) { + for(size_t i = 0; i < file_id_count; ++i) { const MfDesfireFileId file_id = *(const MfDesfireFileId*)simple_array_cget(file_ids, i); const MfDesfireFileSettings* file_settings_cur = simple_array_cget(file_settings, i); const MfDesfireFileType file_type = file_settings_cur->type; MfDesfireFileData* file_data = simple_array_get(data, i); + bool can_read_data = false; + for(size_t j = 0; j < file_settings_cur->access_rights_len; j++) { + uint8_t read_access = (file_settings_cur->access_rights[j] >> 12) & 0x0f; + uint8_t read_write_access = (file_settings_cur->access_rights[j] >> 4) & 0x0f; + can_read_data = (read_access == 0x0e) || (read_write_access == 0x0e); + if(can_read_data) break; + } + if(!can_read_data) { + FURI_LOG_D(TAG, "Can't read file %zu data without authentication", i); + continue; + } + if(file_type == MfDesfireFileTypeStandard || file_type == MfDesfireFileTypeBackup) { error = mf_desfire_poller_read_file_data( instance, file_id, 0, file_settings_cur->data.size, file_data); @@ -432,8 +461,6 @@ MfDesfireError mf_desfire_poller_read_file_data_multi( error = mf_desfire_poller_read_file_records( instance, file_id, 0, file_settings_cur->data.size, file_data); } - - if(error != MfDesfireErrorNone) break; } return error; @@ -448,22 +475,36 @@ MfDesfireError do { error = mf_desfire_poller_read_key_settings(instance, &data->key_settings); + if(error == MfDesfireErrorAuthentication) { + FURI_LOG_D(TAG, "Auth is required to read master key settings and app ids"); + data->key_settings.is_free_directory_list = false; + error = MfDesfireErrorNone; + break; + } if(error != MfDesfireErrorNone) break; error = mf_desfire_poller_read_key_versions( instance, data->key_versions, data->key_settings.max_keys); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read key version: %d", error); + break; + } error = mf_desfire_poller_read_file_ids(instance, data->file_ids); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file ids: %d", error); + break; + } error = mf_desfire_poller_read_file_settings_multi( instance, data->file_ids, data->file_settings); - if(error != MfDesfireErrorNone) break; + if(error != MfDesfireErrorNone) { + FURI_LOG_E(TAG, "Failed to read file settings: %d", error); + break; + } error = mf_desfire_poller_read_file_data_multi( instance, data->file_ids, data->file_settings, data->file_data); - if(error != MfDesfireErrorNone) break; } while(false); @@ -484,11 +525,13 @@ MfDesfireError mf_desfire_poller_read_applications( simple_array_init(data, app_id_count); } - for(uint32_t i = 0; i < app_id_count; ++i) { + for(size_t i = 0; i < app_id_count; ++i) { do { + FURI_LOG_D(TAG, "Selecting app %zu", i); error = mf_desfire_poller_select_application(instance, simple_array_cget(app_ids, i)); if(error != MfDesfireErrorNone) break; + FURI_LOG_D(TAG, "Reading app %zu", i); MfDesfireApplication* current_app = simple_array_get(data, i); error = mf_desfire_poller_read_application(instance, current_app); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h index 1c80af36fb6..19e38bebbc7 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.h @@ -47,6 +47,8 @@ struct MfDesfirePoller { MfDesfireError mf_desfire_process_error(Iso14443_4aError error); +MfDesfireError mf_desfire_process_status_code(uint8_t status_code); + const MfDesfireData* mf_desfire_poller_get_data(MfDesfirePoller* instance); #ifdef __cplusplus diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6e597e7d05b..703bcc50cc1 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 09b8b9ee821..c0edf9ffa51 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.7,, +Version,+,60.8,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -2435,6 +2435,10 @@ Function,+,mf_classic_poller_get_nt,MfClassicError,"MfClassicPoller*, uint8_t, M Function,+,mf_classic_poller_get_nt_nested,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicKeyType, MfClassicNt*" Function,+,mf_classic_poller_halt,MfClassicError,MfClassicPoller* Function,+,mf_classic_poller_read_block,MfClassicError,"MfClassicPoller*, uint8_t, MfClassicBlock*" +Function,+,mf_classic_poller_send_custom_parity_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_encrypted_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,mf_classic_poller_send_standard_frame,MfClassicError,"MfClassicPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,mf_classic_poller_sync_auth,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, MfClassicAuthContext*" Function,+,mf_classic_poller_sync_change_value,MfClassicError,"Nfc*, uint8_t, MfClassicKey*, MfClassicKeyType, int32_t, int32_t*" Function,+,mf_classic_poller_sync_collect_nt,MfClassicError,"Nfc*, uint8_t, MfClassicKeyType, MfClassicNt*" From 70f93a48f513478e77df3b8bb0af6e472ccfe9f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Thu, 18 Apr 2024 00:17:40 +0900 Subject: [PATCH 13/22] [FL-2969] FuriHal: add ADC API (#3583) * Examples: remove unused context * FuriHal: add simple ADC API * Examples: add ADC example app * FuriHal: add extended configuration options for ADC API * FuriHal: add ADC clock configuration, fix calibration routine for single ended mode, new optimized parameters, documentation. * FuriHal: add FuriHalAdcChannelTEMPSENSOR sampling time note * FuriHal: update FuriHalAdcChannelVBAT description. * FuriHal: use insomnia while ADC is acquired. * Examples: cleanup example_adc a little bit --- .../examples/example_adc/application.fam | 9 + .../examples/example_adc/example_adc.c | 176 +++++++++++ .../example_custom_font/example_custom_font.c | 2 +- .../examples/example_images/example_images.c | 2 +- targets/f18/api_symbols.csv | 13 +- targets/f18/furi_hal/furi_hal.c | 1 + targets/f18/furi_hal/furi_hal_resources.c | 168 +++++++++-- targets/f18/furi_hal/furi_hal_resources.h | 5 +- targets/f7/api_symbols.csv | 13 +- targets/f7/furi_hal/furi_hal.c | 1 + targets/f7/furi_hal/furi_hal_adc.c | 281 ++++++++++++++++++ targets/f7/furi_hal/furi_hal_clock.c | 1 + targets/f7/furi_hal/furi_hal_resources.c | 90 +++++- targets/f7/furi_hal/furi_hal_resources.h | 14 +- targets/furi_hal_include/furi_hal.h | 1 + targets/furi_hal_include/furi_hal_adc.h | 229 ++++++++++++++ 16 files changed, 949 insertions(+), 57 deletions(-) create mode 100644 applications/examples/example_adc/application.fam create mode 100644 applications/examples/example_adc/example_adc.c create mode 100644 targets/f7/furi_hal/furi_hal_adc.c create mode 100644 targets/furi_hal_include/furi_hal_adc.h diff --git a/applications/examples/example_adc/application.fam b/applications/examples/example_adc/application.fam new file mode 100644 index 00000000000..68aa2ac4d42 --- /dev/null +++ b/applications/examples/example_adc/application.fam @@ -0,0 +1,9 @@ +App( + appid="example_adc", + name="Example: ADC", + apptype=FlipperAppType.EXTERNAL, + entry_point="example_adc_main", + requires=["gui"], + stack_size=1 * 1024, + fap_category="Examples", +) diff --git a/applications/examples/example_adc/example_adc.c b/applications/examples/example_adc/example_adc.c new file mode 100644 index 00000000000..53f8d5c12d4 --- /dev/null +++ b/applications/examples/example_adc/example_adc.c @@ -0,0 +1,176 @@ +/** + * @file example_adc.c + * @brief ADC example. + */ +#include +#include + +#include +#include +#include + +const uint8_t font[] = + "`\2\3\2\3\4\1\2\4\5\11\0\376\6\376\7\377\1M\2\263\3\370 \6\315\364\371\6!\12\315" + "\364\201\260\35\312Q\0\42\11\315tJI\316\13\0#\14\315\264\223dP*\203R'\1$\15\315\264" + "\262A\311\266D\251l\71\0%\15\315\264\7%\61)J\42\345 \0&\14\315\264\263$\13\223\266$" + "\7\1'\10\315\364\201\60\347\10(\10\315\364\32[\313\0)\11\315\64\322b[\35\2*\12\315\264\263" + "(\222j\71\15+\11\315\364I\331\226\23\1,\10\315\364\271\205Y\10-\10\315\364\31t\26\0.\10" + "\315\364\71\346(\0/\14\315\364\221\60\13\263\60\13C\0\60\13\315\264\245Jb)E:\12\61\12\315" + "\364\201Ll\333A\0\62\12\315\264\245bV\33r\20\63\13\315\264\245Z\232D\221\216\2\64\14\315\364" + "\201LJ\242!\313v\20\65\14\315t\207$\134\223(\322Q\0\66\13\315\264\245p\252D\221\216\2\67" + "\12\315t\207\60+\326a\0\70\13\315\264\245\222T\211\42\35\5\71\13\315\264\245J\24\215\221\216\2:" + "\11\315\364i\71!G\1;\12\315\364I\71!\314B\0<\11\315\364\341\254Z\7\1=\12\315\364)" + "C<\344$\0>\11\315\364\301\264V\207\1\77\12\315\264\245Z\35\312a\0@\14\315\264\245J\242$" + "J\272\203\0A\15\315\264\245J\224\14I\224D\71\10B\13\315t\247\312T\211\222\35\5C\12\315\264" + "\245JX\212t\24D\15\315t\247J\224DI\224\354(\0E\14\315t\207$\234\302p\310A\0F" + "\12\315t\207$\234\302:\1G\14\315\264\245J\230(Q\244\243\0H\17\315t\243$J\206$J\242" + "$\312A\0I\11\315\264\267\260m\7\1J\12\315\364\221\260%\212t\24K\14\315t\243\244\244iI" + "T\7\1L\11\315t\303\216C\16\2M\17\315t\243dH\206$J\242$\312A\0N\16\315t\243" + "D\251(Q\22%Q\16\2O\15\315\264\245J\224DI\24\351(\0P\12\315t\247J\224LaN" + "Q\15\315\264\245J\224DI\42\251\61\0R\14\315t\247J\224L\225(\7\1S\13\315\264\245\222\232" + "D\221\216\2T\10\315\264\267\260;\12U\16\315t\243$J\242$J\242HG\1V\15\315t\243$" + "J\242$Jj\71\14W\17\315t\243$J\242dH\206$\312A\0X\15\315t\243$\212\64\251\22" + "\345 \0Y\13\315t\243$Jja\35\6Z\12\315t\207\60k\34r\20[\10\315\264\264\260G\31" + "\134\12\315\264\303\64L\303\64\14]\10\315t\304\276\351\0^\11\315\364\201,\311\271\1_\7\315\364y" + "\35\4`\10\315t\322\234'\0a\14\315\364IK\224$R\222\203\0b\13\315t\303p\252D\311\216" + "\2c\12\315\364IR%\335A\0d\14\315\364\221\60Z\242$\212v\20e\12\315\364I\322\220\244;" + "\10f\12\315\364\221,\333\302:\12g\14\315\364IK\224D\321\30I\0h\14\315t\303p\252DI" + "\224\203\0i\12\315\364\201\34\21k;\10j\12\315\364\201\34\21\273e\0k\13\315t\303J\244%Q" + "\35\4l\10\315\264\305n;\10m\14\315\364)CRQ\22\245\216\1n\13\315\364)%\245\224D\71" + "\10o\12\315\364IR%\212t\24p\13\315\364)S%J\246\60\4q\13\315\364IK\224D\321X" + "\1r\11\315\364)%\245\230\23s\12\315\364I\313\232\354(\0t\13\315\364\201\60\333\302\64\7\1u" + "\15\315\364)Q\22%\211\224\344 \0v\13\315\364)Q\22%\265\34\6w\13\315\364)\25%Q\272" + "\203\0x\12\315\364)Q\244Iu\20y\15\315\364)Q\22%Q\64F\22\0z\12\315\364)CV" + "\33r\20{\12\315\364\212\265\64\254&\0|\7\315\264\302~\7}\12\315t\322\260\232\205\265\14~\11" + "\315\364II;\13\0\177\6\315\364\371\6\0\0\0\4\377\377\0"; + +#define FONT_HEIGHT (8u) + +typedef float (*ValueConverter)(FuriHalAdcHandle* handle, uint16_t value); + +typedef struct { + const GpioPinRecord* pin; + float value; + ValueConverter converter; + const char* suffix; +} DataItem; + +typedef struct { + size_t count; + DataItem* items; +} Data; + +const GpioPinRecord item_vref = {.name = "VREF", .channel = FuriHalAdcChannelVREFINT}; +const GpioPinRecord item_temp = {.name = "TEMP", .channel = FuriHalAdcChannelTEMPSENSOR}; +const GpioPinRecord item_vbat = {.name = "VBAT", .channel = FuriHalAdcChannelVBAT}; + +static void app_draw_callback(Canvas* canvas, void* ctx) { + furi_assert(ctx); + Data* data = ctx; + + canvas_set_custom_u8g2_font(canvas, font); + char buffer[64]; + int32_t x = 0, y = FONT_HEIGHT; + for(size_t i = 0; i < data->count; i++) { + if(i == canvas_height(canvas) / FONT_HEIGHT) { + x = 64; + y = FONT_HEIGHT; + } + + snprintf( + buffer, + sizeof(buffer), + "%4s: %4.0f%s\n", + data->items[i].pin->name, + (double)data->items[i].value, + data->items[i].suffix); + canvas_draw_str(canvas, x, y, buffer); + y += FONT_HEIGHT; + } +} + +static void app_input_callback(InputEvent* input_event, void* ctx) { + furi_assert(ctx); + FuriMessageQueue* event_queue = ctx; + furi_message_queue_put(event_queue, input_event, FuriWaitForever); +} + +int32_t example_adc_main(void* p) { + UNUSED(p); + + // Data + Data data = {}; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + data.count++; + } + } + data.count += 3; // Special channels + data.items = malloc(data.count * sizeof(DataItem)); + size_t item_pos = 0; + for(size_t i = 0; i < gpio_pins_count; i++) { + if(gpio_pins[i].channel != FuriHalAdcChannelNone) { + furi_hal_gpio_init(gpio_pins[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + data.items[item_pos].pin = &gpio_pins[i]; + data.items[item_pos].converter = furi_hal_adc_convert_to_voltage; + data.items[item_pos].suffix = "mV"; + item_pos++; + } + } + data.items[item_pos].pin = &item_vref; + data.items[item_pos].converter = furi_hal_adc_convert_vref; + data.items[item_pos].suffix = "mV"; + item_pos++; + data.items[item_pos].pin = &item_temp; + data.items[item_pos].converter = furi_hal_adc_convert_temp; + data.items[item_pos].suffix = "C"; + item_pos++; + data.items[item_pos].pin = &item_vbat; + data.items[item_pos].converter = furi_hal_adc_convert_vbat; + data.items[item_pos].suffix = "mV"; + item_pos++; + furi_assert(item_pos == data.count); + + // Alloc message queue + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + // Configure view port + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, app_draw_callback, &data); + view_port_input_callback_set(view_port, app_input_callback, event_queue); + + // Register view port in GUI + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + // Initialize ADC + FuriHalAdcHandle* adc_handle = furi_hal_adc_acquire(); + furi_hal_adc_configure(adc_handle); + + // Process events + InputEvent event; + bool running = true; + while(running) { + if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) { + if(event.type == InputTypePress && event.key == InputKeyBack) { + running = false; + } + } else { + for(size_t i = 0; i < data.count; i++) { + data.items[i].value = data.items[i].converter( + adc_handle, furi_hal_adc_read(adc_handle, data.items[i].pin->channel)); + } + view_port_update(view_port); + } + } + + furi_hal_adc_release(adc_handle); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_record_close(RECORD_GUI); + free(data.items); + + return 0; +} diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 405db46e30a..a175838e315 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -94,7 +94,7 @@ int32_t example_custom_font_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index ba9a354f608..60269a81f75 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -39,7 +39,7 @@ int32_t example_images_main(void* p) { // Configure view port ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, app_draw_callback, view_port); + view_port_draw_callback_set(view_port, app_draw_callback, NULL); view_port_input_callback_set(view_port, app_input_callback, event_queue); // Register view port in GUI diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 703bcc50cc1..43500aa1c40 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -199,6 +199,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -1086,6 +1087,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f18/furi_hal/furi_hal.c b/targets/f18/furi_hal/furi_hal.c index 6e0a254c138..6cfc939b8ce 100644 --- a/targets/f18/furi_hal/furi_hal.c +++ b/targets/f18/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f18/furi_hal/furi_hal_resources.c b/targets/f18/furi_hal/furi_hal_resources.c index 9935d4124cf..45ca3e6c494 100644 --- a/targets/f18/furi_hal/furi_hal_resources.c +++ b/targets/f18/furi_hal/furi_hal_resources.c @@ -68,49 +68,161 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 // 2nd column // 5V: 19 - {.pin = &gpio_ext_pc5, .name = "PC5", .number = 20, .debug = false}, - {.pin = &gpio_ext_pc4, .name = "PC4", .number = 21, .debug = false}, - {.pin = &gpio_ext_pa5, .name = "PA5", .number = 22, .debug = false}, - {.pin = &gpio_ext_pb9, .name = "PB9", .number = 23, .debug = false}, - {.pin = &gpio_ext_pa0, .name = "PA0", .number = 24, .debug = false}, - {.pin = &gpio_ext_pa1, .name = "PA1", .number = 25, .debug = false}, + {.pin = &gpio_ext_pc5, + .name = "PC5", + .channel = FuriHalAdcChannel14, + .number = 20, + .debug = false}, + {.pin = &gpio_ext_pc4, + .name = "PC4", + .channel = FuriHalAdcChannel13, + .number = 21, + .debug = false}, + {.pin = &gpio_ext_pa5, + .name = "PA5", + .channel = FuriHalAdcChannel10, + .number = 22, + .debug = false}, + {.pin = &gpio_ext_pb9, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 23, + .debug = false}, + {.pin = &gpio_ext_pa0, + .name = "PA0", + .channel = FuriHalAdcChannel5, + .number = 24, + .debug = false}, + {.pin = &gpio_ext_pa1, + .name = "PA1", + .channel = FuriHalAdcChannel6, + .number = 25, + .debug = false}, // KEY: 26 // Space // 3v3: 27 - {.pin = &gpio_ext_pa15, .name = "PA15", .number = 28, .debug = false}, + {.pin = &gpio_ext_pa15, + .name = "PA15", + .channel = FuriHalAdcChannelNone, + .number = 28, + .debug = false}, // GND: 29 - {.pin = &gpio_ext_pe4, .name = "PE4", .number = 30, .debug = false}, - {.pin = &gpio_ext_pa2, .name = "PA2", .number = 31, .debug = false}, - {.pin = &gpio_ext_pb4, .name = "PB4", .number = 32, .debug = false}, - {.pin = &gpio_ext_pb5, .name = "PB5", .number = 33, .debug = false}, - {.pin = &gpio_ext_pd0, .name = "PD0", .number = 34, .debug = false}, - {.pin = &gpio_ext_pb13, .name = "PB13", .number = 35, .debug = false}, + {.pin = &gpio_ext_pe4, + .name = "PE4", + .channel = FuriHalAdcChannelNone, + .number = 30, + .debug = false}, + {.pin = &gpio_ext_pa2, + .name = "PA2", + .channel = FuriHalAdcChannel7, + .number = 31, + .debug = false}, + {.pin = &gpio_ext_pb4, + .name = "PB4", + .channel = FuriHalAdcChannelNone, + .number = 32, + .debug = false}, + {.pin = &gpio_ext_pb5, + .name = "PB5", + .channel = FuriHalAdcChannelNone, + .number = 33, + .debug = false}, + {.pin = &gpio_ext_pd0, + .name = "PD0", + .channel = FuriHalAdcChannelNone, + .number = 34, + .debug = false}, + {.pin = &gpio_ext_pb13, + .name = "PB13", + .channel = FuriHalAdcChannelNone, + .number = 35, + .debug = false}, // GND: 36 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_usart_rx, .name = "PB7", .number = 0, .debug = true}, - {.pin = &gpio_speaker, .name = "PB8", .number = 0, .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f18/furi_hal/furi_hal_resources.h b/targets/f18/furi_hal/furi_hal_resources.h index 3d45ad885e2..8f6173eb9dc 100644 --- a/targets/f18/furi_hal/furi_hal_resources.h +++ b/targets/f18/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c0edf9ffa51..dbaadc4a15a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,60.8,, +Version,+,61.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -268,6 +268,7 @@ Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, +Header,+,targets/furi_hal_include/furi_hal_adc.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, @@ -1183,6 +1184,16 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, +Function,+,furi_hal_adc_acquire,FuriHalAdcHandle*, +Function,+,furi_hal_adc_configure,void,FuriHalAdcHandle* +Function,+,furi_hal_adc_configure_ex,void,"FuriHalAdcHandle*, FuriHalAdcScale, FuriHalAdcClock, FuriHalAdcOversample, FuriHalAdcSamplingTime" +Function,+,furi_hal_adc_convert_temp,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_to_voltage,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vbat,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_convert_vref,float,"FuriHalAdcHandle*, uint16_t" +Function,+,furi_hal_adc_init,void, +Function,+,furi_hal_adc_read,uint16_t,"FuriHalAdcHandle*, FuriHalAdcChannel" +Function,+,furi_hal_adc_release,void,FuriHalAdcHandle* Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, diff --git a/targets/f7/furi_hal/furi_hal.c b/targets/f7/furi_hal/furi_hal.c index 1fe8b241b81..7f3959bc37a 100644 --- a/targets/f7/furi_hal/furi_hal.c +++ b/targets/f7/furi_hal/furi_hal.c @@ -31,6 +31,7 @@ void furi_hal_deinit_early(void) { void furi_hal_init(void) { furi_hal_mpu_init(); + furi_hal_adc_init(); furi_hal_clock_init(); furi_hal_random_init(); furi_hal_serial_control_init(); diff --git a/targets/f7/furi_hal/furi_hal_adc.c b/targets/f7/furi_hal/furi_hal_adc.c new file mode 100644 index 00000000000..7a15f2e54b3 --- /dev/null +++ b/targets/f7/furi_hal/furi_hal_adc.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include + +#include + +#include +#include + +struct FuriHalAdcHandle { + ADC_TypeDef* adc; + FuriMutex* mutex; + uint32_t full_scale; +}; + +static const uint32_t furi_hal_adc_clock[] = { + [FuriHalAdcClockSync16] = LL_ADC_CLOCK_SYNC_PCLK_DIV4, + [FuriHalAdcClockSync32] = LL_ADC_CLOCK_SYNC_PCLK_DIV2, + [FuriHalAdcClockSync64] = LL_ADC_CLOCK_SYNC_PCLK_DIV1, +}; + +static const uint8_t furi_hal_adc_clock_div[] = { + [FuriHalAdcClockSync16] = 4, + [FuriHalAdcClockSync32] = 2, + [FuriHalAdcClockSync64] = 1, +}; + +static const uint32_t furi_hal_adc_oversample_ratio[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_RATIO_2, + [FuriHalAdcOversample4] = LL_ADC_OVS_RATIO_4, + [FuriHalAdcOversample8] = LL_ADC_OVS_RATIO_8, + [FuriHalAdcOversample16] = LL_ADC_OVS_RATIO_16, + [FuriHalAdcOversample32] = LL_ADC_OVS_RATIO_32, + [FuriHalAdcOversample64] = LL_ADC_OVS_RATIO_64, + [FuriHalAdcOversample128] = LL_ADC_OVS_RATIO_128, + [FuriHalAdcOversample256] = LL_ADC_OVS_RATIO_256, +}; + +static const uint32_t furi_hal_adc_oversample_shift[] = { + [FuriHalAdcOversample2] = LL_ADC_OVS_SHIFT_RIGHT_1, + [FuriHalAdcOversample4] = LL_ADC_OVS_SHIFT_RIGHT_2, + [FuriHalAdcOversample8] = LL_ADC_OVS_SHIFT_RIGHT_3, + [FuriHalAdcOversample16] = LL_ADC_OVS_SHIFT_RIGHT_4, + [FuriHalAdcOversample32] = LL_ADC_OVS_SHIFT_RIGHT_5, + [FuriHalAdcOversample64] = LL_ADC_OVS_SHIFT_RIGHT_6, + [FuriHalAdcOversample128] = LL_ADC_OVS_SHIFT_RIGHT_7, + [FuriHalAdcOversample256] = LL_ADC_OVS_SHIFT_RIGHT_8, +}; + +static const uint32_t furi_hal_adc_sampling_time[] = { + [FuriHalAdcSamplingtime2_5] = LL_ADC_SAMPLINGTIME_2CYCLES_5, + [FuriHalAdcSamplingtime6_5] = LL_ADC_SAMPLINGTIME_6CYCLES_5, + [FuriHalAdcSamplingtime12_5] = LL_ADC_SAMPLINGTIME_12CYCLES_5, + [FuriHalAdcSamplingtime24_5] = LL_ADC_SAMPLINGTIME_24CYCLES_5, + [FuriHalAdcSamplingtime47_5] = LL_ADC_SAMPLINGTIME_47CYCLES_5, + [FuriHalAdcSamplingtime92_5] = LL_ADC_SAMPLINGTIME_92CYCLES_5, + [FuriHalAdcSamplingtime247_5] = LL_ADC_SAMPLINGTIME_247CYCLES_5, + [FuriHalAdcSamplingtime640_5] = LL_ADC_SAMPLINGTIME_640CYCLES_5, +}; + +static const uint32_t furi_hal_adc_channel_map[] = { + [FuriHalAdcChannel0] = LL_ADC_CHANNEL_0, + [FuriHalAdcChannel1] = LL_ADC_CHANNEL_1, + [FuriHalAdcChannel2] = LL_ADC_CHANNEL_2, + [FuriHalAdcChannel3] = LL_ADC_CHANNEL_3, + [FuriHalAdcChannel4] = LL_ADC_CHANNEL_4, + [FuriHalAdcChannel5] = LL_ADC_CHANNEL_5, + [FuriHalAdcChannel6] = LL_ADC_CHANNEL_6, + [FuriHalAdcChannel7] = LL_ADC_CHANNEL_7, + [FuriHalAdcChannel8] = LL_ADC_CHANNEL_8, + [FuriHalAdcChannel9] = LL_ADC_CHANNEL_9, + [FuriHalAdcChannel10] = LL_ADC_CHANNEL_10, + [FuriHalAdcChannel11] = LL_ADC_CHANNEL_11, + [FuriHalAdcChannel12] = LL_ADC_CHANNEL_12, + [FuriHalAdcChannel13] = LL_ADC_CHANNEL_13, + [FuriHalAdcChannel14] = LL_ADC_CHANNEL_14, + [FuriHalAdcChannel15] = LL_ADC_CHANNEL_15, + [FuriHalAdcChannel16] = LL_ADC_CHANNEL_16, + [FuriHalAdcChannel17] = LL_ADC_CHANNEL_17, + [FuriHalAdcChannel18] = LL_ADC_CHANNEL_18, + [FuriHalAdcChannelVREFINT] = LL_ADC_CHANNEL_VREFINT, + [FuriHalAdcChannelTEMPSENSOR] = LL_ADC_CHANNEL_TEMPSENSOR, + [FuriHalAdcChannelVBAT] = LL_ADC_CHANNEL_VBAT, +}; + +static FuriHalAdcHandle* furi_hal_adc_handle = NULL; + +void furi_hal_adc_init(void) { + furi_hal_adc_handle = malloc(sizeof(FuriHalAdcHandle)); + furi_hal_adc_handle->adc = ADC1; + furi_hal_adc_handle->mutex = furi_mutex_alloc(FuriMutexTypeNormal); +} + +FuriHalAdcHandle* furi_hal_adc_acquire(void) { + furi_check(furi_mutex_acquire(furi_hal_adc_handle->mutex, FuriWaitForever) == FuriStatusOk); + + furi_hal_power_insomnia_enter(); + + furi_hal_bus_enable(FuriHalBusADC); + + return furi_hal_adc_handle; +} + +void furi_hal_adc_release(FuriHalAdcHandle* handle) { + furi_check(handle); + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + LL_VREFBUF_Disable(); + LL_VREFBUF_EnableHIZ(); + + furi_hal_power_insomnia_exit(); + + furi_check(furi_mutex_release(furi_hal_adc_handle->mutex) == FuriStatusOk); +} + +void furi_hal_adc_configure(FuriHalAdcHandle* handle) { + furi_hal_adc_configure_ex( + handle, + FuriHalAdcScale2048, + FuriHalAdcClockSync64, + FuriHalAdcOversample64, + FuriHalAdcSamplingtime247_5); +} + +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time) { + furi_check(handle); + furi_check(scale == FuriHalAdcScale2048 || scale == FuriHalAdcScale2500); + furi_check(clock <= FuriHalAdcClockSync64); + furi_check(oversample <= FuriHalAdcOversampleNone); + furi_check(sampling_time <= FuriHalAdcSamplingtime640_5); + + FuriHalCortexTimer timer; + + if(furi_hal_bus_is_enabled(FuriHalBusADC)) furi_hal_bus_disable(FuriHalBusADC); + + uint32_t trim_value = 0; + switch(scale) { + case FuriHalAdcScale2048: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE0); + trim_value = LL_VREFBUF_SC0_GetCalibration() & 0x3FU; + handle->full_scale = 2048; + break; + case FuriHalAdcScale2500: + LL_VREFBUF_SetVoltageScaling(LL_VREFBUF_VOLTAGE_SCALE1); + trim_value = LL_VREFBUF_SC1_GetCalibration() & 0x3FU; + handle->full_scale = 2500; + break; + default: + furi_crash(); + } + LL_VREFBUF_SetTrimming(trim_value); + LL_VREFBUF_Enable(); + LL_VREFBUF_DisableHIZ(); + + timer = furi_hal_cortex_timer_get(500000); // 500ms to stabilize VREF + while(!LL_VREFBUF_IsVREFReady()) { + furi_check(!furi_hal_cortex_timer_is_expired(timer), "VREF fail"); + }; + + furi_hal_bus_enable(FuriHalBusADC); + + // ADC Common config + LL_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0}; + ADC_CommonInitStruct.CommonClock = furi_hal_adc_clock[clock]; + furi_check( + LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(handle->adc), &ADC_CommonInitStruct) == + SUCCESS); + LL_ADC_SetCommonPathInternalCh( + __LL_ADC_COMMON_INSTANCE(handle->adc), + LL_ADC_PATH_INTERNAL_VREFINT | LL_ADC_PATH_INTERNAL_TEMPSENSOR | + LL_ADC_PATH_INTERNAL_VBAT); + + // ADC config part 1 + LL_ADC_InitTypeDef ADC_InitStruct = {0}; + ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B; //-V1048 + ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT; + ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE; + furi_check(LL_ADC_Init(handle->adc, &ADC_InitStruct) == SUCCESS); + + // ADC config part 2: groups parameters + LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0}; + ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE; //-V1048 + ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE; + ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE; + ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE; + ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_NONE; + ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN; + furi_check(LL_ADC_REG_Init(handle->adc, &ADC_REG_InitStruct) == SUCCESS); + + // ADC config part 3: sequencer and channels + if(oversample == FuriHalAdcOversampleNone) { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_DISABLE); + } else { + LL_ADC_SetOverSamplingScope(handle->adc, LL_ADC_OVS_GRP_REGULAR_CONTINUED); + LL_ADC_ConfigOverSamplingRatioShift( + handle->adc, + furi_hal_adc_oversample_ratio[oversample], + furi_hal_adc_oversample_shift[oversample]); + } + + for(FuriHalAdcChannel channel = FuriHalAdcChannel0; channel < FuriHalAdcChannelNone; + channel++) { + // 47.5 cycles on 64MHz is first meaningful value for internal sources sampling + LL_ADC_SetChannelSamplingTime( + handle->adc, + furi_hal_adc_channel_map[channel], + furi_hal_adc_sampling_time[sampling_time]); + LL_ADC_SetChannelSingleDiff( + handle->adc, furi_hal_adc_channel_map[channel], LL_ADC_SINGLE_ENDED); + } + + // Disable ADC deep power down (enabled by default after reset state) + LL_ADC_DisableDeepPowerDown(handle->adc); + + // Enable ADC internal voltage regulator + LL_ADC_EnableInternalRegulator(handle->adc); + // Delay for ADC internal voltage regulator stabilization. + timer = furi_hal_cortex_timer_get(LL_ADC_DELAY_INTERNAL_REGUL_STAB_US); + while(!furi_hal_cortex_timer_is_expired(timer)) + ; + + // Run ADC self calibration + LL_ADC_StartCalibration(handle->adc, LL_ADC_SINGLE_ENDED); + // Poll for ADC effectively calibrated + while(LL_ADC_IsCalibrationOnGoing(handle->adc) != 0) + ; + // Delay between ADC end of calibration and ADC enable + size_t end = + DWT->CYCCNT + (LL_ADC_DELAY_CALIB_ENABLE_ADC_CYCLES * furi_hal_adc_clock_div[clock]); + while(DWT->CYCCNT < end) + ; + + // Enable ADC + LL_ADC_ClearFlag_ADRDY(handle->adc); + LL_ADC_Enable(handle->adc); + while(LL_ADC_IsActiveFlag_ADRDY(handle->adc) == 0) + ; +} + +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel) { + furi_check(handle); + furi_check(channel <= FuriHalAdcChannelVBAT); + furi_check(LL_ADC_IsEnabled(handle->adc) == 1); + furi_check(LL_ADC_IsDisableOngoing(handle->adc) == 0); + furi_check(LL_ADC_REG_IsConversionOngoing(handle->adc) == 0); + + LL_ADC_REG_SetSequencerRanks( + handle->adc, LL_ADC_REG_RANK_1, furi_hal_adc_channel_map[channel]); + + LL_ADC_REG_StartConversion(handle->adc); + + while(LL_ADC_IsActiveFlag_EOC(handle->adc) == 0) + ; + uint16_t value = LL_ADC_REG_ReadConversionData12(handle->adc); + + return value; +} + +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_DATA_TO_VOLTAGE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value) { + UNUSED(handle); + return (float)__LL_ADC_CALC_VREFANALOG_VOLTAGE(value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value) { + return (float)__LL_ADC_CALC_TEMPERATURE(handle->full_scale, value, LL_ADC_RESOLUTION_12B); +} + +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value) { + return furi_hal_adc_convert_to_voltage(handle, value) * 3; +} diff --git a/targets/f7/furi_hal/furi_hal_clock.c b/targets/f7/furi_hal/furi_hal_clock.c index ad21031fe68..9184fa7157e 100644 --- a/targets/f7/furi_hal/furi_hal_clock.c +++ b/targets/f7/furi_hal/furi_hal_clock.c @@ -121,6 +121,7 @@ void furi_hal_clock_init(void) { LL_RCC_SetSMPSClockSource(LL_RCC_SMPS_CLKSOURCE_HSI); LL_RCC_SetSMPSPrescaler(LL_RCC_SMPS_DIV_1); LL_RCC_SetRFWKPClockSource(LL_RCC_RFWKP_CLKSOURCE_LSE); + LL_RCC_SetADCClockSource(LL_RCC_ADC_CLKSOURCE_SYSCLK); FURI_LOG_I(TAG, "Init OK"); } diff --git a/targets/f7/furi_hal/furi_hal_resources.c b/targets/f7/furi_hal/furi_hal_resources.c index b98d93769d9..486c24230e1 100644 --- a/targets/f7/furi_hal/furi_hal_resources.c +++ b/targets/f7/furi_hal/furi_hal_resources.c @@ -70,28 +70,88 @@ const GpioPin gpio_usb_dp = {.port = GPIOA, .pin = LL_GPIO_PIN_12}; const GpioPinRecord gpio_pins[] = { // 5V: 1 - {.pin = &gpio_ext_pa7, .name = "PA7", .number = 2, .debug = false}, - {.pin = &gpio_ext_pa6, .name = "PA6", .number = 3, .debug = false}, - {.pin = &gpio_ext_pa4, .name = "PA4", .number = 4, .debug = false}, - {.pin = &gpio_ext_pb3, .name = "PB3", .number = 5, .debug = false}, - {.pin = &gpio_ext_pb2, .name = "PB2", .number = 6, .debug = false}, - {.pin = &gpio_ext_pc3, .name = "PC3", .number = 7, .debug = false}, + {.pin = &gpio_ext_pa7, + .name = "PA7", + .channel = FuriHalAdcChannel12, + .number = 2, + .debug = false}, + {.pin = &gpio_ext_pa6, + .name = "PA6", + .channel = FuriHalAdcChannel11, + .number = 3, + .debug = false}, + {.pin = &gpio_ext_pa4, + .name = "PA4", + .channel = FuriHalAdcChannel9, + .number = 4, + .debug = false}, + {.pin = &gpio_ext_pb3, + .name = "PB3", + .channel = FuriHalAdcChannelNone, + .number = 5, + .debug = false}, + {.pin = &gpio_ext_pb2, + .name = "PB2", + .channel = FuriHalAdcChannelNone, + .number = 6, + .debug = false}, + {.pin = &gpio_ext_pc3, + .name = "PC3", + .channel = FuriHalAdcChannel4, + .number = 7, + .debug = false}, // GND: 8 // Space // 3v3: 9 - {.pin = &gpio_swclk, .name = "PA14", .number = 10, .debug = true}, + {.pin = &gpio_swclk, + .name = "PA14", + .channel = FuriHalAdcChannelNone, + .number = 10, + .debug = true}, // GND: 11 - {.pin = &gpio_swdio, .name = "PA13", .number = 12, .debug = true}, - {.pin = &gpio_usart_tx, .name = "PB6", .number = 13, .debug = true}, - {.pin = &gpio_usart_rx, .name = "PB7", .number = 14, .debug = true}, - {.pin = &gpio_ext_pc1, .name = "PC1", .number = 15, .debug = false}, - {.pin = &gpio_ext_pc0, .name = "PC0", .number = 16, .debug = false}, - {.pin = &gpio_ibutton, .name = "PB14", .number = 17, .debug = true}, + {.pin = &gpio_swdio, + .name = "PA13", + .channel = FuriHalAdcChannelNone, + .number = 12, + .debug = true}, + {.pin = &gpio_usart_tx, + .name = "PB6", + .channel = FuriHalAdcChannelNone, + .number = 13, + .debug = true}, + {.pin = &gpio_usart_rx, + .name = "PB7", + .channel = FuriHalAdcChannelNone, + .number = 14, + .debug = true}, + {.pin = &gpio_ext_pc1, + .name = "PC1", + .channel = FuriHalAdcChannel2, + .number = 15, + .debug = false}, + {.pin = &gpio_ext_pc0, + .name = "PC0", + .channel = FuriHalAdcChannel1, + .number = 16, + .debug = false}, + {.pin = &gpio_ibutton, + .name = "PB14", + .channel = FuriHalAdcChannelNone, + .number = 17, + .debug = true}, // GND: 18 /* Dangerous pins, may damage hardware */ - {.pin = &gpio_speaker, .name = "PB8", .debug = true}, - {.pin = &gpio_infrared_tx, .name = "PB9", .debug = true}, + {.pin = &gpio_speaker, + .name = "PB8", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, + {.pin = &gpio_infrared_tx, + .name = "PB9", + .channel = FuriHalAdcChannelNone, + .number = 0, + .debug = true}, }; const size_t gpio_pins_count = COUNT_OF(gpio_pins); diff --git a/targets/f7/furi_hal/furi_hal_resources.h b/targets/f7/furi_hal/furi_hal_resources.h index bb01b9bcdd7..99768654d95 100644 --- a/targets/f7/furi_hal/furi_hal_resources.h +++ b/targets/f7/furi_hal/furi_hal_resources.h @@ -1,9 +1,7 @@ #pragma once #include - -#include -#include +#include #ifdef __cplusplus extern "C" { @@ -41,6 +39,7 @@ typedef struct { typedef struct { const GpioPin* pin; const char* name; + const FuriHalAdcChannel channel; const uint8_t number; const bool debug; } GpioPinRecord; @@ -220,10 +219,11 @@ void furi_hal_resources_deinit_early(void); void furi_hal_resources_init(void); -/** - * Get a corresponding external connector pin number for a gpio - * @param gpio GpioPin - * @return pin number or -1 if gpio is not on the external connector +/** Get a corresponding external connector pin number for a gpio + * + * @param gpio GpioPin + * + * @return pin number or -1 if gpio is not on the external connector */ int32_t furi_hal_resources_get_ext_pin_number(const GpioPin* gpio); diff --git a/targets/furi_hal_include/furi_hal.h b/targets/furi_hal_include/furi_hal.h index 3eaf06da61c..719df0275b6 100644 --- a/targets/furi_hal_include/furi_hal.h +++ b/targets/furi_hal_include/furi_hal.h @@ -12,6 +12,7 @@ struct STOP_EXTERNING_ME {}; #include #include +#include #include #include #include diff --git a/targets/furi_hal_include/furi_hal_adc.h b/targets/furi_hal_include/furi_hal_adc.h new file mode 100644 index 00000000000..ecbdad2ca32 --- /dev/null +++ b/targets/furi_hal_include/furi_hal_adc.h @@ -0,0 +1,229 @@ +/** + * @file furi_hal_adc.h + * @brief ADC HAL API + * + * For the sake of simplicity this API implements only small subset + * of what ADC is actually capable of. Feel free to visit Reference + * Manual for STM32WB series and implement any other modes by your + * self. + * + * Couple things to keep in mind: + * + * - ADC resolution is 12 bits, but effective number of bits is ~10 at the best + * and further depends on how you use and configure it. + * - Analog domain is fed from SMPS which is quite noisy. + * - Because of that we use internal on-chip voltage reference for ADC. + * - It's capable of producing 2 voltages: 2.5V and 2.048V. This is the scale + * for your signal. + * - Only single ended mode is available. But you can implement differential one + * by using low level controls directly. + * - No DMA or interrupt API available at this point. But can be implemented + * with low level controls. + * + * + * How to use: + * + * - furi_hal_gpio_init - Configure your pins in `GpioModeAnalog` + * - furi_hal_adc_acquire - acquire ADC handle to work with + * - furi_hal_adc_configure - configure ADC block + * - furi_hal_adc_read - read value + * - furi_hal_adc_release - release ADC handle + */ + +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriHalAdcHandle FuriHalAdcHandle; + +typedef enum { + FuriHalAdcScale2048, /**< 2.048V scale */ + FuriHalAdcScale2500, /**< 2.5V scale */ +} FuriHalAdcScale; + +typedef enum { + FuriHalAdcClockSync16, /**< 16MHZ, synchronous */ + FuriHalAdcClockSync32, /**< 32MHZ, synchronous */ + FuriHalAdcClockSync64, /**< 64MHz, synchronous */ +} FuriHalAdcClock; + +typedef enum { + FuriHalAdcOversample2, /**< ADC will take 2 samples per each value */ + FuriHalAdcOversample4, /**< ADC will take 4 samples per each value */ + FuriHalAdcOversample8, /**< ADC will take 8 samples per each value */ + FuriHalAdcOversample16, /**< ADC will take 16 samples per each value */ + FuriHalAdcOversample32, /**< ADC will take 32 samples per each value */ + FuriHalAdcOversample64, /**< ADC will take 64 samples per each value */ + FuriHalAdcOversample128, /**< ADC will take 128 samples per each value */ + FuriHalAdcOversample256, /**< ADC will take 256 samples per each value */ + FuriHalAdcOversampleNone, /**< disable oversampling */ +} FuriHalAdcOversample; + +typedef enum { + FuriHalAdcSamplingtime2_5, /**< Sampling time 2.5 ADC clock */ + FuriHalAdcSamplingtime6_5, /**< Sampling time 6.5 ADC clock */ + FuriHalAdcSamplingtime12_5, /**< Sampling time 12.5 ADC clock */ + FuriHalAdcSamplingtime24_5, /**< Sampling time 24.5 ADC clock */ + FuriHalAdcSamplingtime47_5, /**< Sampling time 47.5 ADC clock */ + FuriHalAdcSamplingtime92_5, /**< Sampling time 92.5 ADC clock */ + FuriHalAdcSamplingtime247_5, /**< Sampling time 247.5 ADC clock */ + FuriHalAdcSamplingtime640_5, /**< Sampling time 640.5 ADC clock */ +} FuriHalAdcSamplingTime; + +typedef enum { + /* Channels 0 - 5 are fast channels */ + FuriHalAdcChannel0, /**< Internal channel, see `FuriHalAdcChannelVREFINT`. */ + FuriHalAdcChannel1, /**< Channel 1p */ + FuriHalAdcChannel2, /**< Channel 2p or 1n */ + FuriHalAdcChannel3, /**< Channel 3p or 2n */ + FuriHalAdcChannel4, /**< Channel 4p or 3n */ + FuriHalAdcChannel5, /**< Channel 5p or 4n */ + /* Channels 6 - 18 are slow channels */ + FuriHalAdcChannel6, /**< Channel 6p or 5n */ + FuriHalAdcChannel7, /**< Channel 7p or 6n */ + FuriHalAdcChannel8, /**< Channel 8p or 7n */ + FuriHalAdcChannel9, /**< Channel 9p or 8n */ + FuriHalAdcChannel10, /**< Channel 10p or 9n */ + FuriHalAdcChannel11, /**< Channel 11p or 10n */ + FuriHalAdcChannel12, /**< Channel 12p or 11n */ + FuriHalAdcChannel13, /**< Channel 13p or 12n */ + FuriHalAdcChannel14, /**< Channel 14p or 13n */ + FuriHalAdcChannel15, /**< Channel 15p or 14n */ + FuriHalAdcChannel16, /**< Channel 16p or 15n */ + FuriHalAdcChannel17, /**< Internal channel, see `FuriHalAdcChannelTEMPSENSOR`. */ + FuriHalAdcChannel18, /**< Internal channel, see `FuriHalAdcChannelVBAT`. */ + /* Special Channels: combines one of the 0-18 channel and additional internal peripherals */ + FuriHalAdcChannelVREFINT, /**< Special channel for VREFINT, used for calibration and self test */ + FuriHalAdcChannelTEMPSENSOR, /**< Special channel for on-die temperature sensor, requires at least 5us of sampling time */ + FuriHalAdcChannelVBAT, /**< Special channel for VBAT/3 voltage, requires at least 12us of sampling time */ + /* Special value to indicate that pin is not connected to ADC */ + FuriHalAdcChannelNone, /**< No channel */ +} FuriHalAdcChannel; + +/** Initialize ADC subsystem */ +void furi_hal_adc_init(void); + +/** Acquire ADC handle + * + * Enables appropriate power and clocking domains + * + * @return FuriHalAdcHandle pointer + */ +FuriHalAdcHandle* furi_hal_adc_acquire(void); + +/** Release ADC handle + * + * @param handle The ADC handle + */ +void furi_hal_adc_release(FuriHalAdcHandle* handle); + +/** Configure with default parameters and enable ADC + * + * Parameters used: + * - FuriHalAdcScale2048 - 2.048V VREF Scale. Your signal should be in 0 - + * 2.048V range. + * - FuriHalAdcClockSync64 - Clocked from sysclk bus at 64MHz in synchronous + * mode. Fast, no delay on data bus access. + * - FuriHalAdcOversample64 - Going to acquire and average 64 samples. For + * circuits with slowly or not changing signal. Total time per one read: + * (1/64)*(12.5+247.5)*64 = 260us. The best results you'll get if your signal + * will stay on the same level all this time. + * - FuriHalAdcSamplingtime247_5 - Sampling(transfer from source to internal + * sampling capacitor) time is 247.5 ADC clocks: (1/64)*247.5 = 3.8671875us. + * For relatively high impedance circuits. + * + * Also keep your measurement circuit impedance under 10KOhm or oversampling + * results will be compromised. Verify your signal with oscilloscope(you may + * need fast oscilloscope: 200MHz bandwidth, 125MS/s), ensure that signal is not + * distorted by sampling. + * + * Those parameters were optimized for 0 - 2.048 voltage measurement with ~0.1% + * precision. You can get more, but it will require some magic. + * + * @param handle The ADC handle + */ +void furi_hal_adc_configure(FuriHalAdcHandle* handle); + +/** Configure with extended parameters and enable ADC + * + * General advice is to start with default parameters, figure out what exactly + * is not working for you and then tune it. Also in some cases changing + * circuit(adding caps, lowering impedance, adding opamp) may be better than changing + * parameters. + * + * @warning In general ADC is a world of magic: make sure that you understand + * how your circuit and ADC works. Then carefully read STM32WB + * series reference manual. Setting incorrect parameters leads to + * very poor results. Also internal channels require special + * settings. + * + * @param handle The ADC handle + * @param[in] scale The ADC voltage scale + * @param[in] clock The ADC clock + * @param[in] oversample The ADC oversample mode + * @param[in] sampling_time The ADC sampling time + */ +void furi_hal_adc_configure_ex( + FuriHalAdcHandle* handle, + FuriHalAdcScale scale, + FuriHalAdcClock clock, + FuriHalAdcOversample oversample, + FuriHalAdcSamplingTime sampling_time); + +/** Read single ADC value + * + * @param handle The ADC handle + * @param[in] channel The channel to sample + * + * @return value, 12 bits + */ +uint16_t furi_hal_adc_read(FuriHalAdcHandle* handle, FuriHalAdcChannel channel); + +/** Convert sampled value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_to_voltage(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VREFINT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVREFINT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vref(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled TEMPSENSOR value to temperature + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelTEMPSENSOR` channel + * + * @return temperature in degree C + */ +float furi_hal_adc_convert_temp(FuriHalAdcHandle* handle, uint16_t value); + +/** Convert sampled VBAT value to voltage + * + * @param handle The ADC handle + * @param[in] value The value acquired with `furi_hal_adc_read` for + * `FuriHalAdcChannelVBAT` channel + * + * @return Voltage in mV + */ +float furi_hal_adc_convert_vbat(FuriHalAdcHandle* handle, uint16_t value); + +#ifdef __cplusplus +} +#endif From 4883383b208a7214350b6f35ccd44c5324f735d4 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 18 Apr 2024 08:05:37 +0900 Subject: [PATCH 14/22] [FL-3679] iButton new UI (#3471) * iButton new UI * UI final touches * Satisfy PVS Co-authored-by: Aleksandr Kutuzov --- .../ibutton/scenes/ibutton_scene_config.h | 1 + .../scenes/ibutton_scene_delete_confirm.c | 22 +++++-- .../ibutton/scenes/ibutton_scene_emulate.c | 17 ++--- .../main/ibutton/scenes/ibutton_scene_info.c | 16 +++-- .../main/ibutton/scenes/ibutton_scene_read.c | 6 +- .../ibutton/scenes/ibutton_scene_read_error.c | 7 +-- .../scenes/ibutton_scene_read_exit_confirm.c | 59 ++++++++++++++++++ .../scenes/ibutton_scene_read_key_menu.c | 18 +++--- .../scenes/ibutton_scene_read_success.c | 8 +-- .../scenes/ibutton_scene_saved_key_menu.c | 8 ++- .../main/ibutton/scenes/ibutton_scene_write.c | 19 +++--- assets/icons/Dolphin/DolphinWait_59x54.png | Bin 0 -> 1539 bytes lib/ibutton/ibutton_protocols.c | 11 ++++ lib/ibutton/ibutton_protocols.h | 11 ++++ lib/ibutton/protocols/dallas/dallas_common.c | 34 +++++----- lib/ibutton/protocols/dallas/dallas_common.h | 2 + .../protocols/dallas/protocol_dallas_base.h | 1 + .../protocols/dallas/protocol_ds1971.c | 16 ++++- .../protocols/dallas/protocol_ds1990.c | 10 +++ .../protocols/dallas/protocol_ds1992.c | 16 ++++- .../protocols/dallas/protocol_ds1996.c | 16 ++++- .../protocols/dallas/protocol_ds_generic.c | 10 ++- .../protocols/dallas/protocol_group_dallas.c | 19 ++++++ lib/ibutton/protocols/misc/protocol_cyfral.c | 9 +++ lib/ibutton/protocols/misc/protocol_metakom.c | 8 ++- lib/ibutton/protocols/protocol_group_base.h | 1 + lib/toolbox/protocols/protocol.h | 1 + targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 3 +- 29 files changed, 278 insertions(+), 73 deletions(-) create mode 100644 applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c create mode 100644 assets/icons/Dolphin/DolphinWait_59x54.png diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index 79f6791b3b9..f3fd0fff77e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -17,5 +17,6 @@ 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_delete_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c index b293af952ce..a7eba672a3e 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_delete_confirm.c @@ -7,23 +7,33 @@ void ibutton_scene_delete_confirm_on_enter(void* context) { Widget* widget = ibutton->widget; FuriString* tmp = furi_string_alloc(); + FuriString* uid = furi_string_alloc(); widget_add_button_element(widget, GuiButtonTypeLeft, "Back", ibutton_widget_callback, context); widget_add_button_element( widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context); - furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name); + furi_string_printf(tmp, "\e#Delete %s?\e#\n", ibutton->key_name); + + ibutton_protocols_render_uid(ibutton->protocols, key, uid); + + furi_string_cat_printf( + uid, + "\n%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, ibutton_key_get_protocol_id(key)), + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + + furi_string_cat(tmp, uid); + widget_add_text_box_element( - widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false); + widget, 0, 0, 128, 64, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + furi_string_reset(uid); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(uid); } bool ibutton_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_emulate.c b/applications/main/ibutton/scenes/ibutton_scene_emulate.c index 713b8331c36..070f2291091 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_emulate.c +++ b/applications/main/ibutton/scenes/ibutton_scene_emulate.c @@ -21,17 +21,20 @@ void ibutton_scene_emulate_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "%s\n[%s]", - furi_string_empty(ibutton->file_path) ? "Unsaved Key" : ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, + "Unsaved\n%s", + ibutton_protocols_get_name(ibutton->protocols, ibutton_key_get_protocol_id(key))); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); widget_add_string_multiline_element( - widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "iButton\nemulating"); + widget, 88, 10, AlignCenter, AlignTop, FontPrimary, "Emulating"); ibutton_worker_emulate_set_callback(ibutton->worker, ibutton_scene_emulate_callback, ibutton); ibutton_worker_emulate_start(ibutton->worker, key); diff --git a/applications/main/ibutton/scenes/ibutton_scene_info.c b/applications/main/ibutton/scenes/ibutton_scene_info.c index cf44d6a8654..ba72de6ca76 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_info.c +++ b/applications/main/ibutton/scenes/ibutton_scene_info.c @@ -8,21 +8,24 @@ void ibutton_scene_info_on_enter(void* context) { const iButtonProtocolId protocol_id = ibutton_key_get_protocol_id(key); FuriString* tmp = furi_string_alloc(); + FuriString* brief_data = furi_string_alloc(); furi_string_printf( tmp, - "\e#%s [%s]\e#", + "Name:%s\n\e#%s %s\e#\n", ibutton->key_name, + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + ibutton_protocols_render_brief_data(ibutton->protocols, key, brief_data); + + furi_string_cat(tmp, brief_data); + widget_add_text_box_element( - widget, 0, 2, 128, 12, AlignLeft, AlignTop, furi_string_get_cstr(tmp), true); + widget, 0, 0, 128, 64, AlignLeft, AlignTop, furi_string_get_cstr(tmp), false); furi_string_reset(tmp); - ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp); - - widget_add_string_multiline_element( - widget, 0, 16, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + furi_string_reset(brief_data); if(ibutton_protocols_get_features(ibutton->protocols, protocol_id) & iButtonProtocolFeatureExtData) { @@ -32,6 +35,7 @@ void ibutton_scene_info_on_enter(void* context) { view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); furi_string_free(tmp); + furi_string_free(brief_data); } bool ibutton_scene_info_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_read.c b/applications/main/ibutton/scenes/ibutton_scene_read.c index f360c3ac43a..f490488f5f4 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read.c @@ -12,9 +12,9 @@ void ibutton_scene_read_on_enter(void* context) { iButtonKey* key = ibutton->key; iButtonWorker* worker = ibutton->worker; - popup_set_header(popup, "iButton", 95, 26, AlignCenter, AlignBottom); - popup_set_text(popup, "Apply key to\nFlipper's back", 95, 30, AlignCenter, AlignTop); - popup_set_icon(popup, 0, 5, &I_DolphinWait_61x59); + popup_set_header(popup, "Reading", 95, 26, AlignCenter, AlignBottom); + popup_set_text(popup, "Connect key\nwith pogo pins", 95, 30, AlignCenter, AlignTop); + popup_set_icon(popup, 0, 10, &I_DolphinWait_59x54); view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewPopup); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_error.c b/applications/main/ibutton/scenes/ibutton_scene_read_error.c index e966384bfc0..6655657521d 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_error.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_error.c @@ -14,13 +14,10 @@ void ibutton_scene_read_error_on_enter(void* context) { widget_add_button_element( widget, GuiButtonTypeRight, "More", ibutton_widget_callback, context); - widget_add_string_element( - widget, 128 / 2, 2, AlignCenter, AlignTop, FontPrimary, "Read Error"); - ibutton_protocols_render_error(ibutton->protocols, key, tmp); - widget_add_string_multiline_element( - widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp)); + widget_add_text_box_element( + widget, 0, 0, 128, 48, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_notification_message(ibutton, iButtonNotificationMessageError); ibutton_notification_message(ibutton, iButtonNotificationMessageRedOn); diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c new file mode 100644 index 00000000000..4077de9a636 --- /dev/null +++ b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c @@ -0,0 +1,59 @@ +#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_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c index 1555f2cc20c..890e0a2848b 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_key_menu.c @@ -30,29 +30,29 @@ void ibutton_scene_read_key_menu_on_enter(void* context) { ibutton_scene_read_key_menu_submenu_callback, ibutton); - if(features & iButtonProtocolFeatureExtData) { + if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( submenu, - "View Data", - SubmenuIndexViewData, + "Write ID", + SubmenuIndexWriteBlank, ibutton_scene_read_key_menu_submenu_callback, ibutton); } - if(features & iButtonProtocolFeatureWriteBlank) { + if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( submenu, - "Write Blank", - SubmenuIndexWriteBlank, + "Full Write on Same Type", + SubmenuIndexWriteCopy, ibutton_scene_read_key_menu_submenu_callback, ibutton); } - if(features & iButtonProtocolFeatureWriteCopy) { + if(features & iButtonProtocolFeatureExtData) { submenu_add_item( submenu, - "Write Copy", - SubmenuIndexWriteCopy, + "Data Info", + SubmenuIndexViewData, ibutton_scene_read_key_menu_submenu_callback, ibutton); } diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 2e50bc99647..6dd06ca5269 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -18,9 +18,9 @@ void ibutton_scene_read_success_on_enter(void* context) { furi_string_printf( tmp, - "%s[%s]", - ibutton_protocols_get_name(ibutton->protocols, protocol_id), - ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id)); + "%s %s", + ibutton_protocols_get_manufacturer(ibutton->protocols, protocol_id), + ibutton_protocols_get_name(ibutton->protocols, protocol_id)); widget_add_string_element( widget, 0, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp)); @@ -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, iButtonSceneExitConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c index fc0cf42e2ff..1547a647bf1 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c +++ b/applications/main/ibutton/scenes/ibutton_scene_saved_key_menu.c @@ -21,12 +21,16 @@ void ibutton_scene_saved_key_menu_on_enter(void* context) { if(features & iButtonProtocolFeatureWriteBlank) { submenu_add_item( - submenu, "Write Blank", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); + submenu, "Write ID", SubmenuIndexWriteBlank, ibutton_submenu_callback, ibutton); } if(features & iButtonProtocolFeatureWriteCopy) { submenu_add_item( - submenu, "Write Copy", SubmenuIndexWriteCopy, ibutton_submenu_callback, ibutton); + submenu, + "Full Write on Same Type", + SubmenuIndexWriteCopy, + ibutton_submenu_callback, + ibutton); } submenu_add_item(submenu, "Edit", SubmenuIndexEdit, ibutton_submenu_callback, ibutton); diff --git a/applications/main/ibutton/scenes/ibutton_scene_write.c b/applications/main/ibutton/scenes/ibutton_scene_write.c index 63be635069c..465b0630108 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_write.c +++ b/applications/main/ibutton/scenes/ibutton_scene_write.c @@ -40,25 +40,24 @@ void ibutton_scene_write_on_enter(void* context) { widget_add_icon_element(widget, 3, 10, &I_iButtonKey_49x44); - furi_string_printf( - tmp, - "%s\n[%s]", - ibutton->key_name, - ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + if(furi_string_empty(ibutton->file_path)) { + furi_string_printf( + tmp, "Unsaved\n%s", ibutton_protocols_get_name(ibutton->protocols, protocol_id)); + } else { + furi_string_printf(tmp, "%s", ibutton->key_name); + } widget_add_text_box_element( - widget, 52, 38, 75, 26, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), true); + widget, 52, 23, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(tmp), false); ibutton_worker_write_set_callback(worker, ibutton_scene_write_callback, ibutton); - furi_string_set(tmp, "iButton\nwriting "); - if(ibutton->write_mode == iButtonWriteModeBlank) { - furi_string_cat(tmp, "Blank"); + furi_string_set(tmp, "Writing ID"); ibutton_worker_write_blank_start(worker, key); } else if(ibutton->write_mode == iButtonWriteModeCopy) { - furi_string_cat(tmp, "Copy"); + furi_string_set(tmp, "Full Writing"); ibutton_worker_write_copy_start(worker, key); } diff --git a/assets/icons/Dolphin/DolphinWait_59x54.png b/assets/icons/Dolphin/DolphinWait_59x54.png new file mode 100644 index 0000000000000000000000000000000000000000..bdf8171b8028801b1f82d682cda4366ce6848bc5 GIT binary patch literal 1539 zcmbVMeM}Q)7_TxvVkTomCL+^Y*#zgVcfEdG@1$sHsg_w_rI~d&$Mx=QX`pxI?w~DG zro}ki=He9N&_Ti`78?x@D=!CanvlS37m0^u7O6DJd#U+#XghA8u=x zLmoyiU#_vDR-X|Rc$Sv&Ah#^bPL~zZ1S4OR2Bii_Nr4xL6cq3lu>u*;%SZK+k{mup zssZ>Mb zk6g0IGcM9*GLPGnc6xb%DEdeQ@%#M>ze>UJZbV5C1cG7+hQSg77J{ru1z=W4ifAwa zf#y9v(ZjJ&SR>`+N<_U}@^&l?ug_|IqL>xNlO?4M2~a*nsX!60Hyqn2S`cmEUpJnL z7VJSEKx{zZN_bkTi7P2Wmh$&(Lt&ug4Vle*q{2`|CXO!g0#>w`^m6G&!FU)F01ZJi zDgwqR+y$$(Din5NDgfg&p&^t`EpV!w5j{`is|~mcSE4!`H={~rMh2n9v2>%!Y*wo= zL%NoTa4oDLQY;N3wmp*VajsOmXK+a)4=9o2?HpGW>4cmDPUM6F&Ie%{1q!8DDcZw^ zPv(cKG#0c8@Sak@n0d|%ji#COJjDeJCkRT(;4r1cl&~6OG_Vus)G&iF1gZlX7e!0q zkMWuRn?6Kx3JLehe|jf!BsFAMd@>Ev=E*z(RvIE+nwHy%w~xzY6CsPqU=Lh4yJDcZ z6Po&U^y9~;h!=VtW^kh+xhHRS#n8kB1;N^zTOU-}jt^9|rp!z^xht{srQ)bM)0q#e zk7XJYs}HTYad7eWrjx~6qgb}NbTISa$Ok|3gAR9UWnYJ8T_dt?-;{SRM=k&TY~QI` zxM$?Z)bgaRydCqi&?B{1Hx=0P`#ZN>SakCW8$*W%^6qrp&s7bbn}^zx8n#bjV(w+T z=B(O(%O4Y~>WFP?FjcLMHs4CX@7yi9^68}**^hB>>!Z(e5-Y0JulH^F)Y;Qlo&y4!5XJy&P2X^f>R%j~fugQDrUhJKFb&o3!JHFXY<^gwY|2tX4{xoBiF>$rUelbShQFVSm%Y{Es8*(jTEf3&OGcKdF+D%@4+0EB A-~a#s literal 0 HcmV?d00001 diff --git a/lib/ibutton/ibutton_protocols.c b/lib/ibutton/ibutton_protocols.c index ce4392475b8..84fcccd71c1 100644 --- a/lib/ibutton/ibutton_protocols.c +++ b/lib/ibutton/ibutton_protocols.c @@ -287,6 +287,17 @@ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const return success; } +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result) { + const iButtonProtocolId id = ibutton_key_get_protocol_id(key); + const iButtonProtocolData* data = ibutton_key_get_protocol_data(key); + + GET_PROTOCOL_GROUP(id); + GROUP_BASE->render_uid(GROUP_DATA, data, PROTOCOL_ID, result); +} + void ibutton_protocols_render_data( iButtonProtocols* protocols, const iButtonKey* key, diff --git a/lib/ibutton/ibutton_protocols.h b/lib/ibutton/ibutton_protocols.h index ec4a9fc7239..dd2afbd6ee0 100644 --- a/lib/ibutton/ibutton_protocols.h +++ b/lib/ibutton/ibutton_protocols.h @@ -133,6 +133,17 @@ bool ibutton_protocols_save( */ bool ibutton_protocols_load(iButtonProtocols* protocols, iButtonKey* key, const char* file_name); +/** + * Format a string containing defice UID + * @param [in] protocols pointer to an iButtonProtocols object + * @param [in] key pointer to the key to be rendered + * @param [out] result pointer to the FuriString instance (must be initialized) + */ +void ibutton_protocols_render_uid( + iButtonProtocols* protocols, + const iButtonKey* key, + FuriString* result); + /** * Format a string containing device full data * @param [in] protocols pointer to an iButtonProtocols object diff --git a/lib/ibutton/protocols/dallas/dallas_common.c b/lib/ibutton/protocols/dallas/dallas_common.c index 6e99a3be239..7ce12f7a065 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.c +++ b/lib/ibutton/protocols/dallas/dallas_common.c @@ -208,15 +208,26 @@ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data) { return crc_calculated == crc_received; } +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data) { + furi_string_cat_printf(result, "ID: "); + for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { + furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); + } +} + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, const uint8_t* mem_data, size_t mem_size, const char* mem_name) { + UNUSED(mem_data); + + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(rom_data->bytes); ++i) { furi_string_cat_printf(result, "%02X ", rom_data->bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", rom_data->bytes[0]); const char* size_prefix = ""; size_t mem_size_bits = mem_size * BITS_IN_BYTE; @@ -229,28 +240,23 @@ void dallas_common_render_brief_data( mem_size_bits /= BITS_IN_KBIT; } - furi_string_cat_printf( - result, "\nInternal %s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); - - for(size_t i = 0; i < DALLAS_COMMON_BRIEF_HEAD_COUNT; ++i) { - furi_string_cat_printf(result, "%02X ", mem_data[i]); - } - - furi_string_cat_printf(result, "[ . . . ]"); - - for(size_t i = mem_size - DALLAS_COMMON_BRIEF_TAIL_COUNT; i < mem_size; ++i) { - furi_string_cat_printf(result, " %02X", mem_data[i]); - } + furi_string_cat_printf(result, "%s: %zu %sbit\n", mem_name, mem_size_bits, size_prefix); } void dallas_common_render_crc_error(FuriString* result, const DallasCommonRomData* rom_data) { - furi_string_set(result, "CRC Error\n"); + furi_string_set(result, "\e#CRC Error\e#\n"); const size_t data_size = sizeof(DallasCommonRomData); for(size_t i = 0; i < data_size; ++i) { - furi_string_cat_printf(result, (i < data_size - 1) ? "%02X " : "%02X", rom_data->bytes[i]); + furi_string_cat_printf( + result, (i < data_size - 1) ? "%02X " : "\e!%02X\e!", rom_data->bytes[i]); } + + furi_string_cat_printf( + result, + "\nExpected CRC: \e!%02X\e!", + maxim_crc8(rom_data->bytes, sizeof(DallasCommonRomData) - 1, MAXIM_CRC8_INIT)); } void dallas_common_apply_edits(DallasCommonRomData* rom_data, uint8_t family_code) { diff --git a/lib/ibutton/protocols/dallas/dallas_common.h b/lib/ibutton/protocols/dallas/dallas_common.h index 6f5ff7cc010..90fec3e28a1 100644 --- a/lib/ibutton/protocols/dallas/dallas_common.h +++ b/lib/ibutton/protocols/dallas/dallas_common.h @@ -96,6 +96,8 @@ bool dallas_common_load_rom_data( /* Miscellaneous */ bool dallas_common_is_valid_crc(const DallasCommonRomData* rom_data); +void dallas_common_render_uid(FuriString* result, const DallasCommonRomData* rom_data); + void dallas_common_render_brief_data( FuriString* result, const DallasCommonRomData* rom_data, diff --git a/lib/ibutton/protocols/dallas/protocol_dallas_base.h b/lib/ibutton/protocols/dallas/protocol_dallas_base.h index 55e10993605..05620329f89 100644 --- a/lib/ibutton/protocols/dallas/protocol_dallas_base.h +++ b/lib/ibutton/protocols/dallas/protocol_dallas_base.h @@ -30,6 +30,7 @@ typedef struct { iButtonProtocolDallasEmulateFunc emulate; iButtonProtocolDallasSaveFunc save; iButtonProtocolDallasLoadFunc load; + iButtonProtocolDallasRenderDataFunc render_uid; iButtonProtocolDallasRenderDataFunc render_data; iButtonProtocolDallasRenderDataFunc render_brief_data; iButtonProtocolDallasRenderDataFunc render_error; diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index b65e645846c..d60803fc6d3 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -35,6 +35,7 @@ static bool dallas_ds1971_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1971_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1971_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1971_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1971_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1971_render_error(FuriString*, const iButtonProtocolData*); @@ -58,6 +59,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1971 = { .emulate = dallas_ds1971_emulate, .save = dallas_ds1971_save, .load = dallas_ds1971_load, + .render_uid = dallas_ds1971_render_uid, .render_data = dallas_ds1971_render_data, .render_brief_data = dallas_ds1971_render_brief_data, .render_error = dallas_ds1971_render_error, @@ -209,14 +211,26 @@ bool dallas_ds1971_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1971ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1971ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, 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_ds1990.c b/lib/ibutton/protocols/dallas/protocol_ds1990.c index 86d39f1bd87..67e7545f45d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1990.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1990.c @@ -27,6 +27,7 @@ static bool dallas_ds1990_write_blank(OneWireHost*, iButtonProtocolData*); static void dallas_ds1990_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1990_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1990_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1990_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1990_render_error(FuriString*, const iButtonProtocolData*); static bool dallas_ds1990_is_data_valid(const iButtonProtocolData*); @@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1990 = { .emulate = dallas_ds1990_emulate, .save = dallas_ds1990_save, .load = dallas_ds1990_load, + .render_uid = dallas_ds1990_render_uid, .render_data = NULL, /* No data to render */ .render_brief_data = dallas_ds1990_render_brief_data, .render_error = dallas_ds1990_render_error, @@ -117,12 +119,20 @@ bool dallas_ds1990_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void dallas_ds1990_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1990ProtocolData* data = protocol_data; + + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1990_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1990ProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", data->rom_data.bytes[0]); } void dallas_ds1990_render_error(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 7440882ea0d..5ddd8ef2ce4 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -36,6 +36,7 @@ static bool dallas_ds1992_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1992_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1992_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1992_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1992_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1992_render_error(FuriString*, const iButtonProtocolData*); @@ -57,6 +58,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1992 = { .emulate = dallas_ds1992_emulate, .save = dallas_ds1992_save, .load = dallas_ds1992_load, + .render_uid = dallas_ds1992_render_uid, .render_data = dallas_ds1992_render_data, .render_brief_data = dallas_ds1992_render_brief_data, .render_error = dallas_ds1992_render_error, @@ -182,14 +184,26 @@ bool dallas_ds1992_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1992ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, 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 5970a67bbfe..6af61f35528 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -33,6 +33,7 @@ static bool dallas_ds1996_write_copy(OneWireHost*, iButtonProtocolData*); static void dallas_ds1996_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1996_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1996_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1996_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1996_render_error(FuriString*, const iButtonProtocolData*); @@ -53,6 +54,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1996 = { .emulate = dallas_ds1996_emulate, .save = dallas_ds1996_save, .load = dallas_ds1996_load, + .render_uid = dallas_ds1996_render_uid, .render_data = dallas_ds1996_render_data, .render_brief_data = dallas_ds1996_render_brief_data, .render_error = dallas_ds1996_render_error, @@ -207,15 +209,27 @@ bool dallas_ds1996_save(FlipperFormat* ff, const iButtonProtocolData* protocol_d return success; } +void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1996ProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; + FuriString* data_string = furi_string_alloc(); + pretty_format_bytes_hex_canonical( - result, + data_string, 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/dallas/protocol_ds_generic.c b/lib/ibutton/protocols/dallas/protocol_ds_generic.c index 6c698bb8924..101db1dbe2d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds_generic.c +++ b/lib/ibutton/protocols/dallas/protocol_ds_generic.c @@ -8,7 +8,7 @@ #include "../blanks/tm2004.h" #define DALLAS_GENERIC_FAMILY_CODE 0x00U -#define DALLAS_GENERIC_FAMILY_NAME "DSGeneric" +#define DALLAS_GENERIC_FAMILY_NAME "(non-specific)" typedef struct { OneWireSlave* bus; @@ -24,6 +24,7 @@ static bool ds_generic_write_blank(OneWireHost*, iButtonProtocolData*); static void ds_generic_emulate(OneWireSlave*, iButtonProtocolData*); static bool ds_generic_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool ds_generic_save(FlipperFormat*, const iButtonProtocolData*); +static void ds_generic_render_uid(FuriString*, const iButtonProtocolData*); static void ds_generic_render_brief_data(FuriString*, const iButtonProtocolData*); static void ds_generic_render_error(FuriString*, const iButtonProtocolData*); static bool ds_generic_is_data_valid(const iButtonProtocolData*); @@ -44,6 +45,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds_generic = { .save = ds_generic_save, .load = ds_generic_load, .render_data = NULL, /* No data to render */ + .render_uid = ds_generic_render_uid, .render_brief_data = ds_generic_render_brief_data, .render_error = ds_generic_render_error, .is_valid = ds_generic_is_data_valid, @@ -111,9 +113,15 @@ bool ds_generic_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void ds_generic_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DallasGenericProtocolData* data = protocol_data; + dallas_common_render_uid(result, &data->rom_data); +} + void ds_generic_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DallasGenericProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } diff --git a/lib/ibutton/protocols/dallas/protocol_group_dallas.c b/lib/ibutton/protocols/dallas/protocol_group_dallas.c index 39cabbeb45e..7dad756690a 100644 --- a/lib/ibutton/protocols/dallas/protocol_group_dallas.c +++ b/lib/ibutton/protocols/dallas/protocol_group_dallas.c @@ -51,6 +51,12 @@ static bool ibutton_protocol_group_dallas_get_id_by_name( return true; } + // Handle files that refer to Dallas "Raw Data" as DSGeneric + if(strcmp(name, "DSGeneric") == 0) { + *id = iButtonProtocolDSGeneric; + return true; + } + for(iButtonProtocolLocalId i = 0; i < iButtonProtocolDSMax; ++i) { if(strcmp(ibutton_protocols_dallas[i]->name, name) == 0) { *id = i; @@ -212,6 +218,18 @@ static bool ibutton_protocol_group_dallas_load( return ibutton_protocols_dallas[id]->load(ff, version, data); } +static void ibutton_protocol_group_dallas_render_uid( + iButtonProtocolGroupDallas* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + UNUSED(group); + furi_assert(id < iButtonProtocolDSMax); + const iButtonProtocolDallasBase* protocol = ibutton_protocols_dallas[id]; + furi_assert(protocol->render_uid); + protocol->render_uid(result, data); +} + static void ibutton_protocol_group_dallas_render_data( iButtonProtocolGroupDallas* group, const iButtonProtocolData* data, @@ -298,6 +316,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_dallas = { .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_dallas_save, .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_dallas_load, + .render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_uid, .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_data, .render_brief_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_dallas_render_brief_data, diff --git a/lib/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c index 0cdb8766b00..d38865bae23 100644 --- a/lib/ibutton/protocols/misc/protocol_cyfral.c +++ b/lib/ibutton/protocols/misc/protocol_cyfral.c @@ -325,7 +325,15 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } +static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) { + 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]); + } +} + 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]); } @@ -348,5 +356,6 @@ const ProtocolBase ibutton_protocol_misc_cyfral = { .start = (ProtocolEncoderStart)protocol_cyfral_encoder_start, .yield = (ProtocolEncoderYield)protocol_cyfral_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_cyfral_render_uid, .render_brief_data = (ProtocolRenderData)protocol_cyfral_render_brief_data, }; diff --git a/lib/ibutton/protocols/misc/protocol_metakom.c b/lib/ibutton/protocols/misc/protocol_metakom.c index a2bd2cf7ca4..6d5e0339d6f 100644 --- a/lib/ibutton/protocols/misc/protocol_metakom.c +++ b/lib/ibutton/protocols/misc/protocol_metakom.c @@ -301,12 +301,17 @@ static LevelDuration protocol_metakom_encoder_yield(ProtocolMetakom* proto) { return result; } -static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { +static void protocol_metakom_render_uid(ProtocolMetakom* proto, FuriString* result) { + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < METAKOM_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); } } +static void protocol_metakom_render_brief_data(ProtocolMetakom* proto, FuriString* result) { + protocol_metakom_render_uid(proto, result); +} + const ProtocolBase ibutton_protocol_misc_metakom = { .name = "Metakom", .manufacturer = "Metakom", @@ -324,5 +329,6 @@ const ProtocolBase ibutton_protocol_misc_metakom = { .start = (ProtocolEncoderStart)protocol_metakom_encoder_start, .yield = (ProtocolEncoderYield)protocol_metakom_encoder_yield, }, + .render_uid = (ProtocolRenderData)protocol_metakom_render_uid, .render_brief_data = (ProtocolRenderData)protocol_metakom_render_brief_data, }; diff --git a/lib/ibutton/protocols/protocol_group_base.h b/lib/ibutton/protocols/protocol_group_base.h index c8fec70fe78..ef57fe0bc07 100644 --- a/lib/ibutton/protocols/protocol_group_base.h +++ b/lib/ibutton/protocols/protocol_group_base.h @@ -93,6 +93,7 @@ typedef struct { iButtonProtocolGroupSaveFunc save; iButtonProtocolGroupLoadFunc load; + iButtonProtocolGroupRenderFunc render_uid; iButtonProtocolGroupRenderFunc render_data; iButtonProtocolGroupRenderFunc render_brief_data; iButtonProtocolGroupRenderFunc render_error; diff --git a/lib/toolbox/protocols/protocol.h b/lib/toolbox/protocols/protocol.h index 5a8015b1e1b..0ee165d13e6 100644 --- a/lib/toolbox/protocols/protocol.h +++ b/lib/toolbox/protocols/protocol.h @@ -40,6 +40,7 @@ typedef struct { ProtocolGetData get_data; ProtocolDecoder decoder; ProtocolEncoder encoder; + ProtocolRenderData render_uid; ProtocolRenderData render_data; ProtocolRenderData render_brief_data; ProtocolWriteData write_data; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 43500aa1c40..7cc7639bd41 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index dbaadc4a15a..727a512c909 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.0,, +Version,+,61.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1894,6 +1894,7 @@ Function,+,ibutton_protocols_read,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_render_brief_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_data,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_render_error,void,"iButtonProtocols*, const iButtonKey*, FuriString*" +Function,+,ibutton_protocols_render_uid,void,"iButtonProtocols*, const iButtonKey*, FuriString*" Function,+,ibutton_protocols_save,_Bool,"iButtonProtocols*, const iButtonKey*, const char*" Function,+,ibutton_protocols_write_blank,_Bool,"iButtonProtocols*, iButtonKey*" Function,+,ibutton_protocols_write_copy,_Bool,"iButtonProtocols*, iButtonKey*" From 94c2d7a4e3d577d57531c8fb8c1766bc66080227 Mon Sep 17 00:00:00 2001 From: KRukus9 <65518085+KRukus9@users.noreply.github.com> Date: Wed, 17 Apr 2024 16:12:02 -0700 Subject: [PATCH 15/22] Update tv.ir (#3584) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 1159f2f04d3..5eab4c65c6c 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1827,4 +1827,43 @@ name: Mute type: parsed protocol: NEC address: 01 00 00 00 -command: 17 00 00 00 \ No newline at end of file +command: 17 00 00 00 +# +# Visio TV +# +name: Power +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 08 00 00 00 +# +name: Vol_up +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 02 00 00 00 +# +name: Vol_dn +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 03 00 00 00 +# +name: Ch_next +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 00 00 00 00 +# +name: Ch_prev +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 01 00 00 00 +# +name: Mute +type: parsed +protocol: NEC +address: 04 00 00 00 +command: 09 00 00 00 +# From 00970a4b12e1cafaeebcf6f4c8b2f89dbd774f4a Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 18 Apr 2024 03:34:33 +0300 Subject: [PATCH 16/22] Allow setting view_dispatcher callbacks to NULL again (#3580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/view_dispatcher.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/applications/services/gui/view_dispatcher.c b/applications/services/gui/view_dispatcher.c index 222ba817fd1..d4c2f61e798 100644 --- a/applications/services/gui/view_dispatcher.c +++ b/applications/services/gui/view_dispatcher.c @@ -51,7 +51,6 @@ void view_dispatcher_set_navigation_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherNavigationEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->navigation_event_callback = callback; } @@ -59,7 +58,6 @@ void view_dispatcher_set_custom_event_callback( ViewDispatcher* view_dispatcher, ViewDispatcherCustomEventCallback callback) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->custom_event_callback = callback; } @@ -68,7 +66,6 @@ void view_dispatcher_set_tick_event_callback( ViewDispatcherTickEventCallback callback, uint32_t tick_period) { furi_check(view_dispatcher); - furi_check(callback); view_dispatcher->tick_event_callback = callback; view_dispatcher->tick_period = tick_period; } From be43e498698d623d6e9ddbcb36760a049b331038 Mon Sep 17 00:00:00 2001 From: gornekich Date: Thu, 18 Apr 2024 01:56:39 +0100 Subject: [PATCH 17/22] nfc app: fix false positive verification in bip plugin (#3595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/nfc/plugins/supported_cards/bip.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c index 548afed71f4..8b76c2d0758 100644 --- a/applications/main/nfc/plugins/supported_cards/bip.c +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -61,7 +61,7 @@ bool bip_verify(Nfc* nfc) { MfClassicError error = mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); - if(error == MfClassicErrorNotPresent) { + if(error != MfClassicErrorNone) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); verified = false; } @@ -81,7 +81,8 @@ static bool bip_read(Nfc* nfc, NfcDevice* device) { do { MfClassicType type = MfClassicType1k; MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); - if(error == MfClassicErrorNotPresent) { + if(error != MfClassicErrorNone) break; + if(type != MfClassicType1k) { FURI_LOG_W(TAG, "Card not MIFARE Classic 1k"); break; } From 12112e70bc7065669ff98fc620c22b92bd848be2 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Thu, 18 Apr 2024 02:26:58 +0100 Subject: [PATCH 18/22] BLE: Add GapPairingNone support (#3596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * BLE: Add GapPairingNone support * FuriHal: cleanup naming in ble gap, remove useless config options Co-authored-by: あく --- targets/f7/ble_glue/app_conf.h | 6 ------ targets/f7/ble_glue/gap.c | 13 +++++++++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h index fbf6d0291de..d0e089eb1ea 100644 --- a/targets/f7/ble_glue/app_conf.h +++ b/targets/f7/ble_glue/app_conf.h @@ -9,7 +9,6 @@ /** * Define IO Authentication */ -#define CFG_USED_FIXED_PIN USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN #define CFG_ENCRYPTION_KEY_SIZE_MAX (16) #define CFG_ENCRYPTION_KEY_SIZE_MIN (8) @@ -18,11 +17,6 @@ */ #define CFG_IO_CAPABILITY IO_CAP_DISPLAY_YES_NO -/** - * Define MITM modes - */ -#define CFG_MITM_PROTECTION MITM_PROTECTION_REQUIRED - /** * Define Secure Connections Support */ diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 8f4299c7019..2e4a74a9e6b 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -348,22 +348,31 @@ static void gap_init_svc(Gap* gap) { // Set default PHY hci_le_set_default_phy(ALL_PHYS_PREFERENCE, TX_2M_PREFERRED, RX_2M_PREFERRED); // Set I/O capability + uint8_t auth_req_mitm_mode = MITM_PROTECTION_REQUIRED; + uint8_t auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_FORBIDDEN; bool keypress_supported = false; if(gap->config->pairing_method == GapPairingPinCodeShow) { aci_gap_set_io_capability(IO_CAP_DISPLAY_ONLY); } else if(gap->config->pairing_method == GapPairingPinCodeVerifyYesNo) { aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); keypress_supported = true; + } else if(gap->config->pairing_method == GapPairingNone) { + // "Just works" pairing method (iOS accepts it, it seems Android and Linux don't) + auth_req_mitm_mode = MITM_PROTECTION_NOT_REQUIRED; + auth_req_use_fixed_pin = USE_FIXED_PIN_FOR_PAIRING_ALLOWED; + // If "just works" isn't supported, we want the numeric comparaison method + aci_gap_set_io_capability(IO_CAP_DISPLAY_YES_NO); + keypress_supported = true; } // Setup authentication aci_gap_set_authentication_requirement( gap->config->bonding_mode, - CFG_MITM_PROTECTION, + auth_req_mitm_mode, CFG_SC_SUPPORT, keypress_supported, CFG_ENCRYPTION_KEY_SIZE_MIN, CFG_ENCRYPTION_KEY_SIZE_MAX, - CFG_USED_FIXED_PIN, + auth_req_use_fixed_pin, 0, CFG_IDENTITY_ADDRESS); // Configure whitelist From 0d73a58123f6bada9c46d76d0952a4d0a4749a77 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Fri, 19 Apr 2024 21:01:57 +0900 Subject: [PATCH 19/22] Mosgortrans parser output fixes (#3554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix mosgortrans/troika newlines and mark unused variables * Supress warnings for TODOs that don't need a ticket Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 70 +++++++++---------- .../main/nfc/plugins/supported_cards/troika.c | 4 +- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index ef1e18f8d57..8844ee4afeb 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -505,7 +505,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* card_use_before_date_s.year); if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) { - furi_string_cat(result, "\e#No ticket\n"); + furi_string_cat(result, "\e#No ticket"); return true; } //remaining_trips @@ -625,7 +625,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "Valid to: %02d.%02d.%04d", card_valid_to_date_s.day, card_valid_to_date_s.month, card_valid_to_date_s.year); @@ -663,7 +663,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2016); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "Valid to: %02d.%02d.%04d", card_valid_to_date_s.day, card_valid_to_date_s.month, card_valid_to_date_s.year); @@ -676,7 +676,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2016); furi_string_cat_printf( result, - "Trip from: %02d.%02d.%04d %02d:%02d\n", + "\nTrip from: %02d.%02d.%04d %02d:%02d", card_start_trip_minutes_s.day, card_start_trip_minutes_s.month, card_start_trip_minutes_s.year, @@ -693,7 +693,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2016); furi_string_cat_printf( result, - "Trip switch: %02d.%02d.%04d %02d:%02d\n", + "\nTrip switch: %02d.%02d.%04d %02d:%02d", card_start_switch_trip_minutes_s.day, card_start_switch_trip_minutes_s.month, card_start_switch_trip_minutes_s.year, @@ -703,10 +703,10 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* //transport FuriString* transport = furi_string_alloc(); parse_transport_type(&data_block, transport); - furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + furi_string_cat_printf(result, "\nTransport: %s", furi_string_get_cstr(transport)); //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } furi_string_free(transport); break; @@ -740,9 +740,9 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* card_valid_to_date_s.month, card_valid_to_date_s.year); //remaining_trips - furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + furi_string_cat_printf(result, "Trips left: %d", data_block.remaining_trips); //trip_from - if(data_block.start_trip_date) { + if(data_block.start_trip_date) { // TODO: (-nofl) unused DateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, @@ -751,7 +751,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* } //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } break; } @@ -785,12 +785,12 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "Valid to: %02d.%02d.%04d", card_valid_to_date_s.day, card_valid_to_date_s.month, card_valid_to_date_s.year); //trip_from - if(data_block.start_trip_date) { + if(data_block.start_trip_date) { // TODO: (-nofl) unused DateTime card_start_trip_minutes_s = {0}; from_minutes_to_datetime( data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, @@ -798,7 +798,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 1992); } //trip_switch - if(data_block.passage_5_minutes) { + if(data_block.passage_5_minutes) { // TODO: (-nofl) unused DateTime card_start_switch_trip_minutes_s = {0}; from_minutes_to_datetime( data_block.start_trip_date * 24 * 60 + data_block.start_trip_time + @@ -808,7 +808,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* } //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } break; } @@ -870,10 +870,10 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* furi_string_cat(transport, ""); break; } - furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + furi_string_cat_printf(result, "Transport: %s", furi_string_get_cstr(transport)); //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } furi_string_free(transport); break; @@ -899,7 +899,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); furi_string_cat_printf( result, - "Valid from: %02d.%02d.%04d\n", + "Valid from: %02d.%02d.%04d", card_valid_from_date_s.day, card_valid_from_date_s.month, card_valid_from_date_s.year); @@ -912,7 +912,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 1992); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "\nValid to: %02d.%02d.%04d", card_valid_to_date_s.day, card_valid_to_date_s.month, card_valid_to_date_s.year); @@ -924,7 +924,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 1992); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "\nValid to: %02d.%02d.%04d", card_valid_to_date_s.day, card_valid_to_date_s.month, card_valid_to_date_s.year); @@ -939,7 +939,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 1992); //-time furi_string_cat_printf( result, - "Trip from: %02d.%02d.%04d %02d:%02d\n", + "\nTrip from: %02d.%02d.%04d %02d:%02d", card_start_trip_minutes_s.day, card_start_trip_minutes_s.month, card_start_trip_minutes_s.year, @@ -956,7 +956,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 1992); furi_string_cat_printf( result, - "Trip switch: %02d.%02d.%04d %02d:%02d\n", + "\nTrip switch: %02d.%02d.%04d %02d:%02d", card_start_switch_trip_minutes_s.day, card_start_switch_trip_minutes_s.month, card_start_switch_trip_minutes_s.year, @@ -965,7 +965,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* } //transport FuriString* transport = furi_string_alloc(); - switch(data_block.transport_type) { + switch(data_block.transport_type) { // TODO: (-nofl) unused case 1: furi_string_cat(transport, "Metro"); break; @@ -981,7 +981,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* } //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } furi_string_free(transport); break; @@ -1094,14 +1094,14 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - data_block.start_trip_neg_minutes, &card_start_trip_minutes_s, - 2016); + 2016); // TODO: (-nofl) unused //transport FuriString* transport = furi_string_alloc(); parse_transport_type(&data_block, transport); - furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + furi_string_cat_printf(result, "Transport: %s", furi_string_get_cstr(transport)); // validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } furi_string_free(transport); break; @@ -1121,7 +1121,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* card_use_before_date_s.month, card_use_before_date_s.year); //remaining_funds - furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100); + furi_string_cat_printf(result, "Balance: %ld rub", data_block.remaining_funds / 100); //start_trip_minutes if(data_block.start_trip_minutes) { DateTime card_start_trip_minutes_s = {0}; @@ -1129,7 +1129,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* data_block.start_trip_minutes, &card_start_trip_minutes_s, 2019); furi_string_cat_printf( result, - "Trip from: %02d.%02d.%04d %02d:%02d\n", + "\nTrip from: %02d.%02d.%04d %02d:%02d", card_start_trip_minutes_s.day, card_start_trip_minutes_s.month, card_start_trip_minutes_s.year, @@ -1145,7 +1145,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2019); furi_string_cat_printf( result, - "(M) from: %02d.%02d.%04d %02d:%02d\n", + "\n(M) from: %02d.%02d.%04d %02d:%02d", card_start_m_trip_minutes_s.day, card_start_m_trip_minutes_s.month, card_start_m_trip_minutes_s.year, @@ -1160,7 +1160,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2019); furi_string_cat_printf( result, - "Trip edit: %02d.%02d.%04d %02d:%02d\n", + "\nTrip edit: %02d.%02d.%04d %02d:%02d", card_start_change_trip_minutes_s.day, card_start_change_trip_minutes_s.month, card_start_change_trip_minutes_s.year, @@ -1170,7 +1170,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* //transport //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } break; } @@ -1207,7 +1207,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2019); furi_string_cat_printf( result, - "Valid to: %02d.%02d.%04d\n", + "Valid to: %02d.%02d.%04d", card_use_to_date_s.day, card_use_to_date_s.month, card_use_to_date_s.year); @@ -1221,7 +1221,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2019); //-time furi_string_cat_printf( result, - "Trip from: %02d.%02d.%04d %02d:%02d\n", + "\nTrip from: %02d.%02d.%04d %02d:%02d", card_start_trip_minutes_s.day, card_start_trip_minutes_s.month, card_start_trip_minutes_s.year, @@ -1238,7 +1238,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* 2019); furi_string_cat_printf( result, - "(M) from: %02d.%02d.%04d %02d:%02d\n", + "\n(M) from: %02d.%02d.%04d %02d:%02d", card_start_trip_m_minutes_s.day, card_start_trip_m_minutes_s.month, card_start_trip_m_minutes_s.year, @@ -1248,7 +1248,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* //transport //validator if(data_block.validator) { - furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + furi_string_cat_printf(result, "\nValidator: %05d", data_block.validator); } break; } diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 84e92d3e0c9..cccee769fef 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -211,11 +211,11 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { if(result2) { furi_string_cat_printf( - parsed_data, "\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); + parsed_data, "\n\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); } if(result3) { - furi_string_cat_printf(parsed_data, "\e#TAT\n%s\n", furi_string_get_cstr(tat_result)); + furi_string_cat_printf(parsed_data, "\n\e#TAT\n%s", furi_string_get_cstr(tat_result)); } furi_string_free(tat_result); From 286fd796e7c3a9a3619657d06d5dc049c62e3099 Mon Sep 17 00:00:00 2001 From: Leopold Date: Fri, 19 Apr 2024 20:11:22 +0800 Subject: [PATCH 20/22] add support for S(WTX) request in iso14443_4a_poller (#3590) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gornekich Co-authored-by: あく --- .../iso14443_4a/iso14443_4a_poller_i.c | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c index a4b39859501..961a61d7179 100644 --- a/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c +++ b/lib/nfc/protocols/iso14443_4a/iso14443_4a_poller_i.c @@ -7,6 +7,10 @@ #define TAG "Iso14443_4aPoller" #define ISO14443_4A_FSDI_256 (0x8U) +#define ISO14443_4A_FWT_MAX (4096UL << 14) +#define ISO14443_4A_WTXM_MASK (0x3FU) +#define ISO14443_4A_WTXM_MAX (0x3BU) +#define ISO14443_4A_SWTX (0xF2U) Iso14443_4aError iso14443_4a_poller_halt(Iso14443_4aPoller* instance) { furi_check(instance); @@ -74,9 +78,35 @@ Iso14443_4aError iso14443_4a_poller_send_block( if(iso14443_3a_error != Iso14443_3aErrorNone) { error = iso14443_4a_process_error(iso14443_3a_error); break; + } + + if(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)) { + do { + uint8_t wtxm = bit_buffer_get_byte(instance->rx_buffer, 1) & ISO14443_4A_WTXM_MASK; + if(wtxm > ISO14443_4A_WTXM_MAX) { + return Iso14443_4aErrorProtocol; + } + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_copy_left(instance->tx_buffer, instance->rx_buffer, 1); + bit_buffer_append_byte(instance->tx_buffer, wtxm); + + iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + instance->iso14443_3a_poller, + instance->tx_buffer, + instance->rx_buffer, + MAX(iso14443_4a_get_fwt_fc_max(instance->data) * wtxm, ISO14443_4A_FWT_MAX)); + + if(iso14443_3a_error != Iso14443_3aErrorNone) { + error = iso14443_4a_process_error(iso14443_3a_error); + return error; + } + + } while(bit_buffer_starts_with_byte(instance->rx_buffer, ISO14443_4A_SWTX)); + } - } else if(!iso14443_4_layer_decode_block( - instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { + if(!iso14443_4_layer_decode_block( + instance->iso14443_4_layer, rx_buffer, instance->rx_buffer)) { error = Iso14443_4aErrorProtocol; break; } From 9bef2aaec6ff8bc3deb991206e66dd7866f42ea7 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+Willy-JL@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:10:48 +0100 Subject: [PATCH 21/22] GUI: Fix array out of bounds in menu exit (#3604) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * GUI: Fix array out of bounds in menu exit * Gui: fix incorrect empty menu handling * Gui: add missing item check in menu ok handling * Gui: remove dead code from menu module Co-authored-by: あく --- applications/services/gui/modules/menu.c | 58 ++++++++++-------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/applications/services/gui/modules/menu.c b/applications/services/gui/modules/menu.c index f0a3b82b5a0..84529519338 100644 --- a/applications/services/gui/modules/menu.c +++ b/applications/services/gui/modules/menu.c @@ -44,25 +44,19 @@ static void menu_draw_callback(Canvas* canvas, void* _model) { canvas_set_font(canvas, FontSecondary); shift_position = (0 + position + items_count - 1) % items_count; item = MenuItemArray_get(model->items, shift_position); - if(item->icon) { - canvas_draw_icon_animation(canvas, 4, 3, item->icon); - } + canvas_draw_icon_animation(canvas, 4, 3, item->icon); canvas_draw_str(canvas, 22, 14, item->label); // Second line main canvas_set_font(canvas, FontPrimary); shift_position = (1 + position + items_count - 1) % items_count; item = MenuItemArray_get(model->items, shift_position); - if(item->icon) { - canvas_draw_icon_animation(canvas, 4, 25, item->icon); - } + canvas_draw_icon_animation(canvas, 4, 25, item->icon); canvas_draw_str(canvas, 22, 36, item->label); // Third line canvas_set_font(canvas, FontSecondary); shift_position = (2 + position + items_count - 1) % items_count; item = MenuItemArray_get(model->items, shift_position); - if(item->icon) { - canvas_draw_icon_animation(canvas, 4, 47, item->icon); - } + canvas_draw_icon_animation(canvas, 4, 47, item->icon); canvas_draw_str(canvas, 22, 58, item->label); // Frame and scrollbar elements_frame(canvas, 0, 21, 128 - 5, 21); @@ -107,8 +101,8 @@ static void menu_enter(void* context) { menu->view, MenuModel * model, { - MenuItem* item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + if(MenuItemArray_size(model->items)) { + MenuItem* item = MenuItemArray_get(model->items, model->position); icon_animation_start(item->icon); } }, @@ -121,8 +115,8 @@ static void menu_exit(void* context) { menu->view, MenuModel * model, { - MenuItem* item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + if(MenuItemArray_size(model->items)) { + MenuItem* item = MenuItemArray_get(model->items, model->position); icon_animation_stop(item->icon); } }, @@ -230,19 +224,17 @@ static void menu_process_up(Menu* menu) { menu->view, MenuModel * model, { - MenuItem* item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + if(MenuItemArray_size(model->items)) { + MenuItem* item = MenuItemArray_get(model->items, model->position); icon_animation_stop(item->icon); - } - if(model->position > 0) { - model->position--; - } else { - model->position = MenuItemArray_size(model->items) - 1; - } + if(model->position > 0) { + model->position--; + } else { + model->position = MenuItemArray_size(model->items) - 1; + } - item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + item = MenuItemArray_get(model->items, model->position); icon_animation_start(item->icon); } }, @@ -254,19 +246,17 @@ static void menu_process_down(Menu* menu) { menu->view, MenuModel * model, { - MenuItem* item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + if(MenuItemArray_size(model->items)) { + MenuItem* item = MenuItemArray_get(model->items, model->position); icon_animation_stop(item->icon); - } - if(model->position < MenuItemArray_size(model->items) - 1) { - model->position++; - } else { - model->position = 0; - } + if(model->position < MenuItemArray_size(model->items) - 1) { + model->position++; + } else { + model->position = 0; + } - item = MenuItemArray_get(model->items, model->position); - if(item && item->icon) { + item = MenuItemArray_get(model->items, model->position); icon_animation_start(item->icon); } }, @@ -279,7 +269,7 @@ static void menu_process_ok(Menu* menu) { menu->view, MenuModel * model, { - if(model->position < MenuItemArray_size(model->items)) { + if(MenuItemArray_size(model->items)) { item = MenuItemArray_get(model->items, model->position); } }, From c87683596abf50764d078598fe1f8e0d704e2bb3 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 19 Apr 2024 15:22:07 +0100 Subject: [PATCH 22/22] nfc app: add legacy keys for plantain cards (#3602) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../nfc/plugins/supported_cards/plantain.c | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 03fa6495a5c..ef46efd65e9 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -60,6 +60,29 @@ static const MfClassicKeyPair plantain_4k_keys[] = { {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, }; +static const MfClassicKeyPair plantain_4k_keys_legacy[] = { + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, + {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, + {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff}, + {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, + {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, + {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056}, + {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, + {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x46d78e850a7e, .b = 0xa470f8130991}, + {.a = 0x42e9b54e51ab, .b = 0x0231b86df52e}, {.a = 0x0f01ceff2742, .b = 0x6fec74559ca7}, + {.a = 0xb81f2b0c2f66, .b = 0xa7e2d95f0003}, {.a = 0x9ea3387a63c1, .b = 0x437e59f57561}, + {.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, + {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, + {.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461}, + {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, + {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085}, +}; + static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) { bool success = true; @@ -126,6 +149,21 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { PlantainCardConfig cfg = {}; if(!plantain_get_card_config(&cfg, data->type)) break; + const uint8_t legacy_check_sec_num = 26; + const uint8_t legacy_check_block_num = + mf_classic_get_first_block_num_of_sector(legacy_check_sec_num); + + MfClassicKey key = {0}; + bit_lib_num_to_bytes_be( + plantain_4k_keys_legacy[legacy_check_sec_num].a, COUNT_OF(key.data), key.data); + + error = mf_classic_poller_sync_auth( + nfc, legacy_check_block_num, &key, MfClassicKeyTypeA, NULL); + if(error == MfClassicErrorNone) { + FURI_LOG_D(TAG, "Legacy keys detected"); + cfg.keys = plantain_4k_keys_legacy; + } + MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);