diff --git a/ChangeLog.md b/ChangeLog.md index 5ecb66162..bb0e18e5e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,48 +1,17 @@ ### Breaking Changes: -- OFW: Icons: Some icons replaced and renamed - - If your Asset Packs use these icons they need to be updated - - Pre-included asset packs are already updated to new icons - - `Settings/Cry_dolph_55x52` -> `Settings/dolph_cry_49x54` - - `About/CertificationChina1_122x47` -> `About/CertificationChina1_124x47` +- OFW: JS: Renamed `textbox.emptyText()` to `textbox.clearText()` + - If your JS scripts use these functions they need to be updated + - Same functionality, just different naming chosen upstream ### Added: -- Apps: - - NFC: Mifare Nested (by @AloneLiberty, ported with nfclegacy by @xMasterX) - - Infrared: Cross Remote (by @leedave) - - Games: Color Guess (by @leedave) -- MNTM Settings: Add warning screens for SubGHz bypass and extend (by @Willy-JL) -- SubGHz: Show reason for TX blocked (by @Willy-JL) -- SubGHz: New decoder API `get_string_brief` for short info of a received signal (#119 by @user890104) -- SubGHz: New APIs `furi_hal_subghz_check_tx(freq)` and `subghz_devices_check_tx(dev, freq)` to know if and why TX is blocked (by @Willy-JL) -- OFW: NFC: Skylanders plugin (by @bettse) -- OFW: Desktop: New Akira animation (by @Astrrra) -- OFW: Loader: Add support for R_ARM_REL32 relocations (by @Sameesunkaria) -- OFW: BLE: New connection parameters negotiation scheme (by @skotopes) -- OFW: GUI: Add `ViewHolder` to API (by @nminaylov) +- OFW: NFC: Add Slix capabilities, some bugfixes (by @gornekich) +- OFW: JS: Added `math.is_equal()` and `math.EPSILON` (by @skotopes) ### Updated: -- Apps: - - UL: BT/USB Remote: Split into Mouse Jiggler and Mouse Jiggler Stealth (by @xMasterX) - - Magspoof: GUI and Settings fixes (by @zacharyweiss) - - Slots: Allow balancing of the bet to the user (by @DefinetlyNotAI) - - Count Down Timer: Fix crash below 1 sec (by @0w0miao & @Willy-JL) - - NRF24 Mouse Jacker: Improve addresses.txt reading (by @Willy-JL) - - UL: Metronome: Fix crash (by @xMasterX) - - OFW: NFC Magic: Fix user dict attack results being discarded (by @Astrrra) -- SubGHz: Increased deduplication threshold (500ms to 600ms) to fit Hormann BiSecure remotes (#119 by @user890104) -- OFW: Infrared: Updated universals assets (by @hakuyoku2011) -- OFW: Settings: Settings menu refactoring (by @Astrrra) -- OFW: FuriHal: Move version init to early stage (by @skotopes) -- OFW: JS: Submenu module refactored (by @nminaylov) +- OFW: JS: Refactored and fixed `math` and `textbox` modules (by @nminaylov & @skotopes) ### Fixed: -- OFW: SubGHz: Fix memory corrupt in read raw view crash (by @DrZlo13) -- SubGHz: Improved readability of Hormann BiSecur signals (#119 by @user890104) -- SubGHz: External modules follow extended and bypass settings correctly (by @Willy-JL) -- SubGHz: Fixed restoring RX only frequency (by @Willy-JL) -- SubGHz: Fixed crash when setting frequencies near range limits (by @Willy-JL) -- SubGHz: Fix Radio Device Loader loading GPS plugin (by @Willy-JL) -- Archive: Fixed hidden files in non-browser tabs (by @Willy-JL) +- OFW: Settings: Refactor fixes (by @Astrrra) ### Removed: - Nothing diff --git a/applications/debug/unit_tests/nfc/nfc_test.c b/applications/debug/unit_tests/nfc/nfc_test.c index c6304d53c..8bb88df9a 100644 --- a/applications/debug/unit_tests/nfc/nfc_test.c +++ b/applications/debug/unit_tests/nfc/nfc_test.c @@ -13,6 +13,12 @@ #include #include #include +#include +#include +#include +#include +#include + #include #include @@ -42,6 +48,19 @@ typedef struct { FuriThreadId thread_id; } NfcTestMfClassicSendFrameTest; +typedef enum { + NfcTestSlixPollerSetPasswordStateGetRandomNumber, + NfcTestSlixPollerSetPasswordStateSetPassword, +} NfcTestSlixPollerSetPasswordState; + +typedef struct { + FuriThreadId thread_id; + NfcTestSlixPollerSetPasswordState state; + SlixRandomNumber random_number; + SlixPassword password; + SlixError error; +} NfcTestSlixPollerSetPasswordContext; + typedef struct { Storage* storage; } NfcTest; @@ -627,6 +646,127 @@ MU_TEST(mf_classic_dict_test) { "Remove test dict failed"); } +MU_TEST(slix_file_with_capabilities_test) { + NfcDevice* nfc_device_missed_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_missed_cap, EXT_PATH("unit_tests/nfc/Slix_cap_missed.nfc")), + "nfc_device_load() failed\r\n"); + + NfcDevice* nfc_device_default_cap = nfc_device_alloc(); + mu_assert( + nfc_device_load(nfc_device_default_cap, EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc")), + "nfc_device_load() failed\r\n"); + + mu_assert( + nfc_device_is_equal(nfc_device_missed_cap, nfc_device_default_cap), + "nfc_device_is_equal() failed\r\n"); + + nfc_device_free(nfc_device_default_cap); + nfc_device_free(nfc_device_missed_cap); +} + +NfcCommand slix_poller_set_password_callback(NfcGenericEventEx event, void* context) { + furi_check(event.poller); + furi_check(event.parent_event_data); + furi_check(context); + + NfcCommand command = NfcCommandContinue; + Iso15693_3PollerEvent* iso15_event = event.parent_event_data; + SlixPoller* poller = event.poller; + NfcTestSlixPollerSetPasswordContext* slix_ctx = context; + + if(iso15_event->type == Iso15693_3PollerEventTypeReady) { + iso15693_3_copy( + poller->data->iso15693_3_data, iso15693_3_poller_get_data(poller->iso15693_3_poller)); + + if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateGetRandomNumber) { + slix_ctx->error = slix_poller_get_random_number(poller, &slix_ctx->random_number); + if(slix_ctx->error != SlixErrorNone) { + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } else { + slix_ctx->state = NfcTestSlixPollerSetPasswordStateSetPassword; + } + } else if(slix_ctx->state == NfcTestSlixPollerSetPasswordStateSetPassword) { + slix_ctx->error = slix_poller_set_password( + poller, SlixPasswordTypeRead, slix_ctx->password, slix_ctx->random_number); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + } else { + slix_ctx->error = slix_process_iso15693_3_error(iso15_event->data->error); + furi_thread_flags_set(slix_ctx->thread_id, NFC_TEST_FLAG_WORKER_DONE); + command = NfcCommandStop; + } + + return command; +} + +static void slix_set_password_test(const char* file_path, SlixPassword pass, bool correct_pass) { + FURI_LOG_I(TAG, "Testing file: %s", file_path); + + Nfc* poller = nfc_alloc(); + Nfc* listener = nfc_alloc(); + + NfcDevice* nfc_device = nfc_device_alloc(); + mu_assert(nfc_device_load(nfc_device, file_path), "nfc_device_load() failed\r\n"); + + const SlixData* slix_data = nfc_device_get_data(nfc_device, NfcProtocolSlix); + NfcListener* slix_listener = nfc_listener_alloc(listener, NfcProtocolSlix, slix_data); + nfc_listener_start(slix_listener, NULL, NULL); + + SlixCapabilities slix_capabilities = slix_data->capabilities; + + NfcPoller* slix_poller = nfc_poller_alloc(poller, NfcProtocolSlix); + + NfcTestSlixPollerSetPasswordContext slix_poller_context = { + .thread_id = furi_thread_get_current_id(), + .state = NfcTestSlixPollerSetPasswordStateGetRandomNumber, + .password = pass, + .error = SlixErrorNone, + }; + + nfc_poller_start_ex(slix_poller, slix_poller_set_password_callback, &slix_poller_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\r\n"); + + nfc_poller_stop(slix_poller); + nfc_poller_free(slix_poller); + nfc_listener_stop(slix_listener); + nfc_listener_free(slix_listener); + + mu_assert( + slix_poller_context.state == NfcTestSlixPollerSetPasswordStateSetPassword, + "Poller failed before setting password\r\n"); + + if((slix_capabilities == SlixCapabilitiesAcceptAllPasswords) || (correct_pass)) { + mu_assert(slix_poller_context.error == SlixErrorNone, "Failed to set password\r\n"); + } else { + mu_assert( + slix_poller_context.error == SlixErrorTimeout, + "Must have received SlixErrorTimeout\r\n"); + } + + nfc_device_free(nfc_device); + nfc_free(listener); + nfc_free(poller); +} + +MU_TEST(slix_set_password_default_cap_correct_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x00000000, true); +} + +MU_TEST(slix_set_password_default_cap_incorrect_pass) { + slix_set_password_test(EXT_PATH("unit_tests/nfc/Slix_cap_default.nfc"), 0x12341234, false); +} + +MU_TEST(slix_set_password_access_all_passwords_cap) { + slix_set_password_test( + EXT_PATH("unit_tests/nfc/Slix_cap_accept_all_pass.nfc"), 0x12341234, false); +} + MU_TEST_SUITE(nfc) { nfc_test_alloc(); @@ -668,6 +808,11 @@ MU_TEST_SUITE(nfc) { MU_RUN_TEST(mf_classic_send_frame_test); MU_RUN_TEST(mf_classic_dict_test); + MU_RUN_TEST(slix_file_with_capabilities_test); + MU_RUN_TEST(slix_set_password_default_cap_correct_pass); + MU_RUN_TEST(slix_set_password_default_cap_incorrect_pass); + MU_RUN_TEST(slix_set_password_access_all_passwords_cap); + nfc_test_free(); } diff --git a/applications/debug/unit_tests/nfc/nfc_transport.c b/applications/debug/unit_tests/nfc/nfc_transport.c index 6886ef66d..df0e009e7 100644 --- a/applications/debug/unit_tests/nfc/nfc_transport.c +++ b/applications/debug/unit_tests/nfc/nfc_transport.c @@ -55,6 +55,7 @@ struct Nfc { Iso14443_3aColResStatus col_res_status; Iso14443_3aColResData col_res_data; + bool software_col_res_required; NfcEventCallback callback; void* context; @@ -170,6 +171,7 @@ NfcError nfc_iso14443a_listener_set_col_res_data( furi_check(atqa); nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak); + instance->software_col_res_required = true; return NfcErrorNone; } @@ -275,7 +277,8 @@ static int32_t nfc_worker_listener(void* context) { } else if(message.type == NfcMessageTypeTx) { nfc_test_print( NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits); - if(instance->col_res_status != Iso14443_3aColResStatusDone) { + if(instance->software_col_res_required && + (instance->col_res_status != Iso14443_3aColResStatusDone)) { nfc_worker_listener_pass_col_res( instance, message.data.data, message.data.data_bits); } else { diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc new file mode 100644 index 000000000..0a8db9ae9 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_accept_all_pass.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: AcceptAllPasswords +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc new file mode 100644 index 000000000..d1af957dc --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_default.nfc @@ -0,0 +1,41 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# SLIX capabilities field affects emulation modes. Possible options: Default, AcceptAllPasswords +Capabilities: Default +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc new file mode 100644 index 000000000..35650b0e8 --- /dev/null +++ b/applications/debug/unit_tests/resources/unit_tests/nfc/Slix_cap_missed.nfc @@ -0,0 +1,39 @@ +Filetype: Flipper NFC device +Version: 4 +# Device type can be ISO14443-3A, ISO14443-3B, ISO14443-4A, ISO14443-4B, ISO15693-3, FeliCa, NTAG/Ultralight, Mifare Classic, Mifare DESFire, SLIX, ST25TB +Device type: SLIX +# UID is common for all formats +UID: E0 04 01 08 49 D0 DC 81 +# ISO15693-3 specific data +# Data Storage Format Identifier +DSFID: 01 +# Application Family Identifier +AFI: 3D +# IC Reference - Vendor specific meaning +IC Reference: 01 +# Lock Bits +Lock DSFID: true +Lock AFI: true +# Number of memory blocks, valid range = 1..256 +Block Count: 80 +# Size of a single memory block, valid range = 01...20 (hex) +Block Size: 04 +Data Content: 03 0A 82 ED 86 39 61 D2 03 14 1E 32 B6 CA 00 3C 36 42 0C 33 53 30 37 32 32 34 30 30 00 00 00 00 00 FF 04 01 01 00 00 00 A3 03 1E 00 26 00 00 00 00 00 0F 00 76 03 65 01 00 00 00 00 85 01 34 00 75 09 05 00 01 00 00 00 00 00 00 00 00 00 00 00 D7 FA 00 1C 9E 1C 67 27 00 30 30 30 30 30 30 30 30 30 30 00 00 00 97 25 55 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 32 8C 00 30 53 48 80 DE 00 00 00 00 F4 C3 58 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 11 F3 00 2C DD C3 3E 91 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 E5 FF 00 01 +# Block Security Status: 01 = locked, 00 = not locked +Security Status: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# SLIX specific data +# Passwords are optional. If a password is omitted, a default value will be used +Password Read: 00 00 00 00 +Password Write: 00 00 00 00 +Password Privacy: 0F 0F 0F 0F +Password Destroy: 0F 0F 0F 0F +Password EAS: 00 00 00 00 +# This is the card's secp128r1 elliptic curve signature. It can not be calculated without knowing NXP's private key. +Signature: A6 25 54 03 74 24 C4 38 36 F4 89 70 76 1A 72 27 54 D9 E7 3D 38 CB 4C 1B 3E FD 0E DF 8A F6 7E 3D +Privacy Mode: false +# Protection pointer configuration +Protection Pointer: 32 +Protection Condition: 02 +# SLIX Lock Bits +Lock EAS: true +Lock PPL: true diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c index d004a3f02..0c136e05d 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_disable.c @@ -26,7 +26,7 @@ void desktop_settings_scene_pin_disable_on_enter(void* context) { popup_set_context(app->popup, app); popup_set_callback(app->popup, pin_disable_back_callback); popup_set_icon(app->popup, 0, 2, &I_DolphinMafia_119x62); - popup_set_header(app->popup, "PIN\nDeleted!", 100, 0, AlignCenter, AlignTop); + popup_set_header(app->popup, "Removed", 100, 10, AlignCenter, AlignTop); popup_set_timeout(app->popup, 1500); popup_enable_timeout(app->popup); view_dispatcher_switch_to_view(app->view_dispatcher, DesktopSettingsAppViewIdPopup); diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c index 78d2c389c..c84eb6407 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_pin_menu.c @@ -38,7 +38,7 @@ void desktop_settings_scene_pin_menu_on_enter(void* context) { submenu_add_item( submenu, - "Disable", + "Remove PIN", SCENE_EVENT_DISABLE_PIN, desktop_settings_scene_pin_menu_submenu_callback, app); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c index f041a5f9e..700391c3b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -55,8 +55,8 @@ void storage_settings_scene_formatting_on_enter(void* context) { if(scene_manager_get_scene_state(app->scene_manager, StorageSettingsFormatting)) { power_reboot(PowerBootModeNormal); } else { - dialog_ex_set_icon(dialog_ex, 83, 22, &I_WarningDolphinFlip_45x42); - dialog_ex_set_header(dialog_ex, "Format\ncomplete!", 14, 15, AlignLeft, AlignTop); + dialog_ex_set_icon(dialog_ex, 48, 6, &I_DolphinDone_80x58); + dialog_ex_set_header(dialog_ex, "Formatted", 5, 10, AlignLeft, AlignTop); NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); notification_message(notification, &sequence_single_vibro); notification_message(notification, &sequence_set_green_255); @@ -64,7 +64,7 @@ void storage_settings_scene_formatting_on_enter(void* context) { furi_record_close(RECORD_NOTIFICATION); } } - dialog_ex_set_center_button_text(dialog_ex, "OK"); + dialog_ex_set_left_button_text(dialog_ex, "Finish"); } bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent event) { @@ -73,7 +73,7 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DialogExResultCenter: + case DialogExResultLeft: consumed = scene_manager_search_and_switch_to_previous_scene( app->scene_manager, StorageSettingsStart); break; diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/math.js b/applications/system/js_app/examples/apps/Scripts/Examples/math.js index 49212f904..c5a0bf18d 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/math.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/math.js @@ -1,47 +1,69 @@ let math = require("math"); -let absResult = math.abs(-5); -let acosResult = math.acos(0.5); -let acoshResult = math.acosh(2); -let asinResult = math.asin(0.5); -let asinhResult = math.asinh(2); -let atanResult = math.atan(1); -let atan2Result = math.atan2(1, 1); -let atanhResult = math.atanh(0.5); -let cbrtResult = math.cbrt(27); -let ceilResult = math.ceil(5.3); -let clz32Result = math.clz32(1); -let cosResult = math.cos(math.PI); -let expResult = math.exp(1); -let floorResult = math.floor(5.7); -let maxResult = math.max(3, 5); -let minResult = math.min(3, 5); -let powResult = math.pow(2, 3); -let randomResult = math.random(); -let signResult = math.sign(-5); -let sinResult = math.sin(math.PI / 2); -let sqrtResult = math.sqrt(25); -let truncResult = math.trunc(5.7); +print("math.abs(-5):", math.abs(-5)); +print("math.acos(0.5):", math.acos(0.5)); +print("math.acosh(2):", math.acosh(2)); +print("math.asin(0.5):", math.asin(0.5)); +print("math.asinh(2):", math.asinh(2)); +print("math.atan(1):", math.atan(1)); +print("math.atan2(1, 1):", math.atan2(1, 1)); +print("math.atanh(0.5):", math.atanh(0.5)); +print("math.cbrt(27):", math.cbrt(27)); +print("math.ceil(5.3):", math.ceil(5.3)); +print("math.clz32(1):", math.clz32(1)); +print("math.cos(math.PI):", math.cos(math.PI)); +print("math.exp(1):", math.exp(1)); +print("math.floor(5.7):", math.floor(5.7)); +print("math.max(3, 5):", math.max(3, 5)); +print("math.min(3, 5):", math.min(3, 5)); +print("math.pow(2, 3):", math.pow(2, 3)); +print("math.random():", math.random()); +print("math.sign(-5):", math.sign(-5)); +print("math.sin(math.PI/2):", math.sin(math.PI / 2)); +print("math.sqrt(25):", math.sqrt(25)); +print("math.trunc(5.7):", math.trunc(5.7)); -print("math.abs(-5):", absResult); -print("math.acos(0.5):", acosResult); -print("math.acosh(2):", acoshResult); -print("math.asin(0.5):", asinResult); -print("math.asinh(2):", asinhResult); -print("math.atan(1):", atanResult); -print("math.atan2(1, 1):", atan2Result); -print("math.atanh(0.5):", atanhResult); -print("math.cbrt(27):", cbrtResult); -print("math.ceil(5.3):", ceilResult); -print("math.clz32(1):", clz32Result); -print("math.cos(math.PI):", cosResult); -print("math.exp(1):", expResult); -print("math.floor(5.7):", floorResult); -print("math.max(3, 5):", maxResult); -print("math.min(3, 5):", minResult); -print("math.pow(2, 3):", powResult); -print("math.random():", randomResult); -print("math.sign(-5):", signResult); -print("math.sin(math.PI/2):", sinResult); -print("math.sqrt(25):", sqrtResult); -print("math.trunc(5.7):", truncResult); \ No newline at end of file +// Unit tests. Please add more if you have time and knowledge. +// math.EPSILON on Flipper Zero is 2.22044604925031308085e-16 + +let succeeded = 0; +let failed = 0; + +function test(text, result, expected, epsilon) { + let is_equal = math.is_equal(result, expected, epsilon); + if (is_equal) { + succeeded += 1; + } else { + failed += 1; + print(text, "expected", expected, "got", result); + } +} + +test("math.abs(5)", math.abs(-5), 5, math.EPSILON); +test("math.abs(0.5)", math.abs(-0.5), 0.5, math.EPSILON); +test("math.abs(5)", math.abs(5), 5, math.EPSILON); +test("math.abs(-0.5)", math.abs(0.5), 0.5, math.EPSILON); +test("math.acos(0.5)", math.acos(0.5), 1.0471975511965976, math.EPSILON); +test("math.acosh(2)", math.acosh(2), 1.3169578969248166, math.EPSILON); +test("math.asin(0.5)", math.asin(0.5), 0.5235987755982988, math.EPSILON); +test("math.asinh(2)", math.asinh(2), 1.4436354751788103, math.EPSILON); +test("math.atan(1)", math.atan(1), 0.7853981633974483, math.EPSILON); +test("math.atan2(1, 1)", math.atan2(1, 1), 0.7853981633974483, math.EPSILON); +test("math.atanh(0.5)", math.atanh(0.5), 0.5493061443340549, math.EPSILON); +test("math.cbrt(27)", math.cbrt(27), 3, math.EPSILON); +test("math.ceil(5.3)", math.ceil(5.3), 6, math.EPSILON); +test("math.clz32(1)", math.clz32(1), 31, math.EPSILON); +test("math.floor(5.7)", math.floor(5.7), 5, math.EPSILON); +test("math.max(3, 5)", math.max(3, 5), 5, math.EPSILON); +test("math.min(3, 5)", math.min(3, 5), 3, math.EPSILON); +test("math.pow(2, 3)", math.pow(2, 3), 8, math.EPSILON); +test("math.sign(-5)", math.sign(-5), -1, math.EPSILON); +test("math.sqrt(25)", math.sqrt(25), 5, math.EPSILON); +test("math.trunc(5.7)", math.trunc(5.7), 5, math.EPSILON); +test("math.cos(math.PI)", math.cos(math.PI), -1, math.EPSILON * 18); // Error 3.77475828372553223744e-15 +test("math.exp(1)", math.exp(1), 2.718281828459045, math.EPSILON * 2); // Error 4.44089209850062616169e-16 +test("math.sin(math.PI / 2)", math.sin(math.PI / 2), 1, math.EPSILON * 4.5); // Error 9.99200722162640886381e-16 + +if (failed > 0) { + print("!!!", failed, "Unit tests failed !!!"); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/textbox.js b/applications/system/js_app/examples/apps/Scripts/Examples/textbox.js index bb6c4fc23..6caf37234 100644 --- a/applications/system/js_app/examples/apps/Scripts/Examples/textbox.js +++ b/applications/system/js_app/examples/apps/Scripts/Examples/textbox.js @@ -4,9 +4,9 @@ let textbox = require("textbox"); // Focus (start / end), Font (text / hex) textbox.setConfig("end", "text"); -// Can make sure it's empty before showing, in case of reusing in same script -// (Closing textbox already empties the text, but maybe you added more in a loop for example) -textbox.emptyText(); +// Can make sure it's cleared before showing, in case of reusing in same script +// (Closing textbox already clears the text, but maybe you added more in a loop for example) +textbox.clearText(); // Add default text textbox.addText("Example dynamic updating textbox\n"); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 1dd34d57c..e1c21e9fb 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -389,7 +389,7 @@ static int32_t js_thread(void* arg) { } const char* stack_trace = mjs_get_stack_trace(mjs); if(stack_trace != NULL) { - FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); + FURI_LOG_E(TAG, "Stack trace:\r\n%s", stack_trace); if(worker->app_callback) { worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); } diff --git a/applications/system/js_app/modules/js_math.c b/applications/system/js_app/modules/js_math.c index e7daae41b..b4c1cdca2 100644 --- a/applications/system/js_app/modules/js_math.c +++ b/applications/system/js_app/modules/js_math.c @@ -1,268 +1,313 @@ #include "../js_modules.h" #include "furi_hal_random.h" +#include -#define JS_MATH_PI (double)3.14159265358979323846 -#define JS_MATH_E (double)2.7182818284590452354 +#define JS_MATH_PI ((double)M_PI) +#define JS_MATH_E ((double)M_E) +#define JS_MATH_EPSILON ((double)DBL_EPSILON) + +#define TAG "JsMath" static void ret_bad_args(struct mjs* mjs, const char* error) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error); - mjs_return(mjs, mjs_mk_undefined()); + mjs_return(mjs, MJS_UNDEFINED); } -static bool check_arg_count(struct mjs* mjs, size_t count) { +static bool check_args(struct mjs* mjs, size_t count) { size_t num_args = mjs_nargs(mjs); if(num_args != count) { ret_bad_args(mjs, "Wrong argument count"); return false; } + for(size_t i = 0; i < count; i++) { + if(!mjs_is_number(mjs_arg(mjs, i))) { + ret_bad_args(mjs, "Wrong argument type"); + return false; + } + } return true; } -void js_math_abs(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); +void js_math_is_equal(struct mjs* mjs) { + if(!check_args(mjs, 3)) { + return; } + + double a = mjs_get_double(mjs, mjs_arg(mjs, 0)); + double b = mjs_get_double(mjs, mjs_arg(mjs, 1)); + double e = mjs_get_double(mjs, mjs_arg(mjs, 2)); + double f = fabs(a - b); + + mjs_return(mjs, mjs_mk_boolean(mjs, (f <= e))); +} + +void js_math_abs(struct mjs* mjs) { + if(!check_args(mjs, 1)) { + return; + } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, x < 0 ? mjs_mk_number(mjs, -x) : mjs_arg(mjs, 0)); + + mjs_return(mjs, mjs_mk_number(mjs, fabs(x))); } void js_math_acos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < -1 || x > 1) { - ret_bad_args(mjs, "Invalid input value for Math.acos"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acos"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, JS_MATH_PI / (double)2 - atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, acos(x))); } void js_math_acosh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 1) { - ret_bad_args(mjs, "Invalid input value for Math.acosh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.acosh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x - (double)1.)))); } void js_math_asin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, atan(x / sqrt(1 - x * x)))); + + mjs_return(mjs, mjs_mk_number(mjs, asin(x))); } void js_math_asinh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + 1)))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x + sqrt(x * x + (double)1.)))); } void js_math_atan(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, atan(x))); } void js_math_atan2(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double y = mjs_get_double(mjs, mjs_arg(mjs, 0)); double x = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, atan2(y, x))); } void js_math_atanh(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x <= -1 || x >= 1) { - ret_bad_args(mjs, "Invalid input value for Math.atanh"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)-1. || x > (double)1.) { + ret_bad_args(mjs, "Invalid input value for math.atanh"); + return; } - mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log((1 + x) / (1 - x)))); + + mjs_return(mjs, mjs_mk_number(mjs, (double)0.5 * log(((double)1. + x) / ((double)1. - x)))); } void js_math_cbrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, pow(x, 1.0 / 3.0))); + + mjs_return(mjs, mjs_mk_number(mjs, cbrt(x))); } void js_math_ceil(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)(x + (double)0.5))); + mjs_return(mjs, mjs_mk_number(mjs, ceil(x))); } void js_math_clz32(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + unsigned int x = (unsigned int)mjs_get_int(mjs, mjs_arg(mjs, 0)); int count = 0; while(x) { x >>= 1; count++; } + mjs_return(mjs, mjs_mk_number(mjs, 32 - count)); } void js_math_cos(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); + mjs_return(mjs, mjs_mk_number(mjs, cos(x))); } void js_math_exp(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = 1; - double term = 1; - for(int i = 1; i < 100; i++) { - term *= x / i; - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, exp(x))); } void js_math_floor(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, (int)x)); + + mjs_return(mjs, mjs_mk_number(mjs, floor(x))); } void js_math_log(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); if(x <= 0) { - ret_bad_args(mjs, "Invalid input value for Math.log"); - mjs_return(mjs, MJS_UNDEFINED); + ret_bad_args(mjs, "Invalid input value for math.log"); + return; } - double result = 0; - while(x >= JS_MATH_E) { - x /= JS_MATH_E; - result++; - } - mjs_return(mjs, mjs_mk_number(mjs, result + log(x))); + + mjs_return(mjs, mjs_mk_number(mjs, log(x))); } void js_math_max(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x > y ? x : y)); } void js_math_min(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); double y = mjs_get_double(mjs, mjs_arg(mjs, 1)); + mjs_return(mjs, mjs_mk_number(mjs, x < y ? x : y)); } void js_math_pow(struct mjs* mjs) { - if(!check_arg_count(mjs, 2) || !mjs_is_number(mjs_arg(mjs, 0)) || - !mjs_is_number(mjs_arg(mjs, 1))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 2)) { + return; } + double base = mjs_get_double(mjs, mjs_arg(mjs, 0)); double exponent = mjs_get_double(mjs, mjs_arg(mjs, 1)); - double result = 1; - for(int i = 0; i < exponent; i++) { - result *= base; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, pow(base, exponent))); } void js_math_random(struct mjs* mjs) { - if(!check_arg_count(mjs, 0)) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 0)) { + return; } + + // double clearly provides more bits for entropy then we pack + // 32bit should be enough for now, but fix it maybe const uint32_t random_val = furi_hal_random_get(); - double rnd = (double)random_val / FURI_HAL_RANDOM_MAX; + double rnd = (double)random_val / (double)FURI_HAL_RANDOM_MAX; + mjs_return(mjs, mjs_mk_number(mjs, rnd)); } void js_math_sign(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x == 0 ? 0 : (x < 0 ? -1 : 1))); + + mjs_return( + mjs, + mjs_mk_number( + mjs, fabs(x) <= JS_MATH_EPSILON ? 0 : (x < (double)0. ? (double)-1.0 : (double)1.0))); } void js_math_sin(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - double result = x; - double term = x; - for(int i = 1; i < 10; i++) { - term *= -x * x / ((2 * i) * (2 * i + 1)); - result += term; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sin(x))); } void js_math_sqrt(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - if(x < 0) { - ret_bad_args(mjs, "Invalid input value for Math.sqrt"); - mjs_return(mjs, MJS_UNDEFINED); + if(x < (double)0.) { + ret_bad_args(mjs, "Invalid input value for math.sqrt"); + return; } - double result = 1; - while(result * result < x) { - result += (double)0.001; - } - mjs_return(mjs, mjs_mk_number(mjs, result)); + + mjs_return(mjs, mjs_mk_number(mjs, sqrt(x))); } void js_math_trunc(struct mjs* mjs) { - if(!check_arg_count(mjs, 1) || !mjs_is_number(mjs_arg(mjs, 0))) { - mjs_return(mjs, MJS_UNDEFINED); + if(!check_args(mjs, 1)) { + return; } + double x = mjs_get_double(mjs, mjs_arg(mjs, 0)); - mjs_return(mjs, mjs_mk_number(mjs, x < 0 ? ceil(x) : floor(x))); + + mjs_return(mjs, mjs_mk_number(mjs, x < (double)0. ? ceil(x) : floor(x))); } static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_val_t math_obj = mjs_mk_object(mjs); + mjs_set(mjs, math_obj, "is_equal", ~0, MJS_MK_FN(js_math_is_equal)); mjs_set(mjs, math_obj, "abs", ~0, MJS_MK_FN(js_math_abs)); mjs_set(mjs, math_obj, "acos", ~0, MJS_MK_FN(js_math_acos)); mjs_set(mjs, math_obj, "acosh", ~0, MJS_MK_FN(js_math_acosh)); @@ -288,6 +333,7 @@ static void* js_math_create(struct mjs* mjs, mjs_val_t* object) { mjs_set(mjs, math_obj, "trunc", ~0, MJS_MK_FN(js_math_trunc)); mjs_set(mjs, math_obj, "PI", ~0, mjs_mk_number(mjs, JS_MATH_PI)); mjs_set(mjs, math_obj, "E", ~0, mjs_mk_number(mjs, JS_MATH_E)); + mjs_set(mjs, math_obj, "EPSILON", ~0, mjs_mk_number(mjs, JS_MATH_EPSILON)); *object = math_obj; return (void*)1; } @@ -306,4 +352,4 @@ static const FlipperAppPluginDescriptor plugin_descriptor = { const FlipperAppPluginDescriptor* js_math_ep(void) { return &plugin_descriptor; -} \ No newline at end of file +} diff --git a/applications/system/js_app/modules/js_textbox.c b/applications/system/js_app/modules/js_textbox.c index cf4e8dbbf..d15cd2779 100644 --- a/applications/system/js_app/modules/js_textbox.c +++ b/applications/system/js_app/modules/js_textbox.c @@ -1,13 +1,12 @@ #include -#include -#include +#include #include "../js_modules.h" typedef struct { TextBox* text_box; - ViewDispatcher* view_dispatcher; - FuriThread* thread; + ViewHolder* view_holder; FuriString* text; + bool is_shown; } JsTextboxInst; static JsTextboxInst* get_this_ctx(struct mjs* mjs) { @@ -87,6 +86,9 @@ static void js_textbox_add_text(struct mjs* mjs) { return; } + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + size_t new_len = furi_string_size(textbox->text) + text_len; if(new_len >= 4096) { furi_string_right(textbox->text, new_len / 2); @@ -99,10 +101,13 @@ static void js_textbox_add_text(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static void js_textbox_empty_text(struct mjs* mjs) { +static void js_textbox_clear_text(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; + // Avoid condition race between GUI and JS thread + text_box_set_text(textbox->text_box, ""); + furi_string_reset(textbox->text); text_box_set_text(textbox->text_box, furi_string_get_cstr(textbox->text)); @@ -114,58 +119,34 @@ static void js_textbox_is_open(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - mjs_return(mjs, mjs_mk_boolean(mjs, !!textbox->thread)); -} - -static void textbox_deinit(void* context) { - JsTextboxInst* textbox = context; - furi_thread_join(textbox->thread); - furi_thread_free(textbox->thread); - textbox->thread = NULL; - - view_dispatcher_remove_view(textbox->view_dispatcher, 0); - view_dispatcher_free(textbox->view_dispatcher); - textbox->view_dispatcher = NULL; - furi_record_close(RECORD_GUI); - - text_box_reset(textbox->text_box); - furi_string_reset(textbox->text); + mjs_return(mjs, mjs_mk_boolean(mjs, textbox->is_shown)); } static void textbox_callback(void* context, uint32_t arg) { UNUSED(arg); - textbox_deinit(context); -} - -static bool textbox_exit(void* context) { JsTextboxInst* textbox = context; - view_dispatcher_stop(textbox->view_dispatcher); - furi_timer_pending_callback(textbox_callback, textbox, 0); - return true; + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; } -static int32_t textbox_thread(void* context) { - ViewDispatcher* view_dispatcher = context; - view_dispatcher_run(view_dispatcher); - return 0; +static void textbox_exit(void* context) { + JsTextboxInst* textbox = context; + // Using timer to schedule view_holder stop, will not work under high CPU load + furi_timer_pending_callback(textbox_callback, textbox, 0); } static void js_textbox_show(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - Gui* gui = furi_record_open(RECORD_GUI); - textbox->view_dispatcher = view_dispatcher_alloc(); - view_dispatcher_enable_queue(textbox->view_dispatcher); - view_dispatcher_add_view(textbox->view_dispatcher, 0, text_box_get_view(textbox->text_box)); - view_dispatcher_set_event_callback_context(textbox->view_dispatcher, textbox); - view_dispatcher_set_navigation_event_callback(textbox->view_dispatcher, textbox_exit); - view_dispatcher_attach_to_gui(textbox->view_dispatcher, gui, ViewDispatcherTypeFullscreen); - view_dispatcher_switch_to_view(textbox->view_dispatcher, 0); + if(textbox->is_shown) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Textbox is already shown"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } - textbox->thread = - furi_thread_alloc_ex("JsTextbox", 1024, textbox_thread, textbox->view_dispatcher); - furi_thread_start(textbox->thread); + view_holder_start(textbox->view_holder); + textbox->is_shown = true; mjs_return(mjs, MJS_UNDEFINED); } @@ -174,36 +155,49 @@ static void js_textbox_close(struct mjs* mjs) { JsTextboxInst* textbox = get_this_ctx(mjs); if(!check_arg_count(mjs, 0)) return; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + view_holder_stop(textbox->view_holder); + textbox->is_shown = false; mjs_return(mjs, MJS_UNDEFINED); } static void* js_textbox_create(struct mjs* mjs, mjs_val_t* object) { JsTextboxInst* textbox = malloc(sizeof(JsTextboxInst)); + mjs_val_t textbox_obj = mjs_mk_object(mjs); mjs_set(mjs, textbox_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, textbox)); mjs_set(mjs, textbox_obj, "setConfig", ~0, MJS_MK_FN(js_textbox_set_config)); mjs_set(mjs, textbox_obj, "addText", ~0, MJS_MK_FN(js_textbox_add_text)); - mjs_set(mjs, textbox_obj, "emptyText", ~0, MJS_MK_FN(js_textbox_empty_text)); + mjs_set(mjs, textbox_obj, "clearText", ~0, MJS_MK_FN(js_textbox_clear_text)); mjs_set(mjs, textbox_obj, "isOpen", ~0, MJS_MK_FN(js_textbox_is_open)); mjs_set(mjs, textbox_obj, "show", ~0, MJS_MK_FN(js_textbox_show)); mjs_set(mjs, textbox_obj, "close", ~0, MJS_MK_FN(js_textbox_close)); - textbox->text_box = text_box_alloc(); + textbox->text = furi_string_alloc(); + textbox->text_box = text_box_alloc(); + + Gui* gui = furi_record_open(RECORD_GUI); + textbox->view_holder = view_holder_alloc(); + view_holder_attach_to_gui(textbox->view_holder, gui); + view_holder_set_back_callback(textbox->view_holder, textbox_exit, textbox); + view_holder_set_view(textbox->view_holder, text_box_get_view(textbox->text_box)); + *object = textbox_obj; return textbox; } static void js_textbox_destroy(void* inst) { JsTextboxInst* textbox = inst; - if(textbox->thread) { - view_dispatcher_stop(textbox->view_dispatcher); - textbox_deinit(textbox); - } + + view_holder_stop(textbox->view_holder); + view_holder_free(textbox->view_holder); + textbox->view_holder = NULL; + + furi_record_close(RECORD_GUI); + + text_box_reset(textbox->text_box); + furi_string_reset(textbox->text); + text_box_free(textbox->text_box); furi_string_free(textbox->text); free(textbox); @@ -223,4 +217,4 @@ static const FlipperAppPluginDescriptor textbox_plugin_descriptor = { const FlipperAppPluginDescriptor* js_textbox_ep(void) { return &textbox_plugin_descriptor; -} +} \ No newline at end of file diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c index aae196599..bcdcb364a 100644 --- a/lib/mjs/mjs_core.c +++ b/lib/mjs/mjs_core.c @@ -280,7 +280,7 @@ static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset); int line_no = mjs_get_lineno_by_offset(mjs, offset); char* new_line = NULL; - const char* fmt = "at %s:%d\n"; + const char* fmt = "\tat %s:%d\r\n"; if(filename == NULL) { // fprintf( // stderr, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c index 84e750858..151e4ae4a 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -8,7 +8,7 @@ #define TAG "Iso15693_3Listener" -#define ISO15693_3_LISTENER_BUFFER_SIZE (64U) +#define ISO15693_3_LISTENER_BUFFER_SIZE (256U) Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { furi_assert(nfc); @@ -67,6 +67,7 @@ NfcCommand iso15693_3_listener_run(NfcGenericEvent event, void* context) { if(nfc_event->type == NfcEventTypeRxEnd) { BitBuffer* rx_buffer = nfc_event->data.buffer; + bit_buffer_reset(instance->tx_buffer); if(iso13239_crc_check(Iso13239CrcTypeDefault, rx_buffer)) { iso13239_crc_trim(rx_buffer); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c index a8dec7ae3..6132fbf47 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -64,7 +64,9 @@ static Iso15693_3Error iso15693_3_listener_inventory_handler( if(afi_flag) { const uint8_t afi = *data++; // When AFI flag is set, ignore non-matching requests - if(afi != instance->data->system_info.afi) break; + if(afi != 0) { + if(afi != instance->data->system_info.afi) break; + } } const uint8_t mask_len = *data++; @@ -260,16 +262,9 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( } const uint32_t block_index_start = request->first_block_num; - const uint32_t block_index_end = block_index_start + request->block_count; - - const uint32_t block_count = request->block_count + 1; - const uint32_t block_count_max = instance->data->system_info.block_count; - const uint32_t block_count_available = block_count_max - block_index_start; - - if(block_count > block_count_available) { - error = Iso15693_3ErrorInternal; - break; - } + const uint32_t block_index_end = + MIN((block_index_start + request->block_count + 1), + ((uint32_t)instance->data->system_info.block_count - 1)); error = iso15693_3_listener_extension_handler( instance, diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 533ecff74..f6ce885d4 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -14,6 +14,7 @@ #define SLIX_TYPE_INDICATOR_SLIX (0x02U) #define SLIX_TYPE_INDICATOR_SLIX2 (0x01U) +#define SLIX_CAPABILITIES_KEY "Capabilities" #define SLIX_PASSWORD_READ_KEY "Password Read" #define SLIX_PASSWORD_WRITE_KEY "Password Write" #define SLIX_PASSWORD_PRIVACY_KEY "Password Privacy" @@ -69,6 +70,11 @@ static const SlixTypeFeatures slix_type_features[] = { [SlixTypeSlix2] = SLIX_TYPE_FEATURES_SLIX2, }; +static const char* slix_capabilities_names[SlixCapabilitiesCount] = { + [SlixCapabilitiesDefault] = "Default", + [SlixCapabilitiesAcceptAllPasswords] = "AcceptAllPasswords", +}; + typedef struct { const char* key; SlixTypeFeatures feature_flag; @@ -110,6 +116,7 @@ void slix_reset(SlixData* data) { furi_check(data); iso15693_3_reset(data->iso15693_3_data); + data->capabilities = SlixCapabilitiesDefault; slix_password_set_defaults(data->passwords); memset(&data->system_info, 0, sizeof(SlixSystemInfo)); @@ -123,6 +130,7 @@ void slix_copy(SlixData* data, const SlixData* other) { furi_check(other); iso15693_3_copy(data->iso15693_3_data, other->iso15693_3_data); + data->capabilities = other->capabilities; memcpy(data->passwords, other->passwords, sizeof(SlixPassword) * SlixPasswordTypeCount); memcpy(data->signature, other->signature, sizeof(SlixSignature)); @@ -138,6 +146,30 @@ bool slix_verify(SlixData* data, const FuriString* device_type) { return false; } +static bool slix_load_capabilities(SlixData* data, FlipperFormat* ff) { + bool capabilities_loaded = false; + FuriString* capabilities_str = furi_string_alloc(); + + if(!flipper_format_read_string(ff, SLIX_CAPABILITIES_KEY, capabilities_str)) { + if(flipper_format_rewind(ff)) { + data->capabilities = SlixCapabilitiesDefault; + capabilities_loaded = true; + } + } else { + for(size_t i = 0; i < COUNT_OF(slix_capabilities_names); i++) { + if(furi_string_cmp_str(capabilities_str, slix_capabilities_names[i]) == 0) { + data->capabilities = i; + capabilities_loaded = true; + break; + } + } + } + + furi_string_free(capabilities_str); + + return capabilities_loaded; +} + static bool slix_load_passwords(SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -164,13 +196,14 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { furi_check(ff); bool loaded = false; - do { if(!iso15693_3_load(data->iso15693_3_data, ff, version)) break; const SlixType slix_type = slix_get_type(data); if(slix_type >= SlixTypeCount) break; + if(!slix_load_capabilities(data, ff)) break; + if(!slix_load_passwords(data->passwords, slix_type, ff)) break; if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { @@ -220,6 +253,33 @@ bool slix_load(SlixData* data, FlipperFormat* ff, uint32_t version) { return loaded; } +static bool slix_save_capabilities(const SlixData* data, FlipperFormat* ff) { + bool save_success = false; + + FuriString* tmp_str = furi_string_alloc(); + do { + furi_string_set_str( + tmp_str, "SLIX capabilities field affects emulation modes. Possible options: "); + for(size_t i = 0; i < SlixCapabilitiesCount; i++) { + furi_string_cat_str(tmp_str, slix_capabilities_names[i]); + if(i < SlixCapabilitiesCount - 1) { + furi_string_cat(tmp_str, ", "); + } + } + if(!flipper_format_write_comment_cstr(ff, furi_string_get_cstr(tmp_str))) break; + + if(!flipper_format_write_string_cstr( + ff, SLIX_CAPABILITIES_KEY, slix_capabilities_names[data->capabilities])) + break; + + save_success = true; + } while(false); + + furi_string_free(tmp_str); + + return save_success; +} + static bool slix_save_passwords(const SlixPassword* passwords, SlixType slix_type, FlipperFormat* ff) { bool ret = true; @@ -251,6 +311,8 @@ bool slix_save(const SlixData* data, FlipperFormat* ff) { if(!iso15693_3_save(data->iso15693_3_data, ff)) break; if(!flipper_format_write_comment_cstr(ff, SLIX_PROTOCOL_NAME " specific data")) break; + if(!slix_save_capabilities(data, ff)) break; + if(!flipper_format_write_comment_cstr( ff, "Passwords are optional. If a password is omitted, a default value will be used")) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index 2de26847a..cc2390c6e 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -91,12 +91,20 @@ typedef struct { SlixLockBits lock_bits; } SlixSystemInfo; +typedef enum { + SlixCapabilitiesDefault, + SlixCapabilitiesAcceptAllPasswords, + + SlixCapabilitiesCount, +} SlixCapabilities; + typedef struct { Iso15693_3Data* iso15693_3_data; SlixSystemInfo system_info; SlixSignature signature; SlixPassword passwords[SlixPasswordTypeCount]; SlixPrivacy privacy; + SlixCapabilities capabilities; } SlixData; SlixData* slix_alloc(void); diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index 15ab2cd3c..66c4241cb 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -54,6 +54,13 @@ static SlixError slix_listener_set_password( } SlixListenerSessionState* session_state = &instance->session_state; + + // With AcceptAllPassword capability set skip password validation + if(instance->data->capabilities == SlixCapabilitiesAcceptAllPasswords) { + session_state->password_match[password_type] = true; + break; + } + session_state->password_match[password_type] = (password == slix_get_password(slix_data, password_type)); diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 3c9a7cce4..aeb1180cf 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -114,7 +114,8 @@ static NfcCommand slix_poller_handler_check_privacy_password(SlixPoller* instanc break; } - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; @@ -145,7 +146,8 @@ static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { instance->error = slix_poller_get_random_number(instance, &instance->random_number); if(instance->error != SlixErrorNone) break; - instance->error = slix_poller_set_password(instance, SlixPasswordTypePrivacy, pwd); + instance->error = slix_poller_set_password( + instance, SlixPasswordTypePrivacy, pwd, instance->random_number); if(instance->error != SlixErrorNone) { command = NfcCommandReset; break; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 4ea7f880d..e78f7882a 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -107,12 +107,16 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* * Must ONLY be used inside the callback function. * * @param[in, out] instance pointer to the instance to be used in the transaction. - * @param[out] type SlixPasswordType instance. - * @param[out] password SlixPassword instance. + * @param[in] type SlixPasswordType instance. + * @param[in] password SlixPassword instance. + * @param[in] random_number SlixRandomNumber instance. * @return SlixErrorNone on success, an error code on failure. */ -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number); #ifdef __cplusplus } diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 9b0b5ec55..ee6912cc4 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -92,8 +92,11 @@ SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* return error; } -SlixError - slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { +SlixError slix_poller_set_password( + SlixPoller* instance, + SlixPasswordType type, + SlixPassword password, + SlixRandomNumber random_number) { furi_assert(instance); bool skip_uid = (type == SlixPasswordTypePrivacy); @@ -102,8 +105,8 @@ SlixError uint8_t password_type = (0x01 << type); bit_buffer_append_byte(instance->tx_buffer, password_type); - uint8_t rn_l = instance->random_number >> 8; - uint8_t rn_h = instance->random_number; + uint8_t rn_l = random_number >> 8; + uint8_t rn_h = random_number; uint32_t double_rand_num = (rn_h << 24) | (rn_l << 16) | (rn_h << 8) | rn_l; uint32_t xored_password = double_rand_num ^ password; uint8_t xored_password_arr[4] = {};