Merge branch 'dev' of https://github.com/flipperdevices/flipperzero-firmware into mntm-dev --nobuild

This commit is contained in:
Willy-JL
2024-05-18 05:21:11 +01:00
23 changed files with 665 additions and 283 deletions

View File

@@ -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

View File

@@ -13,6 +13,12 @@
#include <nfc/protocols/mf_ultralight/mf_ultralight_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
#include <nfc/protocols/mf_classic/mf_classic_poller.h>
#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
#include <nfc/protocols/slix/slix.h>
#include <nfc/protocols/slix/slix_i.h>
#include <nfc/protocols/slix/slix_poller.h>
#include <nfc/protocols/slix/slix_poller_i.h>
#include <nfc/nfc_poller.h>
#include <toolbox/keys_dict.h>
@@ -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();
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
// 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 !!!");
}

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -1,268 +1,313 @@
#include "../js_modules.h"
#include "furi_hal_random.h"
#include <float.h>
#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;
}
}

View File

@@ -1,13 +1,12 @@
#include <gui/modules/text_box.h>
#include <gui/view_dispatcher.h>
#include <gui/view.h>
#include <gui/view_holder.h>
#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;
}
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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,

View File

@@ -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"))

View File

@@ -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);

View File

@@ -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));

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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] = {};