From ed34dfa1c6738a1bc40cb9da934817fa574e6d77 Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:53:42 +0900 Subject: [PATCH 01/58] NFC: MF Classic parsers read() fix (dictionary attack skip) (#3355) * NFC: MF Classic lib: MfClassicErrorPartialRead added * unnecessary imports removed * MfClassicError refactor by @gornekich Co-authored-by: gornekich --------- Co-authored-by: gornekich --- .../main/nfc/plugins/supported_cards/aime.c | 2 +- .../main/nfc/plugins/supported_cards/hid.c | 2 +- .../nfc/plugins/supported_cards/plantain.c | 4 ++-- .../main/nfc/plugins/supported_cards/troika.c | 4 ++-- .../nfc/plugins/supported_cards/two_cities.c | 4 ++-- lib/nfc/protocols/mf_classic/mf_classic.h | 1 + .../mf_classic/mf_classic_poller_sync.c | 21 ++++++++----------- 7 files changed, 18 insertions(+), 20 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c index df1e7e0772f..50052f0d199 100644 --- a/applications/main/nfc/plugins/supported_cards/aime.c +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -60,7 +60,7 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } diff --git a/applications/main/nfc/plugins/supported_cards/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c index 66ced4d0c51..b6b0a406ff8 100644 --- a/applications/main/nfc/plugins/supported_cards/hid.c +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -60,7 +60,7 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index a21e1cd415b..b7e7497f74e 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -135,14 +135,14 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error != MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = true; + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 7cf1e4dd8c4..e3dfb703c97 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -137,14 +137,14 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = true; + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index 1748d372d2a..e9309f719ee 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -85,14 +85,14 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNone) { + if(error != MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } nfc_device_set_data(device, NfcProtocolMfClassic, data); - is_read = true; + is_read = (error == MfClassicErrorNone); } while(false); mf_classic_free(data); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.h b/lib/nfc/protocols/mf_classic/mf_classic.h index 146e6a6f157..755c457d167 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.h +++ b/lib/nfc/protocols/mf_classic/mf_classic.h @@ -39,6 +39,7 @@ typedef enum { MfClassicErrorNotPresent, MfClassicErrorProtocol, MfClassicErrorAuth, + MfClassicErrorPartialRead, MfClassicErrorTimeout, } MfClassicError; diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c index 69954452aa9..8566a861254 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_sync.c @@ -475,19 +475,16 @@ MfClassicError nfc_poller_stop(poller); - if(poller_context.error != MfClassicErrorNone) { - error = poller_context.error; - } else { - const MfClassicData* mfc_data = nfc_poller_get_data(poller); - uint8_t sectors_read = 0; - uint8_t keys_found = 0; + const MfClassicData* mfc_data = nfc_poller_get_data(poller); + uint8_t sectors_read = 0; + uint8_t keys_found = 0; - mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found); - if((sectors_read > 0) || (keys_found > 0)) { - mf_classic_copy(data, mfc_data); - } else { - error = MfClassicErrorNotPresent; - } + mf_classic_get_read_sectors_and_keys(mfc_data, §ors_read, &keys_found); + if((sectors_read == 0) && (keys_found == 0)) { + error = MfClassicErrorNotPresent; + } else { + mf_classic_copy(data, mfc_data); + error = mf_classic_is_card_read(mfc_data) ? MfClassicErrorNone : MfClassicErrorPartialRead; } nfc_poller_free(poller); From e0782966d4276ae1f71bd3d902d377aa1ab37ed1 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Tue, 6 Feb 2024 21:29:17 +0400 Subject: [PATCH 02/58] [FL-3661] Troika layout fixes (#3365) * Add support for different troika layouts * Display additional data if debug is enabled in settings * Support for layout 2, where there's no balance * nfc app plugins: fix mfc read error processing * nfc app: clean up troika plugin * nfc app: troika parser more clean up --------- Co-authored-by: gornekich Co-authored-by: hedger --- .../nfc/plugins/supported_cards/plantain.c | 2 +- .../main/nfc/plugins/supported_cards/troika.c | 175 ++++++++++++++++-- .../nfc/plugins/supported_cards/two_cities.c | 2 +- 3 files changed, 165 insertions(+), 14 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index b7e7497f74e..ba8f3429543 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -135,7 +135,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNotPresent) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index e3dfb703c97..20bd685eeb8 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -5,6 +5,7 @@ #include #include #include +#include "furi_hal_rtc.h" #define TAG "Troika" @@ -18,6 +19,19 @@ typedef struct { uint32_t data_sector; } TroikaCardConfig; +typedef enum { + TroikaLayoutUnknown = 0x0, + TroikaLayout2 = 0x2, + TroikaLayoutE = 0xE, +} TroikaLayout; + +typedef enum { + TroikaSublayoutUnknown = 0x0, + TroikaSublayout3 = 0x3, + TroikaSublayout5 = 0x5, + TroikaSublayout6 = 0x6, +} TroikaSubLayout; + static const MfClassicKeyPair troika_1k_keys[] = { {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, @@ -67,7 +81,7 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) config->data_sector = 8; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { - config->data_sector = 4; + config->data_sector = 8; // Further testing needed config->keys = troika_4k_keys; } else { success = false; @@ -76,6 +90,126 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) return success; } +static TroikaLayout troika_get_layout(const MfClassicData* data, uint8_t start_block_num) { + furi_assert(data); + + // Layout is stored in byte 6 of block, length 4 bits (bits 52 - 55), second nibble. + const uint8_t* layout_ptr = &data->block[start_block_num].data[6]; + const uint8_t layout = (*layout_ptr & 0x0F); + + TroikaLayout result = TroikaLayoutUnknown; + switch(layout) { + case TroikaLayout2: + case TroikaLayoutE: + result = layout; + break; + default: + // If debug is enabled - pass the actual layout value for the debug text + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + return layout; + } else { + return TroikaLayoutUnknown; + } + } + + return result; +} + +static TroikaSubLayout troika_get_sub_layout(const MfClassicData* data, uint8_t start_block_num) { + furi_assert(data); + + // Sublayout is stored in byte 7 (bits 56 - 60) of block, length 5 bits (first nibble and one bit from second nibble) + const uint8_t* sub_layout_ptr = &data->block[start_block_num].data[7]; + const uint8_t sub_layout = (*sub_layout_ptr & 0x3F) >> 3; + + TroikaSubLayout result = TroikaSublayoutUnknown; + switch(sub_layout) { + case TroikaSublayout3: + case TroikaSublayout5: + case TroikaSublayout6: + result = sub_layout; + break; + default: + // If debug is enabled - pass the actual sublayout value for the debug text + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + return sub_layout; + } else { + return TroikaSublayoutUnknown; + } + } + + return result; +} + +static bool troika_has_balance(TroikaLayout layout, TroikaSubLayout sub_layout) { + UNUSED(sub_layout); + // Layout 0x2 has no balance + + if(layout == TroikaLayout2) { + return false; + } + + return true; +} + +static uint16_t troika_get_balance( + const MfClassicData* data, + uint8_t start_block_num, + TroikaLayout layout, + TroikaSubLayout sub_layout) { + furi_assert(data); + + // In layout 0x3 balance in bits 188:209 ( from sector start, length 22). + // In layout 0x5 balance in bits 165:185 ( from sector start, length 20). + + uint32_t balance = 0; + uint8_t balance_data_offset = 0; + bool supported_layout = false; + + if(layout == TroikaLayoutE && sub_layout == TroikaSublayout3) { + balance_data_offset = 7; + supported_layout = true; + } else if(layout == TroikaLayoutE && sub_layout == TroikaSublayout5) { + balance_data_offset = 4; + supported_layout = true; + } + + if(supported_layout) { + const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[balance_data_offset]; + balance |= (temp_ptr[0] & 0x3) << 18; + balance |= temp_ptr[1] << 10; + balance |= temp_ptr[2] << 2; + balance |= (temp_ptr[3] & 0xC0) >> 6; + } + + return balance / 100; +} + +static uint32_t troika_get_number( + const MfClassicData* data, + uint8_t start_block_num, + TroikaLayout layout, + TroikaSubLayout sub_layout) { + furi_assert(data); + UNUSED(sub_layout); + + if(layout == TroikaLayoutE || layout == TroikaLayout2) { + const uint8_t* temp_ptr = &data->block[start_block_num].data[2]; + + uint32_t number = 0; + for(size_t i = 1; i < 5; i++) { + number <<= 8; + number |= temp_ptr[i]; + } + number >>= 4; + number |= (temp_ptr[0] & 0xf) << 28; + + return number; + } else { + return 0; + } +} + static bool troika_verify_type(Nfc* nfc, MfClassicType type) { bool verified = false; @@ -171,22 +305,39 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Parse data + // Get the block number of the block that contains the data const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); - const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5]; - uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25; - temp_ptr = &data->block[start_block_num].data[2]; + // Get layout, sublayout, balance and number + TroikaLayout layout = troika_get_layout(data, start_block_num); + TroikaSubLayout sub_layout = troika_get_sub_layout(data, start_block_num); - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + // If debug is enabled - proceed even if layout or sublayout is unknown, that will make collecting data easier + if(layout == TroikaLayoutUnknown || sub_layout == TroikaSublayoutUnknown) break; + } + + uint32_t number = troika_get_number(data, start_block_num, layout, sub_layout); + + furi_string_printf(parsed_data, "\e#Troika\nNum: %lu", number); + + if(troika_has_balance(layout, sub_layout) || + furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + uint16_t balance = troika_get_balance(data, start_block_num, layout, sub_layout); + furi_string_cat_printf(parsed_data, "\nBalance: %u RUR", balance); + } else { + furi_string_cat_printf(parsed_data, "\nBalance: Not available"); + } + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf( + parsed_data, + "\nLayout: %02x\nSublayout: %02x\nData Block: %u", + layout, + sub_layout, + start_block_num); } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance); parsed = true; } while(false); diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index e9309f719ee..7e29cd08510 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -85,7 +85,7 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) { } error = mf_classic_poller_sync_read(nfc, &keys, data); - if(error != MfClassicErrorNotPresent) { + if(error == MfClassicErrorNotPresent) { FURI_LOG_W(TAG, "Failed to read data"); break; } From c736c3f3f09b09db7df80af0964ad058572b1499 Mon Sep 17 00:00:00 2001 From: Tolly Hill Date: Tue, 6 Feb 2024 17:34:54 +0000 Subject: [PATCH 03/58] NFC: Display unread Mifare Classic bytes as question marks (#3384) * NFC: Display unread Mifare Classic bytes as ?? * Apply patch from @gornekich --------- Co-authored-by: gornekich --- .../mf_classic/mf_classic_render.c | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c index 5bd4a6b6ddd..0382b3333a1 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic_render.c @@ -18,13 +18,48 @@ void nfc_render_mf_classic_info( furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total); } +static void + mf_classic_render_raw_data(const uint8_t* data, size_t size, bool data_read, FuriString* str) { + furi_assert((size % 2) == 0); + + for(size_t i = 0; i < size; i += 2) { + if(data_read) { + furi_string_cat_printf(str, "%02X%02X ", data[i], data[i + 1]); + } else { + furi_string_cat_printf(str, "???? "); + } + } +} + +static void + mf_classic_render_block(const MfClassicData* data, uint8_t block_num, FuriString* str) { + if(mf_classic_is_sector_trailer(block_num)) { + uint8_t sec_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sec_num); + + // Render key A + bool key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeA); + mf_classic_render_raw_data(sec_tr->key_a.data, sizeof(MfClassicKey), key_read, str); + + // Render access bits + bool access_bits_read = mf_classic_is_block_read(data, block_num); + mf_classic_render_raw_data( + sec_tr->access_bits.data, sizeof(MfClassicAccessBits), access_bits_read, str); + + // Render key B + key_read = mf_classic_is_key_found(data, sec_num, MfClassicKeyTypeB); + mf_classic_render_raw_data(sec_tr->key_b.data, sizeof(MfClassicKey), key_read, str); + } else { + const uint8_t* block_data = data->block[block_num].data; + bool block_read = mf_classic_is_block_read(data, block_num); + mf_classic_render_raw_data(block_data, sizeof(MfClassicBlock), block_read, str); + } +} + void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) { uint16_t total_blocks = mf_classic_get_total_block_num(data->type); for(size_t i = 0; i < total_blocks; i++) { - for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) { - furi_string_cat_printf( - str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]); - } + mf_classic_render_block(data, i, str); } } From 2c784d3feae51ef01089c816ce73b782cf580df3 Mon Sep 17 00:00:00 2001 From: MMX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:42:06 +0300 Subject: [PATCH 04/58] Fix nfc_protocol_support_scene_save_name_on_event crash (#3418) * Add Clearing of detected protocols list in nfc_scene_start * Apply patch --------- Co-authored-by: hedger --- .../main/nfc/helpers/protocol_support/nfc_protocol_support.c | 3 +-- applications/main/nfc/scenes/nfc_scene_start.c | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index c87ee613f56..da75142ba05 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -521,8 +521,7 @@ static bool scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ? DolphinDeedNfcAddSave : DolphinDeedNfcSave); - const NfcProtocol protocol = - instance->protocols_detected[instance->protocols_detected_selected_idx]; + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); consumed = nfc_protocol_support[protocol]->scene_save_name.on_event( instance, event.event); } else { diff --git a/applications/main/nfc/scenes/nfc_scene_start.c b/applications/main/nfc/scenes/nfc_scene_start.c index c923226fc7e..e8774b4aa5a 100644 --- a/applications/main/nfc/scenes/nfc_scene_start.c +++ b/applications/main/nfc/scenes/nfc_scene_start.c @@ -24,6 +24,8 @@ void nfc_scene_start_on_enter(void* context) { furi_string_reset(nfc->file_name); nfc_device_clear(nfc->nfc_device); iso14443_3a_reset(nfc->iso14443_3a_edit_data); + // Reset detected protocols list + nfc_app_reset_detected_protocols(nfc); submenu_add_item(submenu, "Read", SubmenuIndexRead, nfc_scene_start_submenu_callback, nfc); submenu_add_item( From a83e421d7133bb224d81cd8c412387532a31e1b3 Mon Sep 17 00:00:00 2001 From: Jeremy Cooper Date: Tue, 6 Feb 2024 09:47:44 -0800 Subject: [PATCH 05/58] Add an NFC parser for the San Francisco Bay Area "Clipper" transit card. (#3344) * Add an NFC parser for the San Francisco Bay Area "Clipper" transit card. * Add more agencies and stations, decode vehicle ids, refactor. Add more agencies and station names using data from local research and: * Metrodroid project (GPLv3): https://github.com/metrodroid/metrodroid * Farebot (GPLv3): https://github.com/codebutler/farebot --------- Co-authored-by: gornekich Co-authored-by: hedger --- applications/main/nfc/application.fam | 9 + .../nfc/plugins/supported_cards/clipper.c | 621 ++++++++++++++++++ 2 files changed, 630 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/clipper.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index b92d0ebf1ab..27cb1e9eea9 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -110,6 +110,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="clipper_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="clipper_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/clipper.c"], +) + App( appid="hid_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c new file mode 100644 index 00000000000..c76fb1cdcf8 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -0,0 +1,621 @@ +/* + * clipper.c - Parser for Clipper cards (San Francisco, California). + * + * Based on research, some of which dates to 2007! + * + * Copyright 2024 Jeremy Cooper + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "nfc_supported_card_plugin.h" + +#include +#include +#include +#include +#include +#include + +// +// Table of application ids observed in the wild, and their sources. +// +static const struct { + const MfDesfireApplicationId app; + const char* type; +} clipper_types[] = { + // Application advertised on classic, plastic cards. + {.app = {.data = {0x90, 0x11, 0xf2}}, .type = "Card"}, + // Application advertised on a mobile device. + {.app = {.data = {0x91, 0x11, 0xf2}}, .type = "Mobile Device"}, +}; +static const size_t kNumCardTypes = sizeof(clipper_types) / sizeof(clipper_types[0]); + +struct IdMapping_struct { + uint16_t id; + const char* name; +}; +typedef struct IdMapping_struct IdMapping; + +#define COUNT(_array) sizeof(_array) / sizeof(_array[0]) + +// +// Known transportation agencies and their identifiers. +// +static const IdMapping agency_names[] = { + {.id = 0x0001, .name = "AC Transit"}, + {.id = 0x0004, .name = "BART"}, + {.id = 0x0006, .name = "Caltrain"}, + {.id = 0x0008, .name = "CCTA"}, + {.id = 0x000b, .name = "GGT"}, + {.id = 0x000f, .name = "SamTrans"}, + {.id = 0x0011, .name = "VTA"}, + {.id = 0x0012, .name = "Muni"}, + {.id = 0x0019, .name = "GG Ferry"}, + {.id = 0x001b, .name = "SF Bay Ferry"}, +}; +static const size_t kNumAgencies = COUNT(agency_names); + +// +// Known station names for various agencies. +// +static const IdMapping bart_zones[] = { + {.id = 0x0001, .name = "Colma"}, + {.id = 0x0002, .name = "Daly City"}, + {.id = 0x0003, .name = "Balboa Park"}, + {.id = 0x0004, .name = "Glen Park"}, + {.id = 0x0005, .name = "24th St Mission"}, + {.id = 0x0006, .name = "16th St Mission"}, + {.id = 0x0007, .name = "Civic Center/UN Plaza"}, + {.id = 0x0008, .name = "Powell St"}, + {.id = 0x0009, .name = "Montgomery St"}, + {.id = 0x000a, .name = "Embarcadero"}, + {.id = 0x000b, .name = "West Oakland"}, + {.id = 0x000c, .name = "12th St/Oakland City Center"}, + {.id = 0x000d, .name = "19th St/Oakland"}, + {.id = 0x000e, .name = "MacArthur"}, + {.id = 0x000f, .name = "Rockridge"}, + {.id = 0x0010, .name = "Orinda"}, + {.id = 0x0011, .name = "Lafayette"}, + {.id = 0x0012, .name = "Walnut Creek"}, + {.id = 0x0013, .name = "Pleasant Hill/Contra Costa Centre"}, + {.id = 0x0014, .name = "Concord"}, + {.id = 0x0015, .name = "North Concord/Martinez"}, + {.id = 0x0016, .name = "Pittsburg/Bay Point"}, + {.id = 0x0017, .name = "Ashby"}, + {.id = 0x0018, .name = "Downtown Berkeley"}, + {.id = 0x0019, .name = "North Berkeley"}, + {.id = 0x001a, .name = "El Cerrito Plaza"}, + {.id = 0x001b, .name = "El Cerrito Del Norte"}, + {.id = 0x001c, .name = "Richmond"}, + {.id = 0x001d, .name = "Lake Merrit"}, + {.id = 0x001e, .name = "Fruitvale"}, + {.id = 0x001f, .name = "Coliseum"}, + {.id = 0x0021, .name = "San Leandro"}, + {.id = 0x0022, .name = "Hayward"}, + {.id = 0x0023, .name = "South Hayward"}, + {.id = 0x0024, .name = "Union City"}, + {.id = 0x0025, .name = "Fremont"}, + {.id = 0x0026, .name = "Daly City(2)?"}, + {.id = 0x0027, .name = "Dublin/Pleasanton"}, + {.id = 0x0028, .name = "South San Francisco"}, + {.id = 0x0029, .name = "San Bruno"}, + {.id = 0x002a, .name = "SFO Airport"}, + {.id = 0x002b, .name = "Millbrae"}, + {.id = 0x002c, .name = "West Dublin/Pleasanton"}, + {.id = 0x002d, .name = "OAK Airport"}, + {.id = 0x002e, .name = "Warm Springs/South Fremont"}, +}; +static const size_t kNumBARTZones = COUNT(bart_zones); + +static const IdMapping muni_zones[] = { + {.id = 0x0000, .name = "City Street"}, + {.id = 0x0005, .name = "Embarcadero"}, + {.id = 0x0006, .name = "Montgomery"}, + {.id = 0x0007, .name = "Powell"}, + {.id = 0x0008, .name = "Civic Center"}, + {.id = 0x0009, .name = "Van Ness"}, // Guessed + {.id = 0x000a, .name = "Church"}, + {.id = 0x000b, .name = "Castro"}, + {.id = 0x000c, .name = "Forest Hill"}, // Guessed + {.id = 0x000d, .name = "West Portal"}, +}; +static const size_t kNumMUNIZones = COUNT(muni_zones); + +static const IdMapping actransit_zones[] = { + {.id = 0x0000, .name = "City Street"}, +}; +static const size_t kNumACTransitZones = COUNT(actransit_zones); + +// +// Full agency+zone mapping. +// +static const struct { + uint16_t agency_id; + const IdMapping* zone_map; + size_t zone_count; +} agency_zone_map[] = { + {.agency_id = 0x0001, .zone_map = actransit_zones, .zone_count = kNumACTransitZones}, + {.agency_id = 0x0004, .zone_map = bart_zones, .zone_count = kNumBARTZones}, + {.agency_id = 0x0012, .zone_map = muni_zones, .zone_count = kNumMUNIZones}}; +static const size_t kNumAgencyZoneMaps = COUNT(agency_zone_map); + +// File ids of important files on the card. +static const MfDesfireFileId clipper_ecash_file_id = 2; +static const MfDesfireFileId clipper_histidx_file_id = 6; +static const MfDesfireFileId clipper_identity_file_id = 8; +static const MfDesfireFileId clipper_history_file_id = 14; + +struct ClipperCardInfo_struct { + uint32_t serial_number; + uint16_t counter; + uint16_t last_txn_id; + uint32_t last_updated_tm_1900; + uint16_t last_terminal_id; + int16_t balance_cents; +}; +typedef struct ClipperCardInfo_struct ClipperCardInfo; + +// Forward declarations for helper functions. +static void furi_string_cat_timestamp( + FuriString* str, + const char* date_hdr, + const char* time_hdr, + uint32_t tmst_1900); +static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out); +static bool get_file_contents( + const MfDesfireApplication* app, + const MfDesfireFileId* id, + MfDesfireFileType type, + size_t min_size, + const uint8_t** out); +static bool decode_id_file(const uint8_t* ef8_data, ClipperCardInfo* info); +static bool decode_cash_file(const uint8_t* ef2_data, ClipperCardInfo* info); +static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out); +static bool get_agency_zone_name(uint16_t agency_id, uint16_t zone_id, const char** out); +static void + decode_usd(int16_t amount_cents, bool* out_is_negative, int16_t* out_usd, uint16_t* out_cents); +static bool dump_ride_history( + const uint8_t* index_file, + const uint8_t* history_file, + size_t len, + FuriString* parsed_data); +static bool dump_ride_event(const uint8_t* record, FuriString* parsed_data); + +// Unmarshal a 32-bit integer, big endian, unsigned +static inline uint32_t get_u32be(const uint8_t* field) { + return nfc_util_bytes2num(field, 4); +} + +// Unmarshal a 16-bit integer, big endian, unsigned +static uint16_t get_u16be(const uint8_t* field) { + return nfc_util_bytes2num(field, 2); +} + +// Unmarshal a 16-bit integer, big endian, signed, two's-complement +static int16_t get_i16be(const uint8_t* field) { + uint16_t raw = get_u16be(field); + if(raw > 0x7fff) + return -((uint32_t)0x10000 - raw); + else + return raw; +} + +static bool clipper_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + do { + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + const MfDesfireApplication* app = NULL; + const char* device_description = NULL; + + for(size_t i = 0; i < kNumCardTypes; i++) { + app = mf_desfire_get_application(data, &clipper_types[i].app); + device_description = clipper_types[i].type; + if(app != NULL) break; + } + + // If no matching application was found, abort this parser. + if(app == NULL) break; + + ClipperCardInfo info; + const uint8_t* id_data; + if(!get_file_contents( + app, &clipper_identity_file_id, MfDesfireFileTypeStandard, 5, &id_data)) + break; + if(!decode_id_file(id_data, &info)) break; + + const uint8_t* cash_data; + if(!get_file_contents(app, &clipper_ecash_file_id, MfDesfireFileTypeBackup, 32, &cash_data)) + break; + if(!decode_cash_file(cash_data, &info)) break; + + int16_t balance_usd; + uint16_t balance_cents; + bool _balance_is_negative; + decode_usd(info.balance_cents, &_balance_is_negative, &balance_usd, &balance_cents); + + furi_string_cat_printf( + parsed_data, + "\e#Clipper\n" + "Serial: %" PRIu32 "\n" + "Balance: $%d.%02u\n" + "Type: %s\n" + "\e#Last Update\n", + info.serial_number, + balance_usd, + balance_cents, + device_description); + if(info.last_updated_tm_1900 != 0) + furi_string_cat_timestamp( + parsed_data, "Date: ", "\nTime: ", info.last_updated_tm_1900); + else + furi_string_cat_str(parsed_data, "Never"); + furi_string_cat_printf( + parsed_data, + "\nTerminal: 0x%04x\n" + "Transaction Id: %u\n" + "Counter: %u\n", + info.last_terminal_id, + info.last_txn_id, + info.counter); + + const uint8_t *history_index, *history; + + if(!get_file_contents( + app, &clipper_histidx_file_id, MfDesfireFileTypeBackup, 16, &history_index)) + break; + if(!get_file_contents( + app, &clipper_history_file_id, MfDesfireFileTypeStandard, 512, &history)) + break; + + if(!dump_ride_history(history_index, history, 512, parsed_data)) break; + + parsed = true; + } while(false); + + return parsed; +} + +static bool get_file_contents( + const MfDesfireApplication* app, + const MfDesfireFileId* id, + MfDesfireFileType type, + size_t min_size, + const uint8_t** out) { + const MfDesfireFileSettings* settings = mf_desfire_get_file_settings(app, id); + if(settings == NULL) return false; + if(settings->type != type) return false; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, id); + if(file_data == NULL) return false; + + if(simple_array_get_count(file_data->data) < min_size) return false; + + *out = simple_array_cget_data(file_data->data); + + return true; +} + +static bool decode_id_file(const uint8_t* ef8_data, ClipperCardInfo* info) { + // Identity file (8) + // + // Byte view + // + // 0 1 2 3 4 5 6 7 8 + // +----+----.----.----.----+----.----.----+ + // 0x00 | uk | card_id | unknown | + // +----+----.----.----.----+----.----.----+ + // 0x08 | unknown | + // +----.----.----.----.----.----.----.----+ + // 0x10 ... + // + // + // Field Datatype Description + // ----- -------- ----------- + // uk ?8?? Unknown, 8-bit byte + // card_id U32BE Card identifier + // + info->serial_number = nfc_util_bytes2num(&ef8_data[1], 4); + return true; +} + +static bool decode_cash_file(const uint8_t* ef2_data, ClipperCardInfo* info) { + // ECash file (2) + // + // Byte view + // + // 0 1 2 3 4 5 6 7 8 + // +----.----+----.----+----.----.----.----+ + // 0x00 | unk00 | counter | timestamp_1900 | + // +----.----+----.----+----.----.----.----+ + // 0x08 | term_id | unk01 | + // +----.----+----.----+----.----.----.----+ + // 0x10 | txn_id | balance | unknown | + // +----.----+----.----+----.----.----.----+ + // 0x18 | unknown | + // +---------------------------------------+ + // + // Field Datatype Description + // ----- -------- ----------- + // unk00 U8[2] Unknown bytes + // counter U16BE Unknown, appears to be a counter + // timestamp_1900 U32BE Timestamp of last transaction, in seconds + // since 1900-01-01 GMT. + // unk01 U8[6] Unknown bytes + // txn_id U16BE Id of last transaction. + // balance S16BE Card cash balance, in cents. + // Cards can obtain negative balances in this + // system, so balances are signed integers. + // Maximum card balance is therefore + // $327.67. + // unk02 U8[12] Unknown bytes. + // + info->counter = get_u16be(&ef2_data[2]); + info->last_updated_tm_1900 = get_u32be(&ef2_data[4]); + info->last_terminal_id = get_u16be(&ef2_data[8]); + info->last_txn_id = get_u16be(&ef2_data[0x10]); + info->balance_cents = get_i16be(&ef2_data[0x12]); + return true; +} + +static bool dump_ride_history( + const uint8_t* index_file, + const uint8_t* history_file, + size_t len, + FuriString* parsed_data) { + static const size_t kRideRecordSize = 0x20; + + for(size_t i = 0; i < 16; i++) { + uint8_t record_num = index_file[i]; + if(record_num == 0xff) break; + + size_t record_offset = record_num * kRideRecordSize; + + if(record_offset + kRideRecordSize > len) break; + + const uint8_t* record = &history_file[record_offset]; + if(!dump_ride_event(record, parsed_data)) break; + } + + return true; +} + +static bool dump_ride_event(const uint8_t* record, FuriString* parsed_data) { + // Ride record + // + // 0 1 2 3 4 5 6 7 8 + // +----+----+----.----+----.----+----.----+ + // 0x00 |0x10| ? | agency | ? | fare | + // +----.----+----.----+----.----.----.----+ + // 0x08 | ? | vehicle | time_on | + // +----.----.----.----+----.----+----.----+ + // 0x10 | time_off | zone_on | zone_off| + // +----+----.----.----.----+----+----+----+ + // 0x18 | ? | ? | ? | ? | ? | + // +----+----.----.----.----+----+----+----+ + // + // Field Datatype Description + // ----- -------- ----------- + // agency U16BE Transportation agency identifier. + // Known ids: + // 1 == AC Transit + // 4 == BART + // 18 == SF MUNI + // fare I16BE Fare deducted, in cents. + // vehicle U16BE Vehicle id (0 == not provided) + // time_on U32BE Boarding time, in seconds since 1900-01-01 GMT. + // time_off U32BE Off-boarding time, if present, in seconds + // since 1900-01-01 GMT. Set to zero if no offboard + // has been recorded. + // zone_on U16BE Id of boarding zone or station. Agency-specific. + // zone_off U16BE Id of offboarding zone or station. Agency- + // specific. + if(record[0] != 0x10) return false; + + uint16_t agency_id = get_u16be(&record[2]); + if(agency_id == 0) + // Likely empty record. Skip. + return false; + const char* agency_name; + bool ok = get_map_item(agency_id, agency_names, kNumAgencies, &agency_name); + if(!ok) agency_name = "Unknown"; + + uint16_t vehicle_id = get_u16be(&record[0x0a]); + + int16_t fare_raw_cents = get_i16be(&record[6]); + bool _fare_is_negative; + int16_t fare_usd; + uint16_t fare_cents; + decode_usd(fare_raw_cents, &_fare_is_negative, &fare_usd, &fare_cents); + + uint32_t time_on_raw = get_u32be(&record[0x0c]); + uint32_t time_off_raw = get_u32be(&record[0x10]); + uint16_t zone_id_on = get_u16be(&record[0x14]); + uint16_t zone_id_off = get_u16be(&record[0x16]); + + const char *zone_on, *zone_off; + if(!get_agency_zone_name(agency_id, zone_id_on, &zone_on)) { + zone_on = "Unknown"; + } + if(!get_agency_zone_name(agency_id, zone_id_off, &zone_off)) { + zone_off = "Unknown"; + } + + furi_string_cat_str(parsed_data, "\e#Ride Record\n"); + furi_string_cat_timestamp(parsed_data, "Date: ", "\nTime: ", time_on_raw); + furi_string_cat_printf( + parsed_data, + "\n" + "Fare: $%d.%02u\n" + "Agency: %s (%04x)\n" + "On: %s (%04x)\n", + fare_usd, + fare_cents, + agency_name, + agency_id, + zone_on, + zone_id_on); + if(vehicle_id != 0) { + furi_string_cat_printf(parsed_data, "Vehicle id: %d\n", vehicle_id); + } + if(time_off_raw != 0) { + furi_string_cat_printf(parsed_data, "Off: %s (%04x)\n", zone_off, zone_id_off); + furi_string_cat_timestamp(parsed_data, "Date Off: ", "\nTime Off: ", time_off_raw); + furi_string_cat_str(parsed_data, "\n"); + } + + return true; +} + +static bool get_map_item(uint16_t id, const IdMapping* map, size_t sz, const char** out) { + for(size_t i = 0; i < sz; i++) { + if(map[i].id == id) { + *out = map[i].name; + return true; + } + } + + return false; +} + +static bool get_agency_zone_name(uint16_t agency_id, uint16_t zone_id, const char** out) { + for(size_t i = 0; i < kNumAgencyZoneMaps; i++) { + if(agency_zone_map[i].agency_id == agency_id) { + return get_map_item( + zone_id, agency_zone_map[i].zone_map, agency_zone_map[i].zone_count, out); + } + } + + return false; +} + +// Split a balance/fare amount from raw cents to dollars and cents portion, +// automatically adjusting the cents portion so that it is always positive, +// for easier display. +static void + decode_usd(int16_t amount_cents, bool* out_is_negative, int16_t* out_usd, uint16_t* out_cents) { + *out_usd = amount_cents / 100; + + if(amount_cents >= 0) { + *out_is_negative = false; + *out_cents = amount_cents % 100; + } else { + *out_is_negative = true; + *out_cents = (amount_cents * -1) % 100; + } +} + +// Decode a raw 1900-based timestamp and append a human-readable form to a +// FuriString. +static void furi_string_cat_timestamp( + FuriString* str, + const char* date_hdr, + const char* time_hdr, + uint32_t tmst_1900) { + FuriHalRtcDateTime tm; + + epoch_1900_datetime_to_furi(tmst_1900, &tm); + + FuriString* date_str = furi_string_alloc(); + locale_format_date(date_str, &tm, locale_get_date_format(), "-"); + + FuriString* time_str = furi_string_alloc(); + locale_format_time(time_str, &tm, locale_get_time_format(), true); + + furi_string_cat_printf( + str, + "%s%s%s%s (UTC)", + date_hdr, + furi_string_get_cstr(date_str), + time_hdr, + furi_string_get_cstr(time_str)); + + furi_string_free(date_str); + furi_string_free(time_str); +} + +// Convert a "1900"-based timestamp to Furi time, assuming a UTC/GMT timezone. +static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out) { + uint16_t year, month, day, hour, minute, second; + + // Calculate absolute number of days elapsed since the 1900 epoch + // and save the residual for the time within the day. + uint32_t absolute_days = seconds / 86400; + uint32_t seconds_within_day = seconds % 86400; + + // Calculate day of the week. + // January 1, 1900 was a Monday ("day of week" = 1) + uint8_t dow = (absolute_days + 1) % 7; + + // + // Compute the date by simply marching through time in as large chunks + // as possible. + // + + for(year = 1900;; year++) { + uint16_t year_days = furi_hal_rtc_get_days_per_year(year); + if(absolute_days >= year_days) + absolute_days -= year_days; + else + break; + } + + bool is_leap = furi_hal_rtc_is_leap_year(year); + + for(month = 1;; month++) { + uint8_t days_in_month = furi_hal_rtc_get_days_per_month(is_leap, month); + if(absolute_days >= days_in_month) + absolute_days -= days_in_month; + else + break; + } + + day = absolute_days + 1; + hour = seconds_within_day / 3600; + uint16_t sub_hour = seconds_within_day % 3600; + minute = sub_hour / 60; + second = sub_hour % 60; + + out->year = year; + out->month = month; + out->day = day; + out->hour = hour; + out->minute = minute; + out->second = second; + out->weekday = dow; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin clipper_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = clipper_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor clipper_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &clipper_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* clipper_plugin_ep() { + return &clipper_plugin_descriptor; +} From 9a77dbec568efaf48574df940c6f4671cc3132fa Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Tue, 6 Feb 2024 20:57:06 +0300 Subject: [PATCH 06/58] [FL-3678] NFC UI refactor (#3382) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added new image DolphinSaved_113x58.png for all "saved" pages * New image DolphinDone_80x58.png added * Replaced dolphins on all scenes accroding to new UI specs * New success dolphin image added * Success scene image replaced * Changed image and text for update initial scene * Image and text adjusted for "Original restored" scene * Removed old DolphinNice_96x59.png image * New image for LFRFID scene * Removed unused image * New UI image added to assets * Replaced warning dolphin on mf_classic write initial fail scene * Removed old image * Changed image on scenes to a new one * New dolphin mafia image * Replaced dolphin mafia image to a new one * Removed DolphinMafia_115x62.png * New check symbol on completed state for detect_reader * Adjusted layout elements position * Removed second switching to popup view in order to achieve control in support callbacks In general now we show generic scene and after that in on_enter callback we can redefine it for particular protocol * CardDetected event now also triggers on_event callback * Now on AuthRequest we throw CardDetected custom event * Added callback for read_on_event * Now we show different screen while reading and unlocking * Fixed missing asstes for some scenes * Update DolphinMafia_119x62.png * Adjusted all the scenes with DolphinMafia image * Scenes with save image adjusted * Removed unnecessary assets DolphinMafia_119x62.png and DolphinSaved_113x58.png * All common dolphins moved to Dolphin folder * Moved DolphinReadingSuccess_59x63.png to Dolphin folder * Set proper led color for detect and read scenes * Added new notification sequence for semi_success results * Use new sequence for semi_success nfc reads * Different events are now throwed depending on read result * Added handling of incomplete event for ultralight cards * Replaced image for iButton scene * Updated API for f18 * Fixed issue with unlock retry sequence * Fix after review * Success notification replaced to semi success in case of incomplete mf classic reading * New text for read scene * New read result sound notification logic for mf classic cards * Change MIFARE name accroding to new requirements * New QR code image for MFKey app * Update nfc_scene_mf_classic_mfkey_complete.c scene according to new UI requirements * Update detect_reader.c and check_big_20x17.png * New nfc save confirm scene added * Implemented new flow for 'Detect Reader button' after partial mf classic read according to new UI * UID for 15693 tags now shown on the new line * Fix nfc unit tests * Revert "Fix nfc unit tests" This reverts commit 685ed6bfad1980e42098a8bbe366de5b8b4cfd09. * Rolled back all Mifare renamings in library files * Revert "Change MIFARE name accroding to new requirements" This reverts commit cfb974dc1f5bff1d46a0483741b2b8f4726cdda3. * Now Mifare word is changed only on the app level without changes to lib level * Filename or "Unsaved + CardType" is now showed for saved cards during emulation * Headers added to Write scenes * Reordered menu items accrding to new spec * Filename will be printed for saved tag in info scene * New info render format for 14443_3a cards * New info render format for 14443_3b cards * New info render format for 14443_4a cards * New info render format for iso15693 cards. Also More_Info scene added to display Memory data * New info render format for slix cards. Also More_Info scene added to display Memory data * Fixed "Mifare" word for desfire cards * Aligned text and replaced dolphin image on emulate scene * Fixed Mifare caption after QA * Realigned emulation scene and fixed replaced Mifare to MIFARE --------- Co-authored-by: あく Co-authored-by: gornekich --- .../helpers/protocol_support/felica/felica.c | 1 + .../iso14443_3a/iso14443_3a.c | 2 + .../iso14443_3a/iso14443_3a_render.c | 10 +++- .../iso14443_3a/iso14443_3a_render.h | 2 + .../iso14443_3b/iso14443_3b.c | 1 + .../iso14443_3b/iso14443_3b_render.c | 6 +- .../iso14443_4a/iso14443_4a.c | 1 + .../iso14443_4a/iso14443_4a_render.c | 5 +- .../iso14443_4b/iso14443_4b.c | 1 + .../protocol_support/iso15693_3/iso15693_3.c | 22 ++++++- .../iso15693_3/iso15693_3_render.c | 56 ++++++++++-------- .../iso15693_3/iso15693_3_render.h | 2 + .../protocol_support/mf_classic/mf_classic.c | 1 + .../protocol_support/mf_desfire/mf_desfire.c | 3 + .../mf_ultralight/mf_ultralight.c | 2 + .../protocol_support/nfc_protocol_support.c | 10 ++-- .../nfc/helpers/protocol_support/slix/slix.c | 21 ++++++- .../protocol_support/slix/slix_render.c | 12 ++-- .../protocol_support/slix/slix_render.h | 1 + .../helpers/protocol_support/st25tb/st25tb.c | 1 + applications/main/nfc/nfc_app.c | 9 +++ applications/main/nfc/nfc_app_i.h | 2 + .../nfc/scenes/nfc_scene_select_protocol.c | 2 + .../icons/NFC/NFC_dolphin_emulation_47x61.png | Bin 1541 -> 0 bytes .../icons/NFC/NFC_dolphin_emulation_51x64.png | Bin 0 -> 1591 bytes 25 files changed, 126 insertions(+), 47 deletions(-) delete mode 100644 assets/icons/NFC/NFC_dolphin_emulation_47x61.png create mode 100644 assets/icons/NFC/NFC_dolphin_emulation_51x64.png diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index f9c84912161..6e7aa2d8ab1 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_felica(NfcApp* instance) { const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c index c0d502d0380..05db74a4ed0 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -13,6 +13,8 @@ static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) { const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c index 7306f1072d6..810242fbc6a 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.c @@ -6,13 +6,17 @@ void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const d } } +void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str) { + const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; + furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-A)\n", iso_type); +} + void nfc_render_iso14443_3a_info( const Iso14443_3aData* data, NfcProtocolFormatType format_type, FuriString* str) { if(format_type == NfcProtocolFormatTypeFull) { - const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3'; - furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type); + nfc_render_iso14443_tech_type(data, str); } nfc_render_iso14443_3a_brief(data, str); @@ -30,5 +34,5 @@ void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str) void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) { furi_string_cat_printf(str, "\nATQA: %02X %02X ", data->atqa[1], data->atqa[0]); - furi_string_cat_printf(str, "SAK: %02X", data->sak); + furi_string_cat_printf(str, "\nSAK: %02X", data->sak); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h index 14b91d221d7..34e347aa35e 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a_render.h @@ -9,6 +9,8 @@ void nfc_render_iso14443_3a_info( NfcProtocolFormatType format_type, FuriString* str); +void nfc_render_iso14443_tech_type(const Iso14443_3aData* data, FuriString* str); + void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size); void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c index fee23184624..a71a6575337 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) { const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c index 2e81d57a48b..ec7efa84f92 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_render.c @@ -6,7 +6,7 @@ void nfc_render_iso14443_3b_info( FuriString* str) { if(format_type == NfcProtocolFormatTypeFull) { const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3'; - furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type); + furi_string_cat_printf(str, "Tech: ISO 14443-%c (NFC-B)\n", iso_type); } furi_string_cat_printf(str, "UID:"); @@ -20,7 +20,7 @@ void nfc_render_iso14443_3b_info( if(format_type != NfcProtocolFormatTypeFull) return; - furi_string_cat_printf(str, "\n\e#Protocol info\n"); + furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n"); if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) { furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); @@ -68,7 +68,7 @@ void nfc_render_iso14443_3b_info( iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not "; furi_string_cat_printf(str, "CID: %ssupported", cid_support_str); - furi_string_cat_printf(str, "\n\e#Application data\nRaw:"); + furi_string_cat_printf(str, "\n::::::::::::[Application data]::::::::::::\nRaw:"); size_t app_data_size; const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c index 0a3a592e172..5bd38975d85 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) { const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c index a963e744b9e..4ff07d59632 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a_render.c @@ -14,11 +14,12 @@ void nfc_render_iso14443_4a_info( } void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) { + nfc_render_iso14443_tech_type(iso14443_4a_get_base_data(data), str); nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str); } void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) { - furi_string_cat_printf(str, "\n\e#Protocol info\n"); + furi_string_cat_printf(str, "\n::::::::::::::::[Protocol info]:::::::::::::::\n"); if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) { furi_string_cat(str, "Bit rate PICC <-> PCD:\n 106 kBit/s supported\n"); @@ -72,7 +73,7 @@ void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count); if(hist_bytes_count > 0) { - furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:"); + furi_string_cat_printf(str, "\n:::::::::::::[Historical bytes]:::::::::::::\nRaw:"); for(size_t i = 0; i < hist_bytes_count; ++i) { furi_string_cat_printf(str, " %02X", hist_bytes[i]); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c index a0c70a22e91..ee49f2a42dc 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -14,6 +14,7 @@ static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) { const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 7f861a03265..d645fa3bb74 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) { const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_reset(instance->widget); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); } +static void nfc_scene_more_info_on_enter_iso15693_3(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3); + + FuriString* temp_str = furi_string_alloc(); + nfc_render_iso15693_3_system_info(data, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); +} + static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolIso15693_3); @@ -104,13 +118,19 @@ static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t } const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = { - .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid | + NfcProtocolFeatureMoreInfo, .scene_info = { .on_enter = nfc_scene_info_on_enter_iso15693_3, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_iso15693_3, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_read = { .on_enter = nfc_scene_read_on_enter_iso15693_3, diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c index bb2ab92d39f..07b96d70187 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.c @@ -36,32 +36,7 @@ void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) { } } -void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { - furi_string_cat(str, "\n\e#General info\n"); - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { - furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { - furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { - furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); - } - - furi_string_cat(str, "\e#Lock bits\n"); - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { - furi_string_cat_printf( - str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); - } - - if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { - furi_string_cat_printf( - str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); - } - +void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str) { if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) { furi_string_cat(str, "\e#Memory data\n\e*--------------------\n"); @@ -88,5 +63,34 @@ void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { "(Data is too big. Showing only the first %u bytes.)", display_block_count * block_size); } + } else { + furi_string_cat(str, "\e#No available data\n"); + } +} + +void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) { + furi_string_cat(str, "\n::::::::::::::::[General info]:::::::::::::::::\n"); + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) { + furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref); + } + + furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n"); + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) { + furi_string_cat_printf( + str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); + } + + if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) { + furi_string_cat_printf( + str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not"); } } diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h index d531fd2eb07..87100102ae5 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3_render.h @@ -12,3 +12,5 @@ void nfc_render_iso15693_3_info( void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str); void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str); + +void nfc_render_iso15693_3_system_info(const Iso15693_3Data* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 7feeccf22e9..6cba772509f 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -21,6 +21,7 @@ static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); furi_string_replace(temp_str, "Mifare", "MIFARE"); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c index bc05c2a4c36..ef51d98e0b3 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire.c @@ -14,8 +14,10 @@ static void nfc_scene_info_on_enter_mf_desfire(NfcApp* instance) { const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeFull, temp_str); widget_add_text_scroll_element( @@ -56,6 +58,7 @@ static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); nfc_render_mf_desfire_info(data, NfcProtocolFormatTypeShort, temp_str); widget_add_text_scroll_element( diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 4a8d4d74475..3efa032bc3a 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -20,6 +20,8 @@ static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index da75142ba05..a770df1e53d 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -564,11 +564,11 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { FuriString* temp_str = furi_string_alloc(); const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61); + widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_51x64); if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { widget_add_string_element( - widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID"); + widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating UID"); size_t uid_len; const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); @@ -580,7 +580,8 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { furi_string_trim(temp_str); } else { - widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating"); + widget_add_string_element( + widget, 90, 26, AlignCenter, AlignCenter, FontPrimary, "Emulating"); if(!furi_string_empty(instance->file_name)) { furi_string_set(temp_str, instance->file_name); } else { @@ -588,11 +589,12 @@ static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) { temp_str, "Unsaved\n%s", nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + furi_string_replace_str(temp_str, "Mifare", "MIFARE"); } } widget_add_text_box_element( - widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); + widget, 56, 33, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false); furi_string_free(temp_str); diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index ad858a75fce..8480f881095 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -14,16 +14,30 @@ static void nfc_scene_info_on_enter_slix(NfcApp* instance) { const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str); + widget_reset(instance->widget); widget_add_text_scroll_element( instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); furi_string_free(temp_str); } +static void nfc_scene_more_info_on_enter_slix(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix); + + FuriString* temp_str = furi_string_alloc(); + nfc_render_iso15693_3_system_info(slix_get_base_data(data), temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); +} + static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) { furi_assert(event.protocol == NfcProtocolSlix); @@ -101,13 +115,18 @@ static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) } const NfcProtocolSupportBase nfc_protocol_support_slix = { - .features = NfcProtocolFeatureEmulateFull, + .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo, .scene_info = { .on_enter = nfc_scene_info_on_enter_slix, .on_event = nfc_protocol_support_common_on_event_empty, }, + .scene_more_info = + { + .on_enter = nfc_scene_more_info_on_enter_slix, + .on_event = nfc_protocol_support_common_on_event_empty, + }, .scene_read = { .on_enter = nfc_scene_read_on_enter_slix, diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c index 80f953db973..1be460194f6 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix_render.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.c @@ -1,14 +1,12 @@ #include "slix_render.h" -#include "../iso15693_3/iso15693_3_render.h" - void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) { nfc_render_iso15693_3_brief(slix_get_base_data(data), str); if(format_type != NfcProtocolFormatTypeFull) return; const SlixType slix_type = slix_get_type(data); - furi_string_cat(str, "\n\e#Passwords\n"); + furi_string_cat(str, "\n::::::::::::::::::[Passwords]:::::::::::::::::\n"); static const char* slix_password_names[] = { "Read", @@ -25,7 +23,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ } } - furi_string_cat(str, "\e#Lock bits\n"); + furi_string_cat(str, ":::::::::::::::::::[Lock bits]::::::::::::::::::::\n"); if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) { furi_string_cat_printf( @@ -38,7 +36,7 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ const SlixProtection protection = data->system_info.protection; - furi_string_cat(str, "\e#Page protection\n"); + furi_string_cat(str, "::::::::::::[Page protection]::::::::::::\n"); furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer); const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un"; @@ -52,12 +50,12 @@ void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_typ } if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) { - furi_string_cat(str, "\e#Privacy\n"); + furi_string_cat(str, "::::::::::::::::::::[Privacy]::::::::::::::::::::::\n"); furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis"); } if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) { - furi_string_cat(str, "\e#Signature\n"); + furi_string_cat(str, ":::::::::::::::::::[Signature]::::::::::::::::::\n"); for(uint32_t i = 0; i < 4; ++i) { furi_string_cat_printf(str, "%02X ", data->signature[i]); } diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h index 98ae6dc97fd..bfc216382ee 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix_render.h +++ b/applications/main/nfc/helpers/protocol_support/slix/slix_render.h @@ -3,5 +3,6 @@ #include #include "../nfc_protocol_support_render_common.h" +#include "../iso15693_3/iso15693_3_render.h" void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index e22af48b34e..8f5826dc485 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -13,6 +13,7 @@ static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) { const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb); FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); furi_string_cat_printf( temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str); diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 183f498951c..29c407b289b 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -445,6 +445,15 @@ void nfc_app_reset_detected_protocols(NfcApp* instance) { instance->protocols_detected_num = 0; } +void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string) { + furi_assert(instance); + furi_assert(string); + + if(!furi_string_empty(instance->file_name)) { + furi_string_cat_printf(string, "Name:%s\n", furi_string_get_cstr(instance->file_name)); + } +} + static bool nfc_is_hal_ready() { if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) { // No connection to the chip, show an error screen diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 943d722f824..324ed6a5d0c 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -192,3 +192,5 @@ void nfc_make_app_folder(NfcApp* instance); void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count); void nfc_app_reset_detected_protocols(NfcApp* instance); + +void nfc_append_filename_string_when_present(NfcApp* instance, FuriString* string); diff --git a/applications/main/nfc/scenes/nfc_scene_select_protocol.c b/applications/main/nfc/scenes/nfc_scene_select_protocol.c index 7a5d1252182..52b2664ec61 100644 --- a/applications/main/nfc/scenes/nfc_scene_select_protocol.c +++ b/applications/main/nfc/scenes/nfc_scene_select_protocol.c @@ -29,6 +29,8 @@ void nfc_scene_select_protocol_on_enter(void* context) { "%s %s", prefix, nfc_device_get_protocol_name(instance->protocols_detected[i])); + + furi_string_replace_str(temp_str, "Mifare", "MIFARE"); submenu_add_item( submenu, furi_string_get_cstr(temp_str), diff --git a/assets/icons/NFC/NFC_dolphin_emulation_47x61.png b/assets/icons/NFC/NFC_dolphin_emulation_47x61.png deleted file mode 100644 index 1783531285bed514517fc0821501e718359dd765..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1541 zcmaJ>eM}Q)7{3ZaLqs>76Ex1V5{uLJ`hl&zQLyxiR4J5Flue0VuU9zXKDZvVFu$in zjZPV|k*WB>lBo=wab{s;0v4vrHgq6yL6|@s=rj(*ZALe`w+OgD#xD2qyib0=-}CW4 z_wKW%tO^gC8wNp8xH$>4fiD6cy*LQG!v-3sLy!oej7F>3XoNYQby0aF1bI7aN-wPT zSzjw@m}hD^wN~8M!%5Suc^yp$% z;LAJvZZ<7U;$}9n&swkday_9{j$6k|(n<`UWz^qKx;6aE!&&NN(Msv}w)zef+FQ1w zPdX)Tbt_@>_j_ z1g4yN$-3nhg@rScIaE?HPo@{A*oop?Lg$pk$HB2)6bR6yfWuxok8z`3y<}7u1$MxV zNZ?V3kgJ!xNGj7}g^esv!dkgfMko{tSgVthPF&?syKrI|tWv0yh!WgdnNo|Y)TmMi zk6jWFkaxOJ8D8>lEl2;>9^cDOUul8V6b|{|}|<3A9_V zkuUT8Phq#ch$9gj>1GRf0_>e@Q6LnD8hH{ISl-UEdE_v6wo9ijB}kYxrRx(fq|eo5E&zRs*rRh@+=LHR*h1V=c1idZ;b1lJeL)dauJXW z64={+?e(||3{b$F7+$cL7=MxhGtYzJp6B{075o?>)?~ZM@Am^U<4XHBa7ryUV+Omo z^NSB}*9I*Vo3i}=cBw9McHOY`cJjO|@#%;8vhG*t42yEl!M7sbnHFlhs_u%cu{BLw zhaGH~)>1i`aAdfxzb-yDYp8axDf-aUrDb=dji35@J+L(5s-gM3W`<^_YQ(SZsXOZ| z+MW^|z2V5B)@@rgjw-Cq(dw%_ar$zq^bB!0SN!Wu`o1~8yPWDctj)SpoN(-94Cmev zzx{^qT>j%Hhc_onj|mkQqEx+SO=({jC(e}(-Mu#TV*2AZM;fP!-l&6{t3IlprLTNH zu7%|!`|gA}!?~;;lRx5Kde1<}Db&?0+p;8mU68o-2TkKA6-h@we_Yd@SR^HGPk zTw<~x)L!ghDi?=TUtd5z8164@t6%c>#|vtn#`%LIpusI7pJ(MpaIQm;*_49SCT!aE E4{AO|uK)l5 diff --git a/assets/icons/NFC/NFC_dolphin_emulation_51x64.png b/assets/icons/NFC/NFC_dolphin_emulation_51x64.png new file mode 100644 index 0000000000000000000000000000000000000000..ad5646d16459cbeba7b19f63b5afb6db21f819f3 GIT binary patch literal 1591 zcmeAS@N?(olHy`uVBq!ia0vp^#z5@A!3-oDPVO&fU|`hGbaoE#baqxKD9TUE%t>Wn zsJOLu;zqB-0U~YtrCpCM4C8q8pg=t^Am{4A9?$45g^1P8lh%4VPrama=gz8H4)^VR zdhhHSCog`m;0MdT#z$8UDtZ(M9nAlA?)<}o2keWJuFq`?Y;#-_y8ETa_0Lb=2pjt_ zI;fus+I`iEp<}YeBXuUbctM8qv&-&gelUM_I3zQHDW`m~4oe{8FLA4@3db5GzXbC> z;xoAFxtr|~qsG^wdPXnDcli#UJLmB~Vm7E=_q^GVeTL&Z?S_?$y?Fz?FHN+1JvS?I ztB&`ryDA+|K5AUPs4)3=;fxg~fwHotjeqPJ#6BM_yF4*F^z^Oh=kp6~v4q}24xJX@vryZ0+8WTx0Eg`4^s_!c;)W@LI)6{QAO z`Gq7`WhYyvDB0U7*i={n4aiL`NmQuF&B-gas<2f8n`;GRgM{^!6u?SKvTc#AbGt^BsFfdXux715BHZ@62OEx#qQ7|$vGS)XV)HkryH8ip^Ftsu@R)7K} zpoK*#X;wilZcyuhJX@uVl9B=|ef{$Ca=mh6z5JqdeM3u2OML?)eIp}XpbFjM%Dj@q z3f;V7Wta&rsl~}fnFS@8`FRQ;6BCp2OG|8(fR2UuBDVl;Y+f-mq<~?jUy)d#Z>VPg z@)b;>uP=V3xw&xF#U(+h2=`(&xHzP;AXPsowK%`DC>a=cY04n03ap%qQWHz^i$e1A zb6^1(kda@KU!0L&pkQRGXQH413OGX}19QDxJtGq%GX)b%(z*Q}aq-dQ%X3O>pW3rIp+Qpv^9+MVV!(DQ-pixeDL_vC72l7DJro zLG`BKc8d{Cz4}1M=!2piDH*_ofN2ZFgr{pD2c9!h^MKi*2$=3;z1JuL^9);nPl)UP z|Nnu^&_kE&fCZSXr;B4q#jQQlIQf_qd0c5+MXOX$CUrGc&h{NFs!LOA8_nM2jgySt!IBFjn)|4o^*9%oVS*X zZ0??}n8{i*U;Ri9Uic<}|l_6;w1%IPg#Ybbe;UU!j&K6%UWKMy2+v zd+;yhlYZ_kTP_hFZXj~h;=H7@?t!?7<+Cq)1TE_njXLwt{|dW`N6ZgNp{aEbcu&4{ z?-7XAk9w%%Qq`HH9p(6A`#fReDXS{?wu*1&4cg@tlrG*P{m)}d-osblSZ)cuotM$v zPwW5H$pSs;NycL(D`2Zwc+K;@CAtDnm{r-UW| Dsjf_~ literal 0 HcmV?d00001 From 9d6f51484a11ae71ab758693e2bc0bcb53ae9384 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Tue, 6 Feb 2024 21:20:57 +0300 Subject: [PATCH 07/58] [FL-3755] Rework more info scene for Ultralight cards (#3391) * Changed event handler signature. Now we put whole SceneManagerEvent not only custom event. * Changed signature and implementation of common on_event callback * Changes required due to event signature adjustment * Reset widget on exit from more info scene * Enum for more info scene states for ultralight cards * New implementation of more info logic added * Check simplified * Update nfc_protocol_support.c --------- Co-authored-by: gornekich --- .../helpers/protocol_support/felica/felica.c | 4 +- .../iso14443_3a/iso14443_3a.c | 4 +- .../iso14443_3b/iso14443_3b.c | 6 +- .../iso14443_3b/iso14443_3b_i.h | 2 +- .../iso14443_4a/iso14443_4a.c | 4 +- .../iso14443_4b/iso14443_4b.c | 6 +- .../protocol_support/iso15693_3/iso15693_3.c | 4 +- .../protocol_support/mf_classic/mf_classic.c | 35 +++--- .../mf_ultralight/mf_ultralight.c | 106 ++++++++++++++---- .../mf_ultralight/mf_ultralight_render.c | 3 +- .../protocol_support/nfc_protocol_support.c | 26 ++--- .../nfc_protocol_support_base.h | 5 +- .../nfc_protocol_support_gui_common.c | 4 +- .../nfc_protocol_support_gui_common.h | 3 +- .../nfc/helpers/protocol_support/slix/slix.c | 4 +- .../helpers/protocol_support/st25tb/st25tb.c | 4 +- 16 files changed, 143 insertions(+), 77 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/felica/felica.c b/applications/main/nfc/helpers/protocol_support/felica/felica.c index 6e7aa2d8ab1..affa33b8651 100644 --- a/applications/main/nfc/helpers/protocol_support/felica/felica.c +++ b/applications/main/nfc/helpers/protocol_support/felica/felica.c @@ -59,8 +59,8 @@ static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c index 05db74a4ed0..99e21130171 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3a/iso14443_3a.c @@ -97,8 +97,8 @@ static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c index a71a6575337..43b541111ad 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b.c @@ -60,8 +60,8 @@ static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) { furi_string_free(temp_str); } -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } @@ -69,7 +69,7 @@ bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t return false; } -static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, SceneManagerEvent event) { return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h index 53fe6b3927a..6c7c2a0bc8a 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h +++ b/applications/main/nfc/helpers/protocol_support/iso14443_3b/iso14443_3b_i.h @@ -4,4 +4,4 @@ #include "iso14443_3b.h" -bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event); +bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c index 5bd38975d85..17435ccd46d 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4a/iso14443_4a.c @@ -100,8 +100,8 @@ static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c index ee49f2a42dc..8038e049125 100644 --- a/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c +++ b/applications/main/nfc/helpers/protocol_support/iso14443_4b/iso14443_4b.c @@ -65,8 +65,8 @@ static void nfc_scene_saved_menu_on_enter_iso14443_4b(NfcApp* instance) { UNUSED(instance); } -static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEmulate) { +static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEmulate) { scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate); return true; } @@ -74,7 +74,7 @@ static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t return false; } -static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, SceneManagerEvent event) { return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event); } diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index d645fa3bb74..7efd102f1a8 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -108,8 +108,8 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) { instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance); } -static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 6cba772509f..8ac4a2c6a9b 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -100,8 +100,9 @@ static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) { nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance); } -static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) { - if(event == NfcCustomEventPollerIncomplete) { +static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && + event.event == NfcCustomEventPollerIncomplete) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); } @@ -171,8 +172,8 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } -static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexDetectReader) { +static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexDetectReader) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); dolphin_deed(DolphinDeedNfcDetectReader); return true; @@ -181,27 +182,29 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t e return false; } -static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) { +static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event == SubmenuIndexDetectReader) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); - consumed = true; - } else if(event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); - consumed = true; - } else if(event == SubmenuIndexUpdate) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); - consumed = true; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader); + consumed = true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial); + consumed = true; + } else if(event.event == SubmenuIndexUpdate) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); + consumed = true; + } } return consumed; } -static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) { +static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event == NfcCustomEventTextInputDone) { + if(event.type == SceneManagerEventTypeCustom && event.event == NfcCustomEventTextInputDone) { mf_classic_key_cache_save( instance->mfc_key_cache, nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic)); diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 3efa032bc3a..f0a46dd87d5 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -2,6 +2,7 @@ #include "mf_ultralight_render.h" #include +#include #include "nfc/nfc_app_i.h" @@ -15,6 +16,11 @@ enum { SubmenuIndexWrite, }; +enum { + NfcSceneMoreInfoStateASCII, + NfcSceneMoreInfoStateRawData, +}; + static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) { const NfcDevice* device = instance->nfc_device; const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); @@ -37,11 +43,62 @@ static void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) { const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight); furi_string_reset(instance->text_box_store); - nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + uint32_t scene_state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneMoreInfo); + + if(scene_state == NfcSceneMoreInfoStateASCII) { + pretty_format_bytes_hex_canonical( + instance->text_box_store, + MF_ULTRALIGHT_PAGE_SIZE, + PRETTY_FORMAT_FONT_MONOSPACE, + (uint8_t*)mfu->page, + mfu->pages_read * MF_ULTRALIGHT_PAGE_SIZE); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + widget_add_button_element( + instance->widget, + GuiButtonTypeRight, + "Raw Data", + nfc_protocol_support_common_widget_callback, + instance); - text_box_set_font(instance->text_box, TextBoxFontHex); - text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store)); - view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox); + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "Info", + nfc_protocol_support_common_widget_callback, + instance); + } else if(scene_state == NfcSceneMoreInfoStateRawData) { + nfc_render_mf_ultralight_dump(mfu, instance->text_box_store); + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 48, furi_string_get_cstr(instance->text_box_store)); + + widget_add_button_element( + instance->widget, + GuiButtonTypeLeft, + "ASCII", + nfc_protocol_support_common_widget_callback, + instance); + } +} + +static bool nfc_scene_more_info_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + + if((event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeLeft) || + (event.type == SceneManagerEventTypeBack)) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateASCII); + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } else if(event.type == SceneManagerEventTypeCustom && event.event == GuiButtonTypeRight) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneMoreInfo, NfcSceneMoreInfoStateRawData); + scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo); + consumed = true; + } + return consumed; } static NfcCommand @@ -134,15 +191,17 @@ static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) { nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance); } -bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { - if(event == NfcCustomEventCardDetected) { - scene_manager_set_scene_state( - instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); - nfc_scene_read_setup_view(instance); - } else if((event == NfcCustomEventPollerIncomplete)) { - notification_message(instance->notifications, &sequence_semi_success); - scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); - dolphin_deed(DolphinDeedNfcReadSuccess); +bool nfc_scene_read_on_event_mf_ultralight(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventCardDetected) { + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneRead, NfcSceneMfUltralightReadMenuStateCardFound); + nfc_scene_read_setup_view(instance); + } else if((event.event == NfcCustomEventPollerIncomplete)) { + notification_message(instance->notifications, &sequence_semi_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess); + dolphin_deed(DolphinDeedNfcReadSuccess); + } } return true; } @@ -204,14 +263,17 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { nfc_listener_start(instance->listener, NULL, NULL); } -static bool - nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexUnlock) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); - return true; - } else if(event == SubmenuIndexWrite) { - scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - return true; +static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( + NfcApp* instance, + SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); + return true; + } else if(event.event == SubmenuIndexWrite) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); + return true; + } } return false; } @@ -227,7 +289,7 @@ const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { .scene_more_info = { .on_enter = nfc_scene_more_info_on_enter_mf_ultralight, - .on_event = nfc_protocol_support_common_on_event_empty, + .on_event = nfc_scene_more_info_on_event_mf_ultralight, }, .scene_read = { diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c index 5296f480712..1bc508adce1 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight_render.c @@ -36,10 +36,11 @@ void nfc_render_mf_ultralight_info( } void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) { + furi_string_cat_printf(str, "\e*"); for(size_t i = 0; i < data->pages_read; i++) { const uint8_t* page_data = data->page[i].data; for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) { - furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]); + furi_string_cat_printf(str, " %02X%02X", page_data[j], page_data[j + 1]); } } } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index a770df1e53d..37a9199df94 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -131,16 +131,15 @@ static bool nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) { bool consumed = false; - if(event.type == SceneManagerEventTypeCustom) { - const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event); - } + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); + consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event); return consumed; } static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) { text_box_reset(instance->text_box); + widget_reset(instance->widget); furi_string_reset(instance->text_box_store); } @@ -188,8 +187,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else { const NfcProtocol protocol = instance->protocols_detected[instance->protocols_detected_selected_idx]; - consumed = - nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); } } else if(event.event == NfcCustomEventPollerFailure) { nfc_poller_stop(instance->poller); @@ -202,7 +200,7 @@ static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneMana } else if(event.event == NfcCustomEventCardDetected) { const NfcProtocol protocol = instance->protocols_detected[instance->protocols_detected_selected_idx]; - consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_read.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { nfc_poller_stop(instance->poller); @@ -287,8 +285,7 @@ static bool consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -456,8 +453,7 @@ static bool consumed = true; } else { const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = - nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event); + consumed = nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event); } } else if(event.type == SceneManagerEventTypeBack) { @@ -521,9 +517,11 @@ static bool scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ? DolphinDeedNfcAddSave : DolphinDeedNfcSave); - const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); - consumed = nfc_protocol_support[protocol]->scene_save_name.on_event( - instance, event.event); + + const NfcProtocol protocol = + instance->protocols_detected[instance->protocols_detected_selected_idx]; + consumed = + nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event); } else { consumed = scene_manager_search_and_switch_to_previous_scene( instance->scene_manager, NfcSceneStart); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h index 69a6d34d291..eec736ca291 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_base.h @@ -7,6 +7,7 @@ #include #include "../../nfc_app.h" +#include "../../nfc_app_i.h" /** * @brief Scene entry handler. @@ -19,10 +20,10 @@ typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance); * @brief Scene event handler. * * @param[in,out] instance pointer to the NFC application instance. - * @param[in] event custom event that has occurred. + * @param[in] event scene manager event that has occurred. * @returns true if the event was handled, false otherwise. */ -typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event); +typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, SceneManagerEvent event); /** * @brief Abstract scene interface. diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c index f3a85512555..8c38f847569 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.c @@ -35,8 +35,8 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) { UNUSED(instance); } -bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) { +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event) { UNUSED(instance); UNUSED(event); - return true; + return event.type != SceneManagerEventTypeBack; } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h index 40ba40c8ec1..3230f1a7e40 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_gui_common.h @@ -7,6 +7,7 @@ #include #include "nfc/nfc_app.h" +#include "nfc/nfc_app_i.h" /** * @brief Common submenu indices. @@ -82,4 +83,4 @@ void nfc_protocol_support_common_on_enter_empty(NfcApp* instance); * @param[in] event custom event type that has occurred. * @returns always true. */ -bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event); +bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, SceneManagerEvent event); diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 8480f881095..35592eaa126 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -105,8 +105,8 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) { nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance); } -static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } diff --git a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c index 8f5826dc485..0305d614c89 100644 --- a/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c +++ b/applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c @@ -61,8 +61,8 @@ static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) { furi_string_free(temp_str); } -static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) { - if(event == SubmenuIndexCommonEdit) { +static bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, SceneManagerEvent event) { + if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexCommonEdit) { scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); return true; } From cd8e76bbcd6a92d679b31d364b8df3f5dc104859 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Tue, 6 Feb 2024 21:26:05 +0300 Subject: [PATCH 08/58] [FL-3757] NFC: fix application opening from browser (#3396) * nfc_protocol_support_has_feature is now public * Added function to show different scene depending on supported features of the device * Fix delete button logic when selected from browser * Fix rename button logic when selected from browser * Update nfc_scene_save_success.c --------- Co-authored-by: gornekich Co-authored-by: hedger --- .../helpers/protocol_support/nfc_protocol_support.c | 2 +- .../helpers/protocol_support/nfc_protocol_support.h | 3 +++ applications/main/nfc/nfc_app.c | 12 +++++++++++- applications/main/nfc/scenes/nfc_scene_delete.c | 7 +++++-- .../main/nfc/scenes/nfc_scene_delete_success.c | 5 +++++ .../main/nfc/scenes/nfc_scene_save_success.c | 13 +++++++++++++ 6 files changed, 38 insertions(+), 4 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 37a9199df94..262523ecdd3 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -74,7 +74,7 @@ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) nfc_protocol_support_scenes[scene].on_exit(instance); } -static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { +bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) { return nfc_protocol_support[protocol]->features & feature; } diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h index b6bfde45c04..855642c621d 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.h @@ -76,6 +76,7 @@ #pragma once #include +#include #include "nfc_protocol_support_common.h" @@ -111,3 +112,5 @@ bool nfc_protocol_support_on_event( * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler). */ void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context); + +bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature); \ No newline at end of file diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index 29c407b289b..c0ca2a917fe 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -1,4 +1,5 @@ #include "nfc_app_i.h" +#include "helpers/protocol_support/nfc_protocol_support.h" #include @@ -475,6 +476,15 @@ static bool nfc_is_hal_ready() { } } +static void nfc_show_initial_scene_for_device(NfcApp* nfc) { + NfcProtocol prot = nfc_device_get_protocol(nfc->nfc_device); + uint32_t scene = nfc_protocol_support_has_feature( + prot, NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEmulateUid) ? + NfcSceneEmulate : + NfcSceneSavedMenu; + scene_manager_next_scene(nfc->scene_manager, scene); +} + int32_t nfc_app(void* p) { if(!nfc_is_hal_ready()) return 0; @@ -494,7 +504,7 @@ int32_t nfc_app(void* p) { furi_string_set(nfc->file_path, args); if(nfc_load_file(nfc, nfc->file_path, false)) { - scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate); + nfc_show_initial_scene_for_device(nfc); } else { view_dispatcher_stop(nfc->view_dispatcher); } diff --git a/applications/main/nfc/scenes/nfc_scene_delete.c b/applications/main/nfc/scenes/nfc_scene_delete.c index c1a676168ad..924ed78faf9 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete.c +++ b/applications/main/nfc/scenes/nfc_scene_delete.c @@ -51,8 +51,11 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) { if(nfc_delete(nfc)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess); } else { - scene_manager_search_and_switch_to_previous_scene( - nfc->scene_manager, NfcSceneStart); + if(!scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneStart)) { + scene_manager_stop(nfc->scene_manager); + view_dispatcher_stop(nfc->view_dispatcher); + } } consumed = true; } diff --git a/applications/main/nfc/scenes/nfc_scene_delete_success.c b/applications/main/nfc/scenes/nfc_scene_delete_success.c index 73856c292a1..d41e5254934 100644 --- a/applications/main/nfc/scenes/nfc_scene_delete_success.c +++ b/applications/main/nfc/scenes/nfc_scene_delete_success.c @@ -31,6 +31,11 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) { } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneFileSelect); + + if(!consumed) { + scene_manager_stop(nfc->scene_manager); + view_dispatcher_stop(nfc->view_dispatcher); + } } } } diff --git a/applications/main/nfc/scenes/nfc_scene_save_success.c b/applications/main/nfc/scenes/nfc_scene_save_success.c index ef7863c1388..1c76b3f8771 100644 --- a/applications/main/nfc/scenes/nfc_scene_save_success.c +++ b/applications/main/nfc/scenes/nfc_scene_save_success.c @@ -31,12 +31,25 @@ bool nfc_scene_save_success_on_event(void* context, SceneManagerEvent event) { } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSaveConfirm)) { scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicDetectReader); consumed = true; + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSetType)) { + consumed = scene_manager_search_and_switch_to_another_scene( + nfc->scene_manager, NfcSceneFileSelect); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadSuccess)) { + consumed = scene_manager_search_and_switch_to_another_scene( + nfc->scene_manager, NfcSceneFileSelect); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneFileSelect)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneFileSelect); + } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSavedMenu); } else { consumed = scene_manager_search_and_switch_to_another_scene( nfc->scene_manager, NfcSceneFileSelect); } } } + return consumed; } From e180266c1a2f4fb9e29355d3d75d7e3924264682 Mon Sep 17 00:00:00 2001 From: gsp8181 <3165098+gsp8181@users.noreply.github.com> Date: Wed, 7 Feb 2024 00:25:38 +0530 Subject: [PATCH 09/58] ITSO Parser (UK) (#3325) * Create ITSO parser Standard for UK transport cards outside of Oyster * Update application.fam with ITSO parser * Update date buffer to fix access outside array * nfc app: use rtc function in itso plugin --------- Co-authored-by: gornekich Co-authored-by: hedger --- applications/main/nfc/application.fam | 9 ++ .../main/nfc/plugins/supported_cards/itso.c | 124 ++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/itso.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 27cb1e9eea9..c0acace5d48 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -128,6 +128,15 @@ App( sources=["plugins/supported_cards/hid.c"], ) +App( + appid="itso_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="itso_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/itso.c"], +) + App( appid="nfc_start", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c new file mode 100644 index 00000000000..a4e0c8869a4 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -0,0 +1,124 @@ +/* itso.c - Parser for ITSO cards (United Kingdom). */ +#include "nfc_supported_card_plugin.h" + +#include +#include + +#include +#include + +static const MfDesfireApplicationId itso_app_id = {.data = {0x16, 0x02, 0xa0}}; +static const MfDesfireFileId itso_file_id = 0x0f; + +int64_t swap_int64(int64_t val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL); +} + +uint64_t swap_uint64(uint64_t val) { + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + return (val << 32) | (val >> 32); +} + +static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = false; + + do { + const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire); + + const MfDesfireApplication* app = mf_desfire_get_application(data, &itso_app_id); + if(app == NULL) break; + + typedef struct { + uint64_t part1; + uint64_t part2; + uint64_t part3; + uint64_t part4; + } ItsoFile; + + const MfDesfireFileSettings* file_settings = + mf_desfire_get_file_settings(app, &itso_file_id); + + if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard || + file_settings->data.size < sizeof(ItsoFile)) + break; + + const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &itso_file_id); + if(file_data == NULL) break; + + const ItsoFile* itso_file = simple_array_cget_data(file_data->data); + + uint64_t x1 = swap_uint64(itso_file->part1); + uint64_t x2 = swap_uint64(itso_file->part2); + + char cardBuff[32]; + char dateBuff[18]; + + snprintf(cardBuff, sizeof(cardBuff), "%llx%llx", x1, x2); + snprintf(dateBuff, sizeof(dateBuff), "%llx", x2); + + char* cardp = cardBuff + 4; + cardp[18] = '\0'; + + // All itso card numbers are prefixed with "633597" + if(strncmp(cardp, "633597", 6) != 0) break; + + char* datep = dateBuff + 12; + dateBuff[17] = '\0'; + + // DateStamp is defined in BS EN 1545 - Days passed since 01/01/1997 + uint32_t dateStamp = (int)strtol(datep, NULL, 16); + uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U; + + furi_string_set(parsed_data, "\e#ITSO Card\n"); + + // Digit count in each space-separated group + static const uint8_t digit_count[] = {6, 4, 4, 4}; + + for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) { + for(uint32_t j = 0; j < digit_count[i]; ++j) { + furi_string_push_back(parsed_data, cardp[j + k]); + } + furi_string_push_back(parsed_data, ' '); + } + + FuriHalRtcDateTime timestamp = {0}; + furi_hal_rtc_timestamp_to_datetime(unixTimestamp, ×tamp); + FuriString* timestamp_str = furi_string_alloc(); + locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); + + furi_string_cat(parsed_data, "\nExpiry: "); + furi_string_cat(parsed_data, timestamp_str); + + furi_string_free(timestamp_str); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin itso_plugin = { + .protocol = NfcProtocolMfDesfire, + .verify = NULL, + .read = NULL, + .parse = itso_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor itso_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &itso_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* itso_plugin_ep() { + return &itso_plugin_descriptor; +} From 8c54c14742540113d41dd8068361ecaa602c8996 Mon Sep 17 00:00:00 2001 From: RebornedBrain <138568282+RebornedBrain@users.noreply.github.com> Date: Tue, 6 Feb 2024 22:11:27 +0300 Subject: [PATCH 10/58] [FL-3758] NFC: MFC Unlock with Dictionary (#3411) * New menu item "Unlock with Dictionary" added to classic menu * No retry also returns to dictionary attack if it was previously displayed * nfc app: format code --------- Co-authored-by: gornekich --- .../protocol_support/mf_classic/mf_classic.c | 36 ++++++++++++++++--- .../main/nfc/scenes/nfc_scene_retry_confirm.c | 7 ++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 8ac4a2c6a9b..1486011917d 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -14,6 +14,7 @@ enum { SubmenuIndexDetectReader = SubmenuIndexCommonMax, SubmenuIndexWrite, SubmenuIndexUpdate, + SubmenuIndexDictAttack }; static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) { @@ -120,6 +121,13 @@ static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); + + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); } } @@ -151,6 +159,13 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexDetectReader, nfc_protocol_support_common_submenu_callback, instance); + + submenu_add_item( + submenu, + "Unlock with Dictionary", + SubmenuIndexDictAttack, + nfc_protocol_support_common_submenu_callback, + instance); } submenu_add_item( submenu, @@ -158,6 +173,7 @@ static void nfc_scene_saved_menu_on_enter_mf_classic(NfcApp* instance) { SubmenuIndexWrite, nfc_protocol_support_common_submenu_callback, instance); + submenu_add_item( submenu, "Update from Initial Card", @@ -173,13 +189,20 @@ static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) { } static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { - if(event.type == SceneManagerEventTypeCustom && event.event == SubmenuIndexDetectReader) { - scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); - dolphin_deed(DolphinDeedNfcDetectReader); - return true; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexDetectReader) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSaveConfirm); + dolphin_deed(DolphinDeedNfcDetectReader); + consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + consumed = true; + } } - return false; + return consumed; } static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneManagerEvent event) { @@ -195,6 +218,9 @@ static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, SceneMana } else if(event.event == SubmenuIndexUpdate) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial); consumed = true; + } else if(event.event == SubmenuIndexDictAttack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); + consumed = true; } } diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 99acbb0b803..62299609468 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -28,8 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene( - nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneMfClassicDictAttack); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfUltralightUnlockWarn)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfUltralightUnlockMenu); } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneDetect)) { From 4984e9e4142a33d8ed32f4187afbb709f85c651e Mon Sep 17 00:00:00 2001 From: gornekich Date: Tue, 6 Feb 2024 19:25:55 +0000 Subject: [PATCH 11/58] nfc app: fix incorrect protocol detection in save scene (#3430) --- .../main/nfc/helpers/protocol_support/nfc_protocol_support.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 262523ecdd3..2d46810a2c4 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -518,8 +518,7 @@ static bool DolphinDeedNfcAddSave : DolphinDeedNfcSave); - const NfcProtocol protocol = - instance->protocols_detected[instance->protocols_detected_selected_idx]; + const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device); consumed = nfc_protocol_support[protocol]->scene_save_name.on_event(instance, event); } else { From 29fa68118d827e951ea1c9139e470838cf8d090e Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Wed, 7 Feb 2024 12:38:20 +1000 Subject: [PATCH 12/58] GUI: canvas commit callback has been moved to canvas. Direct Draw apps can now be streamed via RPC. (#3397) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/services/gui/canvas.c | 55 ++++++++++++++++++++++++++++ applications/services/gui/canvas_i.h | 51 ++++++++++++++++++++++++++ applications/services/gui/gui.c | 27 ++------------ applications/services/gui/gui_i.h | 13 ------- 4 files changed, 110 insertions(+), 36 deletions(-) diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 44adcd93951..897f0ae1336 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -18,6 +18,12 @@ Canvas* canvas_init() { Canvas* canvas = malloc(sizeof(Canvas)); canvas->compress_icon = compress_icon_alloc(); + // Initialize mutex + canvas->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + // Initialize callback array + CanvasCallbackPairArray_init(canvas->canvas_callback_pair); + // Setup u8g2 u8g2_Setup_st756x_flipper(&canvas->fb, U8G2_R0, u8x8_hw_spi_stm32, u8g2_gpio_and_delay_stm32); canvas->orientation = CanvasOrientationHorizontal; @@ -36,9 +42,21 @@ Canvas* canvas_init() { void canvas_free(Canvas* canvas) { furi_assert(canvas); compress_icon_free(canvas->compress_icon); + CanvasCallbackPairArray_clear(canvas->canvas_callback_pair); + furi_mutex_free(canvas->mutex); free(canvas); } +static void canvas_lock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_acquire(canvas->mutex, FuriWaitForever) == FuriStatusOk); +} + +static void canvas_unlock(Canvas* canvas) { + furi_assert(canvas); + furi_check(furi_mutex_release(canvas->mutex) == FuriStatusOk); +} + void canvas_reset(Canvas* canvas) { furi_assert(canvas); @@ -52,6 +70,18 @@ void canvas_reset(Canvas* canvas) { void canvas_commit(Canvas* canvas) { furi_assert(canvas); u8g2_SendBuffer(&canvas->fb); + + // Iterate over callbacks + canvas_lock(canvas); + for + M_EACH(p, canvas->canvas_callback_pair, CanvasCallbackPairArray_t) { + p->callback( + canvas_get_buffer(canvas), + canvas_get_buffer_size(canvas), + canvas_get_orientation(canvas), + p->context); + } + canvas_unlock(canvas); } uint8_t* canvas_get_buffer(Canvas* canvas) { @@ -542,3 +572,28 @@ void canvas_set_orientation(Canvas* canvas, CanvasOrientation orientation) { CanvasOrientation canvas_get_orientation(const Canvas* canvas) { return canvas->orientation; } + +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(!CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p)); + CanvasCallbackPairArray_push_back(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} + +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context) { + furi_assert(canvas); + + const CanvasCallbackPair p = {callback, context}; + + canvas_lock(canvas); + furi_assert(CanvasCallbackPairArray_count(canvas->canvas_callback_pair, p) == 1); + CanvasCallbackPairArray_remove_val(canvas->canvas_callback_pair, p); + canvas_unlock(canvas); +} \ No newline at end of file diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index f3b8f17ad79..73502b719b4 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -8,6 +8,30 @@ #include "canvas.h" #include #include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*CanvasCommitCallback)( + uint8_t* data, + size_t size, + CanvasOrientation orientation, + void* context); + +typedef struct { + CanvasCommitCallback callback; + void* context; +} CanvasCallbackPair; + +ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); + +#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) + +ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); /** Canvas structure */ @@ -19,6 +43,8 @@ struct Canvas { uint8_t width; uint8_t height; CompressIcon* compress_icon; + CanvasCallbackPairArray_t canvas_callback_pair; + FuriMutex* mutex; }; /** Allocate memory and initialize canvas @@ -97,3 +123,28 @@ void canvas_draw_u8g2_bitmap( uint8_t height, const uint8_t* bitmap, uint8_t rotation); + +/** Add canvas commit callback. + * + * This callback will be called upon Canvas commit. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_add_framebuffer_callback(Canvas* canvas, CanvasCommitCallback callback, void* context); + +/** Remove canvas commit callback. + * + * @param canvas Canvas instance + * @param callback CanvasCommitCallback + * @param context CanvasCommitCallback context + */ +void canvas_remove_framebuffer_callback( + Canvas* canvas, + CanvasCommitCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 0bdc999b774..9deaf23fe4b 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -265,14 +265,6 @@ static void gui_redraw(Gui* gui) { } canvas_commit(gui->canvas); - for - M_EACH(p, gui->canvas_callback_pair, CanvasCallbackPairArray_t) { - p->callback( - canvas_get_buffer(gui->canvas), - canvas_get_buffer_size(gui->canvas), - canvas_get_orientation(gui->canvas), - p->context); - } } while(false); gui_unlock(gui); @@ -473,12 +465,7 @@ void gui_view_port_send_to_back(Gui* gui, ViewPort* view_port) { void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(!CanvasCallbackPairArray_count(gui->canvas_callback_pair, p)); - CanvasCallbackPairArray_push_back(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_add_framebuffer_callback(gui->canvas, callback, context); // Request redraw gui_update(gui); @@ -487,12 +474,7 @@ void gui_add_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, vo void gui_remove_framebuffer_callback(Gui* gui, GuiCanvasCommitCallback callback, void* context) { furi_assert(gui); - const CanvasCallbackPair p = {callback, context}; - - gui_lock(gui); - furi_assert(CanvasCallbackPairArray_count(gui->canvas_callback_pair, p) == 1); - CanvasCallbackPairArray_remove_val(gui->canvas_callback_pair, p); - gui_unlock(gui); + canvas_remove_framebuffer_callback(gui->canvas, callback, context); } size_t gui_get_framebuffer_size(const Gui* gui) { @@ -542,20 +524,19 @@ Gui* gui_alloc() { gui->thread_id = furi_thread_get_current_id(); // Allocate mutex gui->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - furi_check(gui->mutex); + // Layers for(size_t i = 0; i < GuiLayerMAX; i++) { ViewPortArray_init(gui->layers[i]); } + // Drawing canvas gui->canvas = canvas_init(); - CanvasCallbackPairArray_init(gui->canvas_callback_pair); // Input gui->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); gui->input_events = furi_record_open(RECORD_INPUT_EVENTS); - furi_check(gui->input_events); furi_pubsub_subscribe(gui->input_events, gui_input_events_callback, gui); return gui; diff --git a/applications/services/gui/gui_i.h b/applications/services/gui/gui_i.h index a5e269e034f..3ca9c05c93e 100644 --- a/applications/services/gui/gui_i.h +++ b/applications/services/gui/gui_i.h @@ -10,7 +10,6 @@ #include #include #include -#include #include #include "canvas.h" @@ -44,17 +43,6 @@ ARRAY_DEF(ViewPortArray, ViewPort*, M_PTR_OPLIST); -typedef struct { - GuiCanvasCommitCallback callback; - void* context; -} CanvasCallbackPair; - -ARRAY_DEF(CanvasCallbackPairArray, CanvasCallbackPair, M_POD_OPLIST); - -#define M_OPL_CanvasCallbackPairArray_t() ARRAY_OPLIST(CanvasCallbackPairArray, M_POD_OPLIST) - -ALGO_DEF(CanvasCallbackPairArray, CanvasCallbackPairArray_t); - /** Gui structure */ struct Gui { // Thread and lock @@ -66,7 +54,6 @@ struct Gui { bool direct_draw; ViewPortArray_t layers[GuiLayerMAX]; Canvas* canvas; - CanvasCallbackPairArray_t canvas_callback_pair; // Input FuriMessageQueue* input_queue; From afd41aade1bc1a9a8de23e7754ef4e8c441c0865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Wed, 7 Feb 2024 16:09:29 +0700 Subject: [PATCH 13/58] FuriHal: various GPIO improvements (#3406) --- furi/core/common_defines.h | 4 ++++ targets/f7/furi_hal/furi_hal_gpio.c | 32 +++++++++++++++++++++-------- targets/f7/furi_hal/furi_hal_gpio.h | 1 - 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index b0062e65916..2fc12dedbab 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -21,6 +21,10 @@ extern "C" { #define FURI_PACKED __attribute__((packed)) #endif +#ifndef FURI_ALWAYS_STATIC_INLINE +#define FURI_ALWAYS_STATIC_INLINE __attribute__((always_inline)) static inline +#endif + #ifndef FURI_IS_IRQ_MASKED #define FURI_IS_IRQ_MASKED() (__get_PRIMASK() != 0U) #endif diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index d5221aba9dc..1e703b5d16d 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -129,11 +129,9 @@ void furi_hal_gpio_init_ex( LL_GPIO_SetPinMode(gpio->port, gpio->pin, LL_GPIO_MODE_INPUT); LL_SYSCFG_SetEXTISource(sys_exti_port, sys_exti_line); if(mode == GpioModeInterruptRise || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableRisingTrig_0_31(exti_line); } if(mode == GpioModeInterruptFall || mode == GpioModeInterruptRiseFall) { - LL_EXTI_EnableIT_0_31(exti_line); LL_EXTI_EnableFallingTrig_0_31(exti_line); } if(mode == GpioModeEventRise || mode == GpioModeEventRiseFall) { @@ -149,6 +147,7 @@ void furi_hal_gpio_init_ex( if(LL_SYSCFG_GetEXTISource(sys_exti_line) == sys_exti_port && LL_EXTI_IsEnabledIT_0_31(exti_line)) { LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); LL_EXTI_DisableRisingTrig_0_31(exti_line); LL_EXTI_DisableFallingTrig_0_31(exti_line); } @@ -199,11 +198,15 @@ void furi_hal_gpio_add_int_callback(const GpioPin* gpio, GpioExtiCallback cb, vo furi_assert(cb); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); furi_check(gpio_interrupt[pin_num].callback == NULL); gpio_interrupt[pin_num].callback = cb; gpio_interrupt[pin_num].context = ctx; - gpio_interrupt[pin_num].ready = true; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -211,10 +214,13 @@ void furi_hal_gpio_enable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); if(gpio_interrupt[pin_num].callback) { - gpio_interrupt[pin_num].ready = true; + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); } + FURI_CRITICAL_EXIT(); } @@ -222,8 +228,11 @@ void furi_hal_gpio_disable_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); - uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - gpio_interrupt[pin_num].ready = false; + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + FURI_CRITICAL_EXIT(); } @@ -231,15 +240,20 @@ void furi_hal_gpio_remove_int_callback(const GpioPin* gpio) { furi_assert(gpio); FURI_CRITICAL_ENTER(); + + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_DisableIT_0_31(exti_line); + LL_EXTI_ClearFlag_0_31(exti_line); + uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); gpio_interrupt[pin_num].callback = NULL; gpio_interrupt[pin_num].context = NULL; - gpio_interrupt[pin_num].ready = false; + FURI_CRITICAL_EXIT(); } -static void furi_hal_gpio_int_call(uint16_t pin_num) { - if(gpio_interrupt[pin_num].callback && gpio_interrupt[pin_num].ready) { +FURI_ALWAYS_STATIC_INLINE void furi_hal_gpio_int_call(uint16_t pin_num) { + if(gpio_interrupt[pin_num].callback) { gpio_interrupt[pin_num].callback(gpio_interrupt[pin_num].context); } } diff --git a/targets/f7/furi_hal/furi_hal_gpio.h b/targets/f7/furi_hal/furi_hal_gpio.h index 0999971bcca..9e78872bbc6 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.h +++ b/targets/f7/furi_hal/furi_hal_gpio.h @@ -24,7 +24,6 @@ typedef void (*GpioExtiCallback)(void* ctx); typedef struct { GpioExtiCallback callback; void* context; - volatile bool ready; } GpioInterrupt; /** From 26da5f564b7f72f71b61f4a74f4638be8d435323 Mon Sep 17 00:00:00 2001 From: Kris Bahnsen Date: Thu, 8 Feb 2024 01:22:03 -0800 Subject: [PATCH 14/58] furi/core/timer: resolve timer handle use-after-free post deletion (#3431) When xTimerDelete is called using a dymanic timer handle, the timer handle should immediately be considered unusable for any operation; including checking if the timer is still running. Under high system loads, that memory region may see fast reuse while furi_timer_free is sleeping between timer active checks. That reuse could result in memory at that pointer causing the timer active check to return true. Rework the furi_timer_delete process (in the case of dynamically allocated callback memory) to stop the timer, wait for it to stop, free the memory, and then delete the timer. Timers without dynamically allocated callback memory are just sent a delete command; no need to stop it first. Fixes: ff33bc6aea43 ("Furi: wait for timer wind down in destructor (#1716)") Signed-off-by: Kris Bahnsen --- furi/core/timer.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/furi/core/timer.c b/furi/core/timer.c index 027e4cf40db..f667aae964a 100644 --- a/furi/core/timer.c +++ b/furi/core/timer.c @@ -67,17 +67,22 @@ void furi_timer_free(FuriTimer* instance) { callb = (TimerCallback_t*)pvTimerGetTimerID(hTimer); - furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); - - while(furi_timer_is_running(instance)) furi_delay_tick(2); - if((uint32_t)callb & 1U) { + /* If callback memory was allocated, it is only safe to free it with + * the timer inactive. Send a stop command and wait for the timer to + * be in an inactive state. + */ + furi_check(xTimerStop(hTimer, portMAX_DELAY) == pdPASS); + while(furi_timer_is_running(instance)) furi_delay_tick(2); + /* Callback memory was allocated from dynamic pool, clear flag */ callb = (TimerCallback_t*)((uint32_t)callb & ~1U); /* Return allocated memory to dynamic pool */ free(callb); } + + furi_check(xTimerDelete(hTimer, portMAX_DELAY) == pdPASS); } FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) { @@ -170,4 +175,4 @@ void furi_timer_set_thread_priority(FuriTimerThreadPriority priority) { } else { furi_crash(); } -} \ No newline at end of file +} From f6eb79e1e5b850a8e107462ee47f68803e98602a Mon Sep 17 00:00:00 2001 From: Nick Mooney Date: Fri, 9 Feb 2024 21:00:17 +1300 Subject: [PATCH 15/58] NFC: Add support for Gallagher access control (MIFARE Classic only) (#3306) * Merge remote-tracking branch 'origin/dev' into dev * Add basic API interface (inspired by advanced plugins example), modify NfcSupportedCardsLoadContext to contain a pointer to a resolver * WIP: API resolver implemented / passed to plugin via context, still having resolution issues * Attempt to add constants to the nfc_app_api_table list * WIP: We're defining the constants directly in nfc_app_api.c to see if this fixes our woes, which it does not. * WIP: Remove furi_assert(false) (lmao) * Working implementation of Gallagher decoding via exposed plugin API * lib: api_hashtable: change log level for symbol not found message * nfc app: alloc composite resolver along with supported cards * nfc app: rework nfc api structure * nfc app: fix memory leakage in supported cards Co-authored-by: gornekich --- .../main/nfc/api/gallagher/gallagher_util.c | 59 +++++++++++++ .../main/nfc/api/gallagher/gallagher_util.h | 33 +++++++ .../main/nfc/api/nfc_app_api_interface.h | 9 ++ .../main/nfc/api/nfc_app_api_table.cpp | 27 ++++++ .../main/nfc/api/nfc_app_api_table_i.h | 13 +++ applications/main/nfc/application.fam | 11 ++- .../main/nfc/helpers/nfc_supported_cards.c | 42 ++++++--- .../nfc/plugins/supported_cards/gallagher.c | 87 +++++++++++++++++++ .../api_hashtable/api_hashtable.cpp | 2 +- 9 files changed, 269 insertions(+), 14 deletions(-) create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.c create mode 100644 applications/main/nfc/api/gallagher/gallagher_util.h create mode 100644 applications/main/nfc/api/nfc_app_api_interface.h create mode 100644 applications/main/nfc/api/nfc_app_api_table.cpp create mode 100644 applications/main/nfc/api/nfc_app_api_table_i.h create mode 100644 applications/main/nfc/plugins/supported_cards/gallagher.c diff --git a/applications/main/nfc/api/gallagher/gallagher_util.c b/applications/main/nfc/api/gallagher/gallagher_util.c new file mode 100644 index 00000000000..caa3650e740 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.c @@ -0,0 +1,59 @@ +/* gallagher_util.c - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "gallagher_util.h" + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +/* The Gallagher obfuscation algorithm is a 256-byte substitution table. The below array is generated from + * https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/substitution-table.bin. +*/ +const uint8_t GALLAGHER_DECODE_TABLE[256] = { + 0x2f, 0x6e, 0xdd, 0xdf, 0x1d, 0xf, 0xb0, 0x76, 0xad, 0xaf, 0x7f, 0xbb, 0x77, 0x85, 0x11, + 0x6d, 0xf4, 0xd2, 0x84, 0x42, 0xeb, 0xf7, 0x34, 0x55, 0x4a, 0x3a, 0x10, 0x71, 0xe7, 0xa1, + 0x62, 0x1a, 0x3e, 0x4c, 0x14, 0xd3, 0x5e, 0xb2, 0x7d, 0x56, 0xbc, 0x27, 0x82, 0x60, 0xe3, + 0xae, 0x1f, 0x9b, 0xaa, 0x2b, 0x95, 0x49, 0x73, 0xe1, 0x92, 0x79, 0x91, 0x38, 0x6c, 0x19, + 0xe, 0xa9, 0xe2, 0x8d, 0x66, 0xc7, 0x5a, 0xf5, 0x1c, 0x80, 0x99, 0xbe, 0x4e, 0x41, 0xf0, + 0xe8, 0xa6, 0x20, 0xab, 0x87, 0xc8, 0x1e, 0xa0, 0x59, 0x7b, 0xc, 0xc3, 0x3c, 0x61, 0xcc, + 0x40, 0x9e, 0x6, 0x52, 0x1b, 0x32, 0x8c, 0x12, 0x93, 0xbf, 0xef, 0x3b, 0x25, 0xd, 0xc2, + 0x88, 0xd1, 0xe0, 0x7, 0x2d, 0x70, 0xc6, 0x29, 0x6a, 0x4d, 0x47, 0x26, 0xa3, 0xe4, 0x8b, + 0xf6, 0x97, 0x2c, 0x5d, 0x3d, 0xd7, 0x96, 0x28, 0x2, 0x8, 0x30, 0xa7, 0x22, 0xc9, 0x65, + 0xf8, 0xb7, 0xb4, 0x8a, 0xca, 0xb9, 0xf2, 0xd0, 0x17, 0xff, 0x46, 0xfb, 0x9a, 0xba, 0x8f, + 0xb6, 0x69, 0x68, 0x8e, 0x21, 0x6f, 0xc4, 0xcb, 0xb3, 0xce, 0x51, 0xd4, 0x81, 0x0, 0x2e, + 0x9c, 0x74, 0x63, 0x45, 0xd9, 0x16, 0x35, 0x5f, 0xed, 0x78, 0x9f, 0x1, 0x48, 0x4, 0xc1, + 0x33, 0xd6, 0x4f, 0x94, 0xde, 0x31, 0x9d, 0xa, 0xac, 0x18, 0x4b, 0xcd, 0x98, 0xb8, 0x37, + 0xa2, 0x83, 0xec, 0x3, 0xd8, 0xda, 0xe5, 0x7a, 0x6b, 0x53, 0xd5, 0x15, 0xa4, 0x43, 0xe9, + 0x90, 0x67, 0x58, 0xc0, 0xa5, 0xfa, 0x2a, 0xb1, 0x75, 0x50, 0x39, 0x5c, 0xe6, 0xdc, 0x89, + 0xfc, 0xcf, 0xfe, 0xf9, 0x57, 0x54, 0x64, 0xa8, 0xee, 0x23, 0xb, 0xf1, 0xea, 0xfd, 0xdb, + 0xbd, 0x9, 0xb5, 0x5b, 0x5, 0x86, 0x13, 0xf3, 0x24, 0xc5, 0x3f, 0x44, 0x72, 0x7c, 0x7e, + 0x36}; + +// The second block of a Gallagher credential sector is the literal +// "www.cardax.com " (note two padding spaces) +const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE] = + {'w', 'w', 'w', '.', 'c', 'a', 'r', 'd', 'a', 'x', '.', 'c', 'o', 'm', ' ', ' '}; + +/* Precondition: cardholder_data_obfuscated points to at least 8 safe-to-read bytes of memory. +*/ +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated) { + uint8_t cardholder_data_deobfuscated[8]; + for(int i = 0; i < 8; i++) { + cardholder_data_deobfuscated[i] = GALLAGHER_DECODE_TABLE[cardholder_data_obfuscated[i]]; + } + + // Pull out values from the deobfuscated data + credential->region = (cardholder_data_deobfuscated[3] >> 1) & 0x0F; + credential->facility = ((uint16_t)(cardholder_data_deobfuscated[5] & 0x0F) << 12) + + ((uint16_t)cardholder_data_deobfuscated[1] << 4) + + (((uint16_t)cardholder_data_deobfuscated[7] >> 4) & 0x0F); + credential->card = ((uint32_t)cardholder_data_deobfuscated[0] << 16) + + ((uint32_t)(cardholder_data_deobfuscated[4] & 0x1F) << 11) + + ((uint32_t)cardholder_data_deobfuscated[2] << 3) + + (((uint32_t)cardholder_data_deobfuscated[3] >> 5) & 0x07); + credential->issue = cardholder_data_deobfuscated[7] & 0x0F; +} \ No newline at end of file diff --git a/applications/main/nfc/api/gallagher/gallagher_util.h b/applications/main/nfc/api/gallagher/gallagher_util.h new file mode 100644 index 00000000000..79e09838948 --- /dev/null +++ b/applications/main/nfc/api/gallagher/gallagher_util.h @@ -0,0 +1,33 @@ +/* gallagher_util.h - Utilities for parsing Gallagher cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#pragma once + +#include + +#define GALLAGHER_CREDENTIAL_SECTOR 15 + +#ifdef __cplusplus +extern "C" { +#endif + +extern const uint8_t GALLAGHER_DECODE_TABLE[256]; +extern const uint8_t GALLAGHER_CARDAX_ASCII[MF_CLASSIC_BLOCK_SIZE]; + +typedef struct GallagherCredential { + uint8_t region; + uint8_t issue; + uint16_t facility; + uint32_t card; +} GallagherCredential; + +void gallagher_deobfuscate_and_parse_credential( + GallagherCredential* credential, + const uint8_t* cardholder_data_obfuscated); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/nfc/api/nfc_app_api_interface.h b/applications/main/nfc/api/nfc_app_api_interface.h new file mode 100644 index 00000000000..16235598266 --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const nfc_application_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table.cpp b/applications/main/nfc/api/nfc_app_api_table.cpp new file mode 100644 index 00000000000..ca190665bba --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "nfc_app_api_table_i.h" + +static_assert(!has_hash_collisions(nfc_app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface nfc_application_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = nfc_app_api_table.cbegin(), + .table_cend = nfc_app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const nfc_application_api_interface = + &nfc_application_hashtable_api_interface; diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h new file mode 100644 index 00000000000..08bff072eae --- /dev/null +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -0,0 +1,13 @@ +#include "gallagher/gallagher_util.h" + +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto nfc_app_api_table = sort(create_array_t( + API_METHOD( + gallagher_deobfuscate_and_parse_credential, + void, + (GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)), + API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*))); diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index c0acace5d48..1e6291c4d24 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -9,7 +9,7 @@ App( order=30, resources="resources", sources=[ - "*.c", + "*.c*", "!plugins", "!nfc_cli.c", ], @@ -110,6 +110,15 @@ App( sources=["plugins/supported_cards/umarsh.c"], ) +App( + appid="gallagher_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="gallagher_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/gallagher.c"], +) + App( appid="clipper_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/helpers/nfc_supported_cards.c b/applications/main/nfc/helpers/nfc_supported_cards.c index 6016ae1784c..f5668b506d7 100644 --- a/applications/main/nfc/helpers/nfc_supported_cards.c +++ b/applications/main/nfc/helpers/nfc_supported_cards.c @@ -1,9 +1,11 @@ #include "nfc_supported_cards.h" +#include "../api/nfc_app_api_interface.h" #include "../plugins/supported_cards/nfc_supported_card_plugin.h" #include #include +#include #include #include @@ -45,6 +47,7 @@ typedef struct { } NfcSupportedCardsLoadContext; struct NfcSupportedCards { + CompositeApiResolver* api_resolver; NfcSupportedCardsPluginCache_t plugins_cache_arr; NfcSupportedCardsLoadState load_state; NfcSupportedCardsLoadContext* load_context; @@ -52,6 +55,11 @@ struct NfcSupportedCards { NfcSupportedCards* nfc_supported_cards_alloc() { NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards)); + + instance->api_resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(instance->api_resolver, firmware_api_interface); + composite_api_resolver_add(instance->api_resolver, nfc_application_api_interface); + NfcSupportedCardsPluginCache_init(instance->plugins_cache_arr); return instance; @@ -67,8 +75,9 @@ void nfc_supported_cards_free(NfcSupportedCards* instance) { NfcSupportedCardsPluginCache* plugin_cache = NfcSupportedCardsPluginCache_ref(iter); furi_string_free(plugin_cache->path); } - NfcSupportedCardsPluginCache_clear(instance->plugins_cache_arr); + + composite_api_resolver_free(instance->api_resolver); free(instance); } @@ -100,15 +109,17 @@ static void nfc_supported_cards_load_context_free(NfcSupportedCardsLoadContext* free(instance); } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_plugin(NfcSupportedCardsLoadContext* instance, FuriString* path) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_plugin( + NfcSupportedCardsLoadContext* instance, + const FuriString* path, + const ElfApiInterface* api_interface) { furi_assert(instance); furi_assert(path); const NfcSupportedCardsPlugin* plugin = NULL; do { if(instance->app) flipper_application_free(instance->app); - instance->app = flipper_application_alloc(instance->storage, firmware_api_interface); + instance->app = flipper_application_alloc(instance->storage, api_interface); if(flipper_application_preload(instance->app, furi_string_get_cstr(path)) != FlipperApplicationPreloadStatusSuccess) break; @@ -129,8 +140,9 @@ static const NfcSupportedCardsPlugin* return plugin; } -static const NfcSupportedCardsPlugin* - nfc_supported_cards_get_next_plugin(NfcSupportedCardsLoadContext* instance) { +static const NfcSupportedCardsPlugin* nfc_supported_cards_get_next_plugin( + NfcSupportedCardsLoadContext* instance, + const ElfApiInterface* api_interface) { const NfcSupportedCardsPlugin* plugin = NULL; do { @@ -145,7 +157,7 @@ static const NfcSupportedCardsPlugin* path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path); - plugin = nfc_supported_cards_get_plugin(instance, instance->file_path); + plugin = nfc_supported_cards_get_plugin(instance, instance->file_path, api_interface); } while(plugin == NULL); //-V654 return plugin; @@ -162,8 +174,10 @@ void nfc_supported_cards_load_cache(NfcSupportedCards* instance) { instance->load_context = nfc_supported_cards_load_context_alloc(); while(true) { + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_next_plugin(instance->load_context); + nfc_supported_cards_get_next_plugin(instance->load_context, api_interface); if(plugin == NULL) break; //-V547 NfcSupportedCardsPluginCache plugin_cache = {}; //-V779 @@ -216,8 +230,10 @@ bool nfc_supported_cards_read(NfcSupportedCards* instance, NfcDevice* device, Nf if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasRead) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->verify) { @@ -262,8 +278,10 @@ bool nfc_supported_cards_parse( if(plugin_cache->protocol != protocol) continue; if((plugin_cache->feature & NfcSupportedCardsPluginFeatureHasParse) == 0) continue; - const NfcSupportedCardsPlugin* plugin = - nfc_supported_cards_get_plugin(instance->load_context, plugin_cache->path); + const ElfApiInterface* api_interface = + composite_api_resolver_get(instance->api_resolver); + const NfcSupportedCardsPlugin* plugin = nfc_supported_cards_get_plugin( + instance->load_context, plugin_cache->path, api_interface); if(plugin == NULL) continue; if(plugin->parse) { diff --git a/applications/main/nfc/plugins/supported_cards/gallagher.c b/applications/main/nfc/plugins/supported_cards/gallagher.c new file mode 100644 index 00000000000..8b6a5898701 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/gallagher.c @@ -0,0 +1,87 @@ +/* gallagher.c - NFC supported cards plugin for Gallagher access control cards (New Zealand). + * Author: Nick Mooney (nick@mooney.nz) + * + * Reference: https://github.com/megabug/gallagher-research +*/ + +#include "nfc_supported_card_plugin.h" +#include "../../api/gallagher/gallagher_util.h" + +#include +#include +#include +#include + +static bool gallagher_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + if(!(data->type == MfClassicType1k || data->type == MfClassicType4k)) { + return false; + } + + // It's possible for a single tag to contain multiple credentials, + // but this is currently unimplementecd. + const uint8_t credential_sector_start_block_number = + mf_classic_get_first_block_num_of_sector(GALLAGHER_CREDENTIAL_SECTOR); + + // Test 1: The first 8 bytes and the second 8 bytes should be bitwise inverses. + const uint8_t* credential_block_start_ptr = + &data->block[credential_sector_start_block_number].data[0]; + uint64_t cardholder_credential = nfc_util_bytes2num(credential_block_start_ptr, 8); + uint64_t cardholder_credential_inverse = nfc_util_bytes2num(credential_block_start_ptr + 8, 8); + // Due to endianness, this is testing the bytes in the wrong order, + // but the result still should be correct. + if(cardholder_credential != ~cardholder_credential_inverse) { + return false; + } + + // Test 2: The contents of the second block should be equal to the GALLAGHER_CARDAX_ASCII constant. + const uint8_t* cardax_block_start_ptr = + &data->block[credential_sector_start_block_number + 1].data[0]; + if(memcmp(cardax_block_start_ptr, GALLAGHER_CARDAX_ASCII, MF_CLASSIC_BLOCK_SIZE) != 0) { + return false; + } + + // Deobfuscate the credential data + GallagherCredential credential; + gallagher_deobfuscate_and_parse_credential(&credential, credential_block_start_ptr); + + char display_region = 'A'; + // Per https://github.com/megabug/gallagher-research/blob/master/formats/cardholder/cardholder.md, + // regions are generally A-P. + if(credential.region < 16) { + display_region = display_region + (char)credential.region; + } else { + display_region = '?'; + } + + furi_string_cat_printf( + parsed_data, + "\e#Gallagher NZ\nFacility %c%u\nCard %lu (IL %u)", + display_region, + credential.facility, + credential.card, + credential.issue); + return true; +} + +static const NfcSupportedCardsPlugin gallagher_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = NULL, + .read = NULL, + .parse = gallagher_parse, +}; + +static const FlipperAppPluginDescriptor gallagher_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &gallagher_plugin, +}; + +/* Plugin entry point */ +const FlipperAppPluginDescriptor* gallagher_plugin_ep() { + return &gallagher_plugin_descriptor; +} \ No newline at end of file diff --git a/lib/flipper_application/api_hashtable/api_hashtable.cpp b/lib/flipper_application/api_hashtable/api_hashtable.cpp index ef22ee9ad98..11f2d7ecfbf 100644 --- a/lib/flipper_application/api_hashtable/api_hashtable.cpp +++ b/lib/flipper_application/api_hashtable/api_hashtable.cpp @@ -21,7 +21,7 @@ bool elf_resolve_from_hashtable( auto find_res = std::lower_bound(hashtable_interface->table_cbegin, hashtable_interface->table_cend, key); if((find_res == hashtable_interface->table_cend || (find_res->hash != hash))) { - FURI_LOG_W( + FURI_LOG_T( TAG, "Can't find symbol with hash %lx @ %p!", hash, hashtable_interface->table_cbegin); result = false; } else { From 6bc63b7734a47887fdf9e7be8841cac460fc7f28 Mon Sep 17 00:00:00 2001 From: gornekich Date: Fri, 9 Feb 2024 08:07:54 +0000 Subject: [PATCH 16/58] [FL-3676] Slix disable privacy (#3425) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * slix: add unlock option * slix: add features for nxp get info and signature commands * slix: working unlock * nfc app: rewrite slix unlock * slix poller: simplify unlock state handler * nfc app: fix slix key setting * nfc app: fix navigation * slix poller: code clean up * slix: resolve TODO, clean code * nfc app: fix naming * nfc app: rework slix unlock success scene * slix poller: add documentation * slix listener: fix password comparison Co-authored-by: あく --- applications/main/nfc/helpers/slix_unlock.c | 64 +++++++++++++++ applications/main/nfc/helpers/slix_unlock.h | 30 +++++++ applications/main/nfc/nfc_app.c | 2 + applications/main/nfc/nfc_app_i.h | 2 + .../main/nfc/scenes/nfc_scene_config.h | 5 ++ .../main/nfc/scenes/nfc_scene_extra_actions.c | 10 +++ .../main/nfc/scenes/nfc_scene_retry_confirm.c | 6 +- .../nfc/scenes/nfc_scene_slix_key_input.c | 48 +++++++++++ .../main/nfc/scenes/nfc_scene_slix_unlock.c | 70 ++++++++++++++++ .../nfc/scenes/nfc_scene_slix_unlock_menu.c | 60 ++++++++++++++ .../scenes/nfc_scene_slix_unlock_success.c | 71 ++++++++++++++++ .../iso15693_3/iso15693_3_poller_i.c | 33 +------- lib/nfc/protocols/slix/slix.c | 2 +- lib/nfc/protocols/slix/slix.h | 4 + lib/nfc/protocols/slix/slix_i.c | 27 +++++++ lib/nfc/protocols/slix/slix_i.h | 4 +- lib/nfc/protocols/slix/slix_listener.c | 2 +- lib/nfc/protocols/slix/slix_listener_i.c | 2 +- lib/nfc/protocols/slix/slix_poller.c | 81 +++++++++++++++---- lib/nfc/protocols/slix/slix_poller.h | 34 ++++++++ lib/nfc/protocols/slix/slix_poller_i.c | 74 +++++++++++++++-- lib/nfc/protocols/slix/slix_poller_i.h | 5 ++ 22 files changed, 576 insertions(+), 60 deletions(-) create mode 100644 applications/main/nfc/helpers/slix_unlock.c create mode 100644 applications/main/nfc/helpers/slix_unlock.h create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_key_input.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c create mode 100644 applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c diff --git a/applications/main/nfc/helpers/slix_unlock.c b/applications/main/nfc/helpers/slix_unlock.c new file mode 100644 index 00000000000..931ec179059 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.c @@ -0,0 +1,64 @@ +#include "slix_unlock.h" + +#include + +#define SLIX_UNLOCK_PASSWORD_NUM_MAX (2) + +struct SlixUnlock { + SlixUnlockMethod method; + SlixPassword password_arr[SLIX_UNLOCK_PASSWORD_NUM_MAX]; + size_t password_arr_len; + size_t password_idx; +}; + +static const SlixPassword tonie_box_pass_arr[] = {0x5B6EFD7F, 0x0F0F0F0F}; + +SlixUnlock* slix_unlock_alloc() { + SlixUnlock* instance = malloc(sizeof(SlixUnlock)); + + return instance; +} + +void slix_unlock_free(SlixUnlock* instance) { + furi_assert(instance); + + free(instance); +} + +void slix_unlock_reset(SlixUnlock* instance) { + furi_assert(instance); + + memset(instance, 0, sizeof(SlixUnlock)); +} + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method) { + furi_assert(instance); + + instance->method = method; + if(method == SlixUnlockMethodTonieBox) { + instance->password_arr_len = COUNT_OF(tonie_box_pass_arr); + memcpy(instance->password_arr, tonie_box_pass_arr, sizeof(tonie_box_pass_arr)); + } +} + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password) { + furi_assert(instance); + furi_assert(instance->method == SlixUnlockMethodManual); + + instance->password_arr[0] = password; + instance->password_arr_len = 1; +} + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password) { + furi_assert(instance); + furi_assert(password); + + bool password_set = false; + if(instance->password_arr_len) { + *password = instance->password_arr[instance->password_idx++]; + instance->password_idx %= instance->password_arr_len; + password_set = true; + } + + return password_set; +} diff --git a/applications/main/nfc/helpers/slix_unlock.h b/applications/main/nfc/helpers/slix_unlock.h new file mode 100644 index 00000000000..c378891e4d9 --- /dev/null +++ b/applications/main/nfc/helpers/slix_unlock.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SlixUnlockMethodManual, + SlixUnlockMethodTonieBox, +} SlixUnlockMethod; + +typedef struct SlixUnlock SlixUnlock; + +SlixUnlock* slix_unlock_alloc(); + +void slix_unlock_free(SlixUnlock* instance); + +void slix_unlock_reset(SlixUnlock* instance); + +void slix_unlock_set_method(SlixUnlock* instance, SlixUnlockMethod method); + +void slix_unlock_set_password(SlixUnlock* instance, SlixPassword password); + +bool slix_unlock_get_next_password(SlixUnlock* instance, SlixPassword* password); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/nfc_app.c b/applications/main/nfc/nfc_app.c index c0ca2a917fe..8120268e0ea 100644 --- a/applications/main/nfc/nfc_app.c +++ b/applications/main/nfc/nfc_app.c @@ -51,6 +51,7 @@ NfcApp* nfc_app_alloc() { instance->nfc = nfc_alloc(); instance->mf_ul_auth = mf_ultralight_auth_alloc(); + instance->slix_unlock = slix_unlock_alloc(); instance->mfc_key_cache = mf_classic_key_cache_alloc(); instance->nfc_supported_cards = nfc_supported_cards_alloc(); @@ -141,6 +142,7 @@ void nfc_app_free(NfcApp* instance) { nfc_free(instance->nfc); mf_ultralight_auth_free(instance->mf_ul_auth); + slix_unlock_free(instance->slix_unlock); mf_classic_key_cache_free(instance->mfc_key_cache); nfc_supported_cards_free(instance->nfc_supported_cards); diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 324ed6a5d0c..706815e623e 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -32,6 +32,7 @@ #include "helpers/mfkey32_logger.h" #include "helpers/mf_classic_key_cache.h" #include "helpers/nfc_supported_cards.h" +#include "helpers/slix_unlock.h" #include #include @@ -129,6 +130,7 @@ struct NfcApp { NfcListener* listener; MfUltralightAuth* mf_ul_auth; + SlixUnlock* slix_unlock; NfcMfClassicDictAttackContext nfc_dict_context; Mfkey32Logger* mfkey32_logger; MfUserDict* mf_user_dict; diff --git a/applications/main/nfc/scenes/nfc_scene_config.h b/applications/main/nfc/scenes/nfc_scene_config.h index 70e7c3d468e..035c4949cf2 100644 --- a/applications/main/nfc/scenes/nfc_scene_config.h +++ b/applications/main/nfc/scenes/nfc_scene_config.h @@ -59,4 +59,9 @@ ADD_SCENE(nfc, set_sak, SetSak) ADD_SCENE(nfc, set_atqa, SetAtqa) ADD_SCENE(nfc, set_uid, SetUid) +ADD_SCENE(nfc, slix_unlock_menu, SlixUnlockMenu) +ADD_SCENE(nfc, slix_key_input, SlixKeyInput) +ADD_SCENE(nfc, slix_unlock, SlixUnlock) +ADD_SCENE(nfc, slix_unlock_success, SlixUnlockSuccess) + ADD_SCENE(nfc, generate_info, GenerateInfo) diff --git a/applications/main/nfc/scenes/nfc_scene_extra_actions.c b/applications/main/nfc/scenes/nfc_scene_extra_actions.c index d14f80b6247..2943c0c55ca 100644 --- a/applications/main/nfc/scenes/nfc_scene_extra_actions.c +++ b/applications/main/nfc/scenes/nfc_scene_extra_actions.c @@ -4,6 +4,7 @@ enum SubmenuIndex { SubmenuIndexReadCardType, SubmenuIndexMfClassicKeys, SubmenuIndexMfUltralightUnlock, + SubmenuIndexSlixUnlock, }; void nfc_scene_extra_actions_submenu_callback(void* context, uint32_t index) { @@ -34,6 +35,12 @@ void nfc_scene_extra_actions_on_enter(void* context) { SubmenuIndexMfUltralightUnlock, nfc_scene_extra_actions_submenu_callback, instance); + submenu_add_item( + submenu, + "Unlock SLIX-L", + SubmenuIndexSlixUnlock, + nfc_scene_extra_actions_submenu_callback, + instance); submenu_set_selected_item( submenu, scene_manager_get_scene_state(instance->scene_manager, NfcSceneExtraActions)); view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); @@ -54,6 +61,9 @@ bool nfc_scene_extra_actions_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexReadCardType) { scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol); consumed = true; + } else if(event.event == SubmenuIndexSlixUnlock) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockMenu); + consumed = true; } scene_manager_set_scene_state(instance->scene_manager, NfcSceneExtraActions, event.event); } diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index 62299609468..b6ad8144db7 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -28,7 +28,11 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(event.event == DialogExResultRight) { consumed = scene_manager_previous_scene(nfc->scene_manager); } else if(event.event == DialogExResultLeft) { - if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSlixUnlock)) { + consumed = scene_manager_search_and_switch_to_previous_scene( + nfc->scene_manager, NfcSceneSlixUnlock); + } else if(scene_manager_has_previous_scene( + nfc->scene_manager, NfcSceneMfClassicDictAttack)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicDictAttack); } else if(scene_manager_has_previous_scene( diff --git a/applications/main/nfc/scenes/nfc_scene_slix_key_input.c b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c new file mode 100644 index 00000000000..1724eea8df8 --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c @@ -0,0 +1,48 @@ +#include "../nfc_app_i.h" + +#include + +void nfc_scene_slix_key_input_byte_input_callback(void* context) { + NfcApp* instance = context; + + SlixPassword password = nfc_util_bytes2num(instance->byte_input_store, sizeof(SlixPassword)); + slix_unlock_set_password(instance->slix_unlock, password); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); +} + +void nfc_scene_slix_key_input_on_enter(void* context) { + NfcApp* instance = context; + + // Setup view + ByteInput* byte_input = instance->byte_input; + byte_input_set_header_text(byte_input, "Enter the password in hex"); + byte_input_set_result_callback( + byte_input, + nfc_scene_slix_key_input_byte_input_callback, + NULL, + instance, + instance->byte_input_store, + sizeof(SlixPassword)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewByteInput); +} + +bool nfc_scene_slix_key_input_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventByteInputDone) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + } + return consumed; +} + +void nfc_scene_slix_key_input_on_exit(void* context) { + NfcApp* instance = context; + + // Clear view + byte_input_set_result_callback(instance->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(instance->byte_input, ""); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c new file mode 100644 index 00000000000..b01876e068a --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock.c @@ -0,0 +1,70 @@ +#include "../nfc_app_i.h" + +#include + +NfcCommand nfc_scene_slix_unlock_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolSlix); + + NfcCommand command = NfcCommandContinue; + + NfcApp* instance = context; + SlixPollerEvent* slix_event = event.event_data; + if(slix_event->type == SlixPollerEventTypePrivacyUnlockRequest) { + SlixPassword pwd = 0; + bool get_password_success = slix_unlock_get_next_password(instance->slix_unlock, &pwd); + slix_event->data->privacy_password.password = pwd; + slix_event->data->privacy_password.password_set = get_password_success; + } else if(slix_event->type == SlixPollerEventTypeError) { + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure); + } else if(slix_event->type == SlixPollerEventTypeReady) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + command = NfcCommandStop; + } + + return command; +} + +void nfc_scene_slix_unlock_on_enter(void* context) { + NfcApp* instance = context; + + popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50); + popup_set_header(instance->popup, "Unlocking", 97, 15, AlignCenter, AlignTop); + popup_set_text( + instance->popup, "Apply card to\nFlipper's back", 97, 27, AlignCenter, AlignTop); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix); + nfc_poller_start(instance->poller, nfc_scene_slix_unlock_worker_callback, instance); +} + +bool nfc_scene_slix_unlock_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + UNUSED(instance); + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcCustomEventPollerFailure) { + consumed = true; + } else if(event.event == NfcCustomEventPollerSuccess) { + notification_message(instance->notifications, &sequence_success); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlockSuccess); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneSlixUnlockMenu); + } + + return consumed; +} + +void nfc_scene_slix_unlock_on_exit(void* context) { + NfcApp* instance = context; + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + popup_reset(instance->popup); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c new file mode 100644 index 00000000000..78eb9884edb --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_menu.c @@ -0,0 +1,60 @@ +#include "../nfc_app_i.h" + +enum SubmenuIndex { + SubmenuIndexSlixUnlockMenuManual, + SubmenuIndexSlixUnlockMenuTonieBox, +}; + +void nfc_scene_slix_unlock_menu_submenu_callback(void* context, uint32_t index) { + NfcApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_scene_slix_unlock_menu_on_enter(void* context) { + NfcApp* instance = context; + Submenu* submenu = instance->submenu; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcSceneSlixUnlockMenu); + submenu_add_item( + submenu, + "Enter Password Manually", + SubmenuIndexSlixUnlockMenuManual, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_add_item( + submenu, + "Auth As TommyBox", + SubmenuIndexSlixUnlockMenuTonieBox, + nfc_scene_slix_unlock_menu_submenu_callback, + instance); + submenu_set_selected_item(submenu, state); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu); +} + +bool nfc_scene_slix_unlock_menu_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexSlixUnlockMenuManual) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodManual); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixKeyInput); + consumed = true; + } else if(event.event == SubmenuIndexSlixUnlockMenuTonieBox) { + slix_unlock_set_method(instance->slix_unlock, SlixUnlockMethodTonieBox); + scene_manager_next_scene(instance->scene_manager, NfcSceneSlixUnlock); + consumed = true; + } + scene_manager_set_scene_state( + instance->scene_manager, NfcSceneSlixUnlockMenu, event.event); + } + return consumed; +} + +void nfc_scene_slix_unlock_menu_on_exit(void* context) { + NfcApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c new file mode 100644 index 00000000000..f25eabab2ca --- /dev/null +++ b/applications/main/nfc/scenes/nfc_scene_slix_unlock_success.c @@ -0,0 +1,71 @@ +#include "../nfc_app_i.h" + +static void nfc_scene_slix_unlock_success_widget_callback( + GuiButtonType result, + InputType type, + void* context) { + NfcApp* instance = context; + + if(type == InputTypeShort) { + view_dispatcher_send_custom_event(instance->view_dispatcher, result); + } +} + +void nfc_scene_slix_unlock_success_on_enter(void* context) { + NfcApp* instance = context; + + Widget* widget = instance->widget; + widget_add_string_element(widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "SLIX Unlocked!"); + + FuriString* temp_str = furi_string_alloc_set_str("UID:"); + size_t uid_len = 0; + const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len); + for(size_t i = 0; i < uid_len; i++) { + furi_string_cat_printf(temp_str, " %02X", uid[i]); + } + furi_string_cat_printf(temp_str, "\nPrivacy Mode: Disabled"); + widget_add_string_multiline_element( + widget, 0, 12, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(temp_str)); + furi_string_free(temp_str); + + widget_add_button_element( + widget, + GuiButtonTypeLeft, + "Retry", + nfc_scene_slix_unlock_success_widget_callback, + instance); + widget_add_button_element( + widget, + GuiButtonTypeRight, + "More", + nfc_scene_slix_unlock_success_widget_callback, + instance); + + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget); +} + +bool nfc_scene_slix_unlock_success_on_event(void* context, SceneManagerEvent event) { + NfcApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == GuiButtonTypeLeft) { + scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm); + consumed = true; + } else if(event.event == GuiButtonTypeRight) { + scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm); + consumed = true; + } + + return consumed; +} + +void nfc_scene_slix_unlock_success_on_exit(void* context) { + NfcApp* instance = context; + + widget_reset(instance->widget); +} diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index ca6f5435e3e..94939030547 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -32,17 +32,7 @@ static Iso15693_3Error iso15693_3_poller_filter_error(Iso15693_3Error error) { } } -static Iso15693_3Error iso15693_3_poller_prepare_trx(Iso15693_3Poller* instance) { - furi_assert(instance); - - if(instance->state == Iso15693_3PollerStateIdle) { - return iso15693_3_poller_activate(instance, NULL); - } - - return Iso15693_3ErrorNone; -} - -static Iso15693_3Error iso15693_3_poller_frame_exchange( +Iso15693_3Error iso15693_3_poller_send_frame( Iso15693_3Poller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, @@ -156,7 +146,7 @@ Iso15693_3Error iso15693_3_poller_inventory(Iso15693_3Poller* instance, uint8_t* Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -183,7 +173,7 @@ Iso15693_3Error Iso15693_3Error ret; do { - ret = iso15693_3_poller_frame_exchange( + ret = iso15693_3_poller_send_frame( instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); if(ret != Iso15693_3ErrorNone) break; @@ -284,20 +274,3 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security( return ret; } - -Iso15693_3Error iso15693_3_poller_send_frame( - Iso15693_3Poller* instance, - const BitBuffer* tx_buffer, - BitBuffer* rx_buffer, - uint32_t fwt) { - Iso15693_3Error ret; - - do { - ret = iso15693_3_poller_prepare_trx(instance); - if(ret != Iso15693_3ErrorNone) break; - - ret = iso15693_3_poller_frame_exchange(instance, tx_buffer, rx_buffer, fwt); - } while(false); - - return ret; -} diff --git a/lib/nfc/protocols/slix/slix.c b/lib/nfc/protocols/slix/slix.c index 12dc6750dd1..b575baa728f 100644 --- a/lib/nfc/protocols/slix/slix.c +++ b/lib/nfc/protocols/slix/slix.c @@ -338,7 +338,7 @@ const Iso15693_3Data* slix_get_base_data(const SlixData* data) { } SlixType slix_get_type(const SlixData* data) { - SlixType type = SlixTypeCount; + SlixType type = SlixTypeUnknown; do { if(iso15693_3_get_manufacturer_id(data->iso15693_3_data) != SLIX_NXP_MANUFACTURER_CODE) diff --git a/lib/nfc/protocols/slix/slix.h b/lib/nfc/protocols/slix/slix.h index f6c1453c5dd..26341072b2a 100644 --- a/lib/nfc/protocols/slix/slix.h +++ b/lib/nfc/protocols/slix/slix.h @@ -37,6 +37,7 @@ extern "C" { #define SLIX_TYPE_FEATURE_EAS (1U << 4) #define SLIX_TYPE_FEATURE_SIGNATURE (1U << 5) #define SLIX_TYPE_FEATURE_PROTECTION (1U << 6) +#define SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO (1U << 7) typedef uint32_t SlixTypeFeatures; @@ -56,7 +57,9 @@ typedef enum { SlixTypeSlixS, SlixTypeSlixL, SlixTypeSlix2, + SlixTypeCount, + SlixTypeUnknown, } SlixType; typedef enum { @@ -71,6 +74,7 @@ typedef enum { typedef uint32_t SlixPassword; typedef uint8_t SlixSignature[SLIX_SIGNATURE_SIZE]; typedef bool SlixPrivacy; +typedef uint16_t SlixRandomNumber; typedef struct { uint8_t pointer; diff --git a/lib/nfc/protocols/slix/slix_i.c b/lib/nfc/protocols/slix/slix_i.c index 97d66484c01..26ac80be9da 100644 --- a/lib/nfc/protocols/slix/slix_i.c +++ b/lib/nfc/protocols/slix/slix_i.c @@ -99,6 +99,33 @@ SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer return error; } +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf) { + SlixError error = SlixErrorNone; + + do { + if(slix_error_response_parse(&error, buf)) break; + + typedef struct { + uint8_t flags; + uint8_t random_number[2]; + } SlixGetRandomNumberResponseLayout; + + const size_t size_received = bit_buffer_get_size_bytes(buf); + const size_t size_required = sizeof(SlixGetRandomNumberResponseLayout); + + if(size_received != size_required) { + error = SlixErrorFormat; + break; + } + + const SlixGetRandomNumberResponseLayout* response = + (const SlixGetRandomNumberResponseLayout*)bit_buffer_get_data(buf); + *data = (response->random_number[1] << 8) | response->random_number[0]; + } while(false); + + return error; +} + void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password) { furi_assert(data); furi_assert(password_type < SlixPasswordTypeCount); diff --git a/lib/nfc/protocols/slix/slix_i.h b/lib/nfc/protocols/slix/slix_i.h index b5e445f31d0..4a15b50ff4e 100644 --- a/lib/nfc/protocols/slix/slix_i.h +++ b/lib/nfc/protocols/slix/slix_i.h @@ -48,7 +48,7 @@ extern "C" { #define SLIX_TYPE_FEATURES_SLIX2 \ (SLIX_TYPE_FEATURE_READ | SLIX_TYPE_FEATURE_WRITE | SLIX_TYPE_FEATURE_PRIVACY | \ SLIX_TYPE_FEATURE_DESTROY | SLIX_TYPE_FEATURE_EAS | SLIX_TYPE_FEATURE_SIGNATURE | \ - SLIX_TYPE_FEATURE_PROTECTION) + SLIX_TYPE_FEATURE_PROTECTION | SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO) #define SLIX2_FEATURE_FLAGS \ (SLIX_FEATURE_FLAG_UM_PP | SLIX_FEATURE_FLAG_COUNTER | SLIX_FEATURE_FLAG_EAS_ID | \ @@ -74,6 +74,8 @@ SlixError slix_get_nxp_system_info_response_parse(SlixData* data, const BitBuffe SlixError slix_read_signature_response_parse(SlixSignature data, const BitBuffer* buf); +SlixError slix_get_random_number_response_parse(SlixRandomNumber* data, const BitBuffer* buf); + // Setters void slix_set_password(SlixData* data, SlixPasswordType password_type, SlixPassword password); diff --git a/lib/nfc/protocols/slix/slix_listener.c b/lib/nfc/protocols/slix/slix_listener.c index 204be5ab91a..6ff39038085 100644 --- a/lib/nfc/protocols/slix/slix_listener.c +++ b/lib/nfc/protocols/slix/slix_listener.c @@ -63,7 +63,7 @@ static NfcCommand slix_listener_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) { const SlixError error = slix_listener_process_request(instance, rx_buffer); if(error == SlixErrorWrongPassword) { - command = NfcCommandStop; + command = NfcCommandSleep; } } diff --git a/lib/nfc/protocols/slix/slix_listener_i.c b/lib/nfc/protocols/slix/slix_listener_i.c index dfcb6c88017..15ab2cd3c83 100644 --- a/lib/nfc/protocols/slix/slix_listener_i.c +++ b/lib/nfc/protocols/slix/slix_listener_i.c @@ -31,7 +31,7 @@ static SlixPasswordType slix_listener_get_password_type_by_id(uint8_t id) { static SlixPassword slix_listener_unxor_password(const SlixPassword password_xored, uint16_t random) { - return password_xored ^ ((SlixPassword)random << 16 | random); + return REVERSE_BYTES_U32(password_xored ^ ((SlixPassword)random << 16 | random)); } static SlixError slix_listener_set_password( diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index 46a17119436..d9d38d10207 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -44,33 +44,83 @@ static void slix_poller_free(SlixPoller* instance) { static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { iso15693_3_copy( instance->data->iso15693_3_data, iso15693_3_poller_get_data(instance->iso15693_3_poller)); - - instance->poller_state = SlixPollerStateGetNxpSysInfo; + instance->type = slix_get_type(instance->data); + if(instance->type >= SlixTypeCount) { + instance->error = SlixErrorNotSupported; + instance->poller_state = SlixPollerStateError; + } else { + instance->poller_state = SlixPollerStateGetNxpSysInfo; + } return NfcCommandContinue; } static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { - instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReadSignature; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO)) { + instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReadSignature; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReadSignature; } return NfcCommandContinue; } static NfcCommand slix_poller_handler_read_signature(SlixPoller* instance) { - instance->error = slix_poller_read_signature(instance, &instance->data->signature); - if(instance->error == SlixErrorNone) { - instance->poller_state = SlixPollerStateReady; + if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_SIGNATURE)) { + instance->error = slix_poller_read_signature(instance, &instance->data->signature); + if(instance->error == SlixErrorNone) { + instance->poller_state = SlixPollerStateReady; + } else { + instance->poller_state = SlixPollerStateError; + } } else { - instance->poller_state = SlixPollerStateError; + instance->poller_state = SlixPollerStateReady; } return NfcCommandContinue; } +static NfcCommand slix_poller_handler_privacy_unlock(SlixPoller* instance) { + NfcCommand command = NfcCommandContinue; + instance->poller_state = SlixPollerStateError; + + instance->slix_event.type = SlixPollerEventTypePrivacyUnlockRequest; + command = instance->callback(instance->general_event, instance->context); + + bool slix_unlocked = false; + do { + if(!instance->slix_event_data.privacy_password.password_set) break; + SlixPassword pwd = instance->slix_event_data.privacy_password.password; + FURI_LOG_I(TAG, "Trying to disable privacy mode with password: %08lX", pwd); + + 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); + if(instance->error != SlixErrorNone) { + command = NfcCommandReset; + break; + } + + FURI_LOG_I(TAG, "Privacy mode disabled"); + instance->data->passwords[SlixPasswordTypePrivacy] = pwd; + instance->poller_state = SlixPollerStateIdle; + slix_unlocked = true; + } while(false); + + if(!slix_unlocked) { + instance->error = SlixErrorTimeout; + instance->poller_state = SlixPollerStateError; + furi_delay_ms(100); + } + + return command; +} + static NfcCommand slix_poller_handler_error(SlixPoller* instance) { instance->slix_event_data.error = instance->error; instance->slix_event.type = SlixPollerEventTypeError; @@ -90,6 +140,7 @@ static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum [SlixPollerStateError] = slix_poller_handler_error, [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, + [SlixPollerStatePrivacyUnlock] = slix_poller_handler_privacy_unlock, [SlixPollerStateReady] = slix_poller_handler_ready, }; @@ -117,8 +168,8 @@ static NfcCommand slix_poller_run(NfcGenericEvent event, void* context) { if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { command = slix_poller_state_handler[instance->poller_state](instance); } else if(iso15693_3_event->type == Iso15693_3PollerEventTypeError) { - instance->slix_event.type = SlixPollerEventTypeError; - command = instance->callback(instance->general_event, instance->context); + instance->poller_state = SlixPollerStatePrivacyUnlock; + command = slix_poller_state_handler[instance->poller_state](instance); } return command; @@ -138,11 +189,7 @@ static bool slix_poller_detect(NfcGenericEvent event, void* context) { bool protocol_detected = false; if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) { - if(slix_get_type(instance->data) < SlixTypeCount) { - SlixSystemInfo system_info = {}; - SlixError error = slix_poller_get_nxp_system_info(instance, &system_info); - protocol_detected = (error == SlixErrorNone); - } + protocol_detected = (slix_get_type(instance->data) < SlixTypeCount); } return protocol_detected; diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index 62d60be5fe2..4ea7f880d78 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -18,14 +18,24 @@ typedef struct SlixPoller SlixPoller; */ typedef enum { SlixPollerEventTypeError, /**< An error occured while reading card. */ + SlixPollerEventTypePrivacyUnlockRequest, /**< Poller requests password to disable privacy mode. */ SlixPollerEventTypeReady, /**< The card was successfully read by the poller. */ } SlixPollerEventType; +/** + * @brief Slix poller privacy unlock context data. + */ +typedef struct { + SlixPassword password; /**< Privacy password. */ + bool password_set; /**< Filed to indicate that password was set or not. */ +} SlixPollerEventDataPrivacyUnlockContext; + /** * @brief Slixs poller event data. */ typedef union { SlixError error; /**< Error code indicating card reaing fail reason. */ + SlixPollerEventDataPrivacyUnlockContext privacy_password; /**< Privacy unlock event context. */ } SlixPollerEventData; /** @@ -80,6 +90,30 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* */ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data); +/** + * @brief Get random number from card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the SlixRandomNumber structure to be filled. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data); + +/** + * @brief Set password to card. + * + * 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. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 6d7bdf37795..5000efceb04 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -1,4 +1,5 @@ #include "slix_poller_i.h" +#include #include @@ -6,18 +7,22 @@ #define TAG "SlixPoller" -static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command) { +static void slix_poller_prepare_request(SlixPoller* instance, uint8_t command, bool skip_uid) { bit_buffer_reset(instance->tx_buffer); bit_buffer_reset(instance->rx_buffer); - bit_buffer_append_byte( - instance->tx_buffer, - ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI | - ISO15693_3_REQ_FLAG_T4_ADDRESSED); + uint8_t flags = ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI; + if(!skip_uid) { + flags |= ISO15693_3_REQ_FLAG_T4_ADDRESSED; + } + + bit_buffer_append_byte(instance->tx_buffer, flags); bit_buffer_append_byte(instance->tx_buffer, command); bit_buffer_append_byte(instance->tx_buffer, SLIX_NXP_MANUFACTURER_CODE); - iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + if(!skip_uid) { + iso15693_3_append_uid(instance->data->iso15693_3_data, instance->tx_buffer); + } } SlixError slix_poller_send_frame( @@ -36,7 +41,7 @@ SlixError slix_poller_get_nxp_system_info(SlixPoller* instance, SlixSystemInfo* furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION); + slix_poller_prepare_request(instance, SLIX_CMD_GET_NXP_SYSTEM_INFORMATION, false); SlixError error = SlixErrorNone; @@ -54,7 +59,7 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) furi_assert(instance); furi_assert(data); - slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE); + slix_poller_prepare_request(instance, SLIX_CMD_READ_SIGNATURE, false); SlixError error = SlixErrorNone; @@ -67,3 +72,56 @@ SlixError slix_poller_read_signature(SlixPoller* instance, SlixSignature* data) return error; } + +SlixError slix_poller_get_random_number(SlixPoller* instance, SlixRandomNumber* data) { + furi_assert(instance); + furi_assert(data); + + slix_poller_prepare_request(instance, SLIX_CMD_GET_RANDOM_NUMBER, true); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_POLL_FC); + if(error != SlixErrorNone) break; + + error = slix_get_random_number_response_parse(data, instance->rx_buffer); + } while(false); + + return error; +} + +SlixError + slix_poller_set_password(SlixPoller* instance, SlixPasswordType type, SlixPassword password) { + furi_assert(instance); + + bool skip_uid = (type == SlixPasswordTypePrivacy); + slix_poller_prepare_request(instance, SLIX_CMD_SET_PASSWORD, skip_uid); + + 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; + 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] = {}; + nfc_util_num2bytes(xored_password, 4, xored_password_arr); + bit_buffer_append_bytes(instance->tx_buffer, xored_password_arr, 4); + + SlixError error = SlixErrorNone; + + do { + error = slix_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, SLIX_POLLER_SET_PASSWORD_FWT); + if(error != SlixErrorNone) break; + + size_t rx_len = bit_buffer_get_size_bytes(instance->rx_buffer); + if(rx_len != 1) { + error = SlixErrorWrongPassword; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/slix/slix_poller_i.h b/lib/nfc/protocols/slix/slix_poller_i.h index 1fda1a7d246..7a3b543b7b4 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.h +++ b/lib/nfc/protocols/slix/slix_poller_i.h @@ -4,6 +4,8 @@ #include "slix_poller.h" +#define SLIX_POLLER_SET_PASSWORD_FWT (100000) + #ifdef __cplusplus extern "C" { #endif @@ -12,6 +14,7 @@ typedef enum { SlixPollerStateIdle, SlixPollerStateGetNxpSysInfo, SlixPollerStateReadSignature, + SlixPollerStatePrivacyUnlock, SlixPollerStateReady, SlixPollerStateError, SlixPollerStateNum, @@ -19,9 +22,11 @@ typedef enum { struct SlixPoller { Iso15693_3Poller* iso15693_3_poller; + SlixType type; SlixData* data; SlixPollerState poller_state; SlixError error; + SlixRandomNumber random_number; BitBuffer* tx_buffer; BitBuffer* rx_buffer; From ebd09a198133bbfd649e066b48e64ff006236caa Mon Sep 17 00:00:00 2001 From: Alessandro Rossi Date: Fri, 9 Feb 2024 09:16:14 +0100 Subject: [PATCH 17/58] Added NFC plugin; Some parser (#3335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add parser * Fix microel.c * Fix NFC parser positive return * fix mizip * Fix NFC parser positive return * Add parse to hi! tag * fix false positive reading and kdf * hi formatting * fix oom in kdf * nfc app: fix types in hi plugin * nfc app: fix return in function body in microel plugin Co-authored-by: gornekich Co-authored-by: あく --- applications/main/nfc/application.fam | 27 ++ .../main/nfc/plugins/supported_cards/hi.c | 226 +++++++++++++++ .../nfc/plugins/supported_cards/microel.c | 228 ++++++++++++++++ .../main/nfc/plugins/supported_cards/mizip.c | 257 ++++++++++++++++++ 4 files changed, 738 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/hi.c create mode 100644 applications/main/nfc/plugins/supported_cards/microel.c create mode 100644 applications/main/nfc/plugins/supported_cards/mizip.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 1e6291c4d24..4bcc798237c 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -29,6 +29,33 @@ App( sources=["plugins/supported_cards/all_in_one.c"], ) +App( + appid="microel_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="microel_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/microel.c"], +) + +App( + appid="mizip_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="mizip_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/mizip.c"], +) + +App( + appid="hi_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="hi_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/hi.c"], +) + App( appid="opal_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c new file mode 100644 index 00000000000..21e602877f2 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -0,0 +1,226 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "HI!" +#define KEY_LENGTH 6 +#define HI_KEY_TO_GEN 5 +#define UID_LENGTH 7 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} HiCardConfig; + +static MfClassicKeyPair hi_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0x30871CF60CF1}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x42FFE4C76209, .b = 0x7B30CFD04CBD}, // 005 + {.a = 0x01ED8145BDF8, .b = 0x92257F472FCE}, // 006 + {.a = 0x7583A07D21A6, .b = 0x51CA6EA8EE26}, // 007 + {.a = 0x1E10BF5D6A1D, .b = 0x87B9B9BFABA6}, // 008 + {.a = 0xF9DB1B2B21BA, .b = 0x80A781F4134C}, // 009 + {.a = 0x7F5283FACB72, .b = 0x73250009D75A}, // 010 + {.a = 0xE48E86A03078, .b = 0xCFFBBF08A254}, // 011 + {.a = 0x39AB26301F60, .b = 0xC71A6E532C83}, // 012 + {.a = 0xAD656C6C639F, .b = 0xFD9819CBD20A}, // 013 + {.a = 0xF0E15160DB3E, .b = 0x3F622D515ADD}, // 014 + {.a = 0x03F44E033C42, .b = 0x61E897875F46}, // 015 +}; + +//KDF +void hi_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyB[4][6] = { + {0x1F, 0xC4, 0x4D, 0x94, 0x6A, 0x31}, + {0x12, 0xC1, 0x5C, 0x70, 0xDF, 0x31}, + {0x56, 0xF0, 0x13, 0x1B, 0x63, 0xF2}, + {0x4E, 0xFA, 0xC2, 0xF8, 0xC9, 0xCC}}; + + static const uint8_t xor_table_keyA[4][6] = { + {0xB6, 0xE6, 0xAE, 0x72, 0x91, 0x0D}, + {0x6D, 0x38, 0x50, 0xFB, 0x42, 0x89}, + {0x1E, 0x5F, 0xC7, 0xED, 0xAA, 0x02}, + {0x7E, 0xB9, 0xCA, 0xF1, 0x9C, 0x59}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 2}; + static const uint8_t xorOrderB[6] = {1, 3, 3, 2, 1, 0}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool hi_get_card_config(HiCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = hi_1k_keys; + } else { + success = false; + } + + return success; +} + +static bool hi_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a HI card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a HI Card"); + verified = true; + } while(false); + + return verified; +} + +static bool hi_verify(Nfc* nfc) { + return hi_verify_type(nfc, MfClassicType1k); +} + +static bool hi_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering HI KDF"); + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[HI_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[HI_KEY_TO_GEN][KEY_LENGTH]; + hi_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool hi_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + HiCardConfig cfg = {}; + if(!hi_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //parse data + furi_string_cat_printf(parsed_data, "\e#HI! Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf(parsed_data, "\n"); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin hi_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = hi_verify, + .read = hi_read, + .parse = hi_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor hi_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &hi_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* hi_plugin_ep() { + return &hi_plugin_descriptor; +} diff --git a/applications/main/nfc/plugins/supported_cards/microel.c b/applications/main/nfc/plugins/supported_cards/microel.c new file mode 100644 index 00000000000..899ad2a7ea5 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/microel.c @@ -0,0 +1,228 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "Microel" +#define KEY_LENGTH 6 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +static MfClassicKeyPair microel_1k_keys[] = { + {.a = 0x000000000000, .b = 0x000000000000}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 002 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 003 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 004 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 005 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 006 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 007 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 008 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 009 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 010 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 011 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 012 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 013 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 014 + {.a = 0xffffffffffff, .b = 0xffffffffffff}, // 015 +}; + +const uint8_t verify_sector = 1; + +void calculateSumHex(const uint8_t* uid, size_t uidSize, uint8_t sumHex[]) { + const uint8_t xorKey[] = {0x01, 0x92, 0xA7, 0x75, 0x2B, 0xF9}; + int sum = 0; + + for(size_t i = 0; i < uidSize; i++) { + sum += uid[i]; + } + + int sumTwoDigits = sum % 256; + + if(sumTwoDigits % 2 == 1) { + sumTwoDigits += 2; + } + + for(size_t i = 0; i < sizeof(xorKey); i++) { + sumHex[i] = sumTwoDigits ^ xorKey[i]; + } +} + +void generateKeyA(const uint8_t* uid, uint8_t uidSize, uint8_t keyA[]) { + uint8_t sumHex[6]; + calculateSumHex(uid, uidSize, sumHex); + uint8_t firstCharacter = (sumHex[0] >> 4) & 0xF; + + if(firstCharacter == 0x2 || firstCharacter == 0x3 || firstCharacter == 0xA || + firstCharacter == 0xB) { + // XOR WITH 0x40 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0x40 ^ sumHex[i]; + } + } else if( + firstCharacter == 0x6 || firstCharacter == 0x7 || firstCharacter == 0xE || + firstCharacter == 0xF) { + // XOR WITH 0xC0 + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = 0xC0 ^ sumHex[i]; + } + } else { + //Key a is the same as sumHex + for(size_t i = 0; i < sizeof(sumHex); i++) { + keyA[i] = sumHex[i]; + } + } +} + +void generateKeyB(uint8_t keyA[], size_t keyASize, uint8_t keyB[]) { + for(size_t i = 0; i < keyASize; i++) { + keyB[i] = 0xFF ^ keyA[i]; + } +} + +static bool microel_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering Microel KDF"); + + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + //Get UID and check if it is 4 bytes + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + FURI_LOG_D(TAG, "UID identified: %02X%02X%02X%02X", uid[0], uid[1], uid[2], uid[3]); + if(uid_len != UID_LENGTH) break; + + // Generate keys + uint8_t keyA[KEY_LENGTH]; + uint8_t keyB[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + generateKeyB(keyA, KEY_LENGTH, keyB); + + // Check key 0a to verify if it is a microel card + MfClassicKey key = {0}; + nfc_util_num2bytes(nfc_util_bytes2num(keyA, KEY_LENGTH), COUNT_OF(key.data), key.data); + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(0); // This is 0 + MfClassicAuthContext auth_context; + error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context); + if(error != MfClassicErrorNone) { + break; + } + + // Save keys generated to stucture + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(microel_1k_keys[i].a == 0x000000000000) { + microel_1k_keys[i].a = nfc_util_bytes2num(keyA, KEY_LENGTH); + } + if(microel_1k_keys[i].b == 0x000000000000) { + microel_1k_keys[i].b = nfc_util_bytes2num(keyB, KEY_LENGTH); + } + } + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(microel_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool microel_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + //Get UID + size_t uid_len; + const uint8_t* uid = mf_classic_get_uid(data, &uid_len); + if(uid_len != UID_LENGTH) break; + + // Generate key from uid + uint8_t keyA[KEY_LENGTH]; + generateKeyA(uid, UID_LENGTH, keyA); + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key_for_check_from_array = nfc_util_bytes2num(keyA, KEY_LENGTH); + if(key != key_for_check_from_array) break; + + //Get credit in block number 8 + const uint8_t* temp_ptr = data->block[4].data; + uint16_t balance = (temp_ptr[6] << 8) | (temp_ptr[5]); + uint16_t previus_balance = (data->block[5].data[6] << 8) | (data->block[5].data[5]); + furi_string_cat_printf(parsed_data, "\e#Microel Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin microel_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = + NULL, // the verification I need is based on verifying the keys generated via uid and try to authenticate not like on mizip that there is default b0 but added verify in read function + .read = microel_read, + .parse = microel_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor microel_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = µel_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* microel_plugin_ep() { + return µel_plugin_descriptor; +} \ No newline at end of file diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c new file mode 100644 index 00000000000..bbcf9fdbc9e --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -0,0 +1,257 @@ +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include + +#define TAG "MiZIP" +#define KEY_LENGTH 6 +#define MIZIP_KEY_TO_GEN 5 +#define UID_LENGTH 4 + +typedef struct { + uint64_t a; + uint64_t b; +} MfClassicKeyPair; + +typedef struct { + MfClassicKeyPair* keys; + uint32_t verify_sector; +} MizipCardConfig; + +static MfClassicKeyPair mizip_1k_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 + {.a = 0x0222179AB995, .b = 0x13321774F9B5}, // 005 + {.a = 0xB25CBD76A7B4, .b = 0x7571359B4274}, // 006 + {.a = 0xDA857B4907CC, .b = 0xD26B856175F7}, // 007 + {.a = 0x16D85830C443, .b = 0x8F790871A21E}, // 008 + {.a = 0x88BD5098FC82, .b = 0xFCD0D77745E4}, // 009 + {.a = 0x983349449D78, .b = 0xEA2631FBDEDD}, // 010 + {.a = 0xC599F962F3D9, .b = 0x949B70C14845}, // 011 + {.a = 0x72E668846BE8, .b = 0x45490B5AD707}, // 012 + {.a = 0xBCA105E5685E, .b = 0x248DAF9D674D}, // 013 + {.a = 0x4F6FE072D1FD, .b = 0x4250A05575FA}, // 014 + {.a = 0x56438ABE8152, .b = 0x59A45912B311}, // 015 +}; + +static MfClassicKeyPair mizip_mini_keys[] = { + {.a = 0xa0a1a2a3a4a5, .b = 0xb4c132439eef}, // 000 + {.a = 0x000000000000, .b = 0x000000000000}, // 001 + {.a = 0x000000000000, .b = 0x000000000000}, // 002 + {.a = 0x000000000000, .b = 0x000000000000}, // 003 + {.a = 0x000000000000, .b = 0x000000000000}, // 004 +}; + +//KDF +void mizip_generate_key(uint8_t* uid, uint8_t keyA[5][KEY_LENGTH], uint8_t keyB[5][KEY_LENGTH]) { + // Static XOR table for key generation + static const uint8_t xor_table_keyA[4][6] = { + {0x09, 0x12, 0x5A, 0x25, 0x89, 0xE5}, + {0xAB, 0x75, 0xC9, 0x37, 0x92, 0x2F}, + {0xE2, 0x72, 0x41, 0xAF, 0x2C, 0x09}, + {0x31, 0x7A, 0xB7, 0x2F, 0x44, 0x90}}; + + static const uint8_t xor_table_keyB[4][6] = { + {0xF1, 0x2C, 0x84, 0x53, 0xD8, 0x21}, + {0x73, 0xE7, 0x99, 0xFE, 0x32, 0x41}, + {0xAA, 0x4D, 0x13, 0x76, 0x56, 0xAE}, + {0xB0, 0x13, 0x27, 0x27, 0x2D, 0xFD}}; + + // Permutation table for rearranging elements in uid + static const uint8_t xorOrderA[6] = {0, 1, 2, 3, 0, 1}; + static const uint8_t xorOrderB[6] = {2, 3, 0, 1, 2, 3}; + + // Generate key based on uid and XOR table + for(uint8_t j = 1; j < 5; j++) { + for(uint8_t i = 0; i < 6; i++) { + keyA[j][i] = uid[xorOrderA[i]] ^ xor_table_keyA[j - 1][i]; + keyB[j][i] = uid[xorOrderB[i]] ^ xor_table_keyB[j - 1][i]; + } + } +} + +static bool mizip_get_card_config(MizipCardConfig* config, MfClassicType type) { + bool success = true; + + if(type == MfClassicType1k) { + config->verify_sector = 0; + config->keys = mizip_1k_keys; + } else if(type == MfClassicTypeMini) { + config->verify_sector = 0; + config->keys = mizip_mini_keys; + } else { + success = false; + } + + return success; +} + +static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { + bool verified = false; + + do { + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, type)) break; + + const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + + MfClassicKey key = {0}; + nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + + MfClassicAuthContext auth_context; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeB, &auth_context); + if(error != MfClassicErrorNone) { + FURI_LOG_D( + TAG, "Failed to read block %u: %d, this is not a MiZIP card", block_num, error); + break; + } + FURI_LOG_D(TAG, "Found a MiZIP Card"); + verified = true; + } while(false); + + return verified; +} + +static bool mizip_verify(Nfc* nfc) { + return mizip_verify_type(nfc, MfClassicType1k) || mizip_verify_type(nfc, MfClassicTypeMini); +} + +static bool mizip_read(Nfc* nfc, NfcDevice* device) { + FURI_LOG_D(TAG, "Entering MiZIP KDF"); + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicTypeMini; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error != MfClassicErrorNone) break; + + //temp fix but fix mf_classic_poller_sync_detect_type because view type mfclassic1k and not verify mfmini + data->type = MfClassicTypeMini; + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + uint8_t keyA[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + uint8_t keyB[MIZIP_KEY_TO_GEN][KEY_LENGTH]; + mizip_generate_key(uid, keyA, keyB); + + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { + cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); + cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + } + } + + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error != MfClassicErrorNone) { + FURI_LOG_W(TAG, "Failed to read data"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = mf_classic_is_card_read(data); + } while(false); + + mf_classic_free(data); + + return is_read; +} + +static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + bool parsed = false; + + do { + // Verify card type + MizipCardConfig cfg = {}; + if(!mizip_get_card_config(&cfg, data->type)) break; + + // Verify key + MfClassicSectorTrailer* sec_tr = + mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); + uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + if(key != cfg.keys[cfg.verify_sector].b) return false; + + //Get UID + uint8_t uid[UID_LENGTH]; + memcpy(uid, data->iso14443_3a_data->uid, UID_LENGTH); + + //Get credit + uint8_t credit_pointer = 0x08; + uint8_t previus_credit_pointer = 0x09; + if(data->block[10].data[0] == 0x55) { + credit_pointer = 0x09; + previus_credit_pointer = 0x08; + } + uint16_t balance = (data->block[credit_pointer].data[2] << 8) | + (data->block[credit_pointer].data[1]); + uint16_t previus_balance = (data->block[previus_credit_pointer].data[2] << 8) | + (data->block[previus_credit_pointer].data[1]); + + //parse data + furi_string_cat_printf(parsed_data, "\e#MiZIP Card\n"); + furi_string_cat_printf(parsed_data, "UID:"); + for(size_t i = 0; i < UID_LENGTH; i++) { + furi_string_cat_printf(parsed_data, " %02X", uid[i]); + } + furi_string_cat_printf( + parsed_data, "\nCurrent Credit: %d.%02d E \n", balance / 100, balance % 100); + furi_string_cat_printf( + parsed_data, + "Previus Credit: %d.%02d E \n", + previus_balance / 100, + previus_balance % 100); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin mizip_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = mizip_verify, + .read = mizip_read, + .parse = mizip_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor mizip_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &mizip_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* mizip_plugin_ep() { + return &mizip_plugin_descriptor; +} From 50e0521bf7f2e37e98286520da51db9b6d4c1556 Mon Sep 17 00:00:00 2001 From: Tolly Hill Date: Fri, 9 Feb 2024 08:36:06 +0000 Subject: [PATCH 18/58] NFC: Custom UID entry when adding manually (#3363) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Custom UID entry when adding manually * Fix incorrect types * Add Change UID option post-generation * Update UID derived data when using set_uid method * Fix PVS warnings Co-authored-by: gornekich Co-authored-by: あく --- .../protocol_support/mf_classic/mf_classic.c | 3 ++ .../mf_ultralight/mf_ultralight.c | 11 +++-- .../protocol_support/nfc_protocol_support.c | 9 ++++ .../main/nfc/plugins/supported_cards/hi.c | 2 +- .../main/nfc/plugins/supported_cards/mizip.c | 2 +- .../main/nfc/scenes/nfc_scene_set_uid.c | 4 ++ lib/nfc/helpers/nfc_data_generator.c | 43 +++++-------------- lib/nfc/protocols/mf_classic/mf_classic.c | 20 ++++++++- .../protocols/mf_ultralight/mf_ultralight.c | 14 +++++- 9 files changed, 69 insertions(+), 39 deletions(-) diff --git a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c index 1486011917d..6abaa48cd59 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c +++ b/applications/main/nfc/helpers/protocol_support/mf_classic/mf_classic.c @@ -199,6 +199,9 @@ static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, SceneManag } else if(event.event == SubmenuIndexDictAttack) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack); consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index f0a46dd87d5..eb6911df742 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -266,16 +266,21 @@ static void nfc_scene_emulate_on_enter_mf_ultralight(NfcApp* instance) { static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight( NfcApp* instance, SceneManagerEvent event) { + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubmenuIndexUnlock) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu); - return true; + consumed = true; } else if(event.event == SubmenuIndexWrite) { scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightWrite); - return true; + consumed = true; + } else if(event.event == SubmenuIndexCommonEdit) { + scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid); + consumed = true; } } - return false; + return consumed; } const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = { diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c index 2d46810a2c4..80fbb63de07 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support.c @@ -233,6 +233,15 @@ static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) { nfc_protocol_support_common_submenu_callback, instance); + if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneGenerateInfo)) { + submenu_add_item( + submenu, + "Change UID", + SubmenuIndexCommonEdit, + nfc_protocol_support_common_submenu_callback, + instance); + } + if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) { submenu_add_item( submenu, diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c index 21e602877f2..6807ab00ca8 100644 --- a/applications/main/nfc/plugins/supported_cards/hi.c +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -88,7 +88,7 @@ static bool hi_verify_type(Nfc* nfc, MfClassicType type) { if(!hi_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index bbcf9fdbc9e..b76c381e97b 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -99,7 +99,7 @@ static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { if(!mizip_get_card_config(&cfg, type)) break; const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.verify_sector); - FURI_LOG_D(TAG, "Verifying sector %li", cfg.verify_sector); + FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); diff --git a/applications/main/nfc/scenes/nfc_scene_set_uid.c b/applications/main/nfc/scenes/nfc_scene_set_uid.c index df8a4dc72cc..f09d0a43033 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_uid.c +++ b/applications/main/nfc/scenes/nfc_scene_set_uid.c @@ -44,6 +44,10 @@ bool nfc_scene_set_uid_on_event(void* context, SceneManagerEvent event) { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess); consumed = true; } + } else if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneReadMenu)) { + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcSceneReadMenu); + consumed = true; } else { scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName); consumed = true; diff --git a/lib/nfc/helpers/nfc_data_generator.c b/lib/nfc/helpers/nfc_data_generator.c index 21f062605b6..0ecccc3ac44 100644 --- a/lib/nfc/helpers/nfc_data_generator.c +++ b/lib/nfc/helpers/nfc_data_generator.c @@ -35,26 +35,16 @@ static void nfc_generate_mf_ul_uid(uint8_t* uid) { } static void nfc_generate_mf_ul_common(MfUltralightData* mfu_data) { + uint8_t uid[7]; mfu_data->iso14443_3a_data->uid_len = 7; - nfc_generate_mf_ul_uid(mfu_data->iso14443_3a_data->uid); + nfc_generate_mf_ul_uid(uid); + mf_ultralight_set_uid(mfu_data, uid, 7); + mfu_data->iso14443_3a_data->atqa[0] = 0x44; mfu_data->iso14443_3a_data->atqa[1] = 0x00; mfu_data->iso14443_3a_data->sak = 0x00; } -static void nfc_generate_calc_bcc(uint8_t* uid, uint8_t* bcc0, uint8_t* bcc1) { - *bcc0 = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; - *bcc1 = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; -} - -static void nfc_generate_mf_ul_copy_uid_with_bcc(MfUltralightData* mfu_data) { - memcpy(mfu_data->page[0].data, mfu_data->iso14443_3a_data->uid, 3); - memcpy(mfu_data->page[1].data, &mfu_data->iso14443_3a_data->uid[3], 4); - - nfc_generate_calc_bcc( - mfu_data->iso14443_3a_data->uid, &mfu_data->page[0].data[3], &mfu_data->page[2].data[0]); -} - static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { MfUltralightData* mfu_data = mf_ultralight_alloc(); nfc_generate_mf_ul_common(mfu_data); @@ -62,7 +52,6 @@ static void nfc_generate_mf_ul_orig(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeUnknown; mfu_data->pages_total = 16; mfu_data->pages_read = 16; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); memset(&mfu_data->page[4], 0xff, sizeof(MfUltralightPage)); nfc_device_set_data(nfc_device, NfcProtocolMfUltralight, mfu_data); @@ -74,7 +63,7 @@ static void nfc_generate_mf_ul_with_config_common(MfUltralightData* mfu_data, ui mfu_data->pages_total = num_pages; mfu_data->pages_read = num_pages; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); + uint16_t config_index = (num_pages - 4); mfu_data->page[config_index].data[0] = 0x04; // STRG_MOD_EN mfu_data->page[config_index].data[3] = 0xff; // AUTH0 @@ -150,7 +139,6 @@ static void nfc_generate_ntag203(NfcDevice* nfc_device) { mfu_data->type = MfUltralightTypeNTAG203; mfu_data->pages_total = 42; mfu_data->pages_read = 42; - nfc_generate_mf_ul_copy_uid_with_bcc(mfu_data); mfu_data->page[2].data[1] = 0x48; // Internal byte memcpy(&mfu_data->page[3], default_data_ntag203, sizeof(MfUltralightPage)); //-V1086 @@ -379,14 +367,7 @@ static void nfc_generate_mf_classic_block_0( furi_assert(uid_len == 4 || uid_len == 7); furi_assert(block); - if(uid_len == 4) { - // Calculate BCC - block[uid_len] = 0; - - for(int i = 0; i < uid_len; i++) { - block[uid_len] ^= block[i]; - } - } else { + if(uid_len == 7) { uid_len -= 1; } @@ -402,14 +383,12 @@ static void nfc_generate_mf_classic_block_0( static void nfc_generate_mf_classic(NfcDevice* nfc_device, uint8_t uid_len, MfClassicType type) { MfClassicData* mfc_data = mf_classic_alloc(); - nfc_generate_mf_classic_uid(mfc_data->block[0].data, uid_len); - nfc_generate_mf_classic_common(mfc_data, uid_len, type); + uint8_t uid[ISO14443_3A_MAX_UID_SIZE]; - // Set the UID - mfc_data->iso14443_3a_data->uid[0] = NXP_MANUFACTURER_ID; - for(int i = 1; i < uid_len; i++) { - mfc_data->iso14443_3a_data->uid[i] = mfc_data->block[0].data[i]; - } + nfc_generate_mf_classic_uid(uid, uid_len); + mf_classic_set_uid(mfc_data, uid, uid_len); + + nfc_generate_mf_classic_common(mfc_data, uid_len, type); mf_classic_set_block_read(mfc_data, 0, &mfc_data->block[0]); diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index e68e8c71872..27236df4b20 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -346,7 +346,25 @@ const uint8_t* mf_classic_get_uid(const MfClassicData* data, size_t* uid_len) { bool mf_classic_set_uid(MfClassicData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + uint8_t* block = data->block[0].data; + + // Copy UID to block 0 + memcpy(block, data->iso14443_3a_data->uid, uid_len); + + if(uid_len == 4) { + // Calculate BCC byte + block[uid_len] = 0; + + for(size_t i = 0; i < uid_len; i++) { + block[uid_len] ^= block[i]; + } + } + } + + return uid_valid; } Iso14443_3aData* mf_classic_get_base_data(const MfClassicData* data) { diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index fd845f3c085..3cffa56ff73 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -482,7 +482,19 @@ const uint8_t* mf_ultralight_get_uid(const MfUltralightData* data, size_t* uid_l bool mf_ultralight_set_uid(MfUltralightData* data, const uint8_t* uid, size_t uid_len) { furi_assert(data); - return iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + bool uid_valid = iso14443_3a_set_uid(data->iso14443_3a_data, uid, uid_len); + + if(uid_valid) { + // Copy UID across first 2 pages + memcpy(data->page[0].data, data->iso14443_3a_data->uid, 3); + memcpy(data->page[1].data, &data->iso14443_3a_data->uid[3], 4); + + // Calculate BCC bytes + data->page[0].data[3] = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; + data->page[2].data[0] = uid[3] ^ uid[4] ^ uid[5] ^ uid[6]; + } + + return uid_valid; } Iso14443_3aData* mf_ultralight_get_base_data(const MfUltralightData* data) { From c0db3d541e6fe6ea202b909044c9fd3f5056831d Mon Sep 17 00:00:00 2001 From: Skorpionm <85568270+Skorpionm@users.noreply.github.com> Date: Fri, 9 Feb 2024 12:49:58 +0400 Subject: [PATCH 19/58] [FL-3753] UI SubGhz: fix UI only_rx scene (#3379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * UI SubGhz: fix UI only_rx scene * UI SubGhz: delete unused scene Co-authored-by: あく --- .../main/subghz/scenes/subghz_scene_config.h | 1 - .../subghz/scenes/subghz_scene_show_only_rx.c | 49 ------------------- applications/main/subghz/subghz_i.c | 8 +-- 3 files changed, 4 insertions(+), 54 deletions(-) delete mode 100644 applications/main/subghz/scenes/subghz_scene_show_only_rx.c diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 47655958b4b..60bc8528a79 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -8,7 +8,6 @@ ADD_SCENE(subghz, saved, Saved) ADD_SCENE(subghz, transmitter, Transmitter) ADD_SCENE(subghz, show_error, ShowError) ADD_SCENE(subghz, show_error_sub, ShowErrorSub) -ADD_SCENE(subghz, show_only_rx, ShowOnlyRx) ADD_SCENE(subghz, saved_menu, SavedMenu) ADD_SCENE(subghz, delete, Delete) ADD_SCENE(subghz, delete_success, DeleteSuccess) diff --git a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c b/applications/main/subghz/scenes/subghz_scene_show_only_rx.c deleted file mode 100644 index 3522bf8aa64..00000000000 --- a/applications/main/subghz/scenes/subghz_scene_show_only_rx.c +++ /dev/null @@ -1,49 +0,0 @@ -#include "../subghz_i.h" -#include "../helpers/subghz_custom_event.h" - -void subghz_scene_show_only_rx_popup_callback(void* context) { - SubGhz* subghz = context; - view_dispatcher_send_custom_event(subghz->view_dispatcher, SubGhzCustomEventSceneShowOnlyRX); -} - -void subghz_scene_show_only_rx_on_enter(void* context) { - SubGhz* subghz = context; - - // Setup view - Popup* popup = subghz->popup; - - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; - if(!furi_hal_region_is_provisioned()) { - header_text = "Firmware update needed"; - message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; - } - - popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop); - popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop); - popup_set_icon(popup, 83, 22, &I_WarningDolphinFlip_45x42); - - popup_set_timeout(popup, 1500); - popup_set_context(popup, subghz); - popup_set_callback(popup, subghz_scene_show_only_rx_popup_callback); - popup_enable_timeout(popup); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdPopup); -} - -bool subghz_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) { - SubGhz* subghz = context; - if(event.type == SceneManagerEventTypeCustom) { - if(event.event == SubGhzCustomEventSceneShowOnlyRX) { - scene_manager_previous_scene(subghz->scene_manager); - return true; - } - } - return false; -} - -void subghz_scene_show_only_rx_on_exit(void* context) { - SubGhz* subghz = context; - Popup* popup = subghz->popup; - - popup_reset(popup); -} diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 4358b164dab..b553d00de02 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -60,15 +60,15 @@ void subghz_dialog_message_show_only_rx(SubGhz* subghz) { DialogsApp* dialogs = subghz->dialogs; DialogMessage* message = dialog_message_alloc(); - const char* header_text = "Transmission is blocked"; - const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region"; + const char* header_text = "Transmission is Blocked!"; + const char* message_text = "Transmission on\nthis frequency is\nrestricted in your\nregion"; if(!furi_hal_region_is_provisioned()) { header_text = "Firmware update needed"; message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd"; } - dialog_message_set_header(message, header_text, 63, 3, AlignCenter, AlignTop); - dialog_message_set_text(message, message_text, 0, 17, AlignLeft, AlignTop); + dialog_message_set_header(message, header_text, 63, 0, AlignCenter, AlignTop); + dialog_message_set_text(message, message_text, 1, 13, AlignLeft, AlignTop); dialog_message_set_icon(message, &I_WarningDolphinFlip_45x42, 83, 22); From b2a7bb0696316c94a75d1d5f3e21dcce2068014e Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 11:59:24 +0300 Subject: [PATCH 20/58] [FL-3672] CLI: cat command crash workaround (#3358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cli input len limit, CDC EP type fix Co-authored-by: あく --- applications/services/cli/cli.c | 6 ++- targets/f7/furi_hal/furi_hal_usb_cdc.c | 53 ++++++++++++++++---------- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/applications/services/cli/cli.c b/applications/services/cli/cli.c index 55a603a20ca..41e658df16b 100644 --- a/applications/services/cli/cli.c +++ b/applications/services/cli/cli.c @@ -6,6 +6,8 @@ #define TAG "CliSrv" +#define CLI_INPUT_LEN_LIMIT 256 + Cli* cli_alloc() { Cli* cli = malloc(sizeof(Cli)); @@ -356,7 +358,9 @@ void cli_process_input(Cli* cli) { cli_handle_backspace(cli); } else if(in_chr == CliSymbolAsciiCR) { cli_handle_enter(cli); - } else if(in_chr >= 0x20 && in_chr < 0x7F) { //-V560 + } else if( + (in_chr >= 0x20 && in_chr < 0x7F) && //-V560 + (furi_string_size(cli->line) < CLI_INPUT_LEN_LIMIT)) { if(cli->cursor_position == furi_string_size(cli->line)) { furi_string_push_back(cli->line, in_chr); cli_putc(cli, in_chr); diff --git a/targets/f7/furi_hal/furi_hal_usb_cdc.c b/targets/f7/furi_hal/furi_hal_usb_cdc.c index e9cb51e203c..014c98bad17 100644 --- a/targets/f7/furi_hal/furi_hal_usb_cdc.c +++ b/targets/f7/furi_hal/furi_hal_usb_cdc.c @@ -7,13 +7,13 @@ #include "usb.h" #include "usb_cdc.h" -#define CDC0_RXD_EP 0x01 +#define CDC0_RXD_EP 0x02 #define CDC0_TXD_EP 0x82 -#define CDC0_NTF_EP 0x83 +#define CDC0_NTF_EP 0x81 #define CDC1_RXD_EP 0x04 -#define CDC1_TXD_EP 0x85 -#define CDC1_NTF_EP 0x86 +#define CDC1_TXD_EP 0x84 +#define CDC1_NTF_EP 0x83 #define CDC_NTF_SZ 0x08 @@ -438,7 +438,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { struct usb_string_descriptor* dev_prod_desc = malloc(len * 2 + 2); dev_prod_desc->bLength = len * 2 + 2; dev_prod_desc->bDescriptorType = USB_DTYPE_STRING; - for(uint8_t i = 0; i < len; i++) dev_prod_desc->wString[i] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_prod_desc->wString[i] = name[i]; + } name = (char*)furi_hal_version_get_name_ptr(); len = (name == NULL) ? (0) : (strlen(name)); @@ -446,7 +448,9 @@ static void cdc_init(usbd_device* dev, FuriHalUsbInterface* intf, void* ctx) { dev_serial_desc->bLength = (len + 5) * 2 + 2; dev_serial_desc->bDescriptorType = USB_DTYPE_STRING; memcpy(dev_serial_desc->wString, "f\0l\0i\0p\0_\0", 5 * 2); - for(uint8_t i = 0; i < len; i++) dev_serial_desc->wString[i + 5] = name[i]; + for(uint8_t i = 0; i < len; i++) { + dev_serial_desc->wString[i + 5] = name[i]; + } cdc_if_cur->str_prod_descr = dev_prod_desc; cdc_if_cur->str_serial_descr = dev_serial_desc; @@ -500,18 +504,20 @@ uint8_t furi_hal_cdc_get_ctrl_line_state(uint8_t if_num) { } void furi_hal_cdc_send(uint8_t if_num, uint8_t* buf, uint16_t len) { - if(if_num == 0) + if(if_num == 0) { usbd_ep_write(usb_dev, CDC0_TXD_EP, buf, len); - else + } else { usbd_ep_write(usb_dev, CDC1_TXD_EP, buf, len); + } } int32_t furi_hal_cdc_receive(uint8_t if_num, uint8_t* buf, uint16_t max_len) { int32_t len = 0; - if(if_num == 0) + if(if_num == 0) { len = usbd_ep_read(usb_dev, CDC0_RXD_EP, buf, max_len); - else + } else { len = usbd_ep_read(usb_dev, CDC1_RXD_EP, buf, max_len); + } return ((len < 0) ? 0 : len); } @@ -540,14 +546,16 @@ static void cdc_rx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_RXD_EP) + if(ep == CDC0_RXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->rx_ep_callback != NULL) + if(callbacks[if_num]->rx_ep_callback != NULL) { callbacks[if_num]->rx_ep_callback(cb_ctx[if_num]); + } } } @@ -555,14 +563,16 @@ static void cdc_tx_ep_callback(usbd_device* dev, uint8_t event, uint8_t ep) { UNUSED(dev); UNUSED(event); uint8_t if_num = 0; - if(ep == CDC0_TXD_EP) + if(ep == CDC0_TXD_EP) { if_num = 0; - else + } else { if_num = 1; + } if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->tx_ep_callback != NULL) + if(callbacks[if_num]->tx_ep_callback != NULL) { callbacks[if_num]->tx_ep_callback(cb_ctx[if_num]); + } } } @@ -642,25 +652,28 @@ static usbd_respond cdc_control(usbd_device* dev, usbd_ctlreq* req, usbd_rqc_cal if(((USB_REQ_RECIPIENT | USB_REQ_TYPE) & req->bmRequestType) == (USB_REQ_INTERFACE | USB_REQ_CLASS) && (req->wIndex == 0 || req->wIndex == 2)) { - if(req->wIndex == 0) + if(req->wIndex == 0) { if_num = 0; - else + } else { if_num = 1; + } switch(req->bRequest) { case USB_CDC_SET_CONTROL_LINE_STATE: if(callbacks[if_num] != NULL) { cdc_ctrl_line_state[if_num] = req->wValue; - if(callbacks[if_num]->ctrl_line_callback != NULL) + if(callbacks[if_num]->ctrl_line_callback != NULL) { callbacks[if_num]->ctrl_line_callback( cb_ctx[if_num], cdc_ctrl_line_state[if_num]); + } } return usbd_ack; case USB_CDC_SET_LINE_CODING: memcpy(&cdc_config[if_num], req->data, sizeof(cdc_config[0])); if(callbacks[if_num] != NULL) { - if(callbacks[if_num]->config_callback != NULL) + if(callbacks[if_num]->config_callback != NULL) { callbacks[if_num]->config_callback(cb_ctx[if_num], &cdc_config[if_num]); + } } return usbd_ack; case USB_CDC_GET_LINE_CODING: From 3c73123a81a7aeb1032680a2f33496c1396bb2b9 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Fri, 9 Feb 2024 12:33:47 +0300 Subject: [PATCH 21/58] HID app: keyboard modifiers fix (#3378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HID App: Modifier keys fix * Toggled keys indication * New enter and backspace button icons Co-authored-by: あく --- .../hid_app/assets/Alt_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Backspace_9x7.png | Bin 0 -> 6628 bytes .../hid_app/assets/Cmd_pressed_17x10.png | Bin 0 -> 4234 bytes .../hid_app/assets/Ctrl_pressed_17x10.png | Bin 0 -> 4227 bytes .../system/hid_app/assets/Return_10x7.png | Bin 0 -> 6630 bytes .../hid_app/assets/Shift_pressed_7x10.png | Bin 0 -> 6626 bytes .../system/hid_app/views/hid_keyboard.c | 212 ++++++++++-------- 7 files changed, 115 insertions(+), 97 deletions(-) create mode 100644 applications/system/hid_app/assets/Alt_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Backspace_9x7.png create mode 100644 applications/system/hid_app/assets/Cmd_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Ctrl_pressed_17x10.png create mode 100644 applications/system/hid_app/assets/Return_10x7.png create mode 100644 applications/system/hid_app/assets/Shift_pressed_7x10.png diff --git a/applications/system/hid_app/assets/Alt_pressed_17x10.png b/applications/system/hid_app/assets/Alt_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..e7421c8c0bf7d189200b2b953219f24b287f093d GIT binary patch literal 4227 zcmeHKYitx%6rSawWdYGr&_JS_sX(R7&dl!4?9QZHEZsg<9VoCswK4JT%-wcmU(QUs z+m@h$ii!~mY7_ybJbVI*f%pOy9~GlPFvK6cOf%D;wK(7YC*nbWCrscG>BKq{k_D??BJotWF9o|^bwv3t^{`l<5ldrUW`Od1=(%)N83kO@BZx^Z+ z+!w94e)ViidQ#`ARnPxEyY$GKr^jD7^~A(S_!4eZ`?~Fik8U}(_3)-y7Y=-I;?Bp% z+TVWlp+yCf_u-C*%ZoQe&ODP`yXf;6#QY1L&x{)P zTkDQ96L#IUiF~W_##MOPI_6Y9H(}qg{E_V)c-^l3moD$0Q@H2w$4#$AhECl*|Ge<-!p~oww{P5!KTcV+a&z6q0ecoUjQ+;vc0r1z)ljWb zE8QW=iI@{9i7(Oijz#0TnD&zvt_ZTJrb&loV$AiEwU9F92@N|uPLGqJ zD$;6`i!8M}N;D-R&InXyAz1!k!R{obyjMYEf%DicI4uj)AETtFpb8G%r`q z!=#{IjU_EFAiI~Pp+>sN>J^(gV{NBrAmEx)W4 zRu$0_ImI18OxQZjt>jsL;*n|RbrwTYcQuv4$ z@f4$k70Tm94CP}LFNNH^hvRr3gWcgC5YsdjvJypmMrA@N02TJ|ir`Z?N=B^DgkmYg zARmQUA>#HSA*^^<3rdm2%7hj}aGYuk)nhstueT;l!o{+hpr3R(nQl!@6d4h4@RN6_ z@y2xbL`aR{nFcc1WW5aMcC(zD=Y3wE&(&=-8*4gbqKRphNo&SD77;cB#3D0K0l?D3 zUc_<@BO{@O5{amv%oW(&v;@~lK?VvS0|RKT6%oaXt^vUV*MBB1Q^nxU3#{1g;=Gtsf&t zk#$hF^o}LffX!0?!YGENSnf)~=N|CU7&_8yHe$uf8$fxr5tJ&f?zD{pQotnI&C+@Y>R0&g5F|NZwt4bO0C;te6_uH07QJe*x!(u95%% literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Backspace_9x7.png b/applications/system/hid_app/assets/Backspace_9x7.png new file mode 100644 index 0000000000000000000000000000000000000000..e098cf676167cdd78687b61cc1b61caf6e6b5666 GIT binary patch literal 6628 zcmeHMc|4TuyB~xo$(E!trcGs5W426|8Cy)&Y$0Rj8Rlh{W=4i=A!IwGP05m?<>ysV zwrG)5vP&tI)EkjhRHEfPGqk+t^qzD2e9rH`p84E!U(bDC*Y~;Z?|ohO=ea)5F2{}5 zD$0wLArOd)tqs)~?3KW#C?f~{_icK+4D8MV9=0r7+cl8c)3Y+OATrYuL}o@-1n*@* z8;Mmo*wjH;q9bXGWaOoN3@Gc)$Vs4VFry<2%4iu`hzi(efDHr6nqZ#`Hpw+x`m+Hg z3EJ1*+Lq=B#URiqgb@z3L1C~Y6q;(QFFE(UwAi@&!B= zI~agK#5sreg}S=*EJ$pv#n(FSo}av@Ehx}g)<&h)CKIs+tu%kbr3?rRM|I^>oidAN zV+{IY3MrXqV97gWoX!l-(xG;3sJrDX} zYE7qU>Sf56yggN(Ff_#RK zu)7!WOz6T4hBI~>U}E7_DS;=nm|23^rU9}n$*zippy zeDq5ls@_EXJNKw;70Br#;9SpSB8RO7@fntJtofAK?1zLTpREg-7rAw5b%d|;)qcZ( zwWoS}z3pW3f>t93Mvx&Te6RP!}kM>mU+jVb3t!@odCW>HHa25Z!yO52%h+eRbjT(g$@(45kqec z#BbGcg^+wTY#ffiP_%>5hb1T?b~O3T>XmUu&C88h{Yv?k+5GsO*XL_FCyJny>x<9s z^vKYCDko1-b6mV<_6mxHjjWd0aK7{L#U~Sn3Rr`i2Mt7vsv#V)q>^A{2U0g)8n---*|-~=pkY&F74&ZMa+L0?@+(LPt8NwbVspT%zQP{krb5L zf-6=Qnd0S2YWk-#)Q`%t&F*C^J*rogY?E1heY2*5r6t&JM1~}t!zlsc$;iYT6=X-<_{fsdGX7;r!85)rPTGO|O9os|eMo*{11v`UV=QN`J=N zSgfJL3wF0z5$r1M#OBM@-zIIm%0CiQnc!h-LN#$%yrOP}lMQP*ZuO4k`W*d{Rfk~@ zV71V!8&*tJfr+R6LksT8JpD(jE;{9}@mMp<;FYG^+Aagp8gD0YQIb2>b==kaUk6G( z3dGc{Hm^^p``)nGRdUtI>zHey$n|+bM>%Fwj}_6CNC_(wq`Z;;%UaX0A*rA8*eTgy z${@gi;TnI^0dT463hA;M@*EhP?+nm|Q%6Wlt<*T)llMinw-;lc@?dDdk)k_L= z+vNtIk7Fk3lOdDqHo-SJY^obtc^h|o?a9*H&yI&3e|>vH*S*_TT@Ozfbwze{c2%4R zJ@Kg1jD3I|AN2H(7wv>ouSN4X=}n7Y=IkaW4)k`%8#iN@Vci2?b}nwq>F(%$(|NPw zWCyA6t?QvbM+y#~tffrFzdJf_QzOkfzl<_)w1ttNT%VQMa-2DiE)p4sd>5xKsGf@R9KM<4Ti;pO$}GGFdz6qCis6SCCgIQK(RPonTn6S?`nZDWNniHO)U2 zw=hJ9jBzuXhibudjq8Fea9i=7tPVdxvnJyvhGz7D6W%iySBAGkpF-c6~6A?$rU0gU^B280LBEcr9KlJ=BUaq_IIWDd+?s`Ep zy`%jSy8TS|YlW0+$_x(caHz$)Fk;Cz(zEgIp39yuBJO_B)-JL;XqVDd7R^Ts5{i*77o0pKs%L{ar@Qn99JMfzX|?Z82>l1@Et<*6-UHsh6&# zI;T`AdLdoIX@2{~m}0WS3?>ZPYRzL>Mz!wH0-V7n_OOBKzKX}dtw=uf-vm%$f=u{P;Xd~v%*@veHVHc zeAg(kAV;BPyd&n=IlJP@;FpM(8o7!*Mc(ewo5%r&O!BSrV_)P&WPzIc7N)2(=uyVrYeso!XG_{GT!Th=J>QWe7ulTN+zdYm2TJ&-j)uJPyK=edkvO{Z z!L{I1yyA-eOJx(6CoYXXG%@nF-aK*pf1FM)j|`ab;V7OfJgPC|{g8NjZ^w#}^x>uJ z7m3R&8jlq}+1@nzUaynvCiMPG+@o}5*Ab1pjnDFO*6$4&dj9-rC!^Lbd^BelSy+O+ zzS{u9M1J(9u_g>{LG6RVNy zd|vA0;v@Q#r}lQb*OuniEZ~oHT>qH)c-ZL<@1aele(jB#4})%#B?A>gZfmcnP&=~X zX>Lbt^3=iix>49qXMBdBj(ks8qaqAepXI7a4U&f;@4C;TNH^jG4$CllT^hrB4c_wi#>#QghJ5Xd}B zwyTH8gJw@+@HhxMljjd0#2h~O8VZ4!n2PyyMi3x^`U3%ME*Umja}fq*Gs!S_V;YLa zrvQO$n{WZ(628%u5gx=KGGV6cl}*GX5P$=S=uj~ym@6cS$uKD{36v$nNElSAA_^kI zJiu363Qqt)u?Q>z1-BHlw_#xGm7yjACX3`uwVHtd@5rz~k%&)1B11z%5urv1o*)2; zCK8EA6b6aGz(EbTFpMjri{V`1G6}>q1{Dx81Z=*D&ErBPm~?+$h=>e>f%DL>{&Dy; z+BbNvaE1kt52TpRN1_oZB!`3i(LyM)+y;WoIP_Bsp=%f)Ksp0LUWkAJSZ)KjqGf-F za0b4w>Hh!$s{aju$@t~~KSU5L<%7vU0>JuNB084=d^Kl-=0!&xk20FK4c8E_&C zhlU$5jOh$4hJf;?Gk<_^5U{~erU(Db6Hp(ECISRF3q{1iv1k+krxUP7aFjoWiDek! zh(t6)3dLlQtat(r9gG1shaLbR`P=~MgoI?0xuY!^hC!gdk2nU?MJ&*O46|o*L&V=F zT-hAJMMRe{h{mIgu_!bekH%vN7=qDvB{x7I1jAQ?DXj&iGm@(o7=J8PiJ;(zgNAn+0M`PcZ`N z+yDSPa%MvG>p1%-p+Lmpv2-R23r7(#bT}4IWWfEgL_8dWGp4ir0TzKspQiO2x{${b zh0+Cpc>u^G$Q76p(ux~uAl<0dQqI0uG#gYkyO{a3<}GYdva4w|nkHbMS( zPG)GL0s=O;U@HNSBc($q0+XD?{Kp(YTmFwZ0x~AC{~0sWIfkbGqrKF16RG1roS5Kx zrpLgO7(CdK-%j?Ky#$&0cV06w^6z{E6#Dxizr^ovx_;C3OAP#y^6%*SP1i3m@Jq_S zqwD{TF6HmFA;1OS&O*V;4|}Ix0jwGo9PM2#zeGt2NJVq|60no;x3i`~lH`|2zHBJ* zZ9Ih#$Q%txlYtz}StwBy+0rZ(2W93fLyaQB(j?zY7TQwHU1#56WbphP1N~n8;TI^A dWp+M74uVaT*A7y@Xaed$Y%MoZ|FrOn{ZATUCJF!m literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Cmd_pressed_17x10.png b/applications/system/hid_app/assets/Cmd_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..274c3e070ae42bccbb0b463aa3396a12486704f6 GIT binary patch literal 4234 zcmeHKd2AF_7@y^kvRttTF*Lz(Do`o2bL`IU&ZG;L-O{eIP@qDwiE(!3Z9A}+JJasA zMMRK8G;#^bQPF~;7!DyAP!7QZF`9COK*G^*$Pr0U&WI5FW_G)@N@AMGf8EL3_r33Z z-|v0z_rCY-&YNXpO8S@wm@y3N<1O`!hyHPB=8P`zeQF?a6S^&^!W-~!D?WtlQ{qfT_FD&j2|$CC>vnKV_r)Fs3e8ALtiCV?oY_ z{sUL^aeeb3D5W(w;+g+xP zFByq@i`{mHx7$f@!?47vnnle8o^jdxM_A|e9+%%sIlNgsU*ZeCyQpl5ZPcIJme*aF zyTWn3{;F?9aOsAo?|#_S{WVi1+K|0yF*()$?Tu9zKU?(kj>d-Jw;QhVry4BV^OR!l zr-o;LdA~j}>{4Un%G(o%pZRF%;5(P!9y-Tf$mKMx-PLk-%eieW8(+9{a?gdy^K;GH z*S|40+vk|Qf3~~elfd<5(KU+YW1Ki%sqv|7n8d^@|(qNQ$suzU0Kx?MtL7qIDfE-lL$bgg0c z^&tnI+emC3HLwvaUQ1uj!D#oRy~Z?8zr}ww>&Nxejt;tVW#rsdn=0>iIW()f{}Gdwg%nFDmCIkQoAx*>+pi6mnwTKNYMu^} z#PA?hiYZY|iG*<-Q}jn_G#5d@Jl+~#DC+a2;lpZ*1>l29h*65M&{QZywe?W7;#vSn z1@ur4wY)BhsPRaR)WjrIT#Le5URwxRO8ZA^VnJg&vP7XE3W2E#t1|7D)Gt@sL#Loh z2}KPrAiJHVrUV|4)h;%D#@J5VK)^kX+s?Xm?}jm0`Fw&WBGu^Oc|9&dUtf?Tk|GO+ z$S_VMvl2t{D8P_5&dQRaHQ*%eh-XBZ;bkPTZJ@kiRTIM!(xCv{q5zJAlMpN0{ba!E zL;%4e(k})?QetVw$@w{lO}4dx7#mX{E5%^jsB|b9plF*_lKe7D(z4_yZM@S@`kg#S zav~BD?{sji-)=z3k}xU~3yE-?N=U3iR5V;=Oz4CQMP*(W!CL4CnzEp%1;D{Yj8?)m zi3bzqN(fERM4e5>L336s!`ZEjgJ+z{m<_y%D5gRt>X=5EG~D%L5nwYwtf=QH02q4M zi{OqSQH#XNBaxtsNEg`jv=6S6ENY@h)DVECOKy2<$t|F*0-X$Bhx0{bB~bTY*81ha z3&9?ayi`$P{W?RHyipU-E6GuE7*vd_gyY7g5JV}t1y!s?vJod>C8wlnF0uV;h49Rf+B#cUJn9^%T>y8Vle{oW17_=!e;Fr`v@q$`NrHf&T zGl=(5eo|-gD3`$T&P6)JZzo-ybajY<4jFf5S0`N^VxU9Do!Rxj(Pe({I)%dUFDMQ# zOF3ld3cPAP=6}A#gWcDk4|lCw0-B!D(#a}@^~%zl30tx12~hUXyuRWdmrVWo^)9Gb z`qyz#m3loz<@wEx3-+2ZJi}di#{bR4m1}XD=la~3z(RV!Z literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/assets/Ctrl_pressed_17x10.png b/applications/system/hid_app/assets/Ctrl_pressed_17x10.png new file mode 100644 index 0000000000000000000000000000000000000000..978a1090c2ee9f38e07c66adc5f4a3250885a048 GIT binary patch literal 4227 zcmeHKd2AF_7@y@V3xrq-8ZbCc15{vku06+HuylK@vampbk{aXe%-eS8UhYi0+m=)m z5k#XLLZq~)r2?YdsX;kKlp`ACihpngL5@g7Ih8>0o7wHsDv4<#|8*yC-}k=veZTj; z-}~OTJ8${M7xpy{GGZ9k*Hh%41pQ;sj2T_v`_vHPCUl!oi6`LkjKDHr&3ARtZK+827fKAbf%w28BV56zl+n&BA>&e(Z|<2f5f9c$hO5xgen<@uCJg=29~ zex8+aSgj#@f-hYp*&^)ta_u%lVdrwa@+V zK}}-RrN+kPx2KLi`|*-tcP_s(;!Ug5GN7q>$C;lupKCd@e#)JbyDm(hKiIf+?HhBm zytX;}=H!iB6}Y}Mx^mHcQ?Cy*;+JE&X9t(Q=eQeGj+~va{AN+2+sT`IPM@dVz2_hL z$!B}6#b(anSKmFkug>FjavZtIyS@8tTI}`*`*lusw@b)(0js-nY3YEW*J^iOAAaEZ z^~9EfA&qEJGkrPJGW__t%>GULQ1gKk_wJvVmVNll#o`UU#(ujJ?f4?^mtk4fQNNzs zz1cX|cMH3?%wJdaRFBi{GdVMMy4IA=KKgdkz!Pt7sVv|5Q&Y{2(h>dR!!v)6^=VOl zjCMcXd;FogTaK@0?_WFf_|Pj?#?EcnPJa3e*G z$x&5~gmDd1@JA|D7eT;0-Wp#h>h-4K!%B(;;DbsCQHn9qR47EX^-$FODga3Z^iU6_ zxH^icNl1xQ#zd4~g~DofTL?)^`$sEdL47-tNTDDKfvEzkGVPYsE?3$^qo7O@f)1|0A=BRgXuEe^X#nvo=t z4jV!wYhhT#Fjj^}s13w~m<(Ad1lvZXK}i4w3kIx`B|ruc=OE1vWG4LM zhUKh+4kd|vK_nIu;5g-wP==^zxJ;kW2?S<ul3Uzba!1k}PbUM=;Jgt@4pje_wRU-MC)mT07s(2& zU#+W>H)=9^B{@nCgR*{=a9qC>ydWmGpa@kcdES7PoD$20a2bN?kxJKAz5I|=u-jN0 zEBXDT;P6{XGh+wI57(R(EHvU+(acJAorN^I5(%hrA%=3xfJfj8(o_F2!gKV4dZxW~ zyc}t#0ECe=Lo$|s5=NyqOldWvb;nNXUz|8~gEmD5{E|8-UQi3EbTLeE2Jt@1PwFfl z+>M>ZGef40Oo2GrRsbx{MEAr%)LF1;ycIY1-DO$HA*s z5C02=ZtTAHe6(ZrV$k%87EM<$tapE{8L;|>CqUU#^?37pUNZFS*LUbD>qYHtJlEsS zEzUXA=*u-?bVmKX`-grTI484o#X+BEf%Dk~pZhD0?SE0-Sife}tnE{FZ1NrKai^hh m?~{KXUS8M2gr<4F@v#8)@&iOJi~l5OEV)w1|fvJX;-qOX!-f7 zBwIv6B}+&tZR#5lDvD_NJu_*0-@e!Tc3to9zn;0yoclTFzCX`>KIgvA_1xF9!)2Y_ zY}I+H5C~+pJ%#KB_EK3{P67O%MPCO6yK{iIJ(l2lJ_T2=;S zdC*2`6#+I)P?qXQ+dMf%Ssw$+^QYw`P&S&@kq2e8oIGSU*zW=x29&kIJ_~HpYo_c+ z0VOHgm)_2v>H@_e&?tn78EAvTUJoFPj*eu zpNTPg6q83vKMPCVF6Vl7aE3m)b2DFi{(gIAq5i{*;Umq59~z8DZOX1{y&p6p>8gP2 zdLO5F*~wca=8s%&?Z@8YgVVG2kPtf&73I5R4-@5iR=^BHt?RF)sI|f z1~J;*gLo!%XNJHTyNxih@F(GrN0e(DU$!H?Z?tMPsnxgK@I`1YElAQ|?&qGcbB|Wa zdO!}?@{iMqF5Q5mx#XYzrP?#V!3rPWEuy*A{gd?%>W{*nz>wUQJwtP|EwWFMMPtNVDYyL|tSyAv91 z`@Q9lFP^tHY7xX}r3O@GD|1!bh-6lSox;23l$#j`k4&}3ew28XIeomUyEHnDVMoT4 zX}Y$&IP~6E9t(xotW_9TJHKB+t7gW98jUiD+9L&dE2E?tn*-60Vzqb4k)e9pF(o;3 z_r?|m8lF%J&C$Tdnepd}wlN-Iamt8oO@T9dj6 zpfq$Z+ghiKm#SziKD|rxm^^#!-Cc{08I&he(hII`&{ndsLAyBZops$nXM69_{kfjG zW_hPvm0gaUSkt+_)96ptHyTviO-Gw=w)8_p#svK%DF|DyoWpGk`-}TE#&DB5N^!Z2 zRCLA??&wBrh(6wMNd3L?v3_S#WL3OM8R?V%7tJ5TL zOL|583%HGb7VDB$U0MIgmv6}T;%k-ZU(|e{K9Z_dKl-xi6)<5NsTMWUGA(7S0QnRWEa;u$G#w*tT>rXYr8X z5!iiL6?D%vTc(=8!pG@>HFtUT;)jNpTys`>ubg4@QrmM?r;%u-pQ|`O$qVZ-=Hd6R zgGJuCV)ACntCN}m*C;y*uekai_sA1@JWpspk6GVsi}%2j!ixnduND8c(>AV8>LopP zO*Wb`3Nm7N#Gi5o+{-&dJ8cJi25_@UCi#kOZtUaddBJh#SLh@sA8|PEkma!R`evOK z3v=h+ROo*`hMA;IhEA?t4`1)RzItH!EwfvzP8HpHb|U=5t6L78cW>EtJ~(O8De3I! zEIk=^@?pnX_Ca?1rl)_txQRRcN;HR)*0f+ab2mP*ucs^Cv>Ceu>lHlQv7jxptG(-W z$MyD8?S#BH9*6%L$~|(biZm7f_Sl^Dja0jwVp89+7Dj?<-JbN86U;GmzQ{E6L+G-K zHx+ReCJ_x0t`XZKh9bttR3?o-Ed8)>vTD*@iJ-JtNl~d#sdVSeshnVm>AC>f2qFqqF;F^@VMZZHs9e${oL)TDO0@ z#2`&YZB~&=^jw;jtNP7LF%^N=V)rW+!Kz?Qun}?w+5Tumo=%?D(ZhKg8^w)#8!3!J zx>rL$az~QZmC#1Ule{N~dkgd?jeE-Z7PV_~agQfqPi(?d5XN2Jnbi{$n)S;vm)U9F z+=1Q!-!X#E%~Wa`YmYfz;#g1?GK?73%2MVj^LCG1NA@`{EnmLRkm#_5`XI|9qc_Vf z{k2DpuE(sbg2{re-Gm+N=gPKp$=~-JH4n|3t2 zLbBwb1V6RN%+~yEbn|3IxqaTr^Uv#|9!EXjmZTtQPHS$4AA$pg{L{xmUk6^UZF(q< z?zrC&a++6AdSH=!;?l%L(T697-qcwoZvC$_Y3C(D6aF0KlDuPD1AY(ixAwI!8%i5o zv}T_8d}-tHf+t&>M#c>~h@L{fzs21umvp0kFQz^AdOi~Wn=(dy${$fHZ{*)=Yg z7$PkLCdBXUyY^%h)LX9l%`rpkRdo^5;>YN>=FdjcTh0nQH4uQ10mOEI6u*aV26bRdY$CBjB3FTtQ}CK2XkN<~rm zBp{egi4Xwp5$ik{5t|rzCd_h;s)d*U0&oBk4Jzh@aD@ah5hlYWfUAWsw-AbKwtyhh4*k?Z=n>8bkZypG7b;)?Hd_F$XvvQd zZos!S{U0De^&bG3jIR#xLj@r+K9~$75CU+(X9+>K(LeYsttP%&NI48*bNDhVaJPQY z6tP+V;F)wr#>e+70nNYS{&XEOWl)PsC6IZHP^o+NWFib4K`_}&0vScdnpj~?&8=)v zXtWK<+6qg?Tj6YMY%nC8nU&Rd=Js5nh{k0AU(DH{IR;G!=y+2!9EZcw;8;4w1Wu=6 zF>tggi_S7J!7)wD=-)v&3)o;N(?Wjc2@{QC;h87~9L>UE;aDsRfa7R54BQ-tVX?5L zG#ZORlR+^V1Y4efLjz-g&7lPWNIo}6HX$XMVC7;@gkcbk!5n1LL^Lu@1c0Dl>q?I=b>%9Q2?6!leJLE3$7Hj@ z|2J)E#z8GUZ!?801fL%+8~U7v?!e~Hcb{)V*s}bCLS>mmpfNr#K}g#IFlBy%Sf8gD z!8C3V03JEhA^K&U{j*TO;my%ZIvtM1VexP*4a0)tv8Hr5gN~w`U@=$*j*k5iUC3jJ z!e|1(DhT8eKbKOGb z_;)8JxSr2r;7JS~?8vVt`}AH)&(QziH60`W!6$&wzaR2T{QjovH(kHPz%MEPj;`Ny z{SpJer2IR&{@>_Q{Z<O0=A3CT-{Ro^$$q&hNiwKKFB9&wXFl_qnd`eP8$Ixn_1bY_wET zT&xIzK$NVls7_!n4>m<98SrW*-Lnzw&I0b%{?^v3A#=eS1qeiH4nzu+#ClS*ve;f4 zv=Li{gH08b#X91)SV~sX$AGf-tegbO2D3WSpp2H1hA4r3I@mCvtPb`$U=x4lO1@U0 zBu4wvTUyf`pcn)ig}@quHYf~^ghG?hcqrP4gu;;ULDEmlesEc#&!o;>3LXE?o10ulS_k=*yRXs%hK4vCOdWhR)$66DdEJQYmaSi5fBoQj9IX{)(+cy5#klSjyjr_C z;!PHyOq{^P8*4QSS_gm^Px@Cr-c3}kP0jGt(j1&SRf(xx!H5%DCv0fsJ}K3T*k}kb z*wc@ADsW~7!x?)FFtPB*VUUMb*SEarM7rN>Q)^akXuau)P}M6+(pur=oUnVZTFRrg zHrilH!%ftD!tUh*=sQ_j9!nnaA^vUeJ=DY4T4!PF?idk`P;5B6)}TAu`~BfPZ?|Ro z#7B>5QFW&3-?&9(DM8K@0_S?C6WJ^ci1&z;L-mKm7GESJ`D|^-{K&1#sv>-xt`4jX zSaZ6+-^*4iH)s`dXnZ<%el%rEM4MCXz45>4qKUmmdxU6;4}s$!UP!%qt#yF+?vJOr ziBmfAA48TcRC%vgn}pnFPHirl|Gk4vcQfbe0 zmp-n$ctg}Oh{0+VsKR#Un)dO?d5xAb?^;qivJM`ZX^Z_B>2|^H(h)P>A^knc)rELo#ZY=bXQ+asi_JP)6F+AZgB4U-ZLR^>isJR8u|XT;8I6 zvBkbCPsoMjso>%ad5eTQ7!Pqq@`xSHzH|DeoKW*~VphFSyt!dP{LX6&G@KHJP|CF> zXLq`%Yd?{ZrKmV8**908VrC_+v0)_7>BN##3B&pRLz{;Tgo~>nN|cH`$3WF-jd1zp zD22j0&oe5)vSq&ZMsw5RHS4awz!>QuVk<81=gCAY7>l>BKk!h+UD0GmkxI-071GfZ zl*+;@7G@bDJp~or)9I?mq*)v8rY}3DQ<-d)QFLvyx}3Q=+QDw$yc;?iI|q(t=DX$_ z7MyaFcQ|rlefOqrgFh8ttI#Zhj<$5P4nc%#Nm@rz5EgEEhufD9l?|y(8cl1+#pN?n z(P@jK?Ut<2D3dPJD%0*tBkt5{Or;l=y8nyRvt%S$seymSFBpAZar=Vps@rAPrA5Xq z9}x1+8*TA2-k7xJ>ZXUDyp=pRp4tVii>mh*jHfC$OuT4*2~1f;C`ZjTNz2tWP)k+# zE8fa%H65P6r`^KH_JXa*R8RGF(#EU2qcIl}+^vnN#`a6}YxNzi{Phf1?ayEBG z0=oyRf$qI-!BplOd)VDKb0#5M79rurhwiE7`v-)z>y84Nx9A zCL7Ec1Q;+};!oKF&XwIE-4??h!$wLJY@uws6YF?6H!!Yzl}2*%5u0+G9Gl%YwrZ?e zny=jVve-hgr+1=G$ zaWeGegRTv%gRJ%?yksnU4>57Dzb78wf?JMr3mofO(w^PZ+4HLF zM(3$cQo(DN!+(wDA30S+nTdaMZ2qPunq^)YW$;)lBSEo#Z$|40<|MjMh!6P?qF?p8 zDy|9}-Wcu}zB7C@{OzQ|^x6-4AC^wnOgqbwBVQ3(ZpGQnc+)cDUFH@w*Qe(vvpy_;%%g-lRdq49?v80ywlVyv^`{-(p(VMI59P` zcyM>+a3$Ys#QQksj|yr<4aX_eJ2Y*3+>DHJq;ihs-lq7XlL4m&P1^!|dVF9d^Gj|A z2fVR)vxUP-Ob+!?8p zrl35pL?L<+UCnVp$EBDm-|MlNvL&z@STk&#nnkrf8eX7Lpmy|d!Imab)4nDvMzNn; zqfc^IlG@dfCdT7}$A<@sG^f}0SMrSO*5?~Nnua|#4@*I;?Qzepotjc@(9hPlRPES> z-UZ(^PR!4iYn|+jIeyNz=tA%qVoWVZo-5DYGkya(Xs=hfV*g6A%{JQo9OJBk9H)#| zE|(X&%*!d7F6ud6yKx?^FZazV@`@U=%z=!9TV1SF3KeEB=dcr)#Il=Z$z{iyvk#1V z8R{#l;sHu;%Gv4$TyI-<%vI`HAIzc0na+DBRuD*Ut#bG|D6Vg1?<-pK%2$ySmwa}3 z)_Heq&f9$Eysz;1;Em}`?Wc~P4tE2fXFsF}%XC$xb-~HrE54|HqlM^2mTGZ?^oof@5mKUiXQ&_IK)!rj0CH zzgSdW(R94%@%HBNw>n*9SAo~xqF#k7yN;^mZhV@Xy?%el@Uv%6x)?RK;p5r6$bw?z zMed8>_TGuXh_)lo-aa=8yZ`!q>AY6WG#{q-^QtGjlY+}pd5`61bVzbb<<=!GNvuMy z^B&X6Aw&!$&+PAVt0~E;UdS8my!Jlh(TL-1?tQBZx;59U-wnA=7Y|klIBoqN0?o*Z zCpn!p$uo!EYDeKdlzIhjic38{%836U+N7n^r`w zM4I?aiQZfH?9D1_Fj@1-Yv$&Y-q1^;_ZXDMI{Zhk$@g6)hqs)#bmvp&7%(opGx@!1?-otI94kt-&yA9K8$U5a%T5%SO4N85SrM@<>QzXlN)R6pP^U1CVGU zk%&ZLkQfXc)PM`ZI6}Gz&JipZLwv@d0s;n~#S^l)9HEIq))i2!Nnp>q?g|b>$iqmW2B3z8H?iWwQLk{x@xL z#zBogFS8X(06srVGW0nOoq?^N?>^rKvn2Tkg-SAsL}z>sK|tRIFeQG1Sf8gDfpks) z03JEBTlC8~>nB@*KqvYESd0oH9$t+D& zfX@O0w%~Kw5;_DzFv&^Gf6fu?|Cl2nV`BTCF*BQEn18gFxNa-zE=G2qZbYpSWs+}n%=zGr+-(5K|}Ou?U?r6D?F W(tV}+OFw`b5Nq>|)W6JpWB(JHjv}J~ literal 0 HcmV?d00001 diff --git a/applications/system/hid_app/views/hid_keyboard.c b/applications/system/hid_app/views/hid_keyboard.c index 9060c1d6a64..1ee2c01c9c1 100644 --- a/applications/system/hid_app/views/hid_keyboard.c +++ b/applications/system/hid_app/views/hid_keyboard.c @@ -20,19 +20,18 @@ typedef struct { uint8_t x; uint8_t y; uint8_t last_key_code; - uint16_t modifier_code; bool ok_pressed; bool back_pressed; bool connected; - char key_string[5]; HidTransport transport; } HidKeyboardModel; typedef struct { uint8_t width; - char* key; + char key; + char shift_key; const Icon* icon; - char* shift_key; + const Icon* icon_toggled; uint8_t value; } HidKeyboardKey; @@ -66,85 +65,97 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { {.width = 1, .icon = &I_ButtonF12_5x8, .value = HID_KEYBOARD_F12}, }, { - {.width = 1, .icon = NULL, .key = "1", .shift_key = "!", .value = HID_KEYBOARD_1}, - {.width = 1, .icon = NULL, .key = "2", .shift_key = "@", .value = HID_KEYBOARD_2}, - {.width = 1, .icon = NULL, .key = "3", .shift_key = "#", .value = HID_KEYBOARD_3}, - {.width = 1, .icon = NULL, .key = "4", .shift_key = "$", .value = HID_KEYBOARD_4}, - {.width = 1, .icon = NULL, .key = "5", .shift_key = "%", .value = HID_KEYBOARD_5}, - {.width = 1, .icon = NULL, .key = "6", .shift_key = "^", .value = HID_KEYBOARD_6}, - {.width = 1, .icon = NULL, .key = "7", .shift_key = "&", .value = HID_KEYBOARD_7}, - {.width = 1, .icon = NULL, .key = "8", .shift_key = "*", .value = HID_KEYBOARD_8}, - {.width = 1, .icon = NULL, .key = "9", .shift_key = "(", .value = HID_KEYBOARD_9}, - {.width = 1, .icon = NULL, .key = "0", .shift_key = ")", .value = HID_KEYBOARD_0}, - {.width = 2, .icon = &I_Pin_arrow_left_9x7, .value = HID_KEYBOARD_DELETE}, + {.width = 1, .icon = NULL, .key = '1', .shift_key = '!', .value = HID_KEYBOARD_1}, + {.width = 1, .icon = NULL, .key = '2', .shift_key = '@', .value = HID_KEYBOARD_2}, + {.width = 1, .icon = NULL, .key = '3', .shift_key = '#', .value = HID_KEYBOARD_3}, + {.width = 1, .icon = NULL, .key = '4', .shift_key = '$', .value = HID_KEYBOARD_4}, + {.width = 1, .icon = NULL, .key = '5', .shift_key = '%', .value = HID_KEYBOARD_5}, + {.width = 1, .icon = NULL, .key = '6', .shift_key = '^', .value = HID_KEYBOARD_6}, + {.width = 1, .icon = NULL, .key = '7', .shift_key = '&', .value = HID_KEYBOARD_7}, + {.width = 1, .icon = NULL, .key = '8', .shift_key = '*', .value = HID_KEYBOARD_8}, + {.width = 1, .icon = NULL, .key = '9', .shift_key = '(', .value = HID_KEYBOARD_9}, + {.width = 1, .icon = NULL, .key = '0', .shift_key = ')', .value = HID_KEYBOARD_0}, + {.width = 2, .icon = &I_Backspace_9x7, .value = HID_KEYBOARD_DELETE}, {.width = 0, .value = HID_KEYBOARD_DELETE}, }, { - {.width = 1, .icon = NULL, .key = "q", .shift_key = "Q", .value = HID_KEYBOARD_Q}, - {.width = 1, .icon = NULL, .key = "w", .shift_key = "W", .value = HID_KEYBOARD_W}, - {.width = 1, .icon = NULL, .key = "e", .shift_key = "E", .value = HID_KEYBOARD_E}, - {.width = 1, .icon = NULL, .key = "r", .shift_key = "R", .value = HID_KEYBOARD_R}, - {.width = 1, .icon = NULL, .key = "t", .shift_key = "T", .value = HID_KEYBOARD_T}, - {.width = 1, .icon = NULL, .key = "y", .shift_key = "Y", .value = HID_KEYBOARD_Y}, - {.width = 1, .icon = NULL, .key = "u", .shift_key = "U", .value = HID_KEYBOARD_U}, - {.width = 1, .icon = NULL, .key = "i", .shift_key = "I", .value = HID_KEYBOARD_I}, - {.width = 1, .icon = NULL, .key = "o", .shift_key = "O", .value = HID_KEYBOARD_O}, - {.width = 1, .icon = NULL, .key = "p", .shift_key = "P", .value = HID_KEYBOARD_P}, - {.width = 1, .icon = NULL, .key = "[", .shift_key = "{", .value = HID_KEYBOARD_OPEN_BRACKET}, + {.width = 1, .icon = NULL, .key = 'q', .shift_key = 'Q', .value = HID_KEYBOARD_Q}, + {.width = 1, .icon = NULL, .key = 'w', .shift_key = 'W', .value = HID_KEYBOARD_W}, + {.width = 1, .icon = NULL, .key = 'e', .shift_key = 'E', .value = HID_KEYBOARD_E}, + {.width = 1, .icon = NULL, .key = 'r', .shift_key = 'R', .value = HID_KEYBOARD_R}, + {.width = 1, .icon = NULL, .key = 't', .shift_key = 'T', .value = HID_KEYBOARD_T}, + {.width = 1, .icon = NULL, .key = 'y', .shift_key = 'Y', .value = HID_KEYBOARD_Y}, + {.width = 1, .icon = NULL, .key = 'u', .shift_key = 'U', .value = HID_KEYBOARD_U}, + {.width = 1, .icon = NULL, .key = 'i', .shift_key = 'I', .value = HID_KEYBOARD_I}, + {.width = 1, .icon = NULL, .key = 'o', .shift_key = 'O', .value = HID_KEYBOARD_O}, + {.width = 1, .icon = NULL, .key = 'p', .shift_key = 'P', .value = HID_KEYBOARD_P}, + {.width = 1, .icon = NULL, .key = '[', .shift_key = '{', .value = HID_KEYBOARD_OPEN_BRACKET}, {.width = 1, .icon = NULL, - .key = "]", - .shift_key = "}", + .key = ']', + .shift_key = '}', .value = HID_KEYBOARD_CLOSE_BRACKET}, }, { - {.width = 1, .icon = NULL, .key = "a", .shift_key = "A", .value = HID_KEYBOARD_A}, - {.width = 1, .icon = NULL, .key = "s", .shift_key = "S", .value = HID_KEYBOARD_S}, - {.width = 1, .icon = NULL, .key = "d", .shift_key = "D", .value = HID_KEYBOARD_D}, - {.width = 1, .icon = NULL, .key = "f", .shift_key = "F", .value = HID_KEYBOARD_F}, - {.width = 1, .icon = NULL, .key = "g", .shift_key = "G", .value = HID_KEYBOARD_G}, - {.width = 1, .icon = NULL, .key = "h", .shift_key = "H", .value = HID_KEYBOARD_H}, - {.width = 1, .icon = NULL, .key = "j", .shift_key = "J", .value = HID_KEYBOARD_J}, - {.width = 1, .icon = NULL, .key = "k", .shift_key = "K", .value = HID_KEYBOARD_K}, - {.width = 1, .icon = NULL, .key = "l", .shift_key = "L", .value = HID_KEYBOARD_L}, - {.width = 1, .icon = NULL, .key = ";", .shift_key = ":", .value = HID_KEYBOARD_SEMICOLON}, - {.width = 2, .icon = &I_Pin_arrow_right_9x7, .value = HID_KEYBOARD_RETURN}, + {.width = 1, .icon = NULL, .key = 'a', .shift_key = 'A', .value = HID_KEYBOARD_A}, + {.width = 1, .icon = NULL, .key = 's', .shift_key = 'S', .value = HID_KEYBOARD_S}, + {.width = 1, .icon = NULL, .key = 'd', .shift_key = 'D', .value = HID_KEYBOARD_D}, + {.width = 1, .icon = NULL, .key = 'f', .shift_key = 'F', .value = HID_KEYBOARD_F}, + {.width = 1, .icon = NULL, .key = 'g', .shift_key = 'G', .value = HID_KEYBOARD_G}, + {.width = 1, .icon = NULL, .key = 'h', .shift_key = 'H', .value = HID_KEYBOARD_H}, + {.width = 1, .icon = NULL, .key = 'j', .shift_key = 'J', .value = HID_KEYBOARD_J}, + {.width = 1, .icon = NULL, .key = 'k', .shift_key = 'K', .value = HID_KEYBOARD_K}, + {.width = 1, .icon = NULL, .key = 'l', .shift_key = 'L', .value = HID_KEYBOARD_L}, + {.width = 1, .icon = NULL, .key = ';', .shift_key = ':', .value = HID_KEYBOARD_SEMICOLON}, + {.width = 2, .icon = &I_Return_10x7, .value = HID_KEYBOARD_RETURN}, {.width = 0, .value = HID_KEYBOARD_RETURN}, }, { - {.width = 1, .icon = NULL, .key = "z", .shift_key = "Z", .value = HID_KEYBOARD_Z}, - {.width = 1, .icon = NULL, .key = "x", .shift_key = "X", .value = HID_KEYBOARD_X}, - {.width = 1, .icon = NULL, .key = "c", .shift_key = "C", .value = HID_KEYBOARD_C}, - {.width = 1, .icon = NULL, .key = "v", .shift_key = "V", .value = HID_KEYBOARD_V}, - {.width = 1, .icon = NULL, .key = "b", .shift_key = "B", .value = HID_KEYBOARD_B}, - {.width = 1, .icon = NULL, .key = "n", .shift_key = "N", .value = HID_KEYBOARD_N}, - {.width = 1, .icon = NULL, .key = "m", .shift_key = "M", .value = HID_KEYBOARD_M}, - {.width = 1, .icon = NULL, .key = "/", .shift_key = "?", .value = HID_KEYBOARD_SLASH}, - {.width = 1, .icon = NULL, .key = "\\", .shift_key = "|", .value = HID_KEYBOARD_BACKSLASH}, - {.width = 1, .icon = NULL, .key = "`", .shift_key = "~", .value = HID_KEYBOARD_GRAVE_ACCENT}, + {.width = 1, .icon = NULL, .key = 'z', .shift_key = 'Z', .value = HID_KEYBOARD_Z}, + {.width = 1, .icon = NULL, .key = 'x', .shift_key = 'X', .value = HID_KEYBOARD_X}, + {.width = 1, .icon = NULL, .key = 'c', .shift_key = 'C', .value = HID_KEYBOARD_C}, + {.width = 1, .icon = NULL, .key = 'v', .shift_key = 'V', .value = HID_KEYBOARD_V}, + {.width = 1, .icon = NULL, .key = 'b', .shift_key = 'B', .value = HID_KEYBOARD_B}, + {.width = 1, .icon = NULL, .key = 'n', .shift_key = 'N', .value = HID_KEYBOARD_N}, + {.width = 1, .icon = NULL, .key = 'm', .shift_key = 'M', .value = HID_KEYBOARD_M}, + {.width = 1, .icon = NULL, .key = '/', .shift_key = '?', .value = HID_KEYBOARD_SLASH}, + {.width = 1, .icon = NULL, .key = '\\', .shift_key = '|', .value = HID_KEYBOARD_BACKSLASH}, + {.width = 1, .icon = NULL, .key = '`', .shift_key = '~', .value = HID_KEYBOARD_GRAVE_ACCENT}, {.width = 1, .icon = &I_ButtonUp_7x4, .value = HID_KEYBOARD_UP_ARROW}, - {.width = 1, .icon = NULL, .key = "-", .shift_key = "_", .value = HID_KEYBOARD_MINUS}, + {.width = 1, .icon = NULL, .key = '-', .shift_key = '_', .value = HID_KEYBOARD_MINUS}, }, { - {.width = 1, .icon = &I_Pin_arrow_up_7x9, .value = HID_KEYBOARD_L_SHIFT}, - {.width = 1, .icon = NULL, .key = ",", .shift_key = "<", .value = HID_KEYBOARD_COMMA}, - {.width = 1, .icon = NULL, .key = ".", .shift_key = ">", .value = HID_KEYBOARD_DOT}, - {.width = 4, .icon = NULL, .key = " ", .value = HID_KEYBOARD_SPACEBAR}, + {.width = 1, + .icon = &I_Pin_arrow_up_7x9, + .icon_toggled = &I_Shift_pressed_7x10, + .value = HID_KEYBOARD_L_SHIFT}, + {.width = 1, .icon = NULL, .key = ',', .shift_key = '<', .value = HID_KEYBOARD_COMMA}, + {.width = 1, .icon = NULL, .key = '.', .shift_key = '>', .value = HID_KEYBOARD_DOT}, + {.width = 4, .icon = NULL, .key = ' ', .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, {.width = 0, .value = HID_KEYBOARD_SPACEBAR}, - {.width = 1, .icon = NULL, .key = "'", .shift_key = "\"", .value = HID_KEYBOARD_APOSTROPHE}, - {.width = 1, .icon = NULL, .key = "=", .shift_key = "+", .value = HID_KEYBOARD_EQUAL_SIGN}, + {.width = 1, .icon = NULL, .key = '\'', .shift_key = '\"', .value = HID_KEYBOARD_APOSTROPHE}, + {.width = 1, .icon = NULL, .key = '=', .shift_key = '+', .value = HID_KEYBOARD_EQUAL_SIGN}, {.width = 1, .icon = &I_ButtonLeft_4x7, .value = HID_KEYBOARD_LEFT_ARROW}, {.width = 1, .icon = &I_ButtonDown_7x4, .value = HID_KEYBOARD_DOWN_ARROW}, {.width = 1, .icon = &I_ButtonRight_4x7, .value = HID_KEYBOARD_RIGHT_ARROW}, }, { - {.width = 2, .icon = &I_Ctrl_17x10, .value = HID_KEYBOARD_L_CTRL}, + {.width = 2, + .icon = &I_Ctrl_17x10, + .icon_toggled = &I_Ctrl_pressed_17x10, + .value = HID_KEYBOARD_L_CTRL}, {.width = 0, .value = HID_KEYBOARD_L_CTRL}, - {.width = 2, .icon = &I_Alt_17x10, .value = HID_KEYBOARD_L_ALT}, + {.width = 2, + .icon = &I_Alt_17x10, + .icon_toggled = &I_Alt_pressed_17x10, + .value = HID_KEYBOARD_L_ALT}, {.width = 0, .value = HID_KEYBOARD_L_ALT}, - {.width = 2, .icon = &I_Cmd_17x10, .value = HID_KEYBOARD_L_GUI}, + {.width = 2, + .icon = &I_Cmd_17x10, + .icon_toggled = &I_Cmd_pressed_17x10, + .value = HID_KEYBOARD_L_GUI}, {.width = 0, .value = HID_KEYBOARD_L_GUI}, {.width = 2, .icon = &I_Tab_17x10, .value = HID_KEYBOARD_TAB}, {.width = 0, .value = HID_KEYBOARD_TAB}, @@ -155,13 +166,6 @@ const HidKeyboardKey hid_keyboard_keyset[ROW_COUNT][COLUMN_COUNT] = { }, }; -static void hid_keyboard_to_upper(char* str) { - while(*str) { - *str = toupper((unsigned char)*str); - str++; - } -} - static void hid_keyboard_draw_key( Canvas* canvas, HidKeyboardModel* model, @@ -192,28 +196,32 @@ static void hid_keyboard_draw_key( KEY_HEIGHT); } if(key.icon != NULL) { + const Icon* key_icon = key.icon; + if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || + (model->alt && key.value == HID_KEYBOARD_L_ALT) || + (model->shift && key.value == HID_KEYBOARD_L_SHIFT) || + (model->gui && key.value == HID_KEYBOARD_L_GUI)) { + if(key.icon_toggled) { + key_icon = key.icon_toggled; + } + } // Draw the icon centered on the button canvas_draw_icon( canvas, - MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key.icon->width / 2, - MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key.icon->height / 2, - key.icon); + MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 - key_icon->width / 2, + MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2 - key_icon->height / 2, + key_icon); } else { + char key_str[2] = "\0\0"; // If shift is toggled use the shift key when available - strcpy(model->key_string, (model->shift && key.shift_key != 0) ? key.shift_key : key.key); - // Upper case if ctrl or alt was toggled true - if((model->ctrl && key.value == HID_KEYBOARD_L_CTRL) || - (model->alt && key.value == HID_KEYBOARD_L_ALT) || - (model->gui && key.value == HID_KEYBOARD_L_GUI)) { - hid_keyboard_to_upper(model->key_string); - } + key_str[0] = (model->shift && key.shift_key != 0) ? key.shift_key : key.key; canvas_draw_str_aligned( canvas, MARGIN_LEFT + x * (KEY_WIDTH + KEY_PADDING) + keyWidth / 2 + 1, MARGIN_TOP + y * (KEY_HEIGHT + KEY_PADDING) + KEY_HEIGHT / 2, AlignCenter, AlignCenter, - model->key_string); + key_str); } } @@ -244,6 +252,8 @@ static void hid_keyboard_draw_callback(Canvas* canvas, void* context) { initY = model->y - 4; } + elements_scrollbar(canvas, initY, 3); + for(uint8_t y = initY; y < ROW_COUNT; y++) { const HidKeyboardKey* keyboardKeyRow = hid_keyboard_keyset[y]; uint8_t x = 0; @@ -286,6 +296,14 @@ static void hid_keyboard_get_select_key(HidKeyboardModel* model, HidKeyboardPoin 0); // Skip zero width keys, pretend they are one key } +static void hid_keyboard_modifier_set(Hid* hid, uint16_t keycode, bool is_pressed) { + if(is_pressed) { + hid_hal_keyboard_press(hid, keycode); + } else { + hid_hal_keyboard_release(hid, keycode); + } +} + static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { with_view_model( hid_keyboard->view, @@ -300,35 +318,25 @@ static void hid_keyboard_process(HidKeyboard* hid_keyboard, InputEvent* event) { // Toggle the modifier key when clicked, and click the key if(model->last_key_code == HID_KEYBOARD_L_SHIFT) { model->shift = !model->shift; - if(model->shift) - model->modifier_code |= KEY_MOD_LEFT_SHIFT; - else - model->modifier_code &= ~KEY_MOD_LEFT_SHIFT; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_SHIFT, model->shift); } else if(model->last_key_code == HID_KEYBOARD_L_ALT) { model->alt = !model->alt; - if(model->alt) - model->modifier_code |= KEY_MOD_LEFT_ALT; - else - model->modifier_code &= ~KEY_MOD_LEFT_ALT; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_ALT, model->alt); } else if(model->last_key_code == HID_KEYBOARD_L_CTRL) { model->ctrl = !model->ctrl; - if(model->ctrl) - model->modifier_code |= KEY_MOD_LEFT_CTRL; - else - model->modifier_code &= ~KEY_MOD_LEFT_CTRL; + hid_keyboard_modifier_set( + hid_keyboard->hid, KEY_MOD_LEFT_CTRL, model->ctrl); } else if(model->last_key_code == HID_KEYBOARD_L_GUI) { model->gui = !model->gui; - if(model->gui) - model->modifier_code |= KEY_MOD_LEFT_GUI; - else - model->modifier_code &= ~KEY_MOD_LEFT_GUI; + hid_keyboard_modifier_set(hid_keyboard->hid, KEY_MOD_LEFT_GUI, model->gui); + } else { + hid_hal_keyboard_press(hid_keyboard->hid, model->last_key_code); } - hid_hal_keyboard_press( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + } else if(event->type == InputTypeRelease) { // Release happens after short and long presses - hid_hal_keyboard_release( - hid_keyboard->hid, model->modifier_code | model->last_key_code); + hid_hal_keyboard_release(hid_keyboard->hid, model->last_key_code); model->ok_pressed = false; } } else if(event->key == InputKeyBack) { @@ -364,6 +372,16 @@ static bool hid_keyboard_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeLong && event->key == InputKeyBack) { hid_hal_keyboard_release_all(hid_keyboard->hid); + with_view_model( + hid_keyboard->view, + HidKeyboardModel * model, + { + model->shift = false; + model->alt = false; + model->ctrl = false; + model->gui = false; + }, + true); } else { hid_keyboard_process(hid_keyboard, event); consumed = true; From 962d809ad7b2dde665ef431be90d07b1ad1be4dc Mon Sep 17 00:00:00 2001 From: tomellens <156447023+tomellens@users.noreply.github.com> Date: Fri, 9 Feb 2024 09:39:35 +0000 Subject: [PATCH 22/58] Update tv.ir (#3399) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .../infrared/resources/infrared/assets/tv.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index d10e14df2de..8a646ae5192 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,3 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 +# +# Sony KD-55AG8 Smart TV +# +name: Power +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 15 00 00 00 +# +name: Mute +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 14 00 00 00 +# +name: Vol_up +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 12 00 00 00 +# +name: Vol_dn +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 13 00 00 00 +# +name: Ch_next +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 10 00 00 00 +# +name: Ch_prev +type: parsed +protocol: Sony SIRC +address: 01 00 00 00 +command: 11 00 00 00 From bcdb9cb13cbcea9d487e9f6f18d8f50c95ec3609 Mon Sep 17 00:00:00 2001 From: JuicyPigWalker <34217507+JuicyPigWalker@users.noreply.github.com> Date: Fri, 9 Feb 2024 11:12:46 +0100 Subject: [PATCH 23/58] Update tv.ir (#3421) * Update tv.ir: Added tv "TCL 50P715X1" recorded remote signals. Hope everything is okay, this is my first GitHub contribution. * Update tv.ir: Fixed my own mistakes when uploading the signals. * Infrared: revert 962d809ad7b2dde665ef431be90d07b1ad1be4dc Co-authored-by: Yoel <34217507+yoelci@users.noreply.github.com> Co-authored-by: Aleksandr Kutuzov --- .../infrared/resources/infrared/assets/tv.ir | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index 8a646ae5192..ff8101b9087 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1724,41 +1724,41 @@ type: parsed protocol: RC6 address: 00 00 00 00 command: 21 00 00 00 -# -# Sony KD-55AG8 Smart TV -# +# +# Model TCL 50P715X1 +# name: Power type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 15 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 54 00 00 00 # name: Mute type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 14 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: FC 00 00 00 # name: Vol_up type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 12 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: F4 00 00 00 # name: Vol_dn type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 13 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 74 00 00 00 # name: Ch_next type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 10 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: B4 00 00 00 # name: Ch_prev type: parsed -protocol: Sony SIRC -address: 01 00 00 00 -command: 11 00 00 00 +protocol: RCA +address: 0F 00 00 00 +command: 34 00 00 00 From 14dabf523ac382ddc7c17f65f79440993edc47c5 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 12 Feb 2024 02:04:12 +0000 Subject: [PATCH 24/58] New toolchain with gcc 12 (#3254) * changes for xPack 12.3 * support for gcc 13.2 * Update tools name * Add new linux toolchain * Fixed copro submodule * Fix gdb-py * Fixes for c++ apps * Fix gdb-py3, add udev rules * Fixed udev rules location * Add MacOS arm, fix fbt toolchain download * Fixed downloading error file * fbt: fixed linker warnings; removed gcc 10 from list of supported toolchains * ufbt: fixed supported toolchain versions * nfc: replaced local malloc with calloc * restored code with Warray-bounds to older state * Update fbtenv.cmd * Suppressing warnings * Bump to 25 * Bump to 26 * lint: reformatted macros for new clang-format * Bump to 27 * Fix m type word * Bump to 28 * furi: added FURI_DEPRECATED macro * scripts: toolchain download on Windows: fixing partially extracted cases Co-authored-by: DrunkBatya --- .../debug/unit_tests/furi/furi_memmgr_test.c | 1 + .../main/nfc/scenes/nfc_scene_set_type.c | 2 +- ...subghz_frequency_analyzer_log_item_array.h | 3 + applications/services/dolphin/dolphin.c | 2 +- applications/services/gui/canvas.c | 1 - applications/services/gui/canvas_i.h | 4 +- applications/services/rpc/rpc_storage.c | 3 + fbt_options.py | 3 +- furi/core/common_defines.h | 4 ++ furi/core/core_defines.h | 6 +- furi/core/dangerous_defines.h | 31 +++++----- furi/core/string.h | 20 ++++--- lib/digital_signal/digital_signal.h | 6 +- .../iso14443_3a/iso14443_3a_poller_i.h | 2 +- lib/nfc/protocols/mf_classic/crypto1.c | 2 +- lib/pulse_reader/pulse_reader.c | 4 +- lib/u8g2/u8x8.h | 4 +- scripts/debug/41-udev.rules | 10 ++++ scripts/fbt_tools/gdb.py | 2 +- scripts/toolchain/fbtenv.cmd | 4 +- scripts/toolchain/fbtenv.sh | 56 +++++-------------- .../toolchain/windows-toolchain-download.ps1 | 9 ++- scripts/ufbt/SConstruct | 4 +- site_scons/cc.scons | 2 + site_scons/extapps.scons | 1 - site_scons/firmwareopts.scons | 1 - targets/f18/api_symbols.csv | 14 +++-- targets/f7/api_symbols.csv | 14 +++-- targets/f7/ble_glue/gap.c | 2 +- targets/f7/furi_hal/furi_hal_flash.c | 2 +- targets/f7/furi_hal/furi_hal_memory.c | 4 +- targets/f7/furi_hal/furi_hal_os.c | 4 +- .../f7/platform_specific/cxx_virtual_stub.c | 6 ++ .../f7/platform_specific/cxx_virtual_stub.h | 13 +++++ 34 files changed, 141 insertions(+), 105 deletions(-) create mode 100644 scripts/debug/41-udev.rules create mode 100644 targets/f7/platform_specific/cxx_virtual_stub.c create mode 100644 targets/f7/platform_specific/cxx_virtual_stub.h diff --git a/applications/debug/unit_tests/furi/furi_memmgr_test.c b/applications/debug/unit_tests/furi/furi_memmgr_test.c index a28632cf4da..9012eed7822 100644 --- a/applications/debug/unit_tests/furi/furi_memmgr_test.c +++ b/applications/debug/unit_tests/furi/furi_memmgr_test.c @@ -2,6 +2,7 @@ #include #include #include +#include void test_furi_memmgr() { void* ptr; diff --git a/applications/main/nfc/scenes/nfc_scene_set_type.c b/applications/main/nfc/scenes/nfc_scene_set_type.c index b5102f8013a..fc5f90f0cd1 100644 --- a/applications/main/nfc/scenes/nfc_scene_set_type.c +++ b/applications/main/nfc/scenes/nfc_scene_set_type.c @@ -10,7 +10,7 @@ enum SubmenuIndex { static void nfc_scene_set_type_init_edit_data(Iso14443_3aData* data, size_t uid_len) { // Easiest way to create a zero'd buffer of given length - uint8_t* uid = malloc(uid_len); + uint8_t* uid = calloc(1, uid_len); iso14443_3a_set_uid(data, uid, uid_len); free(uid); } diff --git a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h index 2fa70284a42..6e6b553e007 100644 --- a/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h +++ b/applications/main/subghz/helpers/subghz_frequency_analyzer_log_item_array.h @@ -40,6 +40,8 @@ ARRAY_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItem_t) ARRAY_OPLIST(SubGhzFrequencyAnalyzerLogItemArray, M_OPL_SubGhzFrequencyAnalyzerLogItem_t()) ALGO_DEF(SubGhzFrequencyAnalyzerLogItemArray, SubGhzFrequencyAnalyzerLogItemArray_t) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" FUNC_OBJ_INS_DEF( SubGhzFrequencyAnalyzerLogItemArray_compare_by /* name of the instance */, SubGhzFrequencyAnalyzerLogItemArray_cmp_obj /* name of the interface */, @@ -76,3 +78,4 @@ FUNC_OBJ_INS_DEF( (order_by, SubGhzFrequencyAnalyzerLogOrderBy)) #define M_OPL_SubGhzFrequencyAnalyzerLogItemArray_compare_by_t() \ FUNC_OBJ_INS_OPLIST(SubGhzFrequencyAnalyzerLogItemArray_compare_by, M_DEFAULT_OPLIST) +#pragma GCC diagnostic pop diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index bef7c4a2853..520a01978ff 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -8,7 +8,7 @@ #define DOLPHIN_LOCK_EVENT_FLAG (0x1) #define TAG "Dolphin" -#define HOURS_IN_TICKS(x) ((x)*60 * 60 * 1000) +#define HOURS_IN_TICKS(x) ((x) * 60 * 60 * 1000) static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin); diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 897f0ae1336..9c945712304 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -1,5 +1,4 @@ #include "canvas_i.h" -#include "icon_i.h" #include "icon_animation_i.h" #include diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 73502b719b4..0982830c98f 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -122,7 +122,7 @@ void canvas_draw_u8g2_bitmap( uint8_t width, uint8_t height, const uint8_t* bitmap, - uint8_t rotation); + IconRotation rotation); /** Add canvas commit callback. * @@ -147,4 +147,4 @@ void canvas_remove_framebuffer_callback( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index a934d1c31a1..151d73d61e5 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -395,9 +395,12 @@ static void rpc_system_storage_read_process(const PB_Main* request, void* contex response->has_next = fs_operation_success && (size_left > 0); } else { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" response->content.storage_read_response.file.data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(0)); response->content.storage_read_response.file.data->size = 0; +#pragma GCC diagnostic pop response->content.storage_read_response.has_file = true; response->has_next = false; fs_operation_success = true; diff --git a/fbt_options.py b/fbt_options.py index 277790a4038..9e4d821a454 100644 --- a/fbt_options.py +++ b/fbt_options.py @@ -38,7 +38,8 @@ COPRO_STACK_BIN_DIR = posixpath.join(COPRO_CUBE_DIR, "firmware") # Supported toolchain versions -FBT_TOOLCHAIN_VERSIONS = (" 10.3.",) +# Also specify in scripts/ufbt/SConstruct +FBT_TOOLCHAIN_VERSIONS = (" 12.3.", " 13.2.") OPENOCD_OPTS = [ "-f", diff --git a/furi/core/common_defines.h b/furi/core/common_defines.h index 2fc12dedbab..82029786338 100644 --- a/furi/core/common_defines.h +++ b/furi/core/common_defines.h @@ -13,6 +13,10 @@ extern "C" { #define FURI_WARN_UNUSED __attribute__((warn_unused_result)) #endif +#ifndef FURI_DEPRECATED +#define FURI_DEPRECATED __attribute__((deprecated)) +#endif + #ifndef FURI_WEAK #define FURI_WEAK __attribute__((weak)) #endif diff --git a/furi/core/core_defines.h b/furi/core/core_defines.h index 4309c20c589..732a90cb526 100644 --- a/furi/core/core_defines.h +++ b/furi/core/core_defines.h @@ -84,9 +84,9 @@ extern "C" { #endif #ifndef REVERSE_BYTES_U32 -#define REVERSE_BYTES_U32(x) \ - ((((x)&0x000000FF) << 24) | (((x)&0x0000FF00) << 8) | (((x)&0x00FF0000) >> 8) | \ - (((x)&0xFF000000) >> 24)) +#define REVERSE_BYTES_U32(x) \ + ((((x) & 0x000000FF) << 24) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8) | \ + (((x) & 0xFF000000) >> 24)) #endif #ifndef FURI_BIT diff --git a/furi/core/dangerous_defines.h b/furi/core/dangerous_defines.h index b477710acba..683df2f6110 100644 --- a/furi/core/dangerous_defines.h +++ b/furi/core/dangerous_defines.h @@ -26,19 +26,20 @@ *tmp_x = y; \ *tmp_x; \ }) -#define FURI_CONST_ASSIGN(x, y) \ - _Generic((x), signed char \ - : FURI_CONST_ASSIGN_(signed char, x, y), unsigned char \ - : FURI_CONST_ASSIGN_(unsigned char, x, y), short \ - : FURI_CONST_ASSIGN_(short, x, y), unsigned short \ - : FURI_CONST_ASSIGN_(unsigned short, x, y), int \ - : FURI_CONST_ASSIGN_(int, x, y), unsigned \ - : FURI_CONST_ASSIGN_(unsigned, x, y), long \ - : FURI_CONST_ASSIGN_(long, x, y), unsigned long \ - : FURI_CONST_ASSIGN_(unsigned long, x, y), long long \ - : FURI_CONST_ASSIGN_(long long, x, y), unsigned long long \ - : FURI_CONST_ASSIGN_(unsigned long long, x, y), float \ - : FURI_CONST_ASSIGN_(float, x, y), double \ - : FURI_CONST_ASSIGN_(double, x, y), long double \ - : FURI_CONST_ASSIGN_(long double, x, y)) +#define FURI_CONST_ASSIGN(x, y) \ + _Generic( \ + (x), \ + signed char: FURI_CONST_ASSIGN_(signed char, x, y), \ + unsigned char: FURI_CONST_ASSIGN_(unsigned char, x, y), \ + short: FURI_CONST_ASSIGN_(short, x, y), \ + unsigned short: FURI_CONST_ASSIGN_(unsigned short, x, y), \ + int: FURI_CONST_ASSIGN_(int, x, y), \ + unsigned: FURI_CONST_ASSIGN_(unsigned, x, y), \ + long: FURI_CONST_ASSIGN_(long, x, y), \ + unsigned long: FURI_CONST_ASSIGN_(unsigned long, x, y), \ + long long: FURI_CONST_ASSIGN_(long long, x, y), \ + unsigned long long: FURI_CONST_ASSIGN_(unsigned long long, x, y), \ + float: FURI_CONST_ASSIGN_(float, x, y), \ + double: FURI_CONST_ASSIGN_(double, x, y), \ + long double: FURI_CONST_ASSIGN_(long double, x, y)) #endif diff --git a/furi/core/string.h b/furi/core/string.h index 7529deacd7c..0e3e6a88e6a 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -568,26 +568,30 @@ void furi_string_utf8_decode(char c, FuriStringUTF8State* state, FuriStringUnico /** * @brief Select for 1 argument */ -#define FURI_STRING_SELECT1(func1, func2, a) \ - _Generic((a), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a) +#define FURI_STRING_SELECT1(func1, func2, a) \ + _Generic((a), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \ + a) /** * @brief Select for 2 arguments */ -#define FURI_STRING_SELECT2(func1, func2, a, b) \ - _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b) +#define FURI_STRING_SELECT2(func1, func2, a, b) \ + _Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \ + a, b) /** * @brief Select for 3 arguments */ -#define FURI_STRING_SELECT3(func1, func2, a, b, c) \ - _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c) +#define FURI_STRING_SELECT3(func1, func2, a, b, c) \ + _Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \ + a, b, c) /** * @brief Select for 4 arguments */ -#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \ - _Generic((b), char* : func2, const char* : func2, FuriString* : func1, const FuriString* : func1)(a, b, c, d) +#define FURI_STRING_SELECT4(func1, func2, a, b, c, d) \ + _Generic((b), char*: func2, const char*: func2, FuriString*: func1, const FuriString*: func1)( \ + a, b, c, d) /** * @brief Allocate new FuriString and set it content to string (or C string). diff --git a/lib/digital_signal/digital_signal.h b/lib/digital_signal/digital_signal.h index f29facfd83d..1ab6370096c 100644 --- a/lib/digital_signal/digital_signal.h +++ b/lib/digital_signal/digital_signal.h @@ -26,9 +26,9 @@ extern "C" { // DigitalSignal uses 10 picosecond time units (1 tick = 10 ps). // Use the macros below to convert the time from other units. -#define DIGITAL_SIGNAL_MS(x) ((x)*100000000UL) -#define DIGITAL_SIGNAL_US(x) ((x)*100000UL) -#define DIGITAL_SIGNAL_NS(x) ((x)*100UL) +#define DIGITAL_SIGNAL_MS(x) ((x) * 100000000UL) +#define DIGITAL_SIGNAL_US(x) ((x) * 100000UL) +#define DIGITAL_SIGNAL_NS(x) ((x) * 100UL) #define DIGITAL_SIGNAL_PS(x) ((x) / 10UL) typedef struct DigitalSignal DigitalSignal; diff --git a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h index 764f1a6b593..664d54598e5 100644 --- a/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h +++ b/lib/nfc/protocols/iso14443_3a/iso14443_3a_poller_i.h @@ -11,7 +11,7 @@ extern "C" { #define ISO14443_3A_POLLER_MAX_BUFFER_SIZE (512U) #define ISO14443_3A_POLLER_SEL_CMD(cascade_lvl) (0x93 + 2 * (cascade_lvl)) -#define ISO14443_3A_POLLER_SEL_PAR(bytes, bits) (((bytes) << 4 & 0xf0U) | ((bits)&0x0fU)) +#define ISO14443_3A_POLLER_SEL_PAR(bytes, bits) (((bytes) << 4 & 0xf0U) | ((bits) & 0x0fU)) #define ISO14443_3A_POLLER_SDD_CL (0x88U) typedef enum { diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c index 02bc677ba69..0758b05fb2a 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.c +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -6,7 +6,7 @@ // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git #define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) diff --git a/lib/pulse_reader/pulse_reader.c b/lib/pulse_reader/pulse_reader.c index 1c3cb4a586f..0fcafe67cbe 100644 --- a/lib/pulse_reader/pulse_reader.c +++ b/lib/pulse_reader/pulse_reader.c @@ -212,8 +212,8 @@ uint32_t pulse_reader_receive(PulseReader* signal, int timeout_us) { /* probably larger values, so choose a wider data type */ if(signal->unit_divider > 1) { - delta_unit = - (uint32_t)((uint64_t)delta * (uint64_t)signal->unit_multiplier / signal->unit_divider); + delta_unit = (uint32_t)((uint64_t)delta * (uint64_t)signal->unit_multiplier / + signal->unit_divider); } else { delta_unit = delta * signal->unit_multiplier; } diff --git a/lib/u8g2/u8x8.h b/lib/u8g2/u8x8.h index 389fdfa9b0a..834284f5d85 100644 --- a/lib/u8g2/u8x8.h +++ b/lib/u8g2/u8x8.h @@ -688,8 +688,8 @@ uint8_t u8x8_byte_sed1520(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_ #define U8X8_MSG_GPIO(x) (64 + (x)) #ifdef U8X8_USE_PINS -#define u8x8_GetPinIndex(u8x8, msg) ((msg)&0x3f) -#define u8x8_GetPinValue(u8x8, msg) ((u8x8)->pins[(msg)&0x3f]) +#define u8x8_GetPinIndex(u8x8, msg) ((msg) & 0x3f) +#define u8x8_GetPinValue(u8x8, msg) ((u8x8)->pins[(msg) & 0x3f]) #endif #define U8X8_MSG_GPIO_D0 U8X8_MSG_GPIO(U8X8_PIN_D0) diff --git a/scripts/debug/41-udev.rules b/scripts/debug/41-udev.rules new file mode 100644 index 00000000000..6b4b37b9204 --- /dev/null +++ b/scripts/debug/41-udev.rules @@ -0,0 +1,10 @@ +#Flipper Zero serial port +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess", GROUP="dialout" +#Flipper Zero DFU +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", ATTRS{manufacturer}=="STMicroelectronics", TAG+="uaccess", GROUP="dialout" +#Flipper ESP32s2 BlackMagic +SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="40??", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess", GROUP="dialout" +#Flipper U2F +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5741", ATTRS{manufacturer}=="Flipper Devices Inc.", ENV{ID_SECURITY_TOKEN}="1" +#ST-Link-V3 +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="37??", ATTRS{manufacturer}=="STMicroelectronics", TAG+="uaccess", GROUP="dialout" diff --git a/scripts/fbt_tools/gdb.py b/scripts/fbt_tools/gdb.py index ea29e9c92e8..f1c90040d3e 100644 --- a/scripts/fbt_tools/gdb.py +++ b/scripts/fbt_tools/gdb.py @@ -1,7 +1,7 @@ def generate(env): env.SetDefault( GDB="gdb", - GDBPY="gdb-py", + GDBPY="gdb-py3", GDBCOM="$GDB $GDBOPTS $SOURCES", # no $TARGET GDBPYCOM="$GDBPY $GDBOPTS $GDBPYOPTS $SOURCES", # no $TARGET ) diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 51708b8c488..186cbf28a53 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=23" +set "FLIPPER_TOOLCHAIN_VERSION=28" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" @@ -46,7 +46,7 @@ set "HOME=%USERPROFILE%" set "PYTHONHOME=%FBT_TOOLCHAIN_ROOT%\python" set "PYTHONPATH=" set "PYTHONNOUSERSITE=1" -set "PATH=%FBT_TOOLCHAIN_ROOT%\python;%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\protoc\bin;%FBT_TOOLCHAIN_ROOT%\openocd\bin;%PATH%" +set "PATH=%FBT_TOOLCHAIN_ROOT%\bin;%FBT_TOOLCHAIN_ROOT%\python;%PATH%" set "PROMPT=(fbt) %PROMPT%" :already_set diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 990776b27a0..ea7c9019b8a 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"23"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"28"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; @@ -27,7 +27,7 @@ fbtenv_show_usage() fbtenv_curl() { - curl --progress-bar -SLo "$1" "$2"; + curl --progress-bar -SLo "$1" "$2" -w "%{http_code}" | grep -q 200; } fbtenv_wget() @@ -38,11 +38,7 @@ fbtenv_wget() fbtenv_restore_env() { TOOLCHAIN_ARCH_DIR_SED="$(echo "$TOOLCHAIN_ARCH_DIR" | sed 's/\//\\\//g')" - PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/python\/bin://g")"; PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/bin://g")"; - PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/protobuf\/bin://g")"; - PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openocd\/bin://g")"; - PATH="$(echo "$PATH" | sed "s/$TOOLCHAIN_ARCH_DIR_SED\/openssl\/bin://g")"; if [ -n "${PS1:-""}" ]; then PS1="$(echo "$PS1" | sed 's/\[fbt\]//g')"; elif [ -n "${PROMPT:-""}" ]; then @@ -57,7 +53,7 @@ fbtenv_restore_env() unset REQUESTS_CA_BUNDLE; fi - if [ "$SYS_TYPE" = "Linux" ]; then + if [ "$SYS_TYPE" = "linux" ]; then if [ -n "$SAVED_TERMINFO_DIRS" ]; then export TERMINFO_DIRS="$SAVED_TERMINFO_DIRS"; else @@ -135,38 +131,17 @@ fbtenv_check_env_vars() fbtenv_get_kernel_type() { - SYS_TYPE="$(uname -s)"; + SYS_TYPE="$(uname -s | tr '[:upper:]' '[:lower:]')"; ARCH_TYPE="$(uname -m)"; - if [ "$ARCH_TYPE" != "x86_64" ] && [ "$SYS_TYPE" != "Darwin" ]; then - echo "We only provide toolchain for x86_64 CPUs, sorry.."; - return 1; - fi - if [ "$SYS_TYPE" = "Darwin" ]; then - fbtenv_check_rosetta || return 1; - TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-darwin"; - TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-darwin-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; - elif [ "$SYS_TYPE" = "Linux" ]; then - TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/x86_64-linux"; - TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-linux-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; - elif echo "$SYS_TYPE" | grep -q "MINGW"; then + if echo "$SYS_TYPE" | grep -q "MINGW"; then echo "In MinGW shell, use \"[u]fbt.cmd\" instead of \"[u]fbt\""; return 1; - else + elif [ $SYS_TYPE != "linux" ] && [ $SYS_TYPE != "darwin" ]; then echo "Your system configuration is not supported. Sorry.. Please report us your configuration."; return 1; fi - return 0; -} - -fbtenv_check_rosetta() -{ - if [ "$ARCH_TYPE" = "arm64" ]; then - if ! pgrep -q oahd; then - echo "Flipper Zero Toolchain needs Rosetta2 to run under Apple Silicon"; - echo "Please install it by typing 'softwareupdate --install-rosetta --agree-to-license'"; - return 1; - fi - fi + TOOLCHAIN_ARCH_DIR="$FBT_TOOLCHAIN_PATH/toolchain/$ARCH_TYPE-$SYS_TYPE"; + TOOLCHAIN_URL="https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-12.3-$ARCH_TYPE-$SYS_TYPE-flipper-$FBT_TOOLCHAIN_VERSION.tar.gz"; return 0; } @@ -196,7 +171,10 @@ fbtenv_download_toolchain_tar() { echo "Downloading toolchain:"; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; - "$FBT_DOWNLOADER" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR.part" "$TOOLCHAIN_URL" || return 1; + "$FBT_DOWNLOADER" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR.part" "$TOOLCHAIN_URL" || { + echo "Failed to download $TOOLCHAIN_URL"; + return 1; + }; # restoring oroginal filename if file downloaded successfully mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR.part" "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" echo "done"; @@ -328,11 +306,7 @@ fbtenv_main() fbtenv_check_download_toolchain || return 1; fbtenv_set_shell_prompt; fbtenv_print_config; - PATH="$TOOLCHAIN_ARCH_DIR/python/bin:$PATH"; PATH="$TOOLCHAIN_ARCH_DIR/bin:$PATH"; - PATH="$TOOLCHAIN_ARCH_DIR/protobuf/bin:$PATH"; - PATH="$TOOLCHAIN_ARCH_DIR/openocd/bin:$PATH"; - PATH="$TOOLCHAIN_ARCH_DIR/openssl/bin:$PATH"; export PATH; export SAVED_SSL_CERT_FILE="${SSL_CERT_FILE:-""}"; @@ -341,15 +315,15 @@ fbtenv_main() export SAVED_PYTHONPATH="${PYTHONPATH:-""}"; export SAVED_PYTHONHOME="${PYTHONHOME:-""}"; - export SSL_CERT_FILE="$TOOLCHAIN_ARCH_DIR/python/lib/python3.11/site-packages/certifi/cacert.pem"; + export SSL_CERT_FILE="$TOOLCHAIN_ARCH_DIR/lib/python3.11/site-packages/certifi/cacert.pem"; export REQUESTS_CA_BUNDLE="$SSL_CERT_FILE"; export PYTHONNOUSERSITE=1; export PYTHONPATH=; export PYTHONHOME=; - if [ "$SYS_TYPE" = "Linux" ]; then + if [ "$SYS_TYPE" = "linux" ]; then export SAVED_TERMINFO_DIRS="${TERMINFO_DIRS:-""}"; - export TERMINFO_DIRS="$TOOLCHAIN_ARCH_DIR/ncurses/share/terminfo"; + export TERMINFO_DIRS="$TOOLCHAIN_ARCH_DIR/share/terminfo"; fi } diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index f30b157dab6..9a4d2e18f5f 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -6,8 +6,8 @@ $download_dir = (Get-Item "$PSScriptRoot\..\..").FullName $toolchain_version = $args[0] $toolchain_target_path = $args[1] -$toolchain_url = "https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-10.3-x86_64-windows-flipper-$toolchain_version.zip" -$toolchain_dist_folder = "gcc-arm-none-eabi-10.3-x86_64-windows-flipper" +$toolchain_url = "https://update.flipperzero.one/builds/toolchain/gcc-arm-none-eabi-12.3-x86_64-windows-flipper-$toolchain_version.zip" +$toolchain_dist_folder = "gcc-arm-none-eabi-12.3-x86_64-windows-flipper" $toolchain_zip = "$toolchain_dist_folder-$toolchain_version.zip" $toolchain_zip_temp_path = "$download_dir\$toolchain_zip" @@ -29,6 +29,11 @@ if (!(Test-Path -LiteralPath "$toolchain_target_path\..")) { New-Item "$toolchain_target_path\.." -ItemType Directory -Force } +if (Test-Path -LiteralPath "$toolchain_dist_temp_path") { + Write-Host "Cleaning up temp toolchain path.." + Remove-Item -LiteralPath "$toolchain_dist_temp_path" -Force -Recurse +} + Write-Host -NoNewline "Extracting Windows toolchain.." # This is faster than Expand-Archive Add-Type -Assembly "System.IO.Compression.Filesystem" diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index 8df1ae110ab..a26c2e404a5 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -74,7 +74,7 @@ env = core_env.Clone( "crosscc", { "toolchain_prefix": "arm-none-eabi-", - "versions": (" 10.3",), + "versions": (" 12.3.", " 13.2."), }, ), "fwbin", @@ -361,7 +361,7 @@ for template_file in project_template_dir.Dir(".vscode").glob("*"): dist_env.WhereIs("arm-none-eabi-gcc") ), "@UFBT_TOOLCHAIN_GDB_PY@": _path_as_posix( - dist_env.WhereIs("arm-none-eabi-gdb-py") + dist_env.WhereIs("arm-none-eabi-gdb-py3") ), "@UFBT_TOOLCHAIN_OPENOCD@": _path_as_posix(dist_env.WhereIs("openocd")), "@UFBT_APP_DIR@": _path_as_posix(original_app_dir.abspath), diff --git a/site_scons/cc.scons b/site_scons/cc.scons index 55ab72ba654..029f9d415e9 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -23,6 +23,7 @@ ENV.AppendUnique( "-Wall", "-Wextra", "-Werror", + "-Wno-error=deprecated-declarations", "-Wno-address-of-packed-member", "-Wredundant-decls", "-Wdouble-promotion", @@ -44,5 +45,6 @@ ENV.AppendUnique( "-mfpu=fpv4-sp-d16", "-mlittle-endian", "-mthumb", + "-Wl,--no-warn-rwx-segment", ], ) diff --git a/site_scons/extapps.scons b/site_scons/extapps.scons index 769b3eb1540..22d0be86773 100644 --- a/site_scons/extapps.scons +++ b/site_scons/extapps.scons @@ -41,7 +41,6 @@ appenv.AppendUnique( "-Xlinker", "-Map=${TARGET}.map", "-specs=nano.specs", - "-specs=nosys.specs", ], LIBS=[ "m", diff --git a/site_scons/firmwareopts.scons b/site_scons/firmwareopts.scons index e4cc8db5824..6af861324f5 100644 --- a/site_scons/firmwareopts.scons +++ b/site_scons/firmwareopts.scons @@ -35,7 +35,6 @@ else: ENV.AppendUnique( LINKFLAGS=[ "-specs=nano.specs", - "-specs=nosys.specs", "-Wl,--gc-sections", "-Wl,--undefined=uxTopUsedPriority", "-Wl,--wrap,_malloc_r", diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index ffb664a3e06..4d6f9e32bfa 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,+,55.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -176,6 +176,7 @@ Header,+,targets/f7/furi_hal/furi_hal_serial_control.h,, Header,+,targets/f7/furi_hal/furi_hal_serial_types.h,, Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, @@ -306,6 +307,7 @@ Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" +Function,+,__cxa_pure_virtual,void, Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double @@ -393,6 +395,7 @@ Function,-,_fsetpos_r,int,"_reent*, FILE*, const fpos_t*" Function,-,_ftell_r,long,"_reent*, FILE*" Function,-,_ftello_r,_off_t,"_reent*, FILE*" Function,-,_funopen_r,FILE*,"_reent*, const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,_fwalk_sglue,int,"_reent*, int (*)(_reent*, __FILE*), _glue*" Function,-,_fwrite_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" Function,-,_fwrite_unlocked_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" Function,-,_getc_r,int,"_reent*, FILE*" @@ -2062,7 +2065,6 @@ Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" -Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -2153,7 +2155,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" Function,-,serial_svc_is_started,_Bool, Function,-,serial_svc_notify_buffer_is_empty,void, Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" @@ -2563,8 +2564,13 @@ Variable,-,ITM_RxBuffer,volatile int32_t, Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, +Variable,-,__atexit,_atexit*, +Variable,-,__atexit0,_atexit, +Variable,-,__sf,__FILE[3], +Variable,-,__sglue,_glue, +Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], -Variable,+,_global_impure_ptr,_reent*, +Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index f852a69be62..837ad9ffc3b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,54.0,, +Version,+,55.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -243,6 +243,7 @@ Header,+,targets/f7/furi_hal/furi_hal_spi_types.h,, Header,+,targets/f7/furi_hal/furi_hal_subghz.h,, Header,+,targets/f7/furi_hal/furi_hal_target_hw.h,, Header,+,targets/f7/furi_hal/furi_hal_usb_cdc.h,, +Header,+,targets/f7/platform_specific/cxx_virtual_stub.h,, Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, @@ -375,6 +376,7 @@ Function,+,__aeabi_uldivmod,void*,"uint64_t, uint64_t" Function,-,__assert,void,"const char*, int, const char*" Function,+,__assert_func,void,"const char*, int, const char*, const char*" Function,+,__clear_cache,void,"void*, void*" +Function,+,__cxa_pure_virtual,void, Function,-,__eprintf,void,"const char*, const char*, unsigned int, const char*" Function,+,__errno,int*, Function,-,__fpclassifyd,int,double @@ -462,6 +464,7 @@ Function,-,_fsetpos_r,int,"_reent*, FILE*, const fpos_t*" Function,-,_ftell_r,long,"_reent*, FILE*" Function,-,_ftello_r,_off_t,"_reent*, FILE*" Function,-,_funopen_r,FILE*,"_reent*, const void*, int (*)(void*, char*, int), int (*)(void*, const char*, int), fpos_t (*)(void*, fpos_t, int), int (*)(void*)" +Function,-,_fwalk_sglue,int,"_reent*, int (*)(_reent*, __FILE*), _glue*" Function,-,_fwrite_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" Function,-,_fwrite_unlocked_r,size_t,"_reent*, const void*, size_t, size_t, FILE*" Function,-,_getc_r,int,"_reent*, FILE*" @@ -2646,7 +2649,6 @@ Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" -Function,-,pselect,int,"int, fd_set*, fd_set*, fd_set*, const timespec*, const sigset_t*" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* Function,-,pulse_reader_receive,uint32_t,"PulseReader*, int" @@ -2737,7 +2739,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,select,int,"int, fd_set*, fd_set*, fd_set*, timeval*" Function,-,serial_svc_is_started,_Bool, Function,-,serial_svc_notify_buffer_is_empty,void, Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" @@ -3347,8 +3348,13 @@ Variable,-,ITM_RxBuffer,volatile int32_t, Variable,-,MSIRangeTable,const uint32_t[16], Variable,-,SmpsPrescalerTable,const uint32_t[4][6], Variable,+,SystemCoreClock,uint32_t, +Variable,-,__atexit,_atexit*, +Variable,-,__atexit0,_atexit, +Variable,-,__sf,__FILE[3], +Variable,-,__sglue,_glue, +Variable,-,__stdio_exit_handler,void (*)(), Variable,+,_ctype_,const char[], -Variable,+,_global_impure_ptr,_reent*, +Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index f0533567eae..8e3ec58b737 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -11,7 +11,7 @@ #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 -#define GAP_INTERVAL_TO_MS(x) (uint16_t)((x)*1.25) +#define GAP_INTERVAL_TO_MS(x) (uint16_t)((x) * 1.25) typedef struct { uint16_t gap_svc_handle; diff --git a/targets/f7/furi_hal/furi_hal_flash.c b/targets/f7/furi_hal/furi_hal_flash.c index 7ac7a8bd12e..37eec744c41 100644 --- a/targets/f7/furi_hal/furi_hal_flash.c +++ b/targets/f7/furi_hal/furi_hal_flash.c @@ -43,7 +43,7 @@ */ #define FURI_HAL_FLASH_C2_LOCK_TIMEOUT_MS (3000U) /* 3 seconds */ -#define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__)&0x7U) == (0x00UL)) +#define IS_ADDR_ALIGNED_64BITS(__VALUE__) (((__VALUE__) & 0x7U) == (0x00UL)) #define IS_FLASH_PROGRAM_ADDRESS(__VALUE__) \ (((__VALUE__) >= FLASH_BASE) && ((__VALUE__) <= (FLASH_BASE + FLASH_SIZE - 8UL)) && \ (((__VALUE__) % 8UL) == 0UL)) diff --git a/targets/f7/furi_hal/furi_hal_memory.c b/targets/f7/furi_hal/furi_hal_memory.c index 3f8df1f4409..0d60a3af5b6 100644 --- a/targets/f7/furi_hal/furi_hal_memory.c +++ b/targets/f7/furi_hal/furi_hal_memory.c @@ -46,8 +46,8 @@ void furi_hal_memory_init() { } uint32_t sram2a_busy_size = (uint32_t)&__sram2a_free__ - (uint32_t)&__sram2a_start__; - uint32_t sram2a_unprotected_size = (sbrsa)*1024; - uint32_t sram2b_unprotected_size = (snbrsa)*1024; + uint32_t sram2a_unprotected_size = (sbrsa) * 1024; + uint32_t sram2b_unprotected_size = (snbrsa) * 1024; memory->region[SRAM_A].start = (uint8_t*)&__sram2a_free__; memory->region[SRAM_B].start = (uint8_t*)&__sram2b_start__; diff --git a/targets/f7/furi_hal/furi_hal_os.c b/targets/f7/furi_hal/furi_hal_os.c index 85f2d2e45de..efa760b470d 100644 --- a/targets/f7/furi_hal/furi_hal_os.c +++ b/targets/f7/furi_hal/furi_hal_os.c @@ -17,8 +17,8 @@ #define FURI_HAL_IDLE_TIMER_CLK_HZ 32768 #define FURI_HAL_OS_TICK_HZ configTICK_RATE_HZ -#define FURI_HAL_OS_IDLE_CNT_TO_TICKS(x) (((x)*FURI_HAL_OS_TICK_HZ) / FURI_HAL_IDLE_TIMER_CLK_HZ) -#define FURI_HAL_OS_TICKS_TO_IDLE_CNT(x) (((x)*FURI_HAL_IDLE_TIMER_CLK_HZ) / FURI_HAL_OS_TICK_HZ) +#define FURI_HAL_OS_IDLE_CNT_TO_TICKS(x) (((x) * FURI_HAL_OS_TICK_HZ) / FURI_HAL_IDLE_TIMER_CLK_HZ) +#define FURI_HAL_OS_TICKS_TO_IDLE_CNT(x) (((x) * FURI_HAL_IDLE_TIMER_CLK_HZ) / FURI_HAL_OS_TICK_HZ) #define FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH (FURI_HAL_OS_IDLE_CNT_TO_TICKS(FURI_HAL_IDLE_TIMER_MAX)) #define FURI_HAL_OS_MAX_SLEEP (FURI_HAL_IDLE_TIMER_TICK_PER_EPOCH - 1) diff --git a/targets/f7/platform_specific/cxx_virtual_stub.c b/targets/f7/platform_specific/cxx_virtual_stub.c new file mode 100644 index 00000000000..a81e5a5e0e2 --- /dev/null +++ b/targets/f7/platform_specific/cxx_virtual_stub.c @@ -0,0 +1,6 @@ +#include "cxx_virtual_stub.h" +#include + +void __cxa_pure_virtual() { + furi_crash("C++ pure virtual call"); +} diff --git a/targets/f7/platform_specific/cxx_virtual_stub.h b/targets/f7/platform_specific/cxx_virtual_stub.h new file mode 100644 index 00000000000..46211030e3f --- /dev/null +++ b/targets/f7/platform_specific/cxx_virtual_stub.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void __cxa_pure_virtual(); + +#ifdef __cplusplus +} +#endif From 6836a7b7c51c8c26a18c75a63af7e4af689adbe9 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 12 Feb 2024 05:16:34 +0300 Subject: [PATCH 25/58] [FL-3764] Expansion module service improvements (#3429) * Separate expansion control and worker threads * Add edge case checks * Reduce expansion control thread stack size, add comments * Fix crash when disabling expansion modules * Show a different RPC icon for expansion modules * Restore expansion interrupt on changing logging settings * Improve responsiveness in heavy games at the expense of dropped frames * Improve furi_hal_serial API * Fix a typo * Remove too optimistic furi_check, replace with condition * Fix premature RX interrupt during serial configuration * Disable expansion interrupt if the handle was acquired * Do not use a timer callback Co-authored-by: Aleksandr Kutuzov --- .../services/expansion/application.fam | 2 +- applications/services/expansion/expansion.c | 490 ++++++------------ .../services/expansion/expansion_worker.c | 396 ++++++++++++++ .../services/expansion/expansion_worker.h | 78 +++ applications/services/rpc/rpc.c | 6 + applications/services/rpc/rpc_gui.c | 38 +- .../StatusBar/Exp_module_connected_12x8.png | Bin 0 -> 3612 bytes targets/f18/api_symbols.csv | 3 +- targets/f7/api_symbols.csv | 3 +- targets/f7/furi_hal/furi_hal_serial.c | 22 +- targets/f7/furi_hal/furi_hal_serial.h | 10 + targets/f7/furi_hal/furi_hal_serial_control.c | 77 ++- 12 files changed, 751 insertions(+), 374 deletions(-) create mode 100644 applications/services/expansion/expansion_worker.c create mode 100644 applications/services/expansion/expansion_worker.h create mode 100644 assets/icons/StatusBar/Exp_module_connected_12x8.png diff --git a/applications/services/expansion/application.fam b/applications/services/expansion/application.fam index 1402e8413a9..dbdde0a5216 100644 --- a/applications/services/expansion/application.fam +++ b/applications/services/expansion/application.fam @@ -8,5 +8,5 @@ App( ], requires=["rpc_start"], provides=["expansion_settings"], - order=10, + order=150, ) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index e385734b79b..8f260f9154d 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -1,19 +1,17 @@ #include "expansion.h" -#include -#include #include #include +#include -#include - +#include "expansion_worker.h" #include "expansion_settings.h" -#include "expansion_protocol.h" #define TAG "ExpansionSrv" -#define EXPANSION_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) +#define EXPANSION_CONTROL_QUEUE_SIZE (8UL) +#define EXPANSION_CONTROL_STACK_SIZE (768UL) typedef enum { ExpansionStateDisabled, @@ -22,368 +20,196 @@ typedef enum { } ExpansionState; typedef enum { - ExpansionSessionStateHandShake, - ExpansionSessionStateConnected, - ExpansionSessionStateRpcActive, -} ExpansionSessionState; - -typedef enum { - ExpansionSessionExitReasonUnknown, - ExpansionSessionExitReasonUser, - ExpansionSessionExitReasonError, - ExpansionSessionExitReasonTimeout, -} ExpansionSessionExitReason; - -typedef enum { - ExpansionFlagStop = 1 << 0, - ExpansionFlagData = 1 << 1, - ExpansionFlagError = 1 << 2, -} ExpansionFlag; + ExpansionMessageTypeEnable, + ExpansionMessageTypeDisable, + ExpansionMessageTypeSetListenSerial, + ExpansionMessageTypeModuleConnected, + ExpansionMessageTypeModuleDisconnected, +} ExpansionMessageType; + +typedef union { + FuriHalSerialId serial_id; +} ExpansionMessageData; -#define EXPANSION_ALL_FLAGS (ExpansionFlagData | ExpansionFlagStop) +typedef struct { + ExpansionMessageType type; + ExpansionMessageData data; + FuriApiLock api_lock; +} ExpansionMessage; struct Expansion { - ExpansionState state; - ExpansionSessionState session_state; - ExpansionSessionExitReason exit_reason; - FuriStreamBuffer* rx_buf; - FuriSemaphore* tx_semaphore; - FuriMutex* state_mutex; - FuriThread* worker_thread; + FuriThread* thread; + FuriMessageQueue* queue; FuriHalSerialId serial_id; - FuriHalSerialHandle* serial_handle; - RpcSession* rpc_session; + ExpansionWorker* worker; + ExpansionState state; }; -static void expansion_detect_callback(void* context); +static const char* const expansion_uart_names[] = { + "USART", + "LPUART", +}; -// Called in UART IRQ context -static void expansion_serial_rx_callback( - FuriHalSerialHandle* handle, - FuriHalSerialRxEvent event, - void* context) { - furi_assert(handle); +// Called from the serial control thread +static void expansion_detect_callback(void* context) { furi_assert(context); - - Expansion* instance = context; - - if(event == FuriHalSerialRxEventData) { - const uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagData); - } -} - -static size_t expansion_receive_callback(uint8_t* data, size_t data_size, void* context) { - Expansion* instance = context; - - size_t received_size = 0; - - while(true) { - received_size += furi_stream_buffer_receive( - instance->rx_buf, data + received_size, data_size - received_size, 0); - - if(received_size == data_size) break; - - const uint32_t flags = furi_thread_flags_wait( - EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); - - if(flags & FuriFlagError) { - if(flags == (unsigned)FuriFlagErrorTimeout) { - // Exiting due to timeout - instance->exit_reason = ExpansionSessionExitReasonTimeout; - } else { - // Exiting due to an unspecified error - instance->exit_reason = ExpansionSessionExitReasonError; - } - break; - } else if(flags & ExpansionFlagStop) { - // Exiting due to explicit request - instance->exit_reason = ExpansionSessionExitReasonUser; - break; - } else if(flags & ExpansionFlagError) { - // Exiting due to RPC error - instance->exit_reason = ExpansionSessionExitReasonError; - break; - } else if(flags & ExpansionFlagData) { - // Go to buffer reading - continue; - } - } - - return received_size; -} - -static inline bool expansion_receive_frame(Expansion* instance, ExpansionFrame* frame) { - return expansion_protocol_decode(frame, expansion_receive_callback, instance) == - ExpansionProtocolStatusOk; -} - -static size_t expansion_send_callback(const uint8_t* data, size_t data_size, void* context) { Expansion* instance = context; - furi_hal_serial_tx(instance->serial_handle, data, data_size); - furi_hal_serial_tx_wait_complete(instance->serial_handle); - return data_size; -} - -static inline bool expansion_send_frame(Expansion* instance, const ExpansionFrame* frame) { - return expansion_protocol_encode(frame, expansion_send_callback, instance) == - ExpansionProtocolStatusOk; -} - -static bool expansion_send_heartbeat(Expansion* instance) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeHeartbeat, - .content.heartbeat = {}, - }; - - return expansion_send_frame(instance, &frame); -} -static bool expansion_send_status_response(Expansion* instance, ExpansionFrameError error) { - const ExpansionFrame frame = { - .header.type = ExpansionFrameTypeStatus, - .content.status.error = error, + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleConnected, + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; - return expansion_send_frame(instance, &frame); + // Not waiting for available queue space, discarding message if there is none + const FuriStatus status = furi_message_queue_put(instance->queue, &message, 0); + UNUSED(status); } -static bool - expansion_send_data_response(Expansion* instance, const uint8_t* data, size_t data_size) { - furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); +static void expansion_worker_callback(void* context) { + furi_assert(context); + Expansion* instance = context; - ExpansionFrame frame = { - .header.type = ExpansionFrameTypeData, - .content.data.size = data_size, + ExpansionMessage message = { + .type = ExpansionMessageTypeModuleDisconnected, + .api_lock = NULL, // Not locking the API here to avoid a deadlock }; - memcpy(frame.content.data.bytes, data, data_size); - return expansion_send_frame(instance, &frame); + const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); } -// Called in Rpc session thread context -static void expansion_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { - Expansion* instance = context; - - for(size_t sent_data_size = 0; sent_data_size < data_size;) { - if(furi_semaphore_acquire( - instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != - FuriStatusOk) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagError); - break; - } +static void + expansion_control_handler_enable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); - const size_t current_data_size = - MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); - if(!expansion_send_data_response(instance, data + sent_data_size, current_data_size)) - break; - sent_data_size += current_data_size; + if(instance->state != ExpansionStateDisabled) { + return; } -} - -static bool expansion_rpc_session_open(Expansion* instance) { - Rpc* rpc = furi_record_open(RECORD_RPC); - instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); - if(instance->rpc_session) { - instance->tx_semaphore = furi_semaphore_alloc(1, 1); - rpc_session_set_context(instance->rpc_session, instance); - rpc_session_set_send_bytes_callback(instance->rpc_session, expansion_rpc_send_callback); + ExpansionSettings settings = {0}; + if(!expansion_settings_load(&settings)) { + expansion_settings_save(&settings); } - return instance->rpc_session != NULL; -} + if(settings.uart_index < FuriHalSerialIdMax) { + instance->state = ExpansionStateEnabled; + instance->serial_id = settings.uart_index; + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); -static void expansion_rpc_session_close(Expansion* instance) { - if(instance->rpc_session) { - rpc_session_close(instance->rpc_session); - furi_semaphore_free(instance->tx_semaphore); + FURI_LOG_D(TAG, "Detection enabled on %s", expansion_uart_names[instance->serial_id]); } - - furi_record_close(RECORD_RPC); } -static bool - expansion_handle_session_state_handshake(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; - const uint32_t baud_rate = rx_frame->content.baud_rate.baud; +static void + expansion_control_handler_disable(Expansion* instance, const ExpansionMessageData* data) { + UNUSED(data); - FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); - - if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { - instance->session_state = ExpansionSessionStateConnected; - // Send response at previous baud rate - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - furi_hal_serial_set_br(instance->serial_handle, baud_rate); + if(instance->state == ExpansionStateDisabled) { + return; + } else if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); + } else { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } - } else { - if(!expansion_send_status_response(instance, ExpansionFrameErrorBaudRate)) break; - FURI_LOG_E(TAG, "Bad baud rate"); - } - success = true; - } while(false); + instance->state = ExpansionStateDisabled; - return success; + FURI_LOG_D(TAG, "Detection disabled"); } -static bool - expansion_handle_session_state_connected(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; - - do { - if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; - instance->session_state = ExpansionSessionStateRpcActive; - if(!expansion_rpc_session_open(instance)) break; - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; - - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; - - } else { - break; - } - success = true; - } while(false); +static void expansion_control_handler_set_listen_serial( + Expansion* instance, + const ExpansionMessageData* data) { + furi_check(data->serial_id < FuriHalSerialIdMax); - return success; -} + if(instance->state == ExpansionStateRunning) { + expansion_worker_stop(instance->worker); + expansion_worker_free(instance->worker); -static bool - expansion_handle_session_state_rpc_active(Expansion* instance, const ExpansionFrame* rx_frame) { - bool success = false; + } else if(instance->state == ExpansionStateEnabled) { + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); + } - do { - if(rx_frame->header.type == ExpansionFrameTypeData) { - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + instance->state = ExpansionStateEnabled; + instance->serial_id = data->serial_id; - const size_t size_consumed = rpc_session_feed( - instance->rpc_session, - rx_frame->content.data.bytes, - rx_frame->content.data.size, - EXPANSION_PROTOCOL_TIMEOUT_MS); - if(size_consumed != rx_frame->content.data.size) break; + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); - } else if(rx_frame->header.type == ExpansionFrameTypeControl) { - if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; - instance->session_state = ExpansionSessionStateConnected; - expansion_rpc_session_close(instance); - if(!expansion_send_status_response(instance, ExpansionFrameErrorNone)) break; + FURI_LOG_D(TAG, "Listen serial changed to %s", expansion_uart_names[instance->serial_id]); +} - } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { - if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; - furi_semaphore_release(instance->tx_semaphore); +static void expansion_control_handler_module_connected( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateEnabled) { + return; + } - } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { - if(!expansion_send_heartbeat(instance)) break; + furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - } else { - break; - } - success = true; - } while(false); + instance->state = ExpansionStateRunning; + instance->worker = expansion_worker_alloc(instance->serial_id); - return success; + expansion_worker_set_callback(instance->worker, expansion_worker_callback, instance); + expansion_worker_start(instance->worker); } -static inline void expansion_state_machine(Expansion* instance) { - typedef bool (*ExpansionSessionStateHandler)(Expansion*, const ExpansionFrame*); - - static const ExpansionSessionStateHandler expansion_handlers[] = { - [ExpansionSessionStateHandShake] = expansion_handle_session_state_handshake, - [ExpansionSessionStateConnected] = expansion_handle_session_state_connected, - [ExpansionSessionStateRpcActive] = expansion_handle_session_state_rpc_active, - }; - - ExpansionFrame rx_frame; - - while(true) { - if(!expansion_receive_frame(instance, &rx_frame)) break; - if(!expansion_handlers[instance->session_state](instance, &rx_frame)) break; +static void expansion_control_handler_module_disconnected( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateRunning) { + return; } -} -static void expansion_worker_pending_callback(void* context, uint32_t arg) { - furi_assert(context); - UNUSED(arg); + instance->state = ExpansionStateEnabled; + expansion_worker_free(instance->worker); + furi_hal_serial_control_set_expansion_callback( + instance->serial_id, expansion_detect_callback, instance); +} - Expansion* instance = context; - furi_thread_join(instance->worker_thread); +typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); - // Do not re-enable detection interrupt on user-requested exit - if(instance->exit_reason != ExpansionSessionExitReasonUser) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - instance->state = ExpansionStateEnabled; - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - furi_mutex_release(instance->state_mutex); - } -} +static const ExpansionControlHandler expansion_control_handlers[] = { + [ExpansionMessageTypeEnable] = expansion_control_handler_enable, + [ExpansionMessageTypeDisable] = expansion_control_handler_disable, + [ExpansionMessageTypeSetListenSerial] = expansion_control_handler_set_listen_serial, + [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, + [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, +}; -static int32_t expansion_worker(void* context) { +static int32_t expansion_control(void* context) { furi_assert(context); Expansion* instance = context; - furi_hal_power_insomnia_enter(); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - - instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); - furi_check(instance->serial_handle); - - FURI_LOG_D(TAG, "Service started"); - - instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_BUFFER_SIZE, 1); - instance->session_state = ExpansionSessionStateHandShake; - instance->exit_reason = ExpansionSessionExitReasonUnknown; - - furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + for(;;) { + ExpansionMessage message; - furi_hal_serial_async_rx_start( - instance->serial_handle, expansion_serial_rx_callback, instance, false); + FuriStatus status = furi_message_queue_get(instance->queue, &message, FuriWaitForever); + furi_check(status == FuriStatusOk); - if(expansion_send_heartbeat(instance)) { - expansion_state_machine(instance); - } + furi_check(message.type < COUNT_OF(expansion_control_handlers)); + expansion_control_handlers[message.type](instance, &message.data); - if(instance->session_state == ExpansionSessionStateRpcActive) { - expansion_rpc_session_close(instance); + if(message.api_lock != NULL) { + api_lock_unlock(message.api_lock); + } } - FURI_LOG_D(TAG, "Service stopped"); - - furi_hal_serial_control_release(instance->serial_handle); - furi_stream_buffer_free(instance->rx_buf); - - furi_hal_power_insomnia_exit(); - furi_timer_pending_callback(expansion_worker_pending_callback, instance, 0); - return 0; } -// Called from the serial control thread -static void expansion_detect_callback(void* context) { - furi_assert(context); - Expansion* instance = context; - - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - - if(instance->state == ExpansionStateEnabled) { - instance->state = ExpansionStateRunning; - furi_thread_start(instance->worker_thread); - } - - furi_mutex_release(instance->state_mutex); -} - static Expansion* expansion_alloc() { Expansion* instance = malloc(sizeof(Expansion)); - instance->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); - instance->worker_thread = furi_thread_alloc_ex(TAG, 768, expansion_worker, instance); + instance->queue = + furi_message_queue_alloc(EXPANSION_CONTROL_QUEUE_SIZE, sizeof(ExpansionMessage)); + instance->thread = + furi_thread_alloc_ex(TAG, EXPANSION_CONTROL_STACK_SIZE, expansion_control, instance); return instance; } @@ -393,6 +219,7 @@ void expansion_on_system_start(void* arg) { Expansion* instance = expansion_alloc(); furi_record_create(RECORD_EXPANSION, instance); + furi_thread_start(instance->thread); expansion_enable(instance); } @@ -400,42 +227,39 @@ void expansion_on_system_start(void* arg) { // Public API functions void expansion_enable(Expansion* instance) { - ExpansionSettings settings = {}; - if(!expansion_settings_load(&settings)) { - expansion_settings_save(&settings); - } else if(settings.uart_index < FuriHalSerialIdMax) { - expansion_set_listen_serial(instance, settings.uart_index); - } + furi_check(instance); + + ExpansionMessage message = { + .type = ExpansionMessageTypeEnable, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_disable(Expansion* instance) { - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - - if(instance->state == ExpansionStateRunning) { - furi_thread_flags_set(furi_thread_get_id(instance->worker_thread), ExpansionFlagStop); - furi_thread_join(instance->worker_thread); - } else if(instance->state == ExpansionStateEnabled) { - FURI_LOG_D(TAG, "Detection disabled"); - furi_hal_serial_control_set_expansion_callback(instance->serial_id, NULL, NULL); - } + furi_check(instance); - instance->state = ExpansionStateDisabled; + ExpansionMessage message = { + .type = ExpansionMessageTypeDisable, + .api_lock = api_lock_alloc_locked(), + }; - furi_mutex_release(instance->state_mutex); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { - expansion_disable(instance); + furi_check(instance); + furi_check(serial_id < FuriHalSerialIdMax); - furi_check(furi_mutex_acquire(instance->state_mutex, FuriWaitForever) == FuriStatusOk); - - instance->serial_id = serial_id; - instance->state = ExpansionStateEnabled; - - furi_hal_serial_control_set_expansion_callback( - instance->serial_id, expansion_detect_callback, instance); - - furi_mutex_release(instance->state_mutex); + ExpansionMessage message = { + .type = ExpansionMessageTypeSetListenSerial, + .data.serial_id = serial_id, + .api_lock = api_lock_alloc_locked(), + }; - FURI_LOG_D(TAG, "Detection enabled"); + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); } diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c new file mode 100644 index 00000000000..fd92063d258 --- /dev/null +++ b/applications/services/expansion/expansion_worker.c @@ -0,0 +1,396 @@ +#include "expansion_worker.h" + +#include +#include +#include + +#include +#include + +#include "expansion_protocol.h" + +#define TAG "ExpansionSrv" + +#define EXPANSION_WORKER_STACK_SZIE (768UL) +#define EXPANSION_WORKER_BUFFER_SIZE (sizeof(ExpansionFrame) + sizeof(ExpansionFrameChecksum)) + +typedef enum { + ExpansionWorkerStateHandShake, + ExpansionWorkerStateConnected, + ExpansionWorkerStateRpcActive, +} ExpansionWorkerState; + +typedef enum { + ExpansionWorkerExitReasonUnknown, + ExpansionWorkerExitReasonUser, + ExpansionWorkerExitReasonError, + ExpansionWorkerExitReasonTimeout, +} ExpansionWorkerExitReason; + +typedef enum { + ExpansionWorkerFlagStop = 1 << 0, + ExpansionWorkerFlagData = 1 << 1, + ExpansionWorkerFlagError = 1 << 2, +} ExpansionWorkerFlag; + +#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) + +struct ExpansionWorker { + FuriThread* thread; + FuriStreamBuffer* rx_buf; + FuriSemaphore* tx_semaphore; + + FuriHalSerialId serial_id; + FuriHalSerialHandle* serial_handle; + + RpcSession* rpc_session; + + ExpansionWorkerState state; + ExpansionWorkerExitReason exit_reason; + ExpansionWorkerCallback callback; + void* cb_context; +}; + +// Called in UART IRQ context +static void expansion_worker_serial_rx_callback( + FuriHalSerialHandle* handle, + FuriHalSerialRxEvent event, + void* context) { + furi_assert(handle); + furi_assert(context); + + ExpansionWorker* instance = context; + + if(event & (FuriHalSerialRxEventNoiseError | FuriHalSerialRxEventFrameError | + FuriHalSerialRxEventOverrunError)) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + } else if(event & FuriHalSerialRxEventData) { + while(furi_hal_serial_async_rx_available(handle)) { + const uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(instance->rx_buf, &data, sizeof(data), 0); + } + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagData); + } +} + +static size_t expansion_worker_receive_callback(uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + + size_t received_size = 0; + + while(true) { + received_size += furi_stream_buffer_receive( + instance->rx_buf, data + received_size, data_size - received_size, 0); + + if(received_size == data_size) break; + + const uint32_t flags = furi_thread_flags_wait( + EXPANSION_ALL_FLAGS, FuriFlagWaitAny, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)); + + if(flags & FuriFlagError) { + if(flags == (unsigned)FuriFlagErrorTimeout) { + // Exiting due to timeout + instance->exit_reason = ExpansionWorkerExitReasonTimeout; + } else { + // Exiting due to an unspecified error + instance->exit_reason = ExpansionWorkerExitReasonError; + } + break; + } else if(flags & ExpansionWorkerFlagStop) { + // Exiting due to explicit request + instance->exit_reason = ExpansionWorkerExitReasonUser; + break; + } else if(flags & ExpansionWorkerFlagError) { + // Exiting due to RPC error + instance->exit_reason = ExpansionWorkerExitReasonError; + break; + } else if(flags & ExpansionWorkerFlagData) { + // Go to buffer reading + continue; + } + } + + return received_size; +} + +static inline bool + expansion_worker_receive_frame(ExpansionWorker* instance, ExpansionFrame* frame) { + return expansion_protocol_decode(frame, expansion_worker_receive_callback, instance) == + ExpansionProtocolStatusOk; +} + +static size_t + expansion_worker_send_callback(const uint8_t* data, size_t data_size, void* context) { + ExpansionWorker* instance = context; + furi_hal_serial_tx(instance->serial_handle, data, data_size); + furi_hal_serial_tx_wait_complete(instance->serial_handle); + return data_size; +} + +static inline bool + expansion_worker_send_frame(ExpansionWorker* instance, const ExpansionFrame* frame) { + return expansion_protocol_encode(frame, expansion_worker_send_callback, instance) == + ExpansionProtocolStatusOk; +} + +static bool expansion_worker_send_heartbeat(ExpansionWorker* instance) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeHeartbeat, + .content.heartbeat = {}, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool + expansion_worker_send_status_response(ExpansionWorker* instance, ExpansionFrameError error) { + const ExpansionFrame frame = { + .header.type = ExpansionFrameTypeStatus, + .content.status.error = error, + }; + + return expansion_worker_send_frame(instance, &frame); +} + +static bool expansion_worker_send_data_response( + ExpansionWorker* instance, + const uint8_t* data, + size_t data_size) { + furi_assert(data_size <= EXPANSION_PROTOCOL_MAX_DATA_SIZE); + + ExpansionFrame frame = { + .header.type = ExpansionFrameTypeData, + .content.data.size = data_size, + }; + + memcpy(frame.content.data.bytes, data, data_size); + return expansion_worker_send_frame(instance, &frame); +} + +// Called in Rpc session thread context +static void expansion_worker_rpc_send_callback(void* context, uint8_t* data, size_t data_size) { + ExpansionWorker* instance = context; + + for(size_t sent_data_size = 0; sent_data_size < data_size;) { + if(furi_semaphore_acquire( + instance->tx_semaphore, furi_ms_to_ticks(EXPANSION_PROTOCOL_TIMEOUT_MS)) != + FuriStatusOk) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagError); + break; + } + + const size_t current_data_size = + MIN(data_size - sent_data_size, EXPANSION_PROTOCOL_MAX_DATA_SIZE); + if(!expansion_worker_send_data_response(instance, data + sent_data_size, current_data_size)) + break; + sent_data_size += current_data_size; + } +} + +static bool expansion_worker_rpc_session_open(ExpansionWorker* instance) { + Rpc* rpc = furi_record_open(RECORD_RPC); + instance->rpc_session = rpc_session_open(rpc, RpcOwnerUart); + + if(instance->rpc_session) { + instance->tx_semaphore = furi_semaphore_alloc(1, 1); + rpc_session_set_context(instance->rpc_session, instance); + rpc_session_set_send_bytes_callback( + instance->rpc_session, expansion_worker_rpc_send_callback); + } + + return instance->rpc_session != NULL; +} + +static void expansion_worker_rpc_session_close(ExpansionWorker* instance) { + if(instance->rpc_session) { + rpc_session_close(instance->rpc_session); + furi_semaphore_free(instance->tx_semaphore); + } + + furi_record_close(RECORD_RPC); +} + +static bool expansion_worker_handle_state_handshake( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type != ExpansionFrameTypeBaudRate) break; + const uint32_t baud_rate = rx_frame->content.baud_rate.baud; + + FURI_LOG_D(TAG, "Proposed baud rate: %lu", baud_rate); + + if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { + instance->state = ExpansionWorkerStateConnected; + // Send response at previous baud rate + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + furi_hal_serial_set_br(instance->serial_handle, baud_rate); + + } else { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorBaudRate)) + break; + FURI_LOG_E(TAG, "Bad baud rate"); + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_connected( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStartRpc) break; + instance->state = ExpansionWorkerStateRpcActive; + if(!expansion_worker_rpc_session_open(instance)) break; + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +static bool expansion_worker_handle_state_rpc_active( + ExpansionWorker* instance, + const ExpansionFrame* rx_frame) { + bool success = false; + + do { + if(rx_frame->header.type == ExpansionFrameTypeData) { + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + const size_t size_consumed = rpc_session_feed( + instance->rpc_session, + rx_frame->content.data.bytes, + rx_frame->content.data.size, + EXPANSION_PROTOCOL_TIMEOUT_MS); + if(size_consumed != rx_frame->content.data.size) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeControl) { + if(rx_frame->content.control.command != ExpansionFrameControlCommandStopRpc) break; + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; + + } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { + if(rx_frame->content.status.error != ExpansionFrameErrorNone) break; + furi_semaphore_release(instance->tx_semaphore); + + } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { + if(!expansion_worker_send_heartbeat(instance)) break; + + } else { + break; + } + success = true; + } while(false); + + return success; +} + +typedef bool (*ExpansionWorkerStateHandler)(ExpansionWorker*, const ExpansionFrame*); + +static const ExpansionWorkerStateHandler expansion_handlers[] = { + [ExpansionWorkerStateHandShake] = expansion_worker_handle_state_handshake, + [ExpansionWorkerStateConnected] = expansion_worker_handle_state_connected, + [ExpansionWorkerStateRpcActive] = expansion_worker_handle_state_rpc_active, +}; + +static inline void expansion_worker_state_machine(ExpansionWorker* instance) { + ExpansionFrame rx_frame; + + while(true) { + if(!expansion_worker_receive_frame(instance, &rx_frame)) break; + if(!expansion_handlers[instance->state](instance, &rx_frame)) break; + } +} + +static int32_t expansion_worker(void* context) { + furi_assert(context); + ExpansionWorker* instance = context; + + furi_hal_power_insomnia_enter(); + + instance->serial_handle = furi_hal_serial_control_acquire(instance->serial_id); + furi_check(instance->serial_handle); + + FURI_LOG_D(TAG, "Worker started"); + + instance->state = ExpansionWorkerStateHandShake; + instance->exit_reason = ExpansionWorkerExitReasonUnknown; + + furi_hal_serial_init(instance->serial_handle, EXPANSION_PROTOCOL_DEFAULT_BAUD_RATE); + + furi_hal_serial_async_rx_start( + instance->serial_handle, expansion_worker_serial_rx_callback, instance, true); + + if(expansion_worker_send_heartbeat(instance)) { + expansion_worker_state_machine(instance); + } + + if(instance->state == ExpansionWorkerStateRpcActive) { + expansion_worker_rpc_session_close(instance); + } + + FURI_LOG_D(TAG, "Worker stopped"); + + furi_hal_serial_control_release(instance->serial_handle); + furi_hal_power_insomnia_exit(); + + // Do not invoke worker callback on user-requested exit + if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { + instance->callback(instance->cb_context); + } + + return 0; +} + +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id) { + ExpansionWorker* instance = malloc(sizeof(ExpansionWorker)); + + instance->thread = furi_thread_alloc_ex( + TAG "Worker", EXPANSION_WORKER_STACK_SZIE, expansion_worker, instance); + instance->rx_buf = furi_stream_buffer_alloc(EXPANSION_WORKER_BUFFER_SIZE, 1); + instance->serial_id = serial_id; + + // Improves responsiveness in heavy games at the expense of dropped frames + furi_thread_set_priority(instance->thread, FuriThreadPriorityLow); + + return instance; +} + +void expansion_worker_free(ExpansionWorker* instance) { + furi_stream_buffer_free(instance->rx_buf); + furi_thread_join(instance->thread); + furi_thread_free(instance->thread); + free(instance); +} + +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context) { + instance->callback = callback; + instance->cb_context = context; +} + +void expansion_worker_start(ExpansionWorker* instance) { + furi_thread_start(instance->thread); +} + +void expansion_worker_stop(ExpansionWorker* instance) { + furi_thread_flags_set(furi_thread_get_id(instance->thread), ExpansionWorkerFlagStop); + furi_thread_join(instance->thread); +} diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h new file mode 100644 index 00000000000..761f79c1d91 --- /dev/null +++ b/applications/services/expansion/expansion_worker.h @@ -0,0 +1,78 @@ +/** + * @file expansion_worker.h + * @brief Expansion module handling thread wrapper. + * + * The worker is started each time an expansion module is detected + * and handles all of the communication protocols. Likewise, it is stopped + * upon module disconnection or communication error. + * + * @warning This file is a private implementation detail. Please do not attempt to use it in applications. + */ +#pragma once + +#include + +/** + * @brief Expansion worker opaque type declaration. + */ +typedef struct ExpansionWorker ExpansionWorker; + +/** + * @brief Worker callback type. + * + * @see expansion_worker_set_callback() + * + * @param[in,out] context pointer to a user-defined object. + */ +typedef void (*ExpansionWorkerCallback)(void* context); + +/** + * @brief Create an expansion worker instance. + * + * @param[in] serial_id numerical identifier of the serial to be used by the worker. + * @returns pointer to the created instance. + */ +ExpansionWorker* expansion_worker_alloc(FuriHalSerialId serial_id); + +/** + * @brief Delete an expansion worker instance. + * + * @param[in,out] instance pointer to the instance to be deleted. + */ +void expansion_worker_free(ExpansionWorker* instance); + +/** + * @brief Set the module disconnect callback. + * + * The callback will be triggered upon worker stop EXCEPT + * when it was stopped via an expansion_worker_stop() call. + * + * In other words, the callback will ONLY be triggered if the worker was + * stopped due to the user disconnecting/resetting/powering down the module, + * or due to some communication error. + * + * @param[in,out] instance pointer to the worker instance to be modified. + * @param[in] callback pointer to the callback function to be called under the above conditions. + * @param[in] context pointer to a user-defined object, will be passed as a parameter to the callback. + */ +void expansion_worker_set_callback( + ExpansionWorker* instance, + ExpansionWorkerCallback callback, + void* context); + +/** + * @brief Start the expansion module worker. + * + * @param[in,out] instance pointer to the worker instance to be started. + */ +void expansion_worker_start(ExpansionWorker* instance); + +/** + * @brief Stop the expansion module worker. + * + * If the worker was stopped via this call (and not because of module disconnect/ + * protocol error), the callback will not be triggered. + * + * @param[in,out] instance pointer to the worker instance to be stopped. + */ +void expansion_worker_stop(ExpansionWorker* instance); diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index 3179dbb5559..d33ea178c84 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -189,6 +189,12 @@ bool rpc_pb_stream_read(pb_istream_t* istream, pb_byte_t* buf, size_t count) { furi_assert(session); furi_assert(istream->bytes_left); + /* TODO FL-3768 this function may be called after + marking the worker for termination */ + if(session->terminate) { + return false; + } + uint32_t flags = 0; size_t bytes_received = 0; diff --git a/applications/services/rpc/rpc_gui.c b/applications/services/rpc/rpc_gui.c index 98860332d65..ee352659024 100644 --- a/applications/services/rpc/rpc_gui.c +++ b/applications/services/rpc/rpc_gui.c @@ -54,6 +54,7 @@ typedef enum { typedef struct { RpcSession* session; Gui* gui; + const Icon* icon; // Receive part ViewPort* virtual_display_view_port; @@ -380,10 +381,19 @@ static void rpc_system_gui_virtual_display_frame_process(const PB_Main* request, (void)session; } +static const Icon* rpc_system_gui_get_owner_icon(RpcOwner owner) { + switch(owner) { + case RpcOwnerUart: + return &I_Exp_module_connected_12x8; + default: + return &I_Rpc_active_7x8; + } +} + static void rpc_active_session_icon_draw_callback(Canvas* canvas, void* context) { - UNUSED(context); furi_assert(canvas); - canvas_draw_icon(canvas, 0, 0, &I_Rpc_active_7x8); + RpcGuiSystem* rpc_gui = context; + canvas_draw_icon(canvas, 0, 0, rpc_gui->icon); } void* rpc_system_gui_alloc(RpcSession* session) { @@ -394,16 +404,16 @@ void* rpc_system_gui_alloc(RpcSession* session) { rpc_gui->session = session; // Active session icon - rpc_gui->rpc_session_active_viewport = view_port_alloc(); - view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(&I_Rpc_active_7x8)); - view_port_draw_callback_set( - rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, session); - if(rpc_session_get_owner(rpc_gui->session) != RpcOwnerBle) { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, true); - } else { - view_port_enabled_set(rpc_gui->rpc_session_active_viewport, false); + const RpcOwner owner = rpc_session_get_owner(rpc_gui->session); + if(owner != RpcOwnerBle) { + rpc_gui->icon = rpc_system_gui_get_owner_icon(owner); + rpc_gui->rpc_session_active_viewport = view_port_alloc(); + view_port_set_width(rpc_gui->rpc_session_active_viewport, icon_get_width(rpc_gui->icon)); + view_port_draw_callback_set( + rpc_gui->rpc_session_active_viewport, rpc_active_session_icon_draw_callback, rpc_gui); + gui_add_view_port( + rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); } - gui_add_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport, GuiLayerStatusBarLeft); RpcHandler rpc_handler = { .message_handler = NULL, @@ -445,8 +455,10 @@ void rpc_system_gui_free(void* context) { rpc_gui->virtual_display_not_empty = false; } - gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); - view_port_free(rpc_gui->rpc_session_active_viewport); + if(rpc_gui->rpc_session_active_viewport) { + gui_remove_view_port(rpc_gui->gui, rpc_gui->rpc_session_active_viewport); + view_port_free(rpc_gui->rpc_session_active_viewport); + } if(rpc_gui->is_streaming) { rpc_gui->is_streaming = false; diff --git a/assets/icons/StatusBar/Exp_module_connected_12x8.png b/assets/icons/StatusBar/Exp_module_connected_12x8.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f09668267a55680aaa66355966bdaddcc173e1 GIT binary patch literal 3612 zcmaJ@cT`i^x4sNWZz4rRoDh&Eq)}pGLP+?eE*)E@!P9chufOLUf-f000tJ zmINo>D$aX_h532kZ|H?)01!2!e?zI($=vmd4qC}jl0dv1eDryWBC&j!mi3W>WE(veJ?bvayudN zPVAM=jfs#uzHjT+K6Yh|j61OVLu6Y^>%=s~BwSRD z1xO25QB(ppRr!GRPz>e}xIyR{z!&&hOc-de%W8n_kJ*;`jLSH|7j~Jifs=IDMmS6s zFbPh;6#ypYd|}z?>dru}0N~N(E`59rgk`U1gzy3GH&o>Ka+3hi9%?KB z@HqmM^&Y!o29RKYthY@I3V5#zz^q)oErI)wfrcJwu}VN#9Dq5-Mri>8A%I7_s%ju` zIRlV2UvkC#ai~IiR-0F;j7m(68p?0m$?>vX-VE%Ktmx@6OsJMTt^4`8;5OLPb82g*x0Gc9027?hzAYVS)xNMJ zf?PKH|utbb^Fxni5YzxTSxWzUh1i5A}qll5~qsdhvg?(U2x_8Yb5O zV5yeb_(oHhKg=g`qL;h9XuN5Wdk_fowz}>Q0H+9Q5MpPQahosz5OTw{@0%*EH15@D z5ZvFmYot-^yNBUroLWm0P8ugl3ER*1&?<|-X-0S7SJw7`zA;hOZ*cU9PG(6$np~bp zVp&q($ig{|nswnK0;cW4d&nw7v3#CaNU90K3K>!BDvg!`GVyAlj#ybouuYPICEV!> z5#(a2yo6={f{Hm|{xs=GJAjDz9$jYvzY$*PhU@zU0P_m7qF*T$`S z^*-PV$b_3Uh!_fRaUk*mQ%jO9$^N}%5oo`f{_Y_WqzM0I$0iMMa*@qL^{2c4Y}xCH zfX9$5byVNVB<_Jqhs5GrkZLkeJY%gmBR!)(BS=k0n7Z7_bQ&$n zBkndxJdP{Rt9H8Q7*n)I|6C@jJ5t`tCcpf4%IkeT3O;M}d!IN@caNqVuupsC+TS&2 z@hR;S{|Z-JB3dr8EtaOKn6+1EFHMoV_g=bff#U0U#Wo@g3ICEln<16KbRlV1YhRE{ z>J&|}PEk)$(uNX?OA1QHO2k~2UAl;g#iyJvR+zi!IZqXfyh$x}Dak(i#aYRjR(zx= zri50+BpTlhb=!;+bZfS4cEI0t%zbcrs;(6z=A~E_sH5U5Q5t{;hQ=g~sZLU}tZdbI*l z;+wCOS4-eN_bXgT&v|w#EXS5vI2Ket`)&V4=q7QK@vU&xLYyR6C9WnoEZ(yh^TNQN ziBX2O4gcYYKiO=8&nKsl(YMd%@v~YDV=% zGR;$g_V`6b^Fnfc^2MhYf7`?gNyH3FmMR}t4h?w7mPH>!k7f4UTrj@)VkLDBN+b13 z_rBMgs@~J3)k4p9&U$S@Q=ruZL@vk1$Js=5LDGyMSxT*=?e){voK^pm6T$tf*;!Rt zP4h*=_ZAEn=nF9;w_fVs(#mnu3wYJsj{o2|(`PJpD+T5|UKZ+rcL=9FnpK5*L6cm| z-`#(l&n;OaFDe*)HDceIGMd65#}H#~r(Yr)9=AX4a%g7eoQ@jDUsBzsYzJ@m0fk`) z`Dpxc;ilp4zig}rUr>3TV2mE7eytpwH@%u$V=8Bw7*QTE(^#(J)BgLla+S5&^q!<1 zx@6SR&`)n)36VdNSh1vieeg5`Zr=z#V4U!ckV+WKqQM3PZoM&&I#cFUL$~Pl`>2D5OK%PqKpY zdYV?E3gwF-GCJr~6Wdt(!#DoNR9d@8{_4S{J=}u{qCri{O*AmH&#*7{J^qCD0sAFO zZM)#>zbDlh1gX8y%h#EyxvWuqtwB`S8w)wIvF zkKX6I%BWG5TQB_LA43nM2zB^YT`Sw=rYl;FEBHh_uK7+|sFFr?^?u$R*NI9k%{kOP z|FWuc_!{!&;;rp@nI@Tu>(%+uHmuGwXHbcb;RO)KLpNdb*Do6YmmNh~J2Y3;m;y$nVzu`WBQz>b~m#J}~TF zx3+22@od0hi-ly{ug|u!7rD9;h7vBn-E5syWf6j==hj5V*A7Uaht6LODVq>_{p)#UnlkhZ zE^{fL)z8DJm zbHro#c6G?CSI_tvDdyr+Y0gBy*>?Qwk-8&2CzhUYmMVv5_P9QA&Af*`&G^jujM++l z?a)?xvo^D$=7ev}LU8?~5bw1)r5t3>=t_OiAM)F82Xau@(fZ|J=Gu$3)p7aJP`1Wq zPgm_AS7V{P&Y|w~ipjj`AbvDvz}S6j-JVxg@QaZbba8}p^Kvt!|@K6J}qHq|-U zp6C_q>xH6#jg3H59A7N>FHt1BqaZw! zO$DJKa9uAL3=Tr_EPX@7VH669MCyPLFoYfyW(Y+X=pyvc21qnqAN2PF=0#*vywOes z^S|Tqc37|vhr>ccp1b#7|GP36|9A&* zoT&fN`>(_S#2^+G>O>7-o@0CQn&+*$6N-h#v#DeblTBnY&;G3BQ6DCU8Q{ZYfe;1| zI7riu>_zw6Innw7v9m*4`2}#ueqK~70v608fzas`v>DRWzz}X=h(qELaJZS7DZvzm z(}U?9hN1NJ5a#+nu>_{qIR@2_^Ak(?4;J}fu{#sNVDTaosBHRqD#e`5WPpBTji&#L z7Cj_^V1P6?$01SX|E}dPEahLc{JFxjN_0J>T0PUFnOfPTq&-_#U zctg+TO}C7y{w3bc5Iky2G~=z?+uQkrw?=uEx)s5c7&zqVL7`B>01!V^$P#8k=%X}T jX9eAd?S;d@hXFo8KvWul)ngp;c?iJD%$`tz^N9FgMHepu literal 0 HcmV?d00001 diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 4d6f9e32bfa..5a5fa4cfc06 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.0,, +Version,+,55.1,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1276,6 +1276,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 837ad9ffc3b..eee91ce1cc9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.0,, +Version,+,55.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -1442,6 +1442,7 @@ Function,+,furi_hal_sd_presence_init,void, Function,+,furi_hal_sd_read_blocks,FuriStatus,"uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_sd_write_blocks,FuriStatus,"const uint32_t*, uint32_t, uint32_t" Function,+,furi_hal_serial_async_rx,uint8_t,FuriHalSerialHandle* +Function,+,furi_hal_serial_async_rx_available,_Bool,FuriHalSerialHandle* Function,+,furi_hal_serial_async_rx_start,void,"FuriHalSerialHandle*, FuriHalSerialAsyncRxCallback, void*, _Bool" Function,+,furi_hal_serial_async_rx_stop,void,FuriHalSerialHandle* Function,+,furi_hal_serial_control_acquire,FuriHalSerialHandle*,FuriHalSerialId diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 1296ee6202d..e0e2d8d52b9 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -730,6 +730,13 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle + furi_hal_serial[handle->id].rx_byte_callback = callback; + furi_hal_serial[handle->id].handle = handle; + furi_hal_serial[handle->id].rx_dma_callback = NULL; + furi_hal_serial[handle->id].context = context; + if(handle->id == FuriHalSerialIdUsart) { if(callback) { furi_hal_serial_usart_deinit_dma_rx(); @@ -753,10 +760,6 @@ static void furi_hal_serial_async_rx_configure( LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } - furi_hal_serial[handle->id].rx_byte_callback = callback; - furi_hal_serial[handle->id].handle = handle; - furi_hal_serial[handle->id].rx_dma_callback = NULL; - furi_hal_serial[handle->id].context = context; } void furi_hal_serial_async_rx_start( @@ -782,6 +785,17 @@ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle) { furi_hal_serial_async_rx_configure(handle, NULL, NULL); } +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle) { + furi_check(FURI_IS_IRQ_MODE()); + furi_assert(handle->id < FuriHalSerialIdMax); + + if(handle->id == FuriHalSerialIdUsart) { + return LL_USART_IsActiveFlag_RXNE_RXFNE(USART1); + } else { + return LL_LPUART_IsActiveFlag_RXNE_RXFNE(LPUART1); + } +} + uint8_t furi_hal_serial_async_rx(FuriHalSerialHandle* handle) { furi_check(FURI_IS_IRQ_MODE()); furi_assert(handle->id < FuriHalSerialIdMax); diff --git a/targets/f7/furi_hal/furi_hal_serial.h b/targets/f7/furi_hal/furi_hal_serial.h index 975406670fb..00010d83c9b 100644 --- a/targets/f7/furi_hal/furi_hal_serial.h +++ b/targets/f7/furi_hal/furi_hal_serial.h @@ -130,6 +130,16 @@ void furi_hal_serial_async_rx_start( */ void furi_hal_serial_async_rx_stop(FuriHalSerialHandle* handle); +/** Check if there is data available for reading + * + * @warning This function must be called only from the callback + * FuriHalSerialAsyncRxCallback + * + * @param handle Serial handle + * @return true if data is available for reading, false otherwise + */ +bool furi_hal_serial_async_rx_available(FuriHalSerialHandle* handle); + /** Get data Serial receive * * @warning This function must be called only from the callback diff --git a/targets/f7/furi_hal/furi_hal_serial_control.c b/targets/f7/furi_hal/furi_hal_serial_control.c index 37454823bcc..d2ab414c264 100644 --- a/targets/f7/furi_hal/furi_hal_serial_control.c +++ b/targets/f7/furi_hal/furi_hal_serial_control.c @@ -47,6 +47,7 @@ typedef struct { FuriHalSerialHandle* log_serial; // Expansion detection + FuriHalSerialHandle* expansion_serial; FuriHalSerialControlExpansionCallback expansion_cb; void* expansion_ctx; } FuriHalSerialControl; @@ -58,7 +59,36 @@ static void furi_hal_serial_control_log_callback(const uint8_t* data, size_t siz furi_hal_serial_tx(handle, data, size); } +static void furi_hal_serial_control_expansion_irq_callback(void* context) { + UNUSED(context); + + FuriHalSerialControlMessage message; + message.type = FuriHalSerialControlMessageTypeExpansionIrq; + message.api_lock = NULL; + furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); +} + +static void + furi_hal_serial_control_enable_expansion_irq(FuriHalSerialHandle* handle, bool enable) { + const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); + + if(enable) { + furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); + furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + } else { + furi_hal_gpio_remove_int_callback(gpio); + furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + } +} + static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) { + // Disable expansion module detection before reconfiguring UARTs + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, false); + } + if(furi_hal_serial_control->log_serial) { furi_log_remove_handler(furi_hal_serial_control->log_handler); furi_hal_serial_deinit(furi_hal_serial_control->log_serial); @@ -74,15 +104,12 @@ static void furi_hal_serial_control_log_set_handle(FuriHalSerialHandle* handle) furi_hal_serial_control->log_handler.context = furi_hal_serial_control->log_serial; furi_log_add_handler(furi_hal_serial_control->log_handler); } -} - -static void furi_hal_serial_control_expansion_irq_callback(void* context) { - UNUSED(context); - FuriHalSerialControlMessage message; - message.type = FuriHalSerialControlMessageTypeExpansionIrq; - message.api_lock = NULL; - furi_message_queue_put(furi_hal_serial_control->queue, &message, 0); + // Re-enable expansion module detection (if applicable) + if(furi_hal_serial_control->expansion_serial) { + furi_hal_serial_control_enable_expansion_irq( + furi_hal_serial_control->expansion_serial, true); + } } static bool furi_hal_serial_control_handler_stop(void* input, void* output) { @@ -93,16 +120,21 @@ static bool furi_hal_serial_control_handler_stop(void* input, void* output) { static bool furi_hal_serial_control_handler_acquire(void* input, void* output) { FuriHalSerialId serial_id = *(FuriHalSerialId*)input; - if(furi_hal_serial_control->handles[serial_id].in_use) { + FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[serial_id]; + + if(handle->in_use) { *(FuriHalSerialHandle**)output = NULL; } else { // Logging if(furi_hal_serial_control->log_config_serial_id == serial_id) { furi_hal_serial_control_log_set_handle(NULL); + // Expansion + } else if(furi_hal_serial_control->expansion_serial == handle) { + furi_hal_serial_control_enable_expansion_irq(handle, false); } // Return handle - furi_hal_serial_control->handles[serial_id].in_use = true; - *(FuriHalSerialHandle**)output = &furi_hal_serial_control->handles[serial_id]; + handle->in_use = true; + *(FuriHalSerialHandle**)output = handle; } return true; @@ -116,9 +148,12 @@ static bool furi_hal_serial_control_handler_release(void* input, void* output) { furi_hal_serial_deinit(handle); handle->in_use = false; - // Return back logging if(furi_hal_serial_control->log_config_serial_id == handle->id) { + // Return back logging furi_hal_serial_control_log_set_handle(handle); + } else if(furi_hal_serial_control->expansion_serial == handle) { + // Re-enable expansion + furi_hal_serial_control_enable_expansion_irq(handle, true); } return true; @@ -157,24 +192,24 @@ static bool furi_hal_serial_control_handler_expansion_set_callback(void* input, FuriHalSerialControlMessageExpCallback* message_input = input; FuriHalSerialHandle* handle = &furi_hal_serial_control->handles[message_input->id]; - const GpioPin* gpio = furi_hal_serial_get_gpio_pin(handle, FuriHalSerialDirectionRx); - if(message_input->callback) { - furi_check(furi_hal_serial_control->expansion_cb == NULL); + const bool enable_irq = message_input->callback != NULL; - furi_hal_serial_disable_direction(handle, FuriHalSerialDirectionRx); - furi_hal_gpio_add_int_callback(gpio, furi_hal_serial_control_expansion_irq_callback, NULL); - furi_hal_gpio_init(gpio, GpioModeInterruptFall, GpioPullUp, GpioSpeedLow); + if(enable_irq) { + furi_check(furi_hal_serial_control->expansion_serial == NULL); + furi_check(furi_hal_serial_control->expansion_cb == NULL); + furi_hal_serial_control->expansion_serial = handle; } else { + furi_check(furi_hal_serial_control->expansion_serial == handle); furi_check(furi_hal_serial_control->expansion_cb != NULL); - - furi_hal_gpio_remove_int_callback(gpio); - furi_hal_serial_enable_direction(handle, FuriHalSerialDirectionRx); + furi_hal_serial_control->expansion_serial = NULL; } furi_hal_serial_control->expansion_cb = message_input->callback; furi_hal_serial_control->expansion_ctx = message_input->context; + furi_hal_serial_control_enable_expansion_irq(handle, enable_irq); + return true; } From b0df85294417c706f1286133c9cdcf0005a23044 Mon Sep 17 00:00:00 2001 From: gornekich Date: Mon, 12 Feb 2024 02:23:44 +0000 Subject: [PATCH 26/58] NFC: fix retry scene navigation logic (#3439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc app: fix retry scene navigation logic * nfc app: fix navigation in exit confirm scene Co-authored-by: あく --- applications/main/nfc/scenes/nfc_scene_exit_confirm.c | 7 +++++++ applications/main/nfc/scenes/nfc_scene_retry_confirm.c | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c index 16593cc89da..7c4a3d19db2 100644 --- a/applications/main/nfc/scenes/nfc_scene_exit_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_exit_confirm.c @@ -31,6 +31,13 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSelectProtocol); + } else if( + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { + const uint32_t possible_scenes[] = {NfcSceneReadMenu, NfcSceneSavedMenu}; + consumed = scene_manager_search_and_switch_to_previous_scene_one_of( + nfc->scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } else { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneStart); diff --git a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c index b6ad8144db7..579373624a8 100644 --- a/applications/main/nfc/scenes/nfc_scene_retry_confirm.c +++ b/applications/main/nfc/scenes/nfc_scene_retry_confirm.c @@ -31,8 +31,10 @@ bool nfc_scene_retry_confirm_on_event(void* context, SceneManagerEvent event) { if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSlixUnlock)) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneSlixUnlock); - } else if(scene_manager_has_previous_scene( - nfc->scene_manager, NfcSceneMfClassicDictAttack)) { + } else if( + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicDictAttack) && + (scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadMenu) || + scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSavedMenu))) { consumed = scene_manager_search_and_switch_to_previous_scene( nfc->scene_manager, NfcSceneMfClassicDictAttack); } else if(scene_manager_has_previous_scene( From 6d09bebf14d8bef47300c05776551a42e047377a Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 12 Feb 2024 05:30:10 +0300 Subject: [PATCH 27/58] [FL-3769] Check universal remote files before loading (#3438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve code readability * Check universal remote files before loading Co-authored-by: あく --- applications/main/infrared/infrared_app_i.h | 2 +- .../main/infrared/infrared_brute_force.c | 25 +++-- applications/main/infrared/infrared_signal.c | 102 ++++++++++++------ applications/main/infrared/infrared_signal.h | 13 ++- 4 files changed, 98 insertions(+), 44 deletions(-) diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index c35d3fa410a..7a9202b28ed 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -121,7 +121,7 @@ struct InfraredApp { InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ FuriString* file_path; /**< Full path to the currently loaded file. */ - FuriString* button_name; /** Name of the button requested in RPC mode. */ + FuriString* button_name; /**< Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ char text_store[INFRARED_TEXT_STORE_NUM][INFRARED_TEXT_STORE_SIZE + 1]; InfraredAppState app_state; /**< Application state. */ diff --git a/applications/main/infrared/infrared_brute_force.c b/applications/main/infrared/infrared_brute_force.c index 373c7270f58..b941a4760fc 100644 --- a/applications/main/infrared/infrared_brute_force.c +++ b/applications/main/infrared/infrared_brute_force.c @@ -57,20 +57,31 @@ bool infrared_brute_force_calculate_messages(InfraredBruteForce* brute_force) { Storage* storage = furi_record_open(RECORD_STORAGE); FlipperFormat* ff = flipper_format_buffered_file_alloc(storage); + FuriString* signal_name = furi_string_alloc(); + InfraredSignal* signal = infrared_signal_alloc(); + + do { + if(!flipper_format_buffered_file_open_existing(ff, brute_force->db_filename)) break; + + bool signals_valid = false; + while(infrared_signal_read_name(ff, signal_name)) { + signals_valid = infrared_signal_read_body(signal, ff) && + infrared_signal_is_valid(signal); + if(!signals_valid) break; - success = flipper_format_buffered_file_open_existing(ff, brute_force->db_filename); - if(success) { - FuriString* signal_name; - signal_name = furi_string_alloc(); - while(flipper_format_read_string(ff, "name", signal_name)) { InfraredBruteForceRecord* record = InfraredBruteForceRecordDict_get(brute_force->records, signal_name); if(record) { //-V547 ++(record->count); } } - furi_string_free(signal_name); - } + + if(!signals_valid) break; + success = true; + } while(false); + + infrared_signal_free(signal); + furi_string_free(signal_name); flipper_format_free(ff); furi_record_close(RECORD_STORAGE); diff --git a/applications/main/infrared/infrared_signal.c b/applications/main/infrared/infrared_signal.c index 2b0d3479111..eec8c19cb9a 100644 --- a/applications/main/infrared/infrared_signal.c +++ b/applications/main/infrared/infrared_signal.c @@ -8,7 +8,23 @@ #define TAG "InfraredSignal" +// Common keys #define INFRARED_SIGNAL_NAME_KEY "name" +#define INFRARED_SIGNAL_TYPE_KEY "type" + +// Type key values +#define INFRARED_SIGNAL_TYPE_RAW "raw" +#define INFRARED_SIGNAL_TYPE_PARSED "parsed" + +// Raw signal keys +#define INFRARED_SIGNAL_DATA_KEY "data" +#define INFRARED_SIGNAL_FREQUENCY_KEY "frequency" +#define INFRARED_SIGNAL_DUTY_CYCLE_KEY "duty_cycle" + +// Parsed signal keys +#define INFRARED_SIGNAL_PROTOCOL_KEY "protocol" +#define INFRARED_SIGNAL_ADDRESS_KEY "address" +#define INFRARED_SIGNAL_COMMAND_KEY "command" struct InfraredSignal { bool is_raw; @@ -88,18 +104,23 @@ static bool infrared_signal_is_raw_valid(const InfraredRawSignal* raw) { static inline bool infrared_signal_save_message(const InfraredMessage* message, FlipperFormat* ff) { const char* protocol_name = infrared_get_protocol_name(message->protocol); - return flipper_format_write_string_cstr(ff, "type", "parsed") && - flipper_format_write_string_cstr(ff, "protocol", protocol_name) && - flipper_format_write_hex(ff, "address", (uint8_t*)&message->address, 4) && - flipper_format_write_hex(ff, "command", (uint8_t*)&message->command, 4); + return flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_PARSED) && + flipper_format_write_string_cstr(ff, INFRARED_SIGNAL_PROTOCOL_KEY, protocol_name) && + flipper_format_write_hex( + ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message->address, 4) && + flipper_format_write_hex( + ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message->command, 4); } static inline bool infrared_signal_save_raw(const InfraredRawSignal* raw, FlipperFormat* ff) { furi_assert(raw->timings_size <= MAX_TIMINGS_AMOUNT); - return flipper_format_write_string_cstr(ff, "type", "raw") && - flipper_format_write_uint32(ff, "frequency", &raw->frequency, 1) && - flipper_format_write_float(ff, "duty_cycle", &raw->duty_cycle, 1) && - flipper_format_write_uint32(ff, "data", raw->timings, raw->timings_size); + return flipper_format_write_string_cstr( + ff, INFRARED_SIGNAL_TYPE_KEY, INFRARED_SIGNAL_TYPE_RAW) && + flipper_format_write_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &raw->frequency, 1) && + flipper_format_write_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &raw->duty_cycle, 1) && + flipper_format_write_uint32( + ff, INFRARED_SIGNAL_DATA_KEY, raw->timings, raw->timings_size); } static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperFormat* ff) { @@ -108,61 +129,72 @@ static inline bool infrared_signal_read_message(InfraredSignal* signal, FlipperF bool success = false; do { - if(!flipper_format_read_string(ff, "protocol", buf)) break; + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_PROTOCOL_KEY, buf)) break; InfraredMessage message; message.protocol = infrared_get_protocol_by_name(furi_string_get_cstr(buf)); - success = flipper_format_read_hex(ff, "address", (uint8_t*)&message.address, 4) && - flipper_format_read_hex(ff, "command", (uint8_t*)&message.command, 4) && - infrared_signal_is_message_valid(&message); - - if(!success) break; + if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_ADDRESS_KEY, (uint8_t*)&message.address, 4)) + break; + if(!flipper_format_read_hex(ff, INFRARED_SIGNAL_COMMAND_KEY, (uint8_t*)&message.command, 4)) + break; + if(!infrared_signal_is_message_valid(&message)) break; infrared_signal_set_message(signal, &message); - } while(0); + success = true; + } while(false); furi_string_free(buf); return success; } static inline bool infrared_signal_read_raw(InfraredSignal* signal, FlipperFormat* ff) { - uint32_t timings_size, frequency; - float duty_cycle; + bool success = false; - bool success = flipper_format_read_uint32(ff, "frequency", &frequency, 1) && - flipper_format_read_float(ff, "duty_cycle", &duty_cycle, 1) && - flipper_format_get_value_count(ff, "data", &timings_size); + do { + uint32_t frequency; + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_FREQUENCY_KEY, &frequency, 1)) break; - if(!success || timings_size > MAX_TIMINGS_AMOUNT) { - return false; - } + float duty_cycle; + if(!flipper_format_read_float(ff, INFRARED_SIGNAL_DUTY_CYCLE_KEY, &duty_cycle, 1)) break; + + uint32_t timings_size; + if(!flipper_format_get_value_count(ff, INFRARED_SIGNAL_DATA_KEY, &timings_size)) break; - uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); - success = flipper_format_read_uint32(ff, "data", timings, timings_size); + if(timings_size > MAX_TIMINGS_AMOUNT) break; - if(success) { + uint32_t* timings = malloc(sizeof(uint32_t) * timings_size); + if(!flipper_format_read_uint32(ff, INFRARED_SIGNAL_DATA_KEY, timings, timings_size)) { + free(timings); + break; + } infrared_signal_set_raw_signal(signal, timings, timings_size, frequency, duty_cycle); - } + free(timings); + + success = true; + } while(false); - free(timings); return success; } -static bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { +bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff) { FuriString* tmp = furi_string_alloc(); bool success = false; do { - if(!flipper_format_read_string(ff, "type", tmp)) break; - if(furi_string_equal(tmp, "raw")) { - success = infrared_signal_read_raw(signal, ff); - } else if(furi_string_equal(tmp, "parsed")) { - success = infrared_signal_read_message(signal, ff); + if(!flipper_format_read_string(ff, INFRARED_SIGNAL_TYPE_KEY, tmp)) break; + + if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_RAW)) { + if(!infrared_signal_read_raw(signal, ff)) break; + } else if(furi_string_equal(tmp, INFRARED_SIGNAL_TYPE_PARSED)) { + if(!infrared_signal_read_message(signal, ff)) break; } else { - FURI_LOG_E(TAG, "Unknown signal type"); + FURI_LOG_E(TAG, "Unknown signal type: %s", furi_string_get_cstr(tmp)); + break; } + + success = true; } while(false); furi_string_free(tmp); diff --git a/applications/main/infrared/infrared_signal.h b/applications/main/infrared/infrared_signal.h index cfa4cfa94e2..69b677f2376 100644 --- a/applications/main/infrared/infrared_signal.h +++ b/applications/main/infrared/infrared_signal.h @@ -127,7 +127,7 @@ void infrared_signal_set_message(InfraredSignal* signal, const InfraredMessage* const InfraredMessage* infrared_signal_get_message(const InfraredSignal* signal); /** - * @brief Read a signal from a FlipperFormat file into an InfraredSignal instance. + * @brief Read a signal and its name from a FlipperFormat file into an InfraredSignal instance. * * The file must be allocated and open prior to this call. The seek position determines * which signal will be read (if there is more than one in the file). Calling this function @@ -151,6 +151,17 @@ bool infrared_signal_read(InfraredSignal* signal, FlipperFormat* ff, FuriString* */ bool infrared_signal_read_name(FlipperFormat* ff, FuriString* name); +/** + * @brief Read a signal from a FlipperFormat file. + * + * Same behaviour as infrared_signal_read(), but only the body is read. + * + * @param[in,out] ff pointer to the FlipperFormat file instance to read from. + * @param[out] body pointer to the InfraredSignal instance to hold the signal body. Must be properly allocated. + * @returns true if a signal body was successfully read, false otherwise (e.g. syntax error). + */ +bool infrared_signal_read_body(InfraredSignal* signal, FlipperFormat* ff); + /** * @brief Read a signal with a particular name from a FlipperFormat file into an InfraredSignal instance. * From 389affd904d141b8d58c54ccf7c8beb534877f52 Mon Sep 17 00:00:00 2001 From: Alessandro Rossi Date: Mon, 12 Feb 2024 03:35:16 +0100 Subject: [PATCH 28/58] Fixed MyKey check LockID (#3412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: gornekich Co-authored-by: あく --- applications/main/nfc/plugins/supported_cards/mykey.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/nfc/plugins/supported_cards/mykey.c b/applications/main/nfc/plugins/supported_cards/mykey.c index 1b7493307cd..e25e1e0a15e 100644 --- a/applications/main/nfc/plugins/supported_cards/mykey.c +++ b/applications/main/nfc/plugins/supported_cards/mykey.c @@ -13,7 +13,7 @@ static bool mykey_is_blank(const St25tbData* data) { } static bool mykey_has_lockid(const St25tbData* data) { - return (data->blocks[5] & 0xFF) == 0x7F; + return (data->blocks[5] >> 24) == 0x7F; } static bool check_invalid_low_nibble(uint8_t value) { From 0154018363175c3c5071e49984a4450944727703 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Mon, 12 Feb 2024 11:54:32 +0300 Subject: [PATCH 29/58] [FL-3579, FL-3601, FL-3714] JavaScript runner (#3286) * FBT: cdefines to env, libs order * API: strtod, modf, itoa, calloc * Apps: elk js * Apps: mjs * JS: scripts as assets * mjs: composite resolver * mjs: stack trace * ELK JS example removed * MJS thread, MJS lib modified to support script interruption * JS console UI * Module system, BadUSB bindings rework * JS notifications, simple dialog, BadUSB demo * Custom dialogs, dialog demo * MJS as system library, some dirty hacks to make it compile * Plugin-based js modules * js_uart(BadUART) module * js_uart: support for byte array arguments * Script icon and various fixes * File browser: multiple extensions filter, running js scripts from app loader * Running js scripts from archive browser * JS Runner as system app * Example scripts moved to /ext/apps/Scripts * JS bytecode listing generation * MJS builtin printf cleanup * JS examples cleanup * mbedtls version fix * Unused lib cleanup * Making PVS happy & TODOs cleanup * TODOs cleanup #2 * MJS: initial typed arrays support * JS: fix mem leak in uart destructor Co-authored-by: SG Co-authored-by: Aleksandr Kutuzov --- .pvsoptions | 2 +- .../main/archive/helpers/archive_browser.h | 1 + .../main/archive/helpers/archive_files.h | 1 + .../archive/scenes/archive_scene_browser.c | 2 + .../main/archive/views/archive_browser_view.c | 1 + .../gui/modules/file_browser_worker.c | 71 +- .../gui/modules/file_browser_worker.h | 4 +- .../services/loader/loader_applications.c | 39 +- applications/system/application.fam | 1 + applications/system/js_app/application.fam | 41 + .../examples/apps/Scripts/array_buf_test.js | 8 + .../js_app/examples/apps/Scripts/bad_uart.js | 20 + .../examples/apps/Scripts/badusb_demo.js | 33 + .../js_app/examples/apps/Scripts/console.js | 5 + .../js_app/examples/apps/Scripts/delay.js | 9 + .../js_app/examples/apps/Scripts/dialog.js | 19 + .../js_app/examples/apps/Scripts/load.js | 3 + .../js_app/examples/apps/Scripts/load_api.js | 3 + .../js_app/examples/apps/Scripts/notify.js | 9 + .../js_app/examples/apps/Scripts/uart_echo.js | 11 + applications/system/js_app/icon.png | Bin 0 -> 3635 bytes applications/system/js_app/js_app.c | 131 ++ applications/system/js_app/js_app_i.h | 10 + applications/system/js_app/js_modules.c | 126 ++ applications/system/js_app/js_modules.h | 25 + applications/system/js_app/js_thread.c | 319 ++++ applications/system/js_app/js_thread.h | 16 + applications/system/js_app/js_thread_i.h | 25 + .../system/js_app/modules/js_badusb.c | 410 +++++ .../system/js_app/modules/js_dialog.c | 154 ++ .../system/js_app/modules/js_flipper.c | 36 + .../system/js_app/modules/js_flipper.h | 4 + .../system/js_app/modules/js_notification.c | 109 ++ applications/system/js_app/modules/js_uart.c | 585 +++++++ .../js_app/plugin_api/app_api_interface.h | 9 + .../js_app/plugin_api/app_api_table.cpp | 27 + .../js_app/plugin_api/app_api_table_i.h | 13 + .../system/js_app/plugin_api/js_plugin_api.h | 18 + .../system/js_app/views/console_font.h | 43 + .../system/js_app/views/console_view.c | 164 ++ .../system/js_app/views/console_view.h | 13 + assets/icons/Archive/js_script_10px.png | Bin 0 -> 3635 bytes lib/SConscript | 1 + lib/mjs/SConscript | 36 + lib/mjs/common/cs_dbg.c | 157 ++ lib/mjs/common/cs_dbg.h | 148 ++ lib/mjs/common/cs_dirent.c | 108 ++ lib/mjs/common/cs_dirent.h | 51 + lib/mjs/common/cs_file.c | 65 + lib/mjs/common/cs_file.h | 48 + lib/mjs/common/cs_time.c | 91 + lib/mjs/common/cs_time.h | 42 + lib/mjs/common/cs_varint.c | 76 + lib/mjs/common/cs_varint.h | 59 + lib/mjs/common/frozen/frozen.c | 1528 +++++++++++++++++ lib/mjs/common/frozen/frozen.h | 359 ++++ lib/mjs/common/mbuf.c | 151 ++ lib/mjs/common/mbuf.h | 111 ++ lib/mjs/common/mg_mem.h | 45 + lib/mjs/common/mg_str.c | 175 ++ lib/mjs/common/mg_str.h | 113 ++ lib/mjs/common/platform.h | 89 + lib/mjs/common/platforms/platform_flipper.c | 65 + lib/mjs/common/platforms/platform_flipper.h | 48 + lib/mjs/common/str_util.c | 537 ++++++ lib/mjs/common/str_util.h | 195 +++ lib/mjs/ffi/ffi.c | 553 ++++++ lib/mjs/ffi/ffi.h | 53 + lib/mjs/mjs_array.c | 232 +++ lib/mjs/mjs_array.h | 26 + lib/mjs/mjs_array_buf.c | 385 +++++ lib/mjs/mjs_array_buf.h | 27 + lib/mjs/mjs_array_buf_public.h | 37 + lib/mjs/mjs_array_public.h | 47 + lib/mjs/mjs_bcode.c | 147 ++ lib/mjs/mjs_bcode.h | 105 ++ lib/mjs/mjs_builtin.c | 162 ++ lib/mjs/mjs_builtin.h | 22 + lib/mjs/mjs_core.c | 422 +++++ lib/mjs/mjs_core.h | 137 ++ lib/mjs/mjs_core_public.h | 286 +++ lib/mjs/mjs_dataview.c | 86 + lib/mjs/mjs_dataview.h | 32 + lib/mjs/mjs_exec.c | 1252 ++++++++++++++ lib/mjs/mjs_exec.h | 27 + lib/mjs/mjs_exec_public.h | 34 + lib/mjs/mjs_features.h | 33 + lib/mjs/mjs_ffi.c | 1223 +++++++++++++ lib/mjs/mjs_ffi.h | 135 ++ lib/mjs/mjs_ffi_public.h | 40 + lib/mjs/mjs_gc.c | 535 ++++++ lib/mjs/mjs_gc.h | 60 + lib/mjs/mjs_gc_public.h | 25 + lib/mjs/mjs_internal.h | 92 + lib/mjs/mjs_json.c | 523 ++++++ lib/mjs/mjs_json.h | 32 + lib/mjs/mjs_license.h | 17 + lib/mjs/mjs_mm.h | 44 + lib/mjs/mjs_object.c | 399 +++++ lib/mjs/mjs_object.h | 59 + lib/mjs/mjs_object_public.h | 126 ++ lib/mjs/mjs_parser.c | 1033 +++++++++++ lib/mjs/mjs_parser.h | 21 + lib/mjs/mjs_primitive.c | 160 ++ lib/mjs/mjs_primitive.h | 41 + lib/mjs/mjs_primitive_public.h | 123 ++ lib/mjs/mjs_string.c | 601 +++++++ lib/mjs/mjs_string.h | 47 + lib/mjs/mjs_string_public.h | 78 + lib/mjs/mjs_tok.c | 247 +++ lib/mjs/mjs_tok.h | 141 ++ lib/mjs/mjs_util.c | 567 ++++++ lib/mjs/mjs_util.h | 57 + lib/mjs/mjs_util_public.h | 67 + targets/f18/api_symbols.csv | 92 +- targets/f18/target.json | 1 + targets/f7/api_symbols.csv | 96 +- targets/f7/target.json | 1 + 118 files changed, 17568 insertions(+), 38 deletions(-) create mode 100644 applications/system/js_app/application.fam create mode 100644 applications/system/js_app/examples/apps/Scripts/array_buf_test.js create mode 100644 applications/system/js_app/examples/apps/Scripts/bad_uart.js create mode 100644 applications/system/js_app/examples/apps/Scripts/badusb_demo.js create mode 100644 applications/system/js_app/examples/apps/Scripts/console.js create mode 100644 applications/system/js_app/examples/apps/Scripts/delay.js create mode 100644 applications/system/js_app/examples/apps/Scripts/dialog.js create mode 100644 applications/system/js_app/examples/apps/Scripts/load.js create mode 100644 applications/system/js_app/examples/apps/Scripts/load_api.js create mode 100644 applications/system/js_app/examples/apps/Scripts/notify.js create mode 100644 applications/system/js_app/examples/apps/Scripts/uart_echo.js create mode 100644 applications/system/js_app/icon.png create mode 100644 applications/system/js_app/js_app.c create mode 100644 applications/system/js_app/js_app_i.h create mode 100644 applications/system/js_app/js_modules.c create mode 100644 applications/system/js_app/js_modules.h create mode 100644 applications/system/js_app/js_thread.c create mode 100644 applications/system/js_app/js_thread.h create mode 100644 applications/system/js_app/js_thread_i.h create mode 100644 applications/system/js_app/modules/js_badusb.c create mode 100644 applications/system/js_app/modules/js_dialog.c create mode 100644 applications/system/js_app/modules/js_flipper.c create mode 100644 applications/system/js_app/modules/js_flipper.h create mode 100644 applications/system/js_app/modules/js_notification.c create mode 100644 applications/system/js_app/modules/js_uart.c create mode 100644 applications/system/js_app/plugin_api/app_api_interface.h create mode 100644 applications/system/js_app/plugin_api/app_api_table.cpp create mode 100644 applications/system/js_app/plugin_api/app_api_table_i.h create mode 100644 applications/system/js_app/plugin_api/js_plugin_api.h create mode 100644 applications/system/js_app/views/console_font.h create mode 100644 applications/system/js_app/views/console_view.c create mode 100644 applications/system/js_app/views/console_view.h create mode 100644 assets/icons/Archive/js_script_10px.png create mode 100644 lib/mjs/SConscript create mode 100644 lib/mjs/common/cs_dbg.c create mode 100644 lib/mjs/common/cs_dbg.h create mode 100644 lib/mjs/common/cs_dirent.c create mode 100644 lib/mjs/common/cs_dirent.h create mode 100644 lib/mjs/common/cs_file.c create mode 100644 lib/mjs/common/cs_file.h create mode 100644 lib/mjs/common/cs_time.c create mode 100644 lib/mjs/common/cs_time.h create mode 100644 lib/mjs/common/cs_varint.c create mode 100644 lib/mjs/common/cs_varint.h create mode 100644 lib/mjs/common/frozen/frozen.c create mode 100644 lib/mjs/common/frozen/frozen.h create mode 100644 lib/mjs/common/mbuf.c create mode 100644 lib/mjs/common/mbuf.h create mode 100644 lib/mjs/common/mg_mem.h create mode 100644 lib/mjs/common/mg_str.c create mode 100644 lib/mjs/common/mg_str.h create mode 100644 lib/mjs/common/platform.h create mode 100644 lib/mjs/common/platforms/platform_flipper.c create mode 100644 lib/mjs/common/platforms/platform_flipper.h create mode 100644 lib/mjs/common/str_util.c create mode 100644 lib/mjs/common/str_util.h create mode 100644 lib/mjs/ffi/ffi.c create mode 100644 lib/mjs/ffi/ffi.h create mode 100644 lib/mjs/mjs_array.c create mode 100644 lib/mjs/mjs_array.h create mode 100644 lib/mjs/mjs_array_buf.c create mode 100644 lib/mjs/mjs_array_buf.h create mode 100644 lib/mjs/mjs_array_buf_public.h create mode 100644 lib/mjs/mjs_array_public.h create mode 100644 lib/mjs/mjs_bcode.c create mode 100644 lib/mjs/mjs_bcode.h create mode 100644 lib/mjs/mjs_builtin.c create mode 100644 lib/mjs/mjs_builtin.h create mode 100644 lib/mjs/mjs_core.c create mode 100644 lib/mjs/mjs_core.h create mode 100644 lib/mjs/mjs_core_public.h create mode 100644 lib/mjs/mjs_dataview.c create mode 100644 lib/mjs/mjs_dataview.h create mode 100644 lib/mjs/mjs_exec.c create mode 100644 lib/mjs/mjs_exec.h create mode 100644 lib/mjs/mjs_exec_public.h create mode 100644 lib/mjs/mjs_features.h create mode 100644 lib/mjs/mjs_ffi.c create mode 100644 lib/mjs/mjs_ffi.h create mode 100644 lib/mjs/mjs_ffi_public.h create mode 100644 lib/mjs/mjs_gc.c create mode 100644 lib/mjs/mjs_gc.h create mode 100644 lib/mjs/mjs_gc_public.h create mode 100644 lib/mjs/mjs_internal.h create mode 100644 lib/mjs/mjs_json.c create mode 100644 lib/mjs/mjs_json.h create mode 100644 lib/mjs/mjs_license.h create mode 100644 lib/mjs/mjs_mm.h create mode 100644 lib/mjs/mjs_object.c create mode 100644 lib/mjs/mjs_object.h create mode 100644 lib/mjs/mjs_object_public.h create mode 100644 lib/mjs/mjs_parser.c create mode 100644 lib/mjs/mjs_parser.h create mode 100644 lib/mjs/mjs_primitive.c create mode 100644 lib/mjs/mjs_primitive.h create mode 100644 lib/mjs/mjs_primitive_public.h create mode 100644 lib/mjs/mjs_string.c create mode 100644 lib/mjs/mjs_string.h create mode 100644 lib/mjs/mjs_string_public.h create mode 100644 lib/mjs/mjs_tok.c create mode 100644 lib/mjs/mjs_tok.h create mode 100644 lib/mjs/mjs_util.c create mode 100644 lib/mjs/mjs_util.h create mode 100644 lib/mjs/mjs_util_public.h diff --git a/.pvsoptions b/.pvsoptions index 3337d7eb5c2..8606eef1540 100644 --- a/.pvsoptions +++ b/.pvsoptions @@ -1 +1 @@ ---ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/* +--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/microtar -e lib/mlib -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e lib/mjs -e */arm-none-eabi/* diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 5e66a3dbbcb..30548284bdd 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -29,6 +29,7 @@ static const char* known_ext[] = { [ArchiveFileTypeBadUsb] = ".txt", [ArchiveFileTypeU2f] = "?", [ArchiveFileTypeApplication] = ".fap", + [ArchiveFileTypeJS] = ".js", [ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 1822befa358..0c7386c922e 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -16,6 +16,7 @@ typedef enum { ArchiveFileTypeU2f, ArchiveFileTypeUpdateManifest, ArchiveFileTypeApplication, + ArchiveFileTypeJS, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, ArchiveFileTypeLoading, diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 370830a0018..d156d171d5f 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -30,6 +30,8 @@ static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) { return "U2F"; case ArchiveFileTypeUpdateManifest: return "UpdaterApp"; + case ArchiveFileTypeJS: + return "JS Runner"; default: return NULL; } diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index ba147f74c8c..2b65486cd72 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -34,6 +34,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeUnknown] = &I_unknown_10px, [ArchiveFileTypeLoading] = &I_loading_10px, [ArchiveFileTypeApplication] = &I_unknown_10px, + [ArchiveFileTypeJS] = &I_js_script_10px, }; void archive_browser_set_callback( diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 857acbbaa41..2636c9555f1 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -32,12 +32,12 @@ typedef enum { (WorkerEvtStop | WorkerEvtLoad | WorkerEvtFolderEnter | WorkerEvtFolderExit | \ WorkerEvtFolderRefresh | WorkerEvtConfigChange) -ARRAY_DEF(idx_last_array, int32_t) +ARRAY_DEF(IdxLastArray, int32_t) +ARRAY_DEF(ExtFilterArray, FuriString*, FURI_STRING_OPLIST) struct BrowserWorker { FuriThread* thread; - FuriString* filter_extension; FuriString* path_start; FuriString* path_current; FuriString* path_next; @@ -46,7 +46,8 @@ struct BrowserWorker { uint32_t load_count; bool skip_assets; bool hide_dot_files; - idx_last_array_t idx_last; + IdxLastArray_t idx_last; + ExtFilterArray_t ext_filter; void* cb_ctx; BrowserWorkerFolderOpenCallback folder_cb; @@ -78,6 +79,31 @@ static bool browser_path_trim(FuriString* path) { } return is_root; } +static void browser_parse_ext_filter(ExtFilterArray_t ext_filter, const char* filter_str) { + if(!filter_str) { + return; + } + + size_t len = strlen(filter_str); + if(len == 0) { + return; + } + + size_t str_offset = 0; + FuriString* ext_temp = furi_string_alloc(); + while(1) { + size_t ext_len = strcspn(&filter_str[str_offset], "|"); + + furi_string_set_strn(ext_temp, &filter_str[str_offset], ext_len); + ExtFilterArray_push_back(ext_filter, ext_temp); + + str_offset += ext_len + 1; + if(str_offset >= len) { + break; + } + } + furi_string_free(ext_temp); +} static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, bool is_folder) { // Skip dot files if enabled @@ -96,12 +122,20 @@ static bool browser_filter_by_name(BrowserWorker* browser, FuriString* name, boo } } else { // Filter files by extension - if((furi_string_empty(browser->filter_extension)) || - (furi_string_cmp_str(browser->filter_extension, "*") == 0)) { + if(ExtFilterArray_size(browser->ext_filter) == 0) { return true; } - if(furi_string_end_with(name, browser->filter_extension)) { - return true; + + ExtFilterArray_it_t it; + for(ExtFilterArray_it(it, browser->ext_filter); !ExtFilterArray_end_p(it); + ExtFilterArray_next(it)) { + FuriString* ext = *ExtFilterArray_cref(it); + if((furi_string_empty(ext)) || (furi_string_cmp_str(ext, "*") == 0)) { + return true; + } + if(furi_string_end_with(name, ext)) { + return true; + } } } return false; @@ -288,7 +322,7 @@ static int32_t browser_worker(void* context) { if(browser_path_is_file(browser->path_next)) { path_extract_filename(browser->path_next, filename, false); } - idx_last_array_reset(browser->idx_last); + IdxLastArray_reset(browser->idx_last); furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtFolderEnter); } @@ -298,7 +332,7 @@ static int32_t browser_worker(void* context) { bool is_root = browser_folder_check_and_switch(path); // Push previous selected item index to history array - idx_last_array_push_back(browser->idx_last, browser->item_sel_idx); + IdxLastArray_push_back(browser->idx_last, browser->item_sel_idx); int32_t file_idx = 0; browser_folder_init(browser, path, filename, &items_cnt, &file_idx); @@ -321,9 +355,9 @@ static int32_t browser_worker(void* context) { int32_t file_idx = 0; browser_folder_init(browser, path, filename, &items_cnt, &file_idx); - if(idx_last_array_size(browser->idx_last) > 0) { + if(IdxLastArray_size(browser->idx_last) > 0) { // Pop previous selected item index from history array - idx_last_array_pop_back(&file_idx, browser->idx_last); + IdxLastArray_pop_back(&file_idx, browser->idx_last); } furi_string_set(browser->path_current, path); FURI_LOG_D( @@ -375,14 +409,15 @@ static int32_t browser_worker(void* context) { BrowserWorker* file_browser_worker_alloc( FuriString* path, const char* base_path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files) { BrowserWorker* browser = malloc(sizeof(BrowserWorker)); - idx_last_array_init(browser->idx_last); + IdxLastArray_init(browser->idx_last); + ExtFilterArray_init(browser->ext_filter); - browser->filter_extension = furi_string_alloc_set(filter_ext); + browser_parse_ext_filter(browser->ext_filter, ext_filter); browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; @@ -407,12 +442,12 @@ void file_browser_worker_free(BrowserWorker* browser) { furi_thread_join(browser->thread); furi_thread_free(browser->thread); - furi_string_free(browser->filter_extension); furi_string_free(browser->path_next); furi_string_free(browser->path_current); furi_string_free(browser->path_start); - idx_last_array_clear(browser->idx_last); + IdxLastArray_clear(browser->idx_last); + ExtFilterArray_clear(browser->ext_filter); free(browser); } @@ -453,12 +488,12 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files) { furi_assert(browser); furi_string_set(browser->path_next, path); - furi_string_set(browser->filter_extension, filter_ext); + browser_parse_ext_filter(browser->ext_filter, ext_filter); browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; furi_thread_flags_set(furi_thread_get_id(browser->thread), WorkerEvtConfigChange); diff --git a/applications/services/gui/modules/file_browser_worker.h b/applications/services/gui/modules/file_browser_worker.h index 3b4be6aa77b..c5eb61b60b3 100644 --- a/applications/services/gui/modules/file_browser_worker.h +++ b/applications/services/gui/modules/file_browser_worker.h @@ -24,7 +24,7 @@ typedef void (*BrowserWorkerLongLoadCallback)(void* context); BrowserWorker* file_browser_worker_alloc( FuriString* path, const char* base_path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files); @@ -51,7 +51,7 @@ void file_browser_worker_set_long_load_callback( void file_browser_worker_set_config( BrowserWorker* browser, FuriString* path, - const char* filter_ext, + const char* ext_filter, bool skip_assets, bool hide_dot_files); diff --git a/applications/services/loader/loader_applications.c b/applications/services/loader/loader_applications.c index 2e1de6134aa..235cf20ec01 100644 --- a/applications/services/loader/loader_applications.c +++ b/applications/services/loader/loader_applications.c @@ -7,9 +7,12 @@ #include #include #include +#include #define TAG "LoaderApplications" +#define JS_RUNNER_APP "JS Runner" + struct LoaderApplications { FuriThread* thread; void (*closed_cb)(void*); @@ -36,7 +39,7 @@ void loader_applications_free(LoaderApplications* loader_applications) { } typedef struct { - FuriString* fap_path; + FuriString* file_path; DialogsApp* dialogs; Storage* storage; Loader* loader; @@ -48,7 +51,7 @@ typedef struct { static LoaderApplicationsApp* loader_applications_app_alloc() { LoaderApplicationsApp* app = malloc(sizeof(LoaderApplicationsApp)); //-V799 - app->fap_path = furi_string_alloc_set(EXT_PATH("apps")); + app->file_path = furi_string_alloc_set(EXT_PATH("apps")); app->dialogs = furi_record_open(RECORD_DIALOGS); app->storage = furi_record_open(RECORD_STORAGE); app->loader = furi_record_open(RECORD_LOADER); @@ -73,7 +76,7 @@ static void loader_applications_app_free(LoaderApplicationsApp* app) { furi_record_close(RECORD_LOADER); furi_record_close(RECORD_DIALOGS); furi_record_close(RECORD_STORAGE); - furi_string_free(app->fap_path); + furi_string_free(app->file_path); free(app); } @@ -84,13 +87,19 @@ static bool loader_applications_item_callback( FuriString* item_name) { LoaderApplicationsApp* loader_applications_app = context; furi_assert(loader_applications_app); - return flipper_application_load_name_and_icon( - path, loader_applications_app->storage, icon_ptr, item_name); + if(furi_string_end_with(path, ".fap")) { + return flipper_application_load_name_and_icon( + path, loader_applications_app->storage, icon_ptr, item_name); + } else { + path_extract_filename(path, item_name, false); + memcpy(*icon_ptr, icon_get_data(&I_js_script_10px), FAP_MANIFEST_MAX_ICON_SIZE); + return true; + } } static bool loader_applications_select_app(LoaderApplicationsApp* loader_applications_app) { const DialogsFileBrowserOptions browser_options = { - .extension = ".fap", + .extension = ".fap|.js", .skip_assets = true, .icon = &I_unknown_10px, .hide_ext = true, @@ -101,8 +110,8 @@ static bool loader_applications_select_app(LoaderApplicationsApp* loader_applica return dialog_file_browser_show( loader_applications_app->dialogs, - loader_applications_app->fap_path, - loader_applications_app->fap_path, + loader_applications_app->file_path, + loader_applications_app->file_path, &browser_options); } @@ -117,9 +126,8 @@ static void loader_pubsub_callback(const void* message, void* context) { } } -static void loader_applications_start_app(LoaderApplicationsApp* app) { - const char* name = furi_string_get_cstr(app->fap_path); - +static void + loader_applications_start_app(LoaderApplicationsApp* app, const char* name, const char* args) { dolphin_deed(DolphinDeedPluginStart); // load app @@ -127,7 +135,7 @@ static void loader_applications_start_app(LoaderApplicationsApp* app) { FuriPubSubSubscription* subscription = furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id); - LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL); + LoaderStatus status = loader_start_with_gui_error(app->loader, name, args); if(status == LoaderStatusOk) { furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever); @@ -144,7 +152,12 @@ static int32_t loader_applications_thread(void* p) { view_holder_start(app->view_holder); while(loader_applications_select_app(app)) { - loader_applications_start_app(app); + if(!furi_string_end_with(app->file_path, ".js")) { + loader_applications_start_app(app, furi_string_get_cstr(app->file_path), NULL); + } else { + loader_applications_start_app( + app, JS_RUNNER_APP, furi_string_get_cstr(app->file_path)); + } } // stop loading animation diff --git a/applications/system/application.fam b/applications/system/application.fam index a59f840e456..2d4dfcbb82c 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -5,6 +5,7 @@ App( provides=[ "updater_app", "storage_move_to_sd", + "js_app", # "archive", ], ) diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam new file mode 100644 index 00000000000..29b4ebb5977 --- /dev/null +++ b/applications/system/js_app/application.fam @@ -0,0 +1,41 @@ +App( + appid="js_app", + name="JS Runner", + apptype=FlipperAppType.SYSTEM, + entry_point="js_app", + stack_size=2 * 1024, + resources="examples", + order=0, +) + +App( + appid="js_dialog", + apptype=FlipperAppType.PLUGIN, + entry_point="js_dialog_ep", + requires=["js_app"], + sources=["modules/js_dialog.c"], +) + +App( + appid="js_notification", + apptype=FlipperAppType.PLUGIN, + entry_point="js_notification_ep", + requires=["js_app"], + sources=["modules/js_notification.c"], +) + +App( + appid="js_badusb", + apptype=FlipperAppType.PLUGIN, + entry_point="js_badusb_ep", + requires=["js_app"], + sources=["modules/js_badusb.c"], +) + +App( + appid="js_uart", + apptype=FlipperAppType.PLUGIN, + entry_point="js_uart_ep", + requires=["js_app"], + sources=["modules/js_uart.c"], +) diff --git a/applications/system/js_app/examples/apps/Scripts/array_buf_test.js b/applications/system/js_app/examples/apps/Scripts/array_buf_test.js new file mode 100644 index 00000000000..ddb572ee783 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/array_buf_test.js @@ -0,0 +1,8 @@ +let arr_1 = Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +print("len =", arr_1.buffer.byteLength); + +let arr_2 = Uint8Array(arr_1.buffer.slice(2, 6)); +print("slice len =", arr_2.buffer.byteLength); +for (let i = 0; i < arr_2.buffer.byteLength; i++) { + print(arr_2[i]); +} diff --git a/applications/system/js_app/examples/apps/Scripts/bad_uart.js b/applications/system/js_app/examples/apps/Scripts/bad_uart.js new file mode 100644 index 00000000000..8166c61f551 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/bad_uart.js @@ -0,0 +1,20 @@ +let uart = require("uart"); +uart.setup(115200); + +// uart.write("\n"); +uart.write([0x0a]); +let console_resp = uart.expect("# ", 1000); +if (console_resp === undefined) { + print("No CLI response"); +} else { + uart.write("uci\n"); + let uci_state = uart.expect([": not found", "Usage: "]); + if (uci_state === 1) { + uart.expect("# "); + uart.write("uci show wireless\n"); + uart.expect(".key="); + print("key:", uart.readln()); + } else { + print("uci cmd not found"); + } +} diff --git a/applications/system/js_app/examples/apps/Scripts/badusb_demo.js b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js new file mode 100644 index 00000000000..21090f6034b --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/badusb_demo.js @@ -0,0 +1,33 @@ +let badusb = require("badusb"); +let notify = require("notification"); +let flipper = require("flipper"); +let dialog = require("dialog"); + +badusb.setup({ vid: 0xAAAA, pid: 0xBBBB, mfr_name: "Flipper", prod_name: "Zero" }); +dialog.message("BadUSB demo", "Press OK to start"); + +if (badusb.isConnected()) { + notify.blink("green", "short"); + print("USB is connected"); + + badusb.println("Hello, world!"); + + badusb.press("CTRL", "a"); + badusb.press("CTRL", "c"); + badusb.press("DOWN"); + delay(1000); + badusb.press("CTRL", "v"); + delay(1000); + badusb.press("CTRL", "v"); + + badusb.println("1234", 200); + + badusb.println("Flipper Model: " + flipper.getModel()); + badusb.println("Flipper Name: " + flipper.getName()); + badusb.println("Battery level: " + to_string(flipper.getBatteryCharge()) + "%"); + + notify.success(); +} else { + print("USB not connected"); + notify.error(); +} diff --git a/applications/system/js_app/examples/apps/Scripts/console.js b/applications/system/js_app/examples/apps/Scripts/console.js new file mode 100644 index 00000000000..06d394c533a --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/console.js @@ -0,0 +1,5 @@ +print("print", 1); +console.log("log", 2); +console.warn("warn", 3); +console.error("error", 4); +console.debug("debug", 5); diff --git a/applications/system/js_app/examples/apps/Scripts/delay.js b/applications/system/js_app/examples/apps/Scripts/delay.js new file mode 100644 index 00000000000..9f64abee800 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/delay.js @@ -0,0 +1,9 @@ +print("start"); +delay(1000) +print("1"); +delay(1000) +print("2"); +delay(1000) +print("3"); +delay(1000) +print("end"); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/dialog.js b/applications/system/js_app/examples/apps/Scripts/dialog.js new file mode 100644 index 00000000000..9fc44f8b9e2 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/dialog.js @@ -0,0 +1,19 @@ +let dialog = require("dialog"); + +let result1 = dialog.message("Dialog demo", "Press OK to start"); +print(result1); + +let dialog_params = ({ + header: "Test_header", + text: "Test_text", + button_left: "Left", + button_right: "Right", + button_center: "OK" +}); + +let result2 = dialog.custom(dialog_params); +if (result2 === "") { + print("Back is pressed"); +} else { + print(result2, "is pressed"); +} diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/load.js new file mode 100644 index 00000000000..59d99b94cb7 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/load.js @@ -0,0 +1,3 @@ +let math = load("/ext/apps/Scripts/api.js"); +let result = math.add(5, 10); +print(result); \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/load_api.js b/applications/system/js_app/examples/apps/Scripts/load_api.js new file mode 100644 index 00000000000..ad3b26e1563 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/load_api.js @@ -0,0 +1,3 @@ +({ + add: function (a, b) { return a + b; }, +}) \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/notify.js b/applications/system/js_app/examples/apps/Scripts/notify.js new file mode 100644 index 00000000000..20f60c732e9 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/notify.js @@ -0,0 +1,9 @@ +let notify = require("notification"); +notify.error(); +delay(1000); +notify.success(); +delay(1000); +for (let i = 0; i < 10; i++) { + notify.blink("red", "short"); + delay(500); +} \ No newline at end of file diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js new file mode 100644 index 00000000000..2f3957fd19e --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -0,0 +1,11 @@ +let uart = require("uart"); +uart.setup(115200); + +while (1) { + let rx_data = uart.readBytes(1, 0); + if (rx_data !== undefined) { + uart.write(rx_data); + let data_view = Uint8Array(rx_data); + print("0x" + to_hex_string(data_view[0])); + } +} \ No newline at end of file diff --git a/applications/system/js_app/icon.png b/applications/system/js_app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..77ac76337ea8a76928e45b79335fae7a7602adac GIT binary patch literal 3635 zcmaJ@c|4Te+rMpvB3VN+jd&`{EcVIRmr-MBY@-rmW-!dw%wR^5l2Wo|%bHM9Lz^Pm zDnhm>63UV#p&=nzddKs7dVcR8Z}0ubIoCP&_xruBbDisaKG#KOM>`2|C2;@%BEIG^o4aN2?EQ4| z833%t6>XC8%$K?X;Lb@&n>R$qh`Y8WILcjki4a z7ij>1Ss@NPf_2YqRp2fS5CrJmI(wuWhkr#?I%{|Upd?@jEIqMBAQ1yJ>9{t@0!=%B zdoB+N;y?-jkU6I;!N6?^VEVhgsR+^D*2`FtS<%2#LogZZr1*A~P(tS^lXQ|1cCWO%KTLD06 zx%1h&ky+xz_~`Js-vpWWy&-=+XqE;x-90zBUUgYe7?5)8;PrWrjr9o88wKtK*9H|I z1^V29fWc)i-F~sWBjT^amSZ_atLD3MuRAxHsO%KqvSmiy_j)z-Q>^JP-C^2LY{Vk} z?|OgOEN~*zJfozpAZqOdjv7*miJxc@wuDiaCe+p&qff8J@9P}VyMIhe?$R9gVudr< zp^@_hlVrWSIdw#O@-%!-HEm5SIaC7Ro_DHqe7yNl3%M@htAJpvB(^IAFprqmzO1w* z36S#FgL9SuAh~uq!CX@SXiG2r1OQc^cO1KwqJVxW3INu5XZGK-*u3?bEVoWF`6ta02i^Ph?RO(6KC)k{@&u#__o4hy{%3^spqOI7L8%RFW$OK+Q3WLkbur&8H2 zWaQ)gqRvB~gIPfMrFeW~Crr}s@_XE|%NJ~XE@G858s+h0Sbg^deY^_RFhowdE*HlW z-6I$qDKjm4{G8Nt^4Y{hJ1iP&bxVC(txUR7)I%)l9PIgD;N)9QW$Izl%k^wu#Qj99 zEmz~~?p?cc)sO!rDdU}t+9TO~Wz+; zK8WOk(v!*)wA;Lqf;5|bIfzJauyI2#8R+Wr9tK4%IM1e;Epe*Z}3x`NwI}j3zCx`I_(xH z-wPHLzVLc@ovMaVJyC;1MKa6?Kiis!{~UCcRM9zmZur*rmVnBn`hbE?`+~nJG`?Q& z|D?}WF!kfocKUMlJ`x9lOmMy=czO)+Vh1ZZEfaI^MWD zJ17@d^S-KoH7@#wO?Jb_XYQNT>GA5Q(9A$9uKm! zvPag|EJRkz<69+JCG{a$ltI}M*@S&v`xacNeX!}(Y4PdI=}j}ni?~JgMap7_O6yK( zm7W8v1E(0Q;MNc_y=tm(Snow~eesJ)4}WF<>&%>aeTAEc5?iFxL(&yoC`FfwsMo?; zQpc3OR(3slvrv%rG;5&bJ>~6(R%Ao?ofQY2Eahge7f7DP_t_=v$cffZIYn7T&*u{- zAjdFA;WQ|%x zPeew<=GEMl13z2V5`NSUbH2&XIg0oQJQElbs6L}~=AlrKkg9No@Quy-B5@*5H$k?V z%2{p|lkik!B-82?z9}!`bqD;lGCSp7iRQ>1QT_>S)j;p+*&ctsLv&Cof_8HmJ=ywL zG-k+-98;l6Qp`|T4wv1xAAVcs%i%H?)(O^{@g$^ zhK!%is)($(U2%qe;fKv!x-?5>kJ!w`(fZzcmr0yzWU;xSQOH*Q#Ip7qd6Elz1}p_YC>@&qr3OC`AR0@J&Xx}V;!km}8)b8nUY zxMy22KH}l%-F<3-L+R`Lc(DA{%oJ*>aEi)Z@~pjkusYD9`}O%*i4x_xxG`mK%E6DU z>tTTl@oi;ouiAw4ar!eyTOV`#zg;&dSPaWK?bYJGR&uAj=DkK=Pwj`$lk<{MDsl%t z@=~U&9%-^Yf~sx87pPrd&wfZy@>Q;^9gp}>VHfJukehJw#>vf#HoU1o7L7b`x3>0y z7zJYxw+n@!k7Y&D`m$y+Lm$zP?3a9QG4|}Q!JDT}escbNz5T_&i?+dx)8z}IQz(vR z7os+pt6f0JLtd}ueZE|fqjFruuBQA~1P}HJAKa*);E|h`vlltHJbE_yQxTykH^7)tCFH28DoZF;e)I_&-5m9h^Z{GzJl5 ztOL`=L!mH`A>Yz7fEyVb8yXsFf#6WME(B@-f$M9-brJf82$&w|pAXFU$RPM3FxIyJ z_~WmT-~bkjj(|WyLql~!b#-VAe+bOj*cbwZL*Q_2K0=$xp|WsnZ7Ngkw}Lg1iD!`L zEE0_h+EB##(t=q?FhAP=9D_ptSC-2BCsq8ULD)Dt1f~PsIM#2VgTwz1rBMDwGg%no zf8+f>g_*7#IuU{)GHJmKJpc0i)HYnv5mpQ$jzwd*(rD!0nRE`Iv1rTy8XW}J*MWof zZX^x{g+X;FI&fWmLl9J#0E6no_JKfq9dLLObpyQbZ#4%8gguqX!cp-=dut?^udhQQ z5fD~JmKMgAP+dc~fj$goV`Bk_8(7*{>f68#taOcF(BE8Z8a|jpq_Tc<3IFBl{gJy- zEEGE5vo(=H3Lz3~88iy$?}Q*oe~tzAN4Ys37v zu=$IBp9Yc2e-8|P;iJQK`8_wV$rOP{m<1M_=T5C8xG literal 0 HcmV?d00001 diff --git a/applications/system/js_app/js_app.c b/applications/system/js_app/js_app.c new file mode 100644 index 00000000000..e99cc32498f --- /dev/null +++ b/applications/system/js_app/js_app.c @@ -0,0 +1,131 @@ +#include +#include "js_thread.h" +#include +#include "js_app_i.h" +#include +#include + +#define TAG "JS app" + +typedef struct { + JsThread* js_thread; + Gui* gui; + ViewDispatcher* view_dispatcher; + Loading* loading; + JsConsoleView* console_view; +} JsApp; + +static uint32_t js_view_exit(void* context) { + UNUSED(context); + return VIEW_NONE; +} + +static void js_app_compact_trace(FuriString* trace_str) { + // Keep only first line + size_t line_end = furi_string_search_char(trace_str, '\n'); + if(line_end > 0) { + furi_string_left(trace_str, line_end); + } + + // Remove full path + FuriString* file_name = furi_string_alloc(); + size_t filename_start = furi_string_search_rchar(trace_str, '/'); + if(filename_start > 0) { + filename_start++; + furi_string_set_n( + file_name, trace_str, filename_start, furi_string_size(trace_str) - filename_start); + furi_string_printf(trace_str, "at %s", furi_string_get_cstr(file_name)); + } + + furi_string_free(file_name); +} + +static void js_callback(JsThreadEvent event, const char* msg, void* context) { + JsApp* app = context; + furi_assert(app); + + if(event == JsThreadEventDone) { + FURI_LOG_I(TAG, "Script done"); + console_view_print(app->console_view, "--- DONE ---"); + } else if(event == JsThreadEventPrint) { + console_view_print(app->console_view, msg); + } else if(event == JsThreadEventError) { + console_view_print(app->console_view, "--- ERROR ---"); + console_view_print(app->console_view, msg); + } else if(event == JsThreadEventErrorTrace) { + FuriString* compact_trace = furi_string_alloc_set_str(msg); + js_app_compact_trace(compact_trace); + console_view_print(app->console_view, furi_string_get_cstr(compact_trace)); + furi_string_free(compact_trace); + console_view_print(app->console_view, "See logs for full trace"); + } +} + +static JsApp* js_app_alloc(void) { + JsApp* app = malloc(sizeof(JsApp)); + + app->view_dispatcher = view_dispatcher_alloc(); + app->loading = loading_alloc(); + + app->gui = furi_record_open("gui"); + view_dispatcher_enable_queue(app->view_dispatcher); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_add_view( + app->view_dispatcher, JsAppViewLoading, loading_get_view(app->loading)); + + app->console_view = console_view_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, JsAppViewConsole, console_view_get_view(app->console_view)); + view_set_previous_callback(console_view_get_view(app->console_view), js_view_exit); + view_dispatcher_switch_to_view(app->view_dispatcher, JsAppViewConsole); + + return app; +} + +static void js_app_free(JsApp* app) { + console_view_free(app->console_view); + view_dispatcher_remove_view(app->view_dispatcher, JsAppViewConsole); + loading_free(app->loading); + view_dispatcher_remove_view(app->view_dispatcher, JsAppViewLoading); + + view_dispatcher_free(app->view_dispatcher); + furi_record_close("gui"); + + free(app); +} + +int32_t js_app(void* arg) { + JsApp* app = js_app_alloc(); + + FuriString* script_path = furi_string_alloc_set(APP_ASSETS_PATH()); + do { + if(arg != NULL && strlen(arg) > 0) { + furi_string_set(script_path, (const char*)arg); + } else { + DialogsFileBrowserOptions browser_options; + dialog_file_browser_set_basic_options(&browser_options, ".js", &I_js_script_10px); + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + if(!dialog_file_browser_show(dialogs, script_path, script_path, &browser_options)) + break; + furi_record_close(RECORD_DIALOGS); + } + FuriString* name = furi_string_alloc(); + path_extract_filename(script_path, name, false); + FuriString* start_text = + furi_string_alloc_printf("Running %s", furi_string_get_cstr(name)); + console_view_print(app->console_view, furi_string_get_cstr(start_text)); + console_view_print(app->console_view, "------------"); + furi_string_free(name); + furi_string_free(start_text); + + app->js_thread = js_thread_run(furi_string_get_cstr(script_path), js_callback, app); + view_dispatcher_run(app->view_dispatcher); + + js_thread_stop(app->js_thread); + } while(0); + + furi_string_free(script_path); + + js_app_free(app); + return 0; +} //-V773 \ No newline at end of file diff --git a/applications/system/js_app/js_app_i.h b/applications/system/js_app/js_app_i.h new file mode 100644 index 00000000000..bc958414065 --- /dev/null +++ b/applications/system/js_app/js_app_i.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include "views/console_view.h" + +typedef enum { + JsAppViewConsole, + JsAppViewLoading, +} JsAppView; diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c new file mode 100644 index 00000000000..0e2d6bf25ff --- /dev/null +++ b/applications/system/js_app/js_modules.c @@ -0,0 +1,126 @@ +#include +#include "js_modules.h" +#include +#include "modules/js_flipper.h" + +#define TAG "JS modules" + +typedef struct { + JsModeConstructor create; + JsModeDestructor destroy; + void* context; +} JsModuleData; + +DICT_DEF2(JsModuleDict, FuriString*, FURI_STRING_OPLIST, JsModuleData, M_POD_OPLIST); + +static const JsModuleDescriptor modules_builtin[] = { + {"flipper", js_flipper_create, NULL}, +}; + +struct JsModules { + struct mjs* mjs; + JsModuleDict_t module_dict; + PluginManager* plugin_manager; +}; + +JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver) { + JsModules* modules = malloc(sizeof(JsModules)); + modules->mjs = mjs; + JsModuleDict_init(modules->module_dict); + + modules->plugin_manager = plugin_manager_alloc( + PLUGIN_APP_ID, PLUGIN_API_VERSION, composite_api_resolver_get(resolver)); + + return modules; +} + +void js_modules_destroy(JsModules* modules) { + JsModuleDict_it_t it; + for(JsModuleDict_it(it, modules->module_dict); !JsModuleDict_end_p(it); + JsModuleDict_next(it)) { + const JsModuleDict_itref_t* module_itref = JsModuleDict_cref(it); + if(module_itref->value.destroy) { + module_itref->value.destroy(module_itref->value.context); + } + } + plugin_manager_free(modules->plugin_manager); + JsModuleDict_clear(modules->module_dict); + free(modules); +} + +mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len) { + FuriString* module_name = furi_string_alloc_set_str(name); + // Check if module is already installed + JsModuleData* module_inst = JsModuleDict_get(modules->module_dict, module_name); + if(module_inst) { //-V547 + furi_string_free(module_name); + mjs_prepend_errorf( + modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module is already installed", name); + return MJS_UNDEFINED; + } + + bool module_found = false; + // Check built-in modules + for(size_t i = 0; i < COUNT_OF(modules_builtin); i++) { //-V1008 + size_t name_compare_len = strlen(modules_builtin[i].name); + + if(name_compare_len != name_len) { + continue; + } + + if(strncmp(name, modules_builtin[i].name, name_compare_len) == 0) { + JsModuleData module = { + .create = modules_builtin[i].create, .destroy = modules_builtin[i].destroy}; + JsModuleDict_set_at(modules->module_dict, module_name, module); + module_found = true; + FURI_LOG_I(TAG, "Using built-in module %s", name); + break; + } + } + + // External module load + if(!module_found) { + FuriString* module_path = furi_string_alloc(); + furi_string_printf(module_path, "%s/js_%s.fal", APP_DATA_PATH("plugins"), name); + FURI_LOG_I(TAG, "Loading external module %s", furi_string_get_cstr(module_path)); + do { + uint32_t plugin_cnt_last = plugin_manager_get_count(modules->plugin_manager); + PluginManagerError load_error = plugin_manager_load_single( + modules->plugin_manager, furi_string_get_cstr(module_path)); + if(load_error != PluginManagerErrorNone) { + break; + } + const JsModuleDescriptor* plugin = + plugin_manager_get_ep(modules->plugin_manager, plugin_cnt_last); + furi_assert(plugin); + + if(strncmp(name, plugin->name, name_len) != 0) { + FURI_LOG_E(TAG, "Module name missmatch %s", plugin->name); + break; + } + JsModuleData module = {.create = plugin->create, .destroy = plugin->destroy}; + JsModuleDict_set_at(modules->module_dict, module_name, module); + + module_found = true; + } while(0); + furi_string_free(module_path); + } + + // Run module constructor + mjs_val_t module_object = MJS_UNDEFINED; + if(module_found) { + module_inst = JsModuleDict_get(modules->module_dict, module_name); + furi_assert(module_inst); + if(module_inst->create) { //-V779 + module_inst->context = module_inst->create(modules->mjs, &module_object); + } + } + + if(module_object == MJS_UNDEFINED) { //-V547 + mjs_prepend_errorf(modules->mjs, MJS_BAD_ARGS_ERROR, "\"%s\" module load fail", name); + } + + furi_string_free(module_name); + + return module_object; +} \ No newline at end of file diff --git a/applications/system/js_app/js_modules.h b/applications/system/js_app/js_modules.h new file mode 100644 index 00000000000..aff4d7e8df1 --- /dev/null +++ b/applications/system/js_app/js_modules.h @@ -0,0 +1,25 @@ +#pragma once +#include "js_thread_i.h" +#include +#include +#include + +#define PLUGIN_APP_ID "js" +#define PLUGIN_API_VERSION 1 + +typedef void* (*JsModeConstructor)(struct mjs* mjs, mjs_val_t* object); +typedef void (*JsModeDestructor)(void* inst); + +typedef struct { + char* name; + JsModeConstructor create; + JsModeDestructor destroy; +} JsModuleDescriptor; + +typedef struct JsModules JsModules; + +JsModules* js_modules_create(struct mjs* mjs, CompositeApiResolver* resolver); + +void js_modules_destroy(JsModules* modules); + +mjs_val_t js_module_require(JsModules* modules, const char* name, size_t name_len); diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c new file mode 100644 index 00000000000..22007d013d3 --- /dev/null +++ b/applications/system/js_app/js_thread.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include +#include +#include "plugin_api/app_api_interface.h" +#include "js_thread.h" +#include "js_thread_i.h" +#include "js_modules.h" + +#define TAG "JS" + +struct JsThread { + FuriThread* thread; + FuriString* path; + CompositeApiResolver* resolver; + JsThreadCallback app_callback; + void* context; + JsModules* modules; +}; + +static void js_str_print(FuriString* msg_str, struct mjs* mjs) { + size_t num_args = mjs_nargs(mjs); + for(size_t i = 0; i < num_args; i++) { + char* name = NULL; + size_t name_len = 0; + int need_free = 0; + mjs_val_t arg = mjs_arg(mjs, i); + mjs_err_t err = mjs_to_string(mjs, &arg, &name, &name_len, &need_free); + if(err != MJS_OK) { + furi_string_cat_printf(msg_str, "err %s ", mjs_strerror(mjs, err)); + } else { + furi_string_cat_printf(msg_str, "%s ", name); + } + if(need_free) { + free(name); + name = NULL; + } + } +} + +static void js_print(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + js_str_print(msg_str, mjs); + + printf("%s\r\n", furi_string_get_cstr(msg_str)); + + JsThread* worker = mjs_get_context(mjs); + furi_assert(worker); + if(worker->app_callback) { + worker->app_callback(JsThreadEventPrint, furi_string_get_cstr(msg_str), worker->context); + } + + furi_string_free(msg_str); + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_console_log(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + js_str_print(msg_str, mjs); + FURI_LOG_I(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_console_warn(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + js_str_print(msg_str, mjs); + FURI_LOG_W(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_console_error(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + js_str_print(msg_str, mjs); + FURI_LOG_E(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_console_debug(struct mjs* mjs) { + FuriString* msg_str = furi_string_alloc(); + js_str_print(msg_str, mjs); + FURI_LOG_D(TAG, "%s", furi_string_get_cstr(msg_str)); + furi_string_free(msg_str); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_exit_flag_poll(struct mjs* mjs) { + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, 0); + if(flags & FuriFlagError) { + return; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + } +} + +bool js_delay_with_flags(struct mjs* mjs, uint32_t time) { + uint32_t flags = furi_thread_flags_wait(ThreadEventStop, FuriFlagWaitAny, time); + if(flags & FuriFlagError) { + return false; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + return true; + } + return false; +} + +void js_flags_set(struct mjs* mjs, uint32_t flags) { + JsThread* worker = mjs_get_context(mjs); + furi_assert(worker); + furi_thread_flags_set(furi_thread_get_id(worker->thread), flags); +} + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags_mask, uint32_t timeout) { + flags_mask |= ThreadEventStop; + uint32_t flags = furi_thread_flags_get(); + furi_check((flags & FuriFlagError) == 0); + if(flags == 0) { + flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout); + } else { + uint32_t state = furi_thread_flags_clear(flags & flags_mask); + furi_check((state & FuriFlagError) == 0); + } + + if(flags & FuriFlagError) { + return 0; + } + if(flags & ThreadEventStop) { + mjs_exit(mjs); + } + return flags; +} + +static void js_delay(struct mjs* mjs) { + bool args_correct = false; + int ms = 0; + + if(mjs_nargs(mjs) == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(mjs_is_number(arg)) { + ms = mjs_get_int(mjs, arg); + args_correct = true; + } + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + js_delay_with_flags(mjs, ms); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_dlsym(void* handle, const char* name) { + CompositeApiResolver* resolver = handle; + Elf32_Addr addr = 0; + uint32_t hash = elf_symbolname_hash(name); + const ElfApiInterface* api = composite_api_resolver_get(resolver); + + if(!api->resolver_callback(api, hash, &addr)) { + FURI_LOG_E(TAG, "FFI: cannot find \"%s\"", name); + return NULL; + } + + return (void*)addr; +} + +static void js_ffi_address(struct mjs* mjs) { + mjs_val_t name_v = mjs_arg(mjs, 0); + size_t len; + const char* name = mjs_get_string(mjs, &name_v, &len); + void* addr = mjs_ffi_resolve(mjs, name); + mjs_return(mjs, mjs_mk_foreign(mjs, addr)); +} + +static void js_require(struct mjs* mjs) { + mjs_val_t name_v = mjs_arg(mjs, 0); + size_t len; + const char* name = mjs_get_string(mjs, &name_v, &len); + mjs_val_t req_object = MJS_UNDEFINED; + if((len == 0) || (name == NULL)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "String argument is expected"); + } else { + JsThread* worker = mjs_get_context(mjs); + furi_assert(worker); + req_object = js_module_require(worker->modules, name, len); + } + mjs_return(mjs, req_object); +} + +static void js_global_to_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-2147483648"; + itoa(num, tmp_str, 10); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +static void js_global_to_hex_string(struct mjs* mjs) { + double num = mjs_get_int(mjs, mjs_arg(mjs, 0)); + char tmp_str[] = "-FFFFFFFF"; + itoa(num, tmp_str, 16); + mjs_val_t ret = mjs_mk_string(mjs, tmp_str, ~0, true); + mjs_return(mjs, ret); +} + +static void js_dump_write_callback(void* ctx, const char* format, ...) { + File* file = ctx; + furi_assert(ctx); + + FuriString* str = furi_string_alloc(); + + va_list args; + va_start(args, format); + furi_string_vprintf(str, format, args); + furi_string_cat(str, "\n"); + va_end(args); + + storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str)); + furi_string_free(str); +} + +static int32_t js_thread(void* arg) { + JsThread* worker = arg; + worker->resolver = composite_api_resolver_alloc(); + composite_api_resolver_add(worker->resolver, firmware_api_interface); + composite_api_resolver_add(worker->resolver, application_api_interface); + + struct mjs* mjs = mjs_create(worker); + worker->modules = js_modules_create(mjs, worker->resolver); + mjs_val_t global = mjs_get_global(mjs); + mjs_set(mjs, global, "print", ~0, MJS_MK_FN(js_print)); + mjs_set(mjs, global, "delay", ~0, MJS_MK_FN(js_delay)); + mjs_set(mjs, global, "to_string", ~0, MJS_MK_FN(js_global_to_string)); + mjs_set(mjs, global, "to_hex_string", ~0, MJS_MK_FN(js_global_to_hex_string)); + mjs_set(mjs, global, "ffi_address", ~0, MJS_MK_FN(js_ffi_address)); + mjs_set(mjs, global, "require", ~0, MJS_MK_FN(js_require)); + + mjs_val_t console_obj = mjs_mk_object(mjs); + mjs_set(mjs, console_obj, "log", ~0, MJS_MK_FN(js_console_log)); + mjs_set(mjs, console_obj, "warn", ~0, MJS_MK_FN(js_console_warn)); + mjs_set(mjs, console_obj, "error", ~0, MJS_MK_FN(js_console_error)); + mjs_set(mjs, console_obj, "debug", ~0, MJS_MK_FN(js_console_debug)); + mjs_set(mjs, global, "console", ~0, console_obj); + + mjs_set_ffi_resolver(mjs, js_dlsym, worker->resolver); + + mjs_set_exec_flags_poller(mjs, js_exit_flag_poll); + + mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL); + + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + FuriString* dump_path = furi_string_alloc_set(worker->path); + furi_string_cat(dump_path, ".lst"); + + Storage* storage = furi_record_open(RECORD_STORAGE); + File* file = storage_file_alloc(storage); + + if(storage_file_open( + file, furi_string_get_cstr(dump_path), FSAM_WRITE, FSOM_CREATE_ALWAYS)) { + mjs_disasm_all(mjs, js_dump_write_callback, file); + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + furi_string_free(dump_path); + } + + if(err != MJS_OK) { + FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); + if(worker->app_callback) { + worker->app_callback(JsThreadEventError, mjs_strerror(mjs, err), worker->context); + } + const char* stack_trace = mjs_get_stack_trace(mjs); + if(stack_trace != NULL) { + FURI_LOG_E(TAG, "Stack trace:\n%s", stack_trace); + if(worker->app_callback) { + worker->app_callback(JsThreadEventErrorTrace, stack_trace, worker->context); + } + } + } else { + if(worker->app_callback) { + worker->app_callback(JsThreadEventDone, NULL, worker->context); + } + } + + js_modules_destroy(worker->modules); + mjs_destroy(mjs); + + composite_api_resolver_free(worker->resolver); + + return 0; +} + +JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context) { + JsThread* worker = malloc(sizeof(JsThread)); //-V799 + worker->path = furi_string_alloc_set(script_path); + worker->thread = furi_thread_alloc_ex("JsThread", 8 * 1024, js_thread, worker); + worker->app_callback = callback; + worker->context = context; + furi_thread_start(worker->thread); + return worker; +} + +void js_thread_stop(JsThread* worker) { + furi_thread_flags_set(furi_thread_get_id(worker->thread), ThreadEventStop); + furi_thread_join(worker->thread); + furi_thread_free(worker->thread); + furi_string_free(worker->path); + free(worker); +} diff --git a/applications/system/js_app/js_thread.h b/applications/system/js_app/js_thread.h new file mode 100644 index 00000000000..969715ec1a4 --- /dev/null +++ b/applications/system/js_app/js_thread.h @@ -0,0 +1,16 @@ +#pragma once + +typedef struct JsThread JsThread; + +typedef enum { + JsThreadEventDone, + JsThreadEventError, + JsThreadEventPrint, + JsThreadEventErrorTrace, +} JsThreadEvent; + +typedef void (*JsThreadCallback)(JsThreadEvent event, const char* msg, void* context); + +JsThread* js_thread_run(const char* script_path, JsThreadCallback callback, void* context); + +void js_thread_stop(JsThread* worker); diff --git a/applications/system/js_app/js_thread_i.h b/applications/system/js_app/js_thread_i.h new file mode 100644 index 00000000000..a73cbb4bc51 --- /dev/null +++ b/applications/system/js_app/js_thread_i.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INST_PROP_NAME "_" + +typedef enum { + ThreadEventStop = (1 << 0), + ThreadEventCustomDataRx = (1 << 1), +} WorkerEventFlags; + +bool js_delay_with_flags(struct mjs* mjs, uint32_t time); + +void js_flags_set(struct mjs* mjs, uint32_t flags); + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); diff --git a/applications/system/js_app/modules/js_badusb.c b/applications/system/js_app/modules/js_badusb.c new file mode 100644 index 00000000000..6b19faea21f --- /dev/null +++ b/applications/system/js_app/modules/js_badusb.c @@ -0,0 +1,410 @@ +#include +#include "../js_modules.h" +#include + +typedef struct { + FuriHalUsbHidConfig* hid_cfg; + FuriHalUsbInterface* usb_if_prev; + uint8_t key_hold_cnt; +} JsBadusbInst; + +static const struct { + char* name; + uint16_t code; +} key_codes[] = { + {"CTRL", KEY_MOD_LEFT_CTRL}, + {"SHIFT", KEY_MOD_LEFT_SHIFT}, + {"ALT", KEY_MOD_LEFT_ALT}, + {"GUI", KEY_MOD_LEFT_GUI}, + + {"DOWN", HID_KEYBOARD_DOWN_ARROW}, + {"LEFT", HID_KEYBOARD_LEFT_ARROW}, + {"RIGHT", HID_KEYBOARD_RIGHT_ARROW}, + {"UP", HID_KEYBOARD_UP_ARROW}, + + {"ENTER", HID_KEYBOARD_RETURN}, + {"PAUSE", HID_KEYBOARD_PAUSE}, + {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK}, + {"DELETE", HID_KEYBOARD_DELETE_FORWARD}, + {"BACKSPACE", HID_KEYBOARD_DELETE}, + {"END", HID_KEYBOARD_END}, + {"ESC", HID_KEYBOARD_ESCAPE}, + {"HOME", HID_KEYBOARD_HOME}, + {"INSERT", HID_KEYBOARD_INSERT}, + {"NUMLOCK", HID_KEYPAD_NUMLOCK}, + {"PAGEUP", HID_KEYBOARD_PAGE_UP}, + {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN}, + {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN}, + {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK}, + {"SPACE", HID_KEYBOARD_SPACEBAR}, + {"TAB", HID_KEYBOARD_TAB}, + {"MENU", HID_KEYBOARD_APPLICATION}, + + {"F1", HID_KEYBOARD_F1}, + {"F2", HID_KEYBOARD_F2}, + {"F3", HID_KEYBOARD_F3}, + {"F4", HID_KEYBOARD_F4}, + {"F5", HID_KEYBOARD_F5}, + {"F6", HID_KEYBOARD_F6}, + {"F7", HID_KEYBOARD_F7}, + {"F8", HID_KEYBOARD_F8}, + {"F9", HID_KEYBOARD_F9}, + {"F10", HID_KEYBOARD_F10}, + {"F11", HID_KEYBOARD_F11}, + {"F12", HID_KEYBOARD_F12}, +}; + +static bool setup_parse_params(struct mjs* mjs, mjs_val_t arg, FuriHalUsbHidConfig* hid_cfg) { + if(!mjs_is_object(arg)) { + return false; + } + mjs_val_t vid_obj = mjs_get(mjs, arg, "vid", ~0); + mjs_val_t pid_obj = mjs_get(mjs, arg, "pid", ~0); + mjs_val_t mfr_obj = mjs_get(mjs, arg, "mfr_name", ~0); + mjs_val_t prod_obj = mjs_get(mjs, arg, "prod_name", ~0); + + if(mjs_is_number(vid_obj) && mjs_is_number(pid_obj)) { + hid_cfg->vid = mjs_get_int32(mjs, vid_obj); + hid_cfg->pid = mjs_get_int32(mjs, pid_obj); + } else { + return false; + } + + if(mjs_is_string(mfr_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &mfr_obj, &str_len); + if((str_len == 0) || (str_temp == NULL)) { + return false; + } + strlcpy(hid_cfg->manuf, str_temp, sizeof(hid_cfg->manuf)); + } + + if(mjs_is_string(prod_obj)) { + size_t str_len = 0; + const char* str_temp = mjs_get_string(mjs, &prod_obj, &str_len); + if((str_len == 0) || (str_temp == NULL)) { + return false; + } + strlcpy(hid_cfg->product, str_temp, sizeof(hid_cfg->product)); + } + + return true; +} + +static void js_badusb_setup(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + + if(badusb->usb_if_prev) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is already started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + size_t num_args = mjs_nargs(mjs); + if(num_args == 0) { + // No arguments: start USB HID with default settings + args_correct = true; + } else if(num_args == 1) { + badusb->hid_cfg = malloc(sizeof(FuriHalUsbHidConfig)); + // Parse argument object + args_correct = setup_parse_params(mjs, mjs_arg(mjs, 0), badusb->hid_cfg); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + badusb->usb_if_prev = furi_hal_usb_get_config(); + + if(!furi_hal_usb_set_config(&usb_hid, badusb->hid_cfg)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "USB is locked, close companion app first"); + badusb->usb_if_prev = NULL; + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_is_connected(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool is_connected = furi_hal_hid_is_connected(); + mjs_return(mjs, mjs_mk_boolean(mjs, is_connected)); +} + +uint16_t get_keycode_by_name(const char* key_name, size_t name_len) { + if(name_len == 1) { // Single char + return (HID_ASCII_TO_KEY(key_name[0])); + } + + for(size_t i = 0; i < COUNT_OF(key_codes); i++) { + size_t key_cmd_len = strlen(key_codes[i].name); + if(key_cmd_len != name_len) { + continue; + } + + if(strncmp(key_name, key_codes[i].name, name_len) == 0) { + return key_codes[i].code; + } + } + + return HID_KEYBOARD_NONE; +} + +static bool parse_keycode(struct mjs* mjs, size_t nargs, uint16_t* keycode) { + uint16_t key_tmp = 0; + for(size_t i = 0; i < nargs; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + if(mjs_is_string(arg)) { + size_t name_len = 0; + const char* key_name = mjs_get_string(mjs, &arg, &name_len); + if((key_name == NULL) || (name_len == 0)) { + // String error + return false; + } + uint16_t str_key = get_keycode_by_name(key_name, name_len); + if(str_key == HID_KEYBOARD_NONE) { + // Unknown key code + return false; + } + if((str_key & 0xFF) && (key_tmp & 0xFF)) { + // Main key is already defined + return false; + } + key_tmp |= str_key; + } else if(mjs_is_number(arg)) { + uint32_t keycode_number = (uint32_t)mjs_get_int32(mjs, arg); + if(((key_tmp & 0xFF) != 0) || (keycode_number > 0xFF)) { + return false; + } + key_tmp |= keycode_number & 0xFF; + } else { + return false; + } + } + *keycode = key_tmp; + return true; +} + +static void js_badusb_press(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args > 0) { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_hold(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args > 0) { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + if(keycode & 0xFF) { + badusb->key_hold_cnt++; + if(badusb->key_hold_cnt > (HID_KB_MAX_KEYS - 1)) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Too many keys are hold"); + furi_hal_hid_kb_release_all(); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + furi_hal_hid_kb_press(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_release(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint16_t keycode = HID_KEYBOARD_NONE; + size_t num_args = mjs_nargs(mjs); + if(num_args == 0) { + furi_hal_hid_kb_release_all(); + badusb->key_hold_cnt = 0; + mjs_return(mjs, MJS_UNDEFINED); + return; + } else { + args_correct = parse_keycode(mjs, num_args, &keycode); + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + if((keycode & 0xFF) && (badusb->key_hold_cnt > 0)) { + badusb->key_hold_cnt--; + } + furi_hal_hid_kb_release(keycode); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void badusb_print(struct mjs* mjs, bool ln) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsBadusbInst* badusb = mjs_get_ptr(mjs, obj_inst); + furi_assert(badusb); + if(badusb->usb_if_prev == NULL) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "HID is not started"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + bool args_correct = false; + const char* text_str = NULL; + size_t text_len = 0; + uint32_t delay_val = 0; + do { + mjs_val_t obj_string = MJS_UNDEFINED; + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + obj_string = mjs_arg(mjs, 0); + } else if(num_args == 2) { + obj_string = mjs_arg(mjs, 0); + mjs_val_t obj_delay = mjs_arg(mjs, 1); + if(!mjs_is_number(obj_delay)) { + break; + } + delay_val = (uint32_t)mjs_get_int32(mjs, obj_delay); + if(delay_val > 60000) { + break; + } + } + + if(!mjs_is_string(obj_string)) { + break; + } + text_str = mjs_get_string(mjs, &obj_string, &text_len); + if((text_str == NULL) || (text_len == 0)) { + break; + } + args_correct = true; + } while(0); + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + for(size_t i = 0; i < text_len; i++) { + uint16_t keycode = HID_ASCII_TO_KEY(text_str[i]); + furi_hal_hid_kb_press(keycode); + furi_hal_hid_kb_release(keycode); + if(delay_val > 0) { + bool need_exit = js_delay_with_flags(mjs, delay_val); + if(need_exit) { + mjs_return(mjs, MJS_UNDEFINED); + return; + } + } + } + if(ln) { + furi_hal_hid_kb_press(HID_KEYBOARD_RETURN); + furi_hal_hid_kb_release(HID_KEYBOARD_RETURN); + } + + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_badusb_print(struct mjs* mjs) { + badusb_print(mjs, false); +} + +static void js_badusb_println(struct mjs* mjs) { + badusb_print(mjs, true); +} + +static void* js_badusb_create(struct mjs* mjs, mjs_val_t* object) { + JsBadusbInst* badusb = malloc(sizeof(JsBadusbInst)); + mjs_val_t badusb_obj = mjs_mk_object(mjs); + mjs_set(mjs, badusb_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, badusb)); + mjs_set(mjs, badusb_obj, "setup", ~0, MJS_MK_FN(js_badusb_setup)); + mjs_set(mjs, badusb_obj, "isConnected", ~0, MJS_MK_FN(js_badusb_is_connected)); + mjs_set(mjs, badusb_obj, "press", ~0, MJS_MK_FN(js_badusb_press)); + mjs_set(mjs, badusb_obj, "hold", ~0, MJS_MK_FN(js_badusb_hold)); + mjs_set(mjs, badusb_obj, "release", ~0, MJS_MK_FN(js_badusb_release)); + mjs_set(mjs, badusb_obj, "print", ~0, MJS_MK_FN(js_badusb_print)); + mjs_set(mjs, badusb_obj, "println", ~0, MJS_MK_FN(js_badusb_println)); + *object = badusb_obj; + return badusb; +} + +static void js_badusb_destroy(void* inst) { + JsBadusbInst* badusb = inst; + if(badusb->usb_if_prev) { + furi_hal_hid_kb_release_all(); + furi_check(furi_hal_usb_set_config(badusb->usb_if_prev, NULL)); + } + if(badusb->hid_cfg) { + free(badusb->hid_cfg); + } + free(badusb); +} + +static const JsModuleDescriptor js_badusb_desc = { + "badusb", + js_badusb_create, + js_badusb_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_badusb_desc, +}; + +const FlipperAppPluginDescriptor* js_badusb_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_dialog.c b/applications/system/js_app/modules/js_dialog.c new file mode 100644 index 00000000000..34de6d64160 --- /dev/null +++ b/applications/system/js_app/modules/js_dialog.c @@ -0,0 +1,154 @@ +#include +#include "../js_modules.h" +#include + +static bool js_dialog_msg_parse_params(struct mjs* mjs, const char** hdr, const char** msg) { + size_t num_args = mjs_nargs(mjs); + if(num_args != 2) { + return false; + } + mjs_val_t header_obj = mjs_arg(mjs, 0); + mjs_val_t msg_obj = mjs_arg(mjs, 1); + if((!mjs_is_string(header_obj)) || (!mjs_is_string(msg_obj))) { + return false; + } + + size_t arg_len = 0; + *hdr = mjs_get_string(mjs, &header_obj, &arg_len); + if(arg_len == 0) { + *hdr = NULL; + } + + *msg = mjs_get_string(mjs, &msg_obj, &arg_len); + if(arg_len == 0) { + *msg = NULL; + } + + return true; +} + +static void js_dialog_message(struct mjs* mjs) { + const char* dialog_header = NULL; + const char* dialog_msg = NULL; + if(!js_dialog_msg_parse_params(mjs, &dialog_header, &dialog_msg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + dialog_message_set_buttons(message, NULL, "OK", NULL); + if(dialog_header) { + dialog_message_set_header(message, dialog_header, 64, 3, AlignCenter, AlignTop); + } + if(dialog_msg) { + dialog_message_set_text(message, dialog_msg, 64, 26, AlignCenter, AlignTop); + } + DialogMessageButton result = dialog_message_show(dialogs, message); + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + mjs_return(mjs, mjs_mk_boolean(mjs, result == DialogMessageButtonCenter)); +} + +static void js_dialog_custom(struct mjs* mjs) { + DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); + DialogMessage* message = dialog_message_alloc(); + + bool params_correct = false; + + do { + if(mjs_nargs(mjs) != 1) { + break; + } + mjs_val_t params_obj = mjs_arg(mjs, 0); + if(!mjs_is_object(params_obj)) { + break; + } + + mjs_val_t text_obj = mjs_get(mjs, params_obj, "header", ~0); + size_t arg_len = 0; + const char* text_str = mjs_get_string(mjs, &text_obj, &arg_len); + if(arg_len == 0) { + text_str = NULL; + } + if(text_str) { + dialog_message_set_header(message, text_str, 64, 3, AlignCenter, AlignTop); + } + + text_obj = mjs_get(mjs, params_obj, "text", ~0); + text_str = mjs_get_string(mjs, &text_obj, &arg_len); + if(arg_len == 0) { + text_str = NULL; + } + if(text_str) { + dialog_message_set_text(message, text_str, 64, 26, AlignCenter, AlignTop); + } + + mjs_val_t btn_obj[3] = { + mjs_get(mjs, params_obj, "button_left", ~0), + mjs_get(mjs, params_obj, "button_center", ~0), + mjs_get(mjs, params_obj, "button_right", ~0), + }; + const char* btn_text[3] = {NULL, NULL, NULL}; + + for(uint8_t i = 0; i < 3; i++) { + if(!mjs_is_string(btn_obj[i])) { + continue; + } + btn_text[i] = mjs_get_string(mjs, &btn_obj[i], &arg_len); + if(arg_len == 0) { + btn_text[i] = NULL; + } + } + + dialog_message_set_buttons(message, btn_text[0], btn_text[1], btn_text[2]); + + DialogMessageButton result = dialog_message_show(dialogs, message); + mjs_val_t return_obj = MJS_UNDEFINED; + if(result == DialogMessageButtonLeft) { + return_obj = mjs_mk_string(mjs, btn_text[0], ~0, true); + } else if(result == DialogMessageButtonCenter) { + return_obj = mjs_mk_string(mjs, btn_text[1], ~0, true); + } else if(result == DialogMessageButtonRight) { + return_obj = mjs_mk_string(mjs, btn_text[2], ~0, true); + } else { + return_obj = mjs_mk_string(mjs, "", ~0, true); + } + + mjs_return(mjs, return_obj); + params_correct = true; + } while(0); + + dialog_message_free(message); + furi_record_close(RECORD_DIALOGS); + + if(!params_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + } +} + +static void* js_dialog_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t dialog_obj = mjs_mk_object(mjs); + mjs_set(mjs, dialog_obj, "message", ~0, MJS_MK_FN(js_dialog_message)); + mjs_set(mjs, dialog_obj, "custom", ~0, MJS_MK_FN(js_dialog_custom)); + *object = dialog_obj; + + return (void*)1; +} + +static const JsModuleDescriptor js_dialog_desc = { + "dialog", + js_dialog_create, + NULL, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_dialog_desc, +}; + +const FlipperAppPluginDescriptor* js_dialog_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_flipper.c b/applications/system/js_app/modules/js_flipper.c new file mode 100644 index 00000000000..17c0ad36b8a --- /dev/null +++ b/applications/system/js_app/modules/js_flipper.c @@ -0,0 +1,36 @@ +#include +#include "../js_modules.h" +#include +#include + +static void js_flipper_get_model(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_string(mjs, furi_hal_version_get_model_name(), ~0, true); + mjs_return(mjs, ret); +} + +static void js_flipper_get_name(struct mjs* mjs) { + const char* name_str = furi_hal_version_get_name_ptr(); + if(name_str == NULL) { + name_str = "Unknown"; + } + mjs_val_t ret = mjs_mk_string(mjs, name_str, ~0, true); + mjs_return(mjs, ret); +} + +static void js_flipper_get_battery(struct mjs* mjs) { + Power* power = furi_record_open(RECORD_POWER); + PowerInfo info; + power_get_info(power, &info); + furi_record_close(RECORD_POWER); + mjs_return(mjs, mjs_mk_number(mjs, info.charge)); +} + +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object) { + mjs_val_t flipper_obj = mjs_mk_object(mjs); + mjs_set(mjs, flipper_obj, "getModel", ~0, MJS_MK_FN(js_flipper_get_model)); + mjs_set(mjs, flipper_obj, "getName", ~0, MJS_MK_FN(js_flipper_get_name)); + mjs_set(mjs, flipper_obj, "getBatteryCharge", ~0, MJS_MK_FN(js_flipper_get_battery)); + *object = flipper_obj; + + return (void*)1; +} diff --git a/applications/system/js_app/modules/js_flipper.h b/applications/system/js_app/modules/js_flipper.h new file mode 100644 index 00000000000..3b05389cc77 --- /dev/null +++ b/applications/system/js_app/modules/js_flipper.h @@ -0,0 +1,4 @@ +#pragma once +#include "../js_thread_i.h" + +void* js_flipper_create(struct mjs* mjs, mjs_val_t* object); diff --git a/applications/system/js_app/modules/js_notification.c b/applications/system/js_app/modules/js_notification.c new file mode 100644 index 00000000000..e1084be1390 --- /dev/null +++ b/applications/system/js_app/modules/js_notification.c @@ -0,0 +1,109 @@ +#include +#include "../js_modules.h" +#include + +static void js_notify(struct mjs* mjs, const NotificationSequence* sequence) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + NotificationApp* notification = mjs_get_ptr(mjs, obj_inst); + furi_assert(notification); + notification_message(notification, sequence); +} + +static void js_notify_success(struct mjs* mjs) { + js_notify(mjs, &sequence_success); + mjs_return(mjs, MJS_UNDEFINED); +} + +static void js_notify_error(struct mjs* mjs) { + js_notify(mjs, &sequence_error); + mjs_return(mjs, MJS_UNDEFINED); +} + +static const struct { + const char* color_name; + const NotificationSequence* sequence_short; + const NotificationSequence* sequence_long; +} led_sequences[] = { + {"blue", &sequence_blink_blue_10, &sequence_blink_blue_100}, + {"red", &sequence_blink_red_10, &sequence_blink_red_100}, + {"green", &sequence_blink_green_10, &sequence_blink_green_100}, + {"yellow", &sequence_blink_yellow_10, &sequence_blink_yellow_100}, + {"cyan", &sequence_blink_cyan_10, &sequence_blink_cyan_100}, + {"magenta", &sequence_blink_magenta_10, &sequence_blink_magenta_100}, +}; + +static void js_notify_blink(struct mjs* mjs) { + const NotificationSequence* sequence = NULL; + do { + size_t num_args = mjs_nargs(mjs); + if(num_args != 2) { + break; + } + mjs_val_t color_obj = mjs_arg(mjs, 0); + mjs_val_t type_obj = mjs_arg(mjs, 1); + if((!mjs_is_string(color_obj)) || (!mjs_is_string(type_obj))) break; + + size_t arg_len = 0; + const char* arg_str = mjs_get_string(mjs, &color_obj, &arg_len); + if((arg_len == 0) || (arg_str == NULL)) break; + + int32_t color_id = -1; + for(size_t i = 0; i < COUNT_OF(led_sequences); i++) { + size_t name_len = strlen(led_sequences[i].color_name); + if(arg_len != name_len) continue; + if(strncmp(arg_str, led_sequences[i].color_name, arg_len) == 0) { + color_id = i; + break; + } + } + if(color_id == -1) break; + + arg_str = mjs_get_string(mjs, &type_obj, &arg_len); + if((arg_len == 0) || (arg_str == NULL)) break; + if(strncmp(arg_str, "short", arg_len) == 0) { + sequence = led_sequences[color_id].sequence_short; + } else if(strncmp(arg_str, "long", arg_len) == 0) { + sequence = led_sequences[color_id].sequence_long; + } + } while(0); + + if(sequence == NULL) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } else { + js_notify(mjs, sequence); + } + mjs_return(mjs, MJS_UNDEFINED); +} + +static void* js_notification_create(struct mjs* mjs, mjs_val_t* object) { + NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION); + mjs_val_t notify_obj = mjs_mk_object(mjs); + mjs_set(mjs, notify_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, notification)); + mjs_set(mjs, notify_obj, "success", ~0, MJS_MK_FN(js_notify_success)); + mjs_set(mjs, notify_obj, "error", ~0, MJS_MK_FN(js_notify_error)); + mjs_set(mjs, notify_obj, "blink", ~0, MJS_MK_FN(js_notify_blink)); + *object = notify_obj; + + return notification; +} + +static void js_notification_destroy(void* inst) { + UNUSED(inst); + furi_record_close(RECORD_NOTIFICATION); +} + +static const JsModuleDescriptor js_notification_desc = { + "notification", + js_notification_create, + js_notification_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_notification_desc, +}; + +const FlipperAppPluginDescriptor* js_notification_ep(void) { + return &plugin_descriptor; +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_uart.c b/applications/system/js_app/modules/js_uart.c new file mode 100644 index 00000000000..8f251c276eb --- /dev/null +++ b/applications/system/js_app/modules/js_uart.c @@ -0,0 +1,585 @@ +#include +#include +#include "../js_modules.h" +#include + +#define TAG "js_uart" +#define RX_BUF_LEN 2048 + +typedef struct { + bool setup_done; + FuriStreamBuffer* rx_stream; + FuriHalSerialHandle* serial_handle; + struct mjs* mjs; +} JsUartInst; + +typedef struct { + size_t len; + char* data; +} PatternArrayItem; + +ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); + +static void + js_uart_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { + JsUartInst* uart = context; + furi_assert(uart); + + if(event & FuriHalSerialRxEventData) { + uint8_t data = furi_hal_serial_async_rx(handle); + furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); + js_flags_set(uart->mjs, ThreadEventCustomDataRx); + } +} + +static void js_uart_setup(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + + if(uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is already configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint32_t baudrate = 0; + + if(mjs_nargs(mjs) == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(mjs_is_number(arg)) { + baudrate = mjs_get_int32(mjs, arg); + args_correct = true; + } + } + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); + uart->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdLpuart); + if(uart->serial_handle) { + furi_hal_serial_init(uart->serial_handle, baudrate); + furi_hal_serial_async_rx_start(uart->serial_handle, js_uart_on_async_rx, uart, false); + uart->setup_done = true; + } +} + +static void js_uart_write(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = true; + + size_t num_args = mjs_nargs(mjs); + for(size_t i = 0; i < num_args; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + if(mjs_is_string(arg)) { + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + args_correct = false; + break; + } + furi_hal_serial_tx(uart->serial_handle, (uint8_t*)arg_str, str_len); + } else if(mjs_is_number(arg)) { + uint32_t byte_val = mjs_get_int32(mjs, arg); + if(byte_val > 0xFF) { + args_correct = false; + break; + } + furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1); + } else if(mjs_is_array(arg)) { + size_t array_len = mjs_array_length(mjs, arg); + for(size_t i = 0; i < array_len; i++) { + mjs_val_t array_arg = mjs_array_get(mjs, arg, i); + if(!mjs_is_number(array_arg)) { + args_correct = false; + break; + } + uint32_t byte_val = mjs_get_int32(mjs, array_arg); + if(byte_val > 0xFF) { + args_correct = false; + break; + } + furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1); + } + if(!args_correct) { + break; + } + } else if(mjs_is_typed_array(arg)) { + mjs_val_t array_buf = arg; + if(mjs_is_data_view(arg)) { + array_buf = mjs_dataview_get_buf(mjs, arg); + } + size_t len = 0; + char* buf = mjs_array_buf_get_ptr(mjs, array_buf, &len); + furi_hal_serial_tx(uart->serial_handle, (uint8_t*)buf, len); + } else { + args_correct = false; + break; + } + } + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } + mjs_return(mjs, MJS_UNDEFINED); +} + +static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t timeout) { + size_t bytes_read = 0; + while(1) { + uint32_t flags = ThreadEventCustomDataRx; + if(furi_stream_buffer_is_empty(uart->rx_stream)) { + flags = js_flags_wait(uart->mjs, ThreadEventCustomDataRx, timeout); + } + if(flags == 0) { // Timeout + break; + } else if(flags & ThreadEventStop) { // Exit flag + bytes_read = 0; + break; + } else if(flags & ThreadEventCustomDataRx) { // New data received + size_t rx_len = + furi_stream_buffer_receive(uart->rx_stream, &buf[bytes_read], len - bytes_read, 0); + bytes_read += rx_len; + if(bytes_read == len) { + break; + } + } + } + return bytes_read; +} + +static void js_uart_read(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + size_t read_len = 0; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + read_len = mjs_get_int32(mjs, arg); + } else if(num_args == 2) { + mjs_val_t len_arg = mjs_arg(mjs, 0); + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { + break; + } + read_len = mjs_get_int32(mjs, len_arg); + timeout = mjs_get_int32(mjs, timeout_arg); + } + } while(0); + + if(read_len == 0) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* read_buf = malloc(read_len); + size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_string(mjs, read_buf, bytes_read, true); + } + mjs_return(mjs, return_obj); + free(read_buf); +} + +static void js_uart_readln(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + bool args_correct = false; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args > 1) { + break; + } else if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + timeout = mjs_get_int32(mjs, arg); + } + args_correct = true; + } while(0); + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + FuriString* rx_buf = furi_string_alloc(); + size_t bytes_read = 0; + char read_char = 0; + + while(1) { + size_t read_len = js_uart_receive(uart, &read_char, 1, timeout); + if(read_len != 1) { + break; + } + if((read_char == '\r') || (read_char == '\n')) { + break; + } else { + furi_string_push_back(rx_buf, read_char); + bytes_read++; + } + } + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_string(mjs, furi_string_get_cstr(rx_buf), bytes_read, true); + } + mjs_return(mjs, return_obj); + furi_string_free(rx_buf); +} + +static void js_uart_read_bytes(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + size_t read_len = 0; + uint32_t timeout = FuriWaitForever; + + do { + size_t num_args = mjs_nargs(mjs); + if(num_args == 1) { + mjs_val_t arg = mjs_arg(mjs, 0); + if(!mjs_is_number(arg)) { + break; + } + read_len = mjs_get_int32(mjs, arg); + } else if(num_args == 2) { + mjs_val_t len_arg = mjs_arg(mjs, 0); + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if((!mjs_is_number(len_arg)) || (!mjs_is_number(timeout_arg))) { + break; + } + read_len = mjs_get_int32(mjs, len_arg); + timeout = mjs_get_int32(mjs, timeout_arg); + } + } while(0); + + if(read_len == 0) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + char* read_buf = malloc(read_len); + size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + + mjs_val_t return_obj = MJS_UNDEFINED; + if(bytes_read > 0) { + return_obj = mjs_mk_array_buf(mjs, read_buf, bytes_read); + } + mjs_return(mjs, return_obj); + free(read_buf); +} + +static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + if((str_len == 0) || (arg_str == NULL)) { + return false; + } + PatternArrayItem* item = PatternArray_push_new(patterns); + item->data = malloc(str_len + 1); + memcpy(item->data, arg_str, str_len); + item->len = str_len; + return true; +} + +static bool js_uart_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { + size_t array_len = mjs_array_length(mjs, arg); + if(array_len == 0) { + return false; + } + char* array_data = malloc(array_len + 1); + + for(size_t i = 0; i < array_len; i++) { + mjs_val_t array_arg = mjs_array_get(mjs, arg, i); + if(!mjs_is_number(array_arg)) { + free(array_data); + return false; + } + + uint32_t byte_val = mjs_get_int32(mjs, array_arg); + if(byte_val > 0xFF) { + free(array_data); + return false; + } + array_data[i] = byte_val; + } + + PatternArrayItem* item = PatternArray_push_new(patterns); + item->data = array_data; + item->len = array_len; + return true; +} + +static bool + js_uart_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) { + size_t num_args = mjs_nargs(mjs); + if(num_args == 2) { + mjs_val_t timeout_arg = mjs_arg(mjs, 1); + if(!mjs_is_number(timeout_arg)) { + return false; + } + *timeout = mjs_get_int32(mjs, timeout_arg); + } else if(num_args != 1) { + return false; + } + mjs_val_t patterns_arg = mjs_arg(mjs, 0); + if(mjs_is_string(patterns_arg)) { // Single string pattern + if(!js_uart_expect_parse_string(mjs, patterns_arg, patterns)) { + return false; + } + } else if(mjs_is_array(patterns_arg)) { + size_t array_len = mjs_array_length(mjs, patterns_arg); + if(array_len == 0) { + return false; + } + mjs_val_t array_arg = mjs_array_get(mjs, patterns_arg, 0); + + if(mjs_is_number(array_arg)) { // Binary array pattern + if(!js_uart_expect_parse_array(mjs, patterns_arg, patterns)) { + return false; + } + } else if((mjs_is_string(array_arg)) || (mjs_is_array(array_arg))) { // Multiple patterns + for(size_t i = 0; i < array_len; i++) { + mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i); + + if(mjs_is_string(arg)) { + if(!js_uart_expect_parse_string(mjs, arg, patterns)) { + return false; + } + } else if(mjs_is_array(arg)) { + if(!js_uart_expect_parse_array(mjs, arg, patterns)) { + return false; + } + } + } + } else { + return false; + } + } else { + return false; + } + return true; +} + +static int32_t + js_uart_expect_check_pattern_start(PatternArray_t patterns, char value, int32_t pattern_last) { + size_t array_len = PatternArray_size(patterns); + if((pattern_last + 1) >= (int32_t)array_len) { + return (-1); + } + for(size_t i = pattern_last + 1; i < array_len; i++) { + if(PatternArray_get(patterns, i)->data[0] == value) { + return i; + } + } + return (-1); +} + +static void js_uart_expect(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); + furi_assert(uart); + if(!uart->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + uint32_t timeout = FuriWaitForever; + PatternArray_t patterns; + PatternArray_it_t it; + PatternArray_init(patterns); + + if(!js_uart_expect_parse_args(mjs, patterns, &timeout)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); + free(item->data); + } + PatternArray_clear(patterns); + return; + } + + size_t pattern_len_max = 0; + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); + if(item->len > pattern_len_max) { + pattern_len_max = item->len; + } + } + + char* compare_buf = malloc(pattern_len_max); + int32_t pattern_found = -1; + int32_t pattern_candidate = -1; + size_t buf_len = 0; + bool is_timeout = false; + + while(1) { + if(buf_len == 0) { + // Empty buffer - read by 1 byte to find pattern start + size_t bytes_read = js_uart_receive(uart, &compare_buf[0], 1, timeout); + if(bytes_read != 1) { + is_timeout = true; + break; + } + pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[0], -1); + if(pattern_candidate == -1) { + continue; + } + buf_len = 1; + } + assert(pattern_candidate >= 0); + + // Read next and try to find pattern match + PatternArrayItem* pattern_cur = PatternArray_get(patterns, pattern_candidate); + pattern_found = pattern_candidate; + for(size_t i = 0; i < pattern_cur->len; i++) { + if(i >= buf_len) { + size_t bytes_read = js_uart_receive(uart, &compare_buf[i], 1, timeout); + if(bytes_read != 1) { + is_timeout = true; + break; + } + buf_len++; + } + if(compare_buf[i] != pattern_cur->data[i]) { + pattern_found = -1; + break; + } + } + if((is_timeout) || (pattern_found >= 0)) { + break; + } + + // Search other patterns with the same start char + pattern_candidate = + js_uart_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate); + if(pattern_candidate >= 0) { + continue; + } + + // Look for another pattern start + for(size_t i = 1; i < buf_len; i++) { + pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[i], -1); + if(pattern_candidate >= 0) { + memmove(&compare_buf[0], &compare_buf[i], buf_len - i); + buf_len -= i; + break; + } + } + if(pattern_candidate >= 0) { + continue; + } + // Nothing found - reset buffer + buf_len = 0; + } + + if(is_timeout) { + FURI_LOG_W(TAG, "Expect: timeout"); + } + + for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { + const PatternArrayItem* item = PatternArray_cref(it); + free(item->data); + } + PatternArray_clear(patterns); + free(compare_buf); + + if(pattern_found >= 0) { + mjs_return(mjs, mjs_mk_number(mjs, pattern_found)); + } else { + mjs_return(mjs, MJS_UNDEFINED); + } +} + +static void* js_uart_create(struct mjs* mjs, mjs_val_t* object) { + JsUartInst* js_uart = malloc(sizeof(JsUartInst)); + js_uart->mjs = mjs; + mjs_val_t uart_obj = mjs_mk_object(mjs); + mjs_set(mjs, uart_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_uart)); + mjs_set(mjs, uart_obj, "setup", ~0, MJS_MK_FN(js_uart_setup)); + mjs_set(mjs, uart_obj, "write", ~0, MJS_MK_FN(js_uart_write)); + mjs_set(mjs, uart_obj, "read", ~0, MJS_MK_FN(js_uart_read)); + mjs_set(mjs, uart_obj, "readln", ~0, MJS_MK_FN(js_uart_readln)); + mjs_set(mjs, uart_obj, "readBytes", ~0, MJS_MK_FN(js_uart_read_bytes)); + mjs_set(mjs, uart_obj, "expect", ~0, MJS_MK_FN(js_uart_expect)); + *object = uart_obj; + + return js_uart; +} + +static void js_uart_destroy(void* inst) { + JsUartInst* js_uart = inst; + if(js_uart->setup_done) { + furi_hal_serial_async_rx_stop(js_uart->serial_handle); + furi_hal_serial_deinit(js_uart->serial_handle); + furi_hal_serial_control_release(js_uart->serial_handle); + js_uart->serial_handle = NULL; + } + + furi_stream_buffer_free(js_uart->rx_stream); + free(js_uart); +} + +static const JsModuleDescriptor js_uart_desc = { + "uart", + js_uart_create, + js_uart_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_uart_desc, +}; + +const FlipperAppPluginDescriptor* js_uart_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/plugin_api/app_api_interface.h b/applications/system/js_app/plugin_api/app_api_interface.h new file mode 100644 index 00000000000..d0db44c4aa0 --- /dev/null +++ b/applications/system/js_app/plugin_api/app_api_interface.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +/* + * Resolver interface with private application's symbols. + * Implementation is contained in app_api_table.c + */ +extern const ElfApiInterface* const application_api_interface; \ No newline at end of file diff --git a/applications/system/js_app/plugin_api/app_api_table.cpp b/applications/system/js_app/plugin_api/app_api_table.cpp new file mode 100644 index 00000000000..aacfb8c181d --- /dev/null +++ b/applications/system/js_app/plugin_api/app_api_table.cpp @@ -0,0 +1,27 @@ +#include +#include + +/* + * This file contains an implementation of a symbol table + * with private app's symbols. It is used by composite API resolver + * to load plugins that use internal application's APIs. + */ +#include "app_api_table_i.h" + +static_assert(!has_hash_collisions(app_api_table), "Detected API method hash collision!"); + +constexpr HashtableApiInterface applicaton_hashtable_api_interface{ + { + .api_version_major = 0, + .api_version_minor = 0, + /* generic resolver using pre-sorted array */ + .resolver_callback = &elf_resolve_from_hashtable, + }, + /* pointers to application's API table boundaries */ + .table_cbegin = app_api_table.cbegin(), + .table_cend = app_api_table.cend(), +}; + +/* Casting to generic resolver to use in Composite API resolver */ +extern "C" const ElfApiInterface* const application_api_interface = + &applicaton_hashtable_api_interface; diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h new file mode 100644 index 00000000000..8f1ebd5f4b3 --- /dev/null +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -0,0 +1,13 @@ +#include +#include "js_plugin_api.h" +/* + * A list of app's private functions and objects to expose for plugins. + * It is used to generate a table of symbols for import resolver to use. + * TBD: automatically generate this table from app's header files + */ +static constexpr auto app_api_table = sort(create_array_t( + API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), + API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), + API_VARIABLE(I_Certification1_103x56, const Icon), + API_VARIABLE(I_Certification2_46x33, const Icon))); \ No newline at end of file diff --git a/applications/system/js_app/plugin_api/js_plugin_api.h b/applications/system/js_app/plugin_api/js_plugin_api.h new file mode 100644 index 00000000000..4918605876d --- /dev/null +++ b/applications/system/js_app/plugin_api/js_plugin_api.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool js_delay_with_flags(struct mjs* mjs, uint32_t time); + +void js_flags_set(struct mjs* mjs, uint32_t flags); + +uint32_t js_flags_wait(struct mjs* mjs, uint32_t flags, uint32_t timeout); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/system/js_app/views/console_font.h b/applications/system/js_app/views/console_font.h new file mode 100644 index 00000000000..d22110aee3f --- /dev/null +++ b/applications/system/js_app/views/console_font.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +/* + Fontname: -misc-spleen-medium-r-normal--8-80-72-72-C-50-ISO10646-1 + Copyright: Copyright (c) 2018-2022, Frederic Cambus + Glyphs: 96/472 + BBX Build Mode: 2 +*/ +static const uint8_t u8g2_font_spleen5x8_mr[] = + "`\2\3\2\3\4\1\1\4\5\10\0\377\6\377\7\377\1\77\2\217\3\325 \6\305\372\274\2!\10\305" + "Zaw(\7\42\12\305:\245$JrV\0#\15\305\332I\62(\245$\31\224\62\0$\13\305Z" + "\331R\23\65e\214\0%\15\305zI\224\24\263\60)%!\0&\16\305ZY\22%\221\224$R\244" + "\244\0'\7\305Za\235\31(\10\305z\215\255\25\0)\10\305:i\261\255\6*\13\305\372X\24I" + "C$\225\1+\12\305\372h\30\15R\230\3,\10\305\372\314a\226\1-\10\305\372\344!'\1.\7" + "\305\372\34s\0/\13\305za\26fa\26\206\0\60\12\305\332R%\261\224\42\35\61\10\305\372\231\330" + "\66\3\62\12\305\332R\61\222\302!\6\63\12\305\332R-M\242H\7\64\14\305\272a\22%\321\220\205" + "\71\0\65\12\305\272C\22\256a\262\3\66\12\305\332R\70U\242H\7\67\13\305\272C\22\205Y\61G" + "\0\70\12\305\332RI\252D\221\16\71\12\305\332R%\212\306H\7:\10\305\372\264\34\317\1;\11\305" + "\372\264\34\12\263\14<\11\305\372HVL\313\0=\11\305\372\224!\36r\20>\11\305\332i\61\253#" + "\0\77\12\305:R\61\253C\71\2@\13\305\332R%Q\22%\235\1A\14\305\332R%J\206$J" + "\242\30B\12\305\272Se\252D\311\16C\10\305\332K\330:\3D\14\305\272S%J\242$Jv\0" + "E\11\305\332K\70\205\351\14F\12\305\332K\30Na\16\1G\14\305\332K\230(Q\22E\63\0H" + "\16\305\272Q\22%C\22%Q\22\305\0I\10\305\332[\330\66\3J\11\305\332[\330\244#\0K\14" + "\305\272Q\22%S%J\242\30L\7\305\272a\327\31M\16\305\272Q\62$C\22%Q\22\305\0N" + "\15\305\272Q\242$JEI\224(\6O\14\305\332R%J\242$\212t\0P\13\305\272S%J\246" + "\60\207\0Q\14\305\332R%J\242$\212D\5R\13\305\272S%J\246J\24\3S\11\305\332K\252" + "\206\311\16T\10\305\272\203\24v\7U\15\305\272Q\22%Q\22%Q\64\3V\14\305\272Q\22%Q" + "\22E\232\16W\16\305\272Q\22%Q\62$C\22\305\0X\14\305\272Q\22E\232T\211b\0Y\14" + "\305\272Q\22%Q\64&;\0Z\12\305\272C\230\65\16\61\0[\10\305:S\330\343\2\134\13\305\32" + "a\32\246a\32&\0]\10\305:c\237\26\0^\11\305\372YR\313\311\0_\7\305\372\334\207\4`" + "\7\305:i\316\21a\12\305\372\240\32-Q\64\3b\14\305\32a\70U\242$Jv\0c\11\305\372" + "\340\22Vg\0d\14\305za\264DI\224D\321\14e\13\305\372\340\22%C\222\316\0f\12\305Z" + "R\230ma\35\1g\14\305\372\340\22%Q\244&\23\0h\14\305\32a\70U\242$J\242\30i\11" + "\305\372\71\42\26e\0j\11\305\372\71\24\66i\0k\13\305\32a))iIT\6l\10\305:a" + "\257\62\0m\15\305\372X\224\14\311\220DI\24\3n\14\305\372\330T\211\222(\211b\0o\13\305\372" + "\240T\211\222(\322\1p\13\305\372\330T\211\222)\14\1q\13\305\372\340\22%Q\64V\0r\12\305" + "\372\340\22%a\35\2s\11\305\372\340\222\252\311\16t\11\305:a\266\205U\31u\14\305\372X\224D" + "I\224D\321\14v\14\305\372X\224DI\24i:\0w\15\305\372X\224D\311\220\14I\24\3x\13" + "\305\372X\24iR%\212\1y\14\305\372X\224DI\24\215\311\4z\12\305\372\330\20f\265!\6{" + "\12\305ZR\230\31\253\12\0|\7\305Za\77\1}\13\305\32j\30jZ\30i\0~\11\305\372\244" + "H\321I\0\177\6\305\372\274\2\0\0\0\4\377\377\0"; diff --git a/applications/system/js_app/views/console_view.c b/applications/system/js_app/views/console_view.c new file mode 100644 index 00000000000..b87e5352847 --- /dev/null +++ b/applications/system/js_app/views/console_view.c @@ -0,0 +1,164 @@ +#include "../js_app_i.h" +#include "console_font.h" + +#define CONSOLE_LINES 8 +#define CONSOLE_CHAR_W 5 +#define LINE_BREAKS_MAX 3 +#define LINE_LEN_MAX (128 / CONSOLE_CHAR_W) + +struct JsConsoleView { + View* view; +}; + +typedef struct { + FuriString* text[CONSOLE_LINES]; +} JsConsoleViewModel; + +static void console_view_draw_callback(Canvas* canvas, void* _model) { + JsConsoleViewModel* model = _model; + + canvas_set_color(canvas, ColorBlack); + canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr); + uint8_t line_h = canvas_current_font_height(canvas); + + for(size_t i = 0; i < CONSOLE_LINES; i++) { + canvas_draw_str(canvas, 0, (i + 1) * line_h - 1, furi_string_get_cstr(model->text[i])); + if(furi_string_size(model->text[i]) > LINE_LEN_MAX) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 128 - 7, (i + 1) * line_h - 1, "..."); + canvas_set_custom_u8g2_font(canvas, u8g2_font_spleen5x8_mr); + } + } +} + +static bool console_view_input_callback(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +void console_view_push_line(JsConsoleView* console_view, const char* text, bool line_trimmed) { + with_view_model( + console_view->view, + JsConsoleViewModel * model, + { + FuriString* str_temp = model->text[0]; + for(size_t i = 0; i < CONSOLE_LINES - 1; i++) { + model->text[i] = model->text[i + 1]; + } + if(!line_trimmed) { + furi_string_printf(str_temp, "%.*s", LINE_LEN_MAX, text); + } else { + // Leave some space for dots + furi_string_printf(str_temp, "%.*s ", LINE_LEN_MAX - 1, text); + } + model->text[CONSOLE_LINES - 1] = str_temp; + }, + true); +} + +void console_view_print(JsConsoleView* console_view, const char* text) { + char line_buf[LINE_LEN_MAX + 1]; + uint8_t line_buf_cnt = 0; + uint8_t utf8_bytes_left = 0; + uint8_t line_break_cnt = 0; + bool line_trim = false; + + for(size_t i = 0; i < strlen(text); i++) { + if(text[i] & 0x80) { // UTF8 or another non-ascii character byte + if(utf8_bytes_left > 0) { + utf8_bytes_left--; + if(utf8_bytes_left == 0) { + line_buf[line_buf_cnt++] = '?'; + } + } else { + if((text[i] & 0xE0) == 0xC0) { + utf8_bytes_left = 1; + } else if((text[i] & 0xF0) == 0xE0) { + utf8_bytes_left = 2; + } else if((text[i] & 0xF8) == 0xF0) { + utf8_bytes_left = 3; + } else { + line_buf[line_buf_cnt++] = '?'; + } + } + } else { + if(utf8_bytes_left > 0) { + utf8_bytes_left = 0; + line_buf[line_buf_cnt++] = '?'; + if(line_buf_cnt >= LINE_LEN_MAX) { + line_break_cnt++; + if(line_break_cnt >= LINE_BREAKS_MAX) { + line_trim = true; + break; + } + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, false); + line_buf_cnt = 1; + line_buf[0] = ' '; + } + } + + if(text[i] == '\n') { + line_buf[line_buf_cnt] = '\0'; + line_buf_cnt = 0; + console_view_push_line(console_view, line_buf, false); + } else { + line_buf[line_buf_cnt++] = text[i]; + } + + if(line_buf_cnt >= LINE_LEN_MAX) { + line_break_cnt++; + if(line_break_cnt >= LINE_BREAKS_MAX) { + line_trim = true; + break; + } + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, false); + line_buf_cnt = 1; + line_buf[0] = ' '; + } + } + } + if(line_buf_cnt > 0) { + line_buf[line_buf_cnt] = '\0'; + console_view_push_line(console_view, line_buf, line_trim); + } +} + +JsConsoleView* console_view_alloc(void) { + JsConsoleView* console_view = malloc(sizeof(JsConsoleView)); + console_view->view = view_alloc(); + view_set_draw_callback(console_view->view, console_view_draw_callback); + view_set_input_callback(console_view->view, console_view_input_callback); + view_allocate_model(console_view->view, ViewModelTypeLocking, sizeof(JsConsoleViewModel)); + + with_view_model( + console_view->view, + JsConsoleViewModel * model, + { + for(size_t i = 0; i < CONSOLE_LINES; i++) { + model->text[i] = furi_string_alloc(); + } + }, + true); + return console_view; +} + +void console_view_free(JsConsoleView* console_view) { + with_view_model( + console_view->view, + JsConsoleViewModel * model, + { + for(size_t i = 0; i < CONSOLE_LINES; i++) { + furi_string_free(model->text[i]); + } + }, + false); + view_free(console_view->view); + free(console_view); +} + +View* console_view_get_view(JsConsoleView* console_view) { + return console_view->view; +} \ No newline at end of file diff --git a/applications/system/js_app/views/console_view.h b/applications/system/js_app/views/console_view.h new file mode 100644 index 00000000000..9fcd1a3dce9 --- /dev/null +++ b/applications/system/js_app/views/console_view.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef struct JsConsoleView JsConsoleView; + +JsConsoleView* console_view_alloc(void); + +void console_view_free(JsConsoleView* console_view); + +View* console_view_get_view(JsConsoleView* console_view); + +void console_view_print(JsConsoleView* console_view, const char* text); diff --git a/assets/icons/Archive/js_script_10px.png b/assets/icons/Archive/js_script_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..77ac76337ea8a76928e45b79335fae7a7602adac GIT binary patch literal 3635 zcmaJ@c|4Te+rMpvB3VN+jd&`{EcVIRmr-MBY@-rmW-!dw%wR^5l2Wo|%bHM9Lz^Pm zDnhm>63UV#p&=nzddKs7dVcR8Z}0ubIoCP&_xruBbDisaKG#KOM>`2|C2;@%BEIG^o4aN2?EQ4| z833%t6>XC8%$K?X;Lb@&n>R$qh`Y8WILcjki4a z7ij>1Ss@NPf_2YqRp2fS5CrJmI(wuWhkr#?I%{|Upd?@jEIqMBAQ1yJ>9{t@0!=%B zdoB+N;y?-jkU6I;!N6?^VEVhgsR+^D*2`FtS<%2#LogZZr1*A~P(tS^lXQ|1cCWO%KTLD06 zx%1h&ky+xz_~`Js-vpWWy&-=+XqE;x-90zBUUgYe7?5)8;PrWrjr9o88wKtK*9H|I z1^V29fWc)i-F~sWBjT^amSZ_atLD3MuRAxHsO%KqvSmiy_j)z-Q>^JP-C^2LY{Vk} z?|OgOEN~*zJfozpAZqOdjv7*miJxc@wuDiaCe+p&qff8J@9P}VyMIhe?$R9gVudr< zp^@_hlVrWSIdw#O@-%!-HEm5SIaC7Ro_DHqe7yNl3%M@htAJpvB(^IAFprqmzO1w* z36S#FgL9SuAh~uq!CX@SXiG2r1OQc^cO1KwqJVxW3INu5XZGK-*u3?bEVoWF`6ta02i^Ph?RO(6KC)k{@&u#__o4hy{%3^spqOI7L8%RFW$OK+Q3WLkbur&8H2 zWaQ)gqRvB~gIPfMrFeW~Crr}s@_XE|%NJ~XE@G858s+h0Sbg^deY^_RFhowdE*HlW z-6I$qDKjm4{G8Nt^4Y{hJ1iP&bxVC(txUR7)I%)l9PIgD;N)9QW$Izl%k^wu#Qj99 zEmz~~?p?cc)sO!rDdU}t+9TO~Wz+; zK8WOk(v!*)wA;Lqf;5|bIfzJauyI2#8R+Wr9tK4%IM1e;Epe*Z}3x`NwI}j3zCx`I_(xH z-wPHLzVLc@ovMaVJyC;1MKa6?Kiis!{~UCcRM9zmZur*rmVnBn`hbE?`+~nJG`?Q& z|D?}WF!kfocKUMlJ`x9lOmMy=czO)+Vh1ZZEfaI^MWD zJ17@d^S-KoH7@#wO?Jb_XYQNT>GA5Q(9A$9uKm! zvPag|EJRkz<69+JCG{a$ltI}M*@S&v`xacNeX!}(Y4PdI=}j}ni?~JgMap7_O6yK( zm7W8v1E(0Q;MNc_y=tm(Snow~eesJ)4}WF<>&%>aeTAEc5?iFxL(&yoC`FfwsMo?; zQpc3OR(3slvrv%rG;5&bJ>~6(R%Ao?ofQY2Eahge7f7DP_t_=v$cffZIYn7T&*u{- zAjdFA;WQ|%x zPeew<=GEMl13z2V5`NSUbH2&XIg0oQJQElbs6L}~=AlrKkg9No@Quy-B5@*5H$k?V z%2{p|lkik!B-82?z9}!`bqD;lGCSp7iRQ>1QT_>S)j;p+*&ctsLv&Cof_8HmJ=ywL zG-k+-98;l6Qp`|T4wv1xAAVcs%i%H?)(O^{@g$^ zhK!%is)($(U2%qe;fKv!x-?5>kJ!w`(fZzcmr0yzWU;xSQOH*Q#Ip7qd6Elz1}p_YC>@&qr3OC`AR0@J&Xx}V;!km}8)b8nUY zxMy22KH}l%-F<3-L+R`Lc(DA{%oJ*>aEi)Z@~pjkusYD9`}O%*i4x_xxG`mK%E6DU z>tTTl@oi;ouiAw4ar!eyTOV`#zg;&dSPaWK?bYJGR&uAj=DkK=Pwj`$lk<{MDsl%t z@=~U&9%-^Yf~sx87pPrd&wfZy@>Q;^9gp}>VHfJukehJw#>vf#HoU1o7L7b`x3>0y z7zJYxw+n@!k7Y&D`m$y+Lm$zP?3a9QG4|}Q!JDT}escbNz5T_&i?+dx)8z}IQz(vR z7os+pt6f0JLtd}ueZE|fqjFruuBQA~1P}HJAKa*);E|h`vlltHJbE_yQxTykH^7)tCFH28DoZF;e)I_&-5m9h^Z{GzJl5 ztOL`=L!mH`A>Yz7fEyVb8yXsFf#6WME(B@-f$M9-brJf82$&w|pAXFU$RPM3FxIyJ z_~WmT-~bkjj(|WyLql~!b#-VAe+bOj*cbwZL*Q_2K0=$xp|WsnZ7Ngkw}Lg1iD!`L zEE0_h+EB##(t=q?FhAP=9D_ptSC-2BCsq8ULD)Dt1f~PsIM#2VgTwz1rBMDwGg%no zf8+f>g_*7#IuU{)GHJmKJpc0i)HYnv5mpQ$jzwd*(rD!0nRE`Iv1rTy8XW}J*MWof zZX^x{g+X;FI&fWmLl9J#0E6no_JKfq9dLLObpyQbZ#4%8gguqX!cp-=dut?^udhQQ z5fD~JmKMgAP+dc~fj$goV`Bk_8(7*{>f68#taOcF(BE8Z8a|jpq_Tc<3IFBl{gJy- zEEGE5vo(=H3Lz3~88iy$?}Q*oe~tzAN4Ys37v zu=$IBp9Yc2e-8|P;iJQK`8_wV$rOP{m<1M_=T5C8xG literal 0 HcmV?d00001 diff --git a/lib/SConscript b/lib/SConscript index 4835724e09b..95f55b37de6 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -38,6 +38,7 @@ libs = env.BuildModules( "lfrfid", "flipper_application", "music_worker", + "mjs", "nanopb", "update_util", "heatshrink", diff --git a/lib/mjs/SConscript b/lib/mjs/SConscript new file mode 100644 index 00000000000..35b45fc91e6 --- /dev/null +++ b/lib/mjs/SConscript @@ -0,0 +1,36 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/mjs", + ], + SDK_HEADERS=[ + File("mjs_core_public.h"), + File("mjs_exec_public.h"), + File("mjs_object_public.h"), + File("mjs_string_public.h"), + File("mjs_array_public.h"), + File("mjs_primitive_public.h"), + File("mjs_util_public.h"), + File("mjs_array_buf_public.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="mjs") +libenv.ApplyLibFlags() + +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + "-Wno-redundant-decls", + "-Wno-unused-function", + ], +) + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/mjs/common/cs_dbg.c b/lib/mjs/common/cs_dbg.c new file mode 100644 index 00000000000..45747b0f370 --- /dev/null +++ b/lib/mjs/common/cs_dbg.c @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_dbg.h" + +#include +#include +#include +#include + +#include "cs_time.h" +#include "str_util.h" + +enum cs_log_level cs_log_level WEAK = +#if CS_ENABLE_DEBUG + LL_VERBOSE_DEBUG; +#else + LL_ERROR; +#endif + +#if CS_ENABLE_STDIO +static char* s_file_level = NULL; + +void cs_log_set_file_level(const char* file_level) WEAK; + +FILE* cs_log_file WEAK = NULL; + +#if CS_LOG_ENABLE_TS_DIFF +double cs_log_ts WEAK; +#endif + +enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE; + +void cs_log_set_file_level(const char* file_level) { + char* fl = s_file_level; + if(file_level != NULL) { + s_file_level = strdup(file_level); + } else { + s_file_level = NULL; + } + free(fl); +} + +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + char prefix[CS_LOG_PREFIX_LEN], *q; + const char* p; + size_t fl = 0, ll = 0, pl = 0; + + if(level > cs_log_level && s_file_level == NULL) return 0; + + p = file + strlen(file); + + while(p != file) { + const char c = *(p - 1); + if(c == '/' || c == '\\') break; + p--; + fl++; + } + + ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5); + if(fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2); + + pl = fl + 1 + ll; + memcpy(prefix, p, fl); + q = prefix + pl; + memset(q, ' ', sizeof(prefix) - pl); + do { + *(--q) = '0' + (ln % 10); + ln /= 10; + } while(ln > 0); + *(--q) = ':'; + + if(s_file_level != NULL) { + enum cs_log_level pll = cs_log_level; + struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl); + struct mg_str k, v; + while((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) { + bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0); + if(!yes) continue; + pll = (enum cs_log_level)(*v.p - '0'); + break; + } + if(level > pll) return 0; + } + + if(cs_log_file == NULL) cs_log_file = stderr; + cs_log_cur_msg_level = level; + fwrite(prefix, 1, sizeof(prefix), cs_log_file); +#if CS_LOG_ENABLE_TS_DIFF + { + double now = cs_time(); + fprintf(cs_log_file, "%7u ", (unsigned int)((now - cs_log_ts) * 1000000)); + cs_log_ts = now; + } +#endif + return 1; +} + +void cs_log_printf(const char* fmt, ...) WEAK; +void cs_log_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(cs_log_file, fmt, ap); + va_end(ap); + fputc('\n', cs_log_file); + fflush(cs_log_file); + cs_log_cur_msg_level = LL_NONE; +} + +void cs_log_set_file(FILE* file) WEAK; +void cs_log_set_file(FILE* file) { + cs_log_file = file; +} + +#else + +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK; +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + (void)level; + (void)file; + (void)ln; + return 0; +} + +void cs_log_printf(const char* fmt, ...) WEAK; +void cs_log_printf(const char* fmt, ...) { + (void)fmt; +} + +void cs_log_set_file_level(const char* file_level) { + (void)file_level; +} + +#endif /* CS_ENABLE_STDIO */ + +void cs_log_set_level(enum cs_log_level level) WEAK; +void cs_log_set_level(enum cs_log_level level) { + cs_log_level = level; +#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO + cs_log_ts = cs_time(); +#endif +} diff --git a/lib/mjs/common/cs_dbg.h b/lib/mjs/common/cs_dbg.h new file mode 100644 index 00000000000..d8b68a447c7 --- /dev/null +++ b/lib/mjs/common/cs_dbg.h @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_DBG_H_ +#define CS_COMMON_CS_DBG_H_ + +#include "platform.h" + +#if CS_ENABLE_STDIO +#include +#endif + +#ifndef CS_ENABLE_DEBUG +#define CS_ENABLE_DEBUG 0 +#endif + +#ifndef CS_LOG_PREFIX_LEN +#define CS_LOG_PREFIX_LEN 24 +#endif + +#ifndef CS_LOG_ENABLE_TS_DIFF +#define CS_LOG_ENABLE_TS_DIFF 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it. + */ +enum cs_log_level { + LL_NONE = -1, + LL_ERROR = 0, + LL_WARN = 1, + LL_INFO = 2, + LL_DEBUG = 3, + LL_VERBOSE_DEBUG = 4, + + _LL_MIN = -2, + _LL_MAX = 5, +}; + +/* + * Set max log level to print; messages with the level above the given one will + * not be printed. + */ +void cs_log_set_level(enum cs_log_level level); + +/* + * A comma-separated set of prefix=level. + * prefix is matched against the log prefix exactly as printed, including line + * number, but partial match is ok. Check stops on first matching entry. + * If nothing matches, default level is used. + * + * Examples: + * main.c:=4 - everything from main C at verbose debug level. + * mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_* + * + */ +void cs_log_set_file_level(const char* file_level); + +/* + * Helper function which prints message prefix with the given `level`. + * If message should be printed (according to the current log level + * and filter), prints the prefix and returns 1, otherwise returns 0. + * + * Clients should typically just use `LOG()` macro. + */ +int cs_log_print_prefix(enum cs_log_level level, const char* fname, int line); + +extern enum cs_log_level cs_log_level; + +#if CS_ENABLE_STDIO + +/* + * Set file to write logs into. If `NULL`, logs go to `stderr`. + */ +void cs_log_set_file(FILE* file); + +/* + * Prints log to the current log file, appends "\n" in the end and flushes the + * stream. + */ +void cs_log_printf(const char* fmt, ...) PRINTF_LIKE(1, 2); + +#if CS_ENABLE_STDIO + +/* + * Format and print message `x` with the given level `l`. Example: + * + * ```c + * LOG(LL_INFO, ("my info message: %d", 123)); + * LOG(LL_DEBUG, ("my debug message: %d", 123)); + * ``` + */ +#define LOG(l, x) \ + do { \ + if(cs_log_print_prefix(l, __FILE__, __LINE__)) { \ + cs_log_printf x; \ + } \ + } while(0) + +#else + +#define LOG(l, x) ((void)l) + +#endif + +#ifndef CS_NDEBUG + +/* + * Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))` + */ +#define DBG(x) LOG(LL_VERBOSE_DEBUG, x) + +#else /* NDEBUG */ + +#define DBG(x) + +#endif + +#else /* CS_ENABLE_STDIO */ + +#define LOG(l, x) +#define DBG(x) + +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_DBG_H_ */ diff --git a/lib/mjs/common/cs_dirent.c b/lib/mjs/common/cs_dirent.c new file mode 100644 index 00000000000..892a43b72e4 --- /dev/null +++ b/lib/mjs/common/cs_dirent.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include "mg_mem.h" +#include "cs_dirent.h" + +/* + * This file contains POSIX opendir/closedir/readdir API implementation + * for systems which do not natively support it (e.g. Windows). + */ + +#ifdef _WIN32 +struct win32_dir { + DIR d; + HANDLE handle; + WIN32_FIND_DATAW info; + struct dirent result; +}; + +DIR *opendir(const char *name) { + struct win32_dir *dir = NULL; + wchar_t wpath[MAX_PATH]; + DWORD attrs; + + if (name == NULL) { + SetLastError(ERROR_BAD_ARGUMENTS); + } else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } else { + to_wchar(name, wpath, ARRAY_SIZE(wpath)); + attrs = GetFileAttributesW(wpath); + if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { + (void) wcscat(wpath, L"\\*"); + dir->handle = FindFirstFileW(wpath, &dir->info); + dir->result.d_name[0] = '\0'; + } else { + MG_FREE(dir); + dir = NULL; + } + } + + return (DIR *) dir; +} + +int closedir(DIR *d) { + struct win32_dir *dir = (struct win32_dir *) d; + int result = 0; + + if (dir != NULL) { + if (dir->handle != INVALID_HANDLE_VALUE) + result = FindClose(dir->handle) ? 0 : -1; + MG_FREE(dir); + } else { + result = -1; + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} + +struct dirent *readdir(DIR *d) { + struct win32_dir *dir = (struct win32_dir *) d; + struct dirent *result = NULL; + + if (dir) { + memset(&dir->result, 0, sizeof(dir->result)); + if (dir->handle != INVALID_HANDLE_VALUE) { + result = &dir->result; + (void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1, + result->d_name, sizeof(result->d_name), NULL, + NULL); + + if (!FindNextFileW(dir->handle, &dir->info)) { + (void) FindClose(dir->handle); + dir->handle = INVALID_HANDLE_VALUE; + } + + } else { + SetLastError(ERROR_FILE_NOT_FOUND); + } + } else { + SetLastError(ERROR_BAD_ARGUMENTS); + } + + return result; +} +#endif + +#endif /* EXCLUDE_COMMON */ + +/* ISO C requires a translation unit to contain at least one declaration */ +typedef int cs_dirent_dummy; diff --git a/lib/mjs/common/cs_dirent.h b/lib/mjs/common/cs_dirent.h new file mode 100644 index 00000000000..603af5c65a4 --- /dev/null +++ b/lib/mjs/common/cs_dirent.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_DIRENT_H_ +#define CS_COMMON_CS_DIRENT_H_ + +#include + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifdef CS_DEFINE_DIRENT +typedef struct { int dummy; } DIR; + +struct dirent { + int d_ino; +#ifdef _WIN32 + char d_name[MAX_PATH]; +#else + /* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */ + char d_name[256]; +#endif +}; + +DIR *opendir(const char *dir_name); +int closedir(DIR *dir); +struct dirent *readdir(DIR *dir); +#endif /* CS_DEFINE_DIRENT */ + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_DIRENT_H_ */ diff --git a/lib/mjs/common/cs_file.c b/lib/mjs/common/cs_file.c new file mode 100644 index 00000000000..d4aea7da2b9 --- /dev/null +++ b/lib/mjs/common/cs_file.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_file.h" + +#include +#include + +#ifdef CS_MMAP +#include +#include +#include +#endif + +#ifdef CS_MMAP +char* cs_read_file(const char* path, size_t* size) WEAK; +char* cs_read_file(const char* path, size_t* size) { + FILE* fp; + char* data = NULL; + if((fp = fopen(path, "rb")) == NULL) { + } else if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + *size = ftell(fp); + data = (char*)malloc(*size + 1); + if(data != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if(fread(data, 1, *size, fp) != *size) { + free(data); + return NULL; + } + data[*size] = '\0'; + } + fclose(fp); + } + return data; +} + +char* cs_mmap_file(const char* path, size_t* size) WEAK; +char* cs_mmap_file(const char* path, size_t* size) { + char* r; + int fd = open(path, O_RDONLY, 0); + struct stat st; + if(fd < 0) return NULL; + fstat(fd, &st); + *size = (size_t)st.st_size; + r = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if(r == MAP_FAILED) return NULL; + return r; +} +#endif diff --git a/lib/mjs/common/cs_file.h b/lib/mjs/common/cs_file.h new file mode 100644 index 00000000000..a30973389de --- /dev/null +++ b/lib/mjs/common/cs_file.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_FILE_H_ +#define CS_COMMON_CS_FILE_H_ + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * Read whole file `path` in memory. It is responsibility of the caller + * to `free()` allocated memory. File content is guaranteed to be + * '\0'-terminated. File size is returned in `size` variable, which does not + * count terminating `\0`. + * Return: allocated memory, or NULL on error. + */ +char *cs_read_file(const char *path, size_t *size); + +#ifdef CS_MMAP +/* + * Only on platforms which support mmapping: mmap file `path` to the returned + * address. File size is written to `*size`. + */ +char *cs_mmap_file(const char *path, size_t *size); +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_FILE_H_ */ diff --git a/lib/mjs/common/cs_time.c b/lib/mjs/common/cs_time.c new file mode 100644 index 00000000000..fc3fa8ffe0b --- /dev/null +++ b/lib/mjs/common/cs_time.c @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_time.h" + +#if CS_ENABLE_STDIO + +#ifndef _WIN32 +#include +/* + * There is no sys/time.h on ARMCC. + */ +#if !(defined(__ARMCC_VERSION) || defined(__ICCARM__)) && !defined(__TI_COMPILER_VERSION__) && \ + (!defined(CS_PLATFORM) || CS_PLATFORM != CS_P_NXP_LPC) +#include +#endif +#else +#include +#endif + +double cs_time(void) WEAK; +double cs_time(void) { + double now; +#ifndef _WIN32 + struct timeval tv; + if(gettimeofday(&tv, NULL /* tz */) != 0) return 0; + now = (double)tv.tv_sec + (((double)tv.tv_usec) / (double)1000000.0); +#else + SYSTEMTIME sysnow; + FILETIME ftime; + GetLocalTime(&sysnow); + SystemTimeToFileTime(&sysnow, &ftime); + /* + * 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64 + * This should not cause a problems in this (21th) century + * 2. Windows FILETIME is a number of 100-nanosecond intervals since January + * 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC, + * thus, we need to convert to seconds and adjust amount (subtract 11644473600 + * seconds) + */ + now = (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) / + 10000000.0) - + 11644473600; +#endif /* _WIN32 */ + return now; +} + +double cs_timegm(const struct tm* tm) { + /* Month-to-day offset for non-leap-years. */ + static const int month_day[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + + /* Most of the calculation is easy; leap years are the main difficulty. */ + int month = tm->tm_mon % 12; + int year = tm->tm_year + tm->tm_mon / 12; + int year_for_leap; + int64_t rt; + + if(month < 0) { /* Negative values % 12 are still negative. */ + month += 12; + --year; + } + + /* This is the number of Februaries since 1900. */ + year_for_leap = (month > 1) ? year + 1 : year; + + rt = tm->tm_sec /* Seconds */ + + 60 * (tm->tm_min /* Minute = 60 seconds */ + + 60 * (tm->tm_hour /* Hour = 60 minutes */ + + 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */ + + 365 * (year - 70) /* Year = 365 days */ + + (year_for_leap - 69) / 4 /* Every 4 years is leap... */ + - (year_for_leap - 1) / 100 /* Except centuries... */ + + (year_for_leap + 299) / 400))); /* Except 400s. */ + return rt < 0 ? -1 : (double)rt; +} + +#endif \ No newline at end of file diff --git a/lib/mjs/common/cs_time.h b/lib/mjs/common/cs_time.h new file mode 100644 index 00000000000..413528c860e --- /dev/null +++ b/lib/mjs/common/cs_time.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_TIME_H_ +#define CS_COMMON_CS_TIME_H_ + +#include + +#include "platform.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Sub-second granularity time(). */ +double cs_time(void); + +/* + * Similar to (non-standard) timegm, converts broken-down time into the number + * of seconds since Unix Epoch. + */ +double cs_timegm(const struct tm* tm); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_CS_TIME_H_ */ diff --git a/lib/mjs/common/cs_varint.c b/lib/mjs/common/cs_varint.c new file mode 100644 index 00000000000..fdd5e91a0d0 --- /dev/null +++ b/lib/mjs/common/cs_varint.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cs_varint.h" + +size_t cs_varint_llen(uint64_t num) { + size_t llen = 0; + + do { + llen++; + } while (num >>= 7); + + return llen; +} + +size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size) { + size_t llen = 0; + + do { + uint8_t byte = num & 0x7f; + num >>= 7; + if (num != 0) byte |= 0x80; + if (llen < buf_size) *buf++ = byte; + llen++; + } while (num != 0); + + return llen; +} + +bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num, + size_t *llen) { + size_t i = 0, shift = 0; + uint64_t n = 0; + + do { + if (i == buf_size || i == (8 * sizeof(*num) / 7 + 1)) return false; + /* + * Each byte of varint contains 7 bits, in little endian order. + * MSB is a continuation bit: it tells whether next byte is used. + */ + n |= ((uint64_t)(buf[i] & 0x7f)) << shift; + /* + * First we increment i, then check whether it is within boundary and + * whether decoded byte had continuation bit set. + */ + i++; + shift += 7; + } while (shift < sizeof(uint64_t) * 8 && (buf[i - 1] & 0x80)); + + *num = n; + *llen = i; + + return true; +} + +uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen) { + uint64_t v; + size_t l; + cs_varint_decode(buf, ~0, &v, &l); + *llen = l; + return v; +} diff --git a/lib/mjs/common/cs_varint.h b/lib/mjs/common/cs_varint.h new file mode 100644 index 00000000000..bef9513cfef --- /dev/null +++ b/lib/mjs/common/cs_varint.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_CS_VARINT_H_ +#define CS_COMMON_CS_VARINT_H_ + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef unsigned char uint8_t; +typedef unsigned __int64 uint64_t; +#else +#include +#include +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* Returns number of bytes required to encode `num`. */ +size_t cs_varint_llen(uint64_t num); + +/* + * Encodes `num` into `buf`. + * Returns number of bytes required to encode `num`. + * Note: return value may be greater than `buf_size` but the function will only + * write `buf_size` bytes. + */ +size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size); + +/* + * Decodes varint stored in `buf`. + * Stores the number of bytes consumed into `llen`. + * If there aren't enough bytes in `buf` to decode a number, returns false. + */ +bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num, + size_t *llen); + +uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_CS_VARINT_H_ */ diff --git a/lib/mjs/common/frozen/frozen.c b/lib/mjs/common/frozen/frozen.c new file mode 100644 index 00000000000..923e6a556b4 --- /dev/null +++ b/lib/mjs/common/frozen/frozen.c @@ -0,0 +1,1528 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka + * Copyright (c) 2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */ + +#include "frozen.h" + +#include +#include +#include +#include +#include + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifdef _WIN32 +#undef snprintf +#undef vsnprintf +#define snprintf cs_win_snprintf +#define vsnprintf cs_win_vsnprintf +int cs_win_snprintf(char* str, size_t size, const char* format, ...); +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap); +#if _MSC_VER >= 1700 +#include +#else +typedef _int64 int64_t; +typedef unsigned _int64 uint64_t; +#endif +#define PRId64 "I64d" +#define PRIu64 "I64u" +#else /* _WIN32 */ +/* wants this for C++ */ +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#endif /* _WIN32 */ + +#ifndef INT64_FMT +#define INT64_FMT PRId64 +#endif +#ifndef UINT64_FMT +#define UINT64_FMT PRIu64 +#endif + +#ifndef va_copy +#define va_copy(x, y) x = y +#endif + +#ifndef JSON_ENABLE_ARRAY +#define JSON_ENABLE_ARRAY 1 +#endif + +struct frozen { + const char* end; + const char* cur; + + const char* cur_name; + size_t cur_name_len; + + /* For callback API */ + char path[JSON_MAX_PATH_LEN]; + size_t path_len; + void* callback_data; + json_walk_callback_t callback; +}; + +struct fstate { + const char* ptr; + size_t path_len; +}; + +#define SET_STATE(fr, ptr, str, len) \ + struct fstate fstate = {(ptr), (fr)->path_len}; \ + json_append_to_path((fr), (str), (len)); + +#define CALL_BACK(fr, tok, value, len) \ + do { \ + if((fr)->callback && ((fr)->path_len == 0 || (fr)->path[(fr)->path_len - 1] != '.')) { \ + struct json_token t = {(value), (int)(len), (tok)}; \ + \ + /* Call the callback with the given value and current name */ \ + (fr)->callback( \ + (fr)->callback_data, (fr)->cur_name, (fr)->cur_name_len, (fr)->path, &t); \ + \ + /* Reset the name */ \ + (fr)->cur_name = NULL; \ + (fr)->cur_name_len = 0; \ + } \ + } while(0) + +static int json_append_to_path(struct frozen* f, const char* str, int size) { + int n = f->path_len; + int left = sizeof(f->path) - n - 1; + if(size > left) size = left; + memcpy(f->path + n, str, size); + f->path[n + size] = '\0'; + f->path_len += size; + return n; +} + +static void json_truncate_path(struct frozen* f, size_t len) { + f->path_len = len; + f->path[len] = '\0'; +} + +static int json_parse_object(struct frozen* f); +static int json_parse_value(struct frozen* f); + +#define EXPECT(cond, err_code) \ + do { \ + if(!(cond)) return (err_code); \ + } while(0) + +#define TRY(expr) \ + do { \ + int _n = expr; \ + if(_n < 0) return _n; \ + } while(0) + +#define END_OF_STRING (-1) + +static int json_left(const struct frozen* f) { + return f->end - f->cur; +} + +static int json_isspace(int ch) { + return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; +} + +static void json_skip_whitespaces(struct frozen* f) { + while(f->cur < f->end && json_isspace(*f->cur)) f->cur++; +} + +static int json_cur(struct frozen* f) { + json_skip_whitespaces(f); + return f->cur >= f->end ? END_OF_STRING : *(unsigned char*)f->cur; +} + +static int json_test_and_skip(struct frozen* f, int expected) { + int ch = json_cur(f); + if(ch == expected) { + f->cur++; + return 0; + } + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; +} + +static int json_isalpha(int ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); +} + +static int json_isdigit(int ch) { + return ch >= '0' && ch <= '9'; +} + +static int json_isxdigit(int ch) { + return json_isdigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'); +} + +static int json_get_escape_len(const char* s, int len) { + switch(*s) { + case 'u': + return len < 6 ? JSON_STRING_INCOMPLETE : + json_isxdigit(s[1]) && json_isxdigit(s[2]) && json_isxdigit(s[3]) && + json_isxdigit(s[4]) ? + 5 : + JSON_STRING_INVALID; + case '"': + case '\\': + case '/': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + return len < 2 ? JSON_STRING_INCOMPLETE : 1; + default: + return JSON_STRING_INVALID; + } +} + +/* identifier = letter { letter | digit | '_' } */ +static int json_parse_identifier(struct frozen* f) { + EXPECT(json_isalpha(json_cur(f)), JSON_STRING_INVALID); + { + SET_STATE(f, f->cur, "", 0); + while(f->cur < f->end && + (*f->cur == '_' || json_isalpha(*f->cur) || json_isdigit(*f->cur))) { + f->cur++; + } + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int json_get_utf8_char_len(unsigned char ch) { + if((ch & 0x80) == 0) return 1; + switch(ch & 0xf0) { + case 0xf0: + return 4; + case 0xe0: + return 3; + default: + return 2; + } +} + +/* string = '"' { quoted_printable_chars } '"' */ +static int json_parse_string(struct frozen* f) { + int n, ch = 0, len = 0; + TRY(json_test_and_skip(f, '"')); + { + SET_STATE(f, f->cur, "", 0); + for(; f->cur < f->end; f->cur += len) { + ch = *(unsigned char*)f->cur; + len = json_get_utf8_char_len((unsigned char)ch); + EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ + EXPECT(len <= json_left(f), JSON_STRING_INCOMPLETE); + if(ch == '\\') { + EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n); + len += n; + } else if(ch == '"') { + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); + f->cur++; + break; + }; + } + } + return ch == '"' ? 0 : JSON_STRING_INCOMPLETE; +} + +/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */ +static int json_parse_number(struct frozen* f) { + int ch = json_cur(f); + SET_STATE(f, f->cur, "", 0); + if(ch == '-') f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + if(f->cur + 1 < f->end && f->cur[0] == '0' && f->cur[1] == 'x') { + f->cur += 2; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++; + } else { + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + if(f->cur < f->end && f->cur[0] == '.') { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + } + if(f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { + f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + if((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; + EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); + EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); + while(f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; + } + } + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); + return 0; +} + +#if JSON_ENABLE_ARRAY +/* array = '[' [ value { ',' value } ] ']' */ +static int json_parse_array(struct frozen* f) { + int i = 0, current_path_len; + char buf[20]; + CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); + TRY(json_test_and_skip(f, '[')); + { + { + SET_STATE(f, f->cur - 1, "", 0); + while(json_cur(f) != ']') { + snprintf(buf, sizeof(buf), "[%d]", i); + i++; + current_path_len = json_append_to_path(f, buf, strlen(buf)); + f->cur_name = f->path + strlen(f->path) - strlen(buf) + 1 /*opening brace*/; + f->cur_name_len = strlen(buf) - 2 /*braces*/; + TRY(json_parse_value(f)); + json_truncate_path(f, current_path_len); + if(json_cur(f) == ',') f->cur++; + } + TRY(json_test_and_skip(f, ']')); + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); + } + } + return 0; +} +#endif /* JSON_ENABLE_ARRAY */ + +static int json_expect(struct frozen* f, const char* s, int len, enum json_token_type tok_type) { + int i, n = json_left(f); + SET_STATE(f, f->cur, "", 0); + for(i = 0; i < len; i++) { + if(i >= n) return JSON_STRING_INCOMPLETE; + if(f->cur[i] != s[i]) return JSON_STRING_INVALID; + } + f->cur += len; + json_truncate_path(f, fstate.path_len); + + CALL_BACK(f, tok_type, fstate.ptr, f->cur - fstate.ptr); + + return 0; +} + +/* value = 'null' | 'true' | 'false' | number | string | array | object */ +static int json_parse_value(struct frozen* f) { + int ch = json_cur(f); + + switch(ch) { + case '"': + TRY(json_parse_string(f)); + break; + case '{': + TRY(json_parse_object(f)); + break; +#if JSON_ENABLE_ARRAY + case '[': + TRY(json_parse_array(f)); + break; +#endif + case 'n': + TRY(json_expect(f, "null", 4, JSON_TYPE_NULL)); + break; + case 't': + TRY(json_expect(f, "true", 4, JSON_TYPE_TRUE)); + break; + case 'f': + TRY(json_expect(f, "false", 5, JSON_TYPE_FALSE)); + break; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + TRY(json_parse_number(f)); + break; + default: + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + + return 0; +} + +/* key = identifier | string */ +static int json_parse_key(struct frozen* f) { + int ch = json_cur(f); + if(json_isalpha(ch)) { + TRY(json_parse_identifier(f)); + } else if(ch == '"') { + TRY(json_parse_string(f)); + } else { + return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; + } + return 0; +} + +/* pair = key ':' value */ +static int json_parse_pair(struct frozen* f) { + int current_path_len; + const char* tok; + json_skip_whitespaces(f); + tok = f->cur; + TRY(json_parse_key(f)); + { + f->cur_name = *tok == '"' ? tok + 1 : tok; + f->cur_name_len = *tok == '"' ? f->cur - tok - 2 : f->cur - tok; + current_path_len = json_append_to_path(f, f->cur_name, f->cur_name_len); + } + TRY(json_test_and_skip(f, ':')); + TRY(json_parse_value(f)); + json_truncate_path(f, current_path_len); + return 0; +} + +/* object = '{' pair { ',' pair } '}' */ +static int json_parse_object(struct frozen* f) { + CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); + TRY(json_test_and_skip(f, '{')); + { + SET_STATE(f, f->cur - 1, ".", 1); + while(json_cur(f) != '}') { + TRY(json_parse_pair(f)); + if(json_cur(f) == ',') f->cur++; + } + TRY(json_test_and_skip(f, '}')); + json_truncate_path(f, fstate.path_len); + CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); + } + return 0; +} + +static int json_doit(struct frozen* f) { + if(f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID; + if(f->end == f->cur) return JSON_STRING_INCOMPLETE; + return json_parse_value(f); +} + +int json_escape(struct json_out* out, const char* p, size_t len) WEAK; +int json_escape(struct json_out* out, const char* p, size_t len) { + size_t i, cl, n = 0; + const char* hex_digits = "0123456789abcdef"; + const char* specials = "btnvfr"; + + for(i = 0; i < len; i++) { + unsigned char ch = ((unsigned char*)p)[i]; + if(ch == '"' || ch == '\\') { + n += out->printer(out, "\\", 1); + n += out->printer(out, p + i, 1); + } else if(ch >= '\b' && ch <= '\r') { + n += out->printer(out, "\\", 1); + n += out->printer(out, &specials[ch - '\b'], 1); + } else if(isprint(ch)) { + n += out->printer(out, p + i, 1); + } else if((cl = json_get_utf8_char_len(ch)) == 1) { + n += out->printer(out, "\\u00", 4); + n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1); + n += out->printer(out, &hex_digits[ch % 0xf], 1); + } else { + n += out->printer(out, p + i, cl); + i += cl - 1; + } + } + + return n; +} + +int json_printer_buf(struct json_out* out, const char* buf, size_t len) WEAK; +int json_printer_buf(struct json_out* out, const char* buf, size_t len) { + size_t avail = out->u.buf.size - out->u.buf.len; + size_t n = len < avail ? len : avail; + memcpy(out->u.buf.buf + out->u.buf.len, buf, n); + out->u.buf.len += n; + if(out->u.buf.size > 0) { + size_t idx = out->u.buf.len; + if(idx >= out->u.buf.size) idx = out->u.buf.size - 1; + out->u.buf.buf[idx] = '\0'; + } + return len; +} + +int json_printer_file(struct json_out* out, const char* buf, size_t len) WEAK; +int json_printer_file(struct json_out* out, const char* buf, size_t len) { + return fwrite(buf, 1, len, out->u.fp); +} + +#if JSON_ENABLE_BASE64 +static int b64idx(int c) { + if(c < 26) { + return c + 'A'; + } else if(c < 52) { + return c - 26 + 'a'; + } else if(c < 62) { + return c - 52 + '0'; + } else { + return c == 62 ? '+' : '/'; + } +} + +static int b64rev(int c) { + if(c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if(c >= 'a' && c <= 'z') { + return c + 26 - 'a'; + } else if(c >= '0' && c <= '9') { + return c + 52 - '0'; + } else if(c == '+') { + return 62; + } else if(c == '/') { + return 63; + } else { + return 64; + } +} + +static int b64enc(struct json_out* out, const unsigned char* p, int n) { + char buf[4]; + int i, len = 0; + for(i = 0; i < n; i += 3) { + int a = p[i], b = i + 1 < n ? p[i + 1] : 0, c = i + 2 < n ? p[i + 2] : 0; + buf[0] = b64idx(a >> 2); + buf[1] = b64idx((a & 3) << 4 | (b >> 4)); + buf[2] = b64idx((b & 15) << 2 | (c >> 6)); + buf[3] = b64idx(c & 63); + if(i + 1 >= n) buf[2] = '='; + if(i + 2 >= n) buf[3] = '='; + len += out->printer(out, buf, sizeof(buf)); + } + return len; +} + +static int b64dec(const char* src, int n, char* dst) { + const char* end = src + n; + int len = 0; + while(src + 3 < end) { + int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]), d = b64rev(src[3]); + dst[len++] = (a << 2) | (b >> 4); + if(src[2] != '=') { + dst[len++] = (b << 4) | (c >> 2); + if(src[3] != '=') { + dst[len++] = (c << 6) | d; + } + } + src += 4; + } + return len; +} +#endif /* JSON_ENABLE_BASE64 */ + +static unsigned char hexdec(const char* s) { +#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') + int a = tolower(*(const unsigned char*)s); + int b = tolower(*(const unsigned char*)(s + 1)); + return (HEXTOI(a) << 4) | HEXTOI(b); +} + +int json_vprintf(struct json_out* out, const char* fmt, va_list xap) WEAK; +int json_vprintf(struct json_out* out, const char* fmt, va_list xap) { + int len = 0; + const char *quote = "\"", *null = "null"; + va_list ap; + va_copy(ap, xap); + + while(*fmt != '\0') { + if(strchr(":, \r\n\t[]{}\"", *fmt) != NULL) { + len += out->printer(out, fmt, 1); + fmt++; + } else if(fmt[0] == '%') { + char buf[21]; + size_t skip = 2; + + if(fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) { + int64_t val = va_arg(ap, int64_t); + const char* fmt2 = fmt[3] == 'u' ? "%" UINT64_FMT : "%" INT64_FMT; + snprintf(buf, sizeof(buf), fmt2, val); + len += out->printer(out, buf, strlen(buf)); + skip += 2; + } else if(fmt[1] == 'z' && fmt[2] == 'u') { + size_t val = va_arg(ap, size_t); + snprintf(buf, sizeof(buf), "%lu", (unsigned long)val); + len += out->printer(out, buf, strlen(buf)); + skip += 1; + } else if(fmt[1] == 'M') { + json_printf_callback_t f = va_arg(ap, json_printf_callback_t); + len += f(out, &ap); + } else if(fmt[1] == 'B') { + int val = va_arg(ap, int); + const char* str = val ? "true" : "false"; + len += out->printer(out, str, strlen(str)); + } else if(fmt[1] == 'H') { +#if JSON_ENABLE_HEX + const char* hex = "0123456789abcdef"; + int i, n = va_arg(ap, int); + const unsigned char* p = va_arg(ap, const unsigned char*); + len += out->printer(out, quote, 1); + for(i = 0; i < n; i++) { + len += out->printer(out, &hex[(p[i] >> 4) & 0xf], 1); + len += out->printer(out, &hex[p[i] & 0xf], 1); + } + len += out->printer(out, quote, 1); +#endif /* JSON_ENABLE_HEX */ + } else if(fmt[1] == 'V') { +#if JSON_ENABLE_BASE64 + const unsigned char* p = va_arg(ap, const unsigned char*); + int n = va_arg(ap, int); + len += out->printer(out, quote, 1); + len += b64enc(out, p, n); + len += out->printer(out, quote, 1); +#endif /* JSON_ENABLE_BASE64 */ + } else if(fmt[1] == 'Q' || (fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 'Q')) { + size_t l = 0; + const char* p; + + if(fmt[1] == '.') { + l = (size_t)va_arg(ap, int); + skip += 2; + } + p = va_arg(ap, char*); + + if(p == NULL) { + len += out->printer(out, null, 4); + } else { + if(fmt[1] == 'Q') { + l = strlen(p); + } + len += out->printer(out, quote, 1); + len += json_escape(out, p, l); + len += out->printer(out, quote, 1); + } + } else { + /* + * we delegate printing to the system printf. + * The goal here is to delegate all modifiers parsing to the system + * printf, as you can see below we still have to parse the format + * types. + * + * Currently, %s with strings longer than 20 chars will require + * double-buffering (an auxiliary buffer will be allocated from heap). + * TODO(dfrank): reimplement %s and %.*s in order to avoid that. + */ + + const char* end_of_format_specifier = "sdfFeEgGlhuIcx.*-0123456789"; + int n = strspn(fmt + 1, end_of_format_specifier); + char* pbuf = buf; + int need_len, size = sizeof(buf); + char fmt2[20]; + va_list ap_copy; + strncpy(fmt2, fmt, n + 1 > (int)sizeof(fmt2) ? sizeof(fmt2) : (size_t)n + 1); + fmt2[n + 1] = '\0'; + + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + + if(need_len < 0) { + /* + * Windows & eCos vsnprintf implementation return -1 on overflow + * instead of needed size. + */ + pbuf = NULL; + while(need_len < 0) { + free(pbuf); + size *= 2; + if((pbuf = (char*)malloc(size)) == NULL) break; + va_copy(ap_copy, ap); + need_len = vsnprintf(pbuf, size, fmt2, ap_copy); + va_end(ap_copy); + } + } else if(need_len >= (int)sizeof(buf)) { + /* + * resulting string doesn't fit into a stack-allocated buffer `buf`, + * so we need to allocate a new buffer from heap and use it + */ + if((pbuf = (char*)malloc(need_len + 1)) != NULL) { + va_copy(ap_copy, ap); + vsnprintf(pbuf, need_len + 1, fmt2, ap_copy); + va_end(ap_copy); + } + } + if(pbuf == NULL) { + buf[0] = '\0'; + pbuf = buf; + } + + /* + * however we need to parse the type ourselves in order to advance + * the va_list by the correct amount; there is no portable way to + * inherit the advancement made by vprintf. + * 32-bit (linux or windows) passes va_list by value. + */ + if((n + 1 == strlen("%" PRId64) && strcmp(fmt2, "%" PRId64) == 0) || + (n + 1 == strlen("%" PRIu64) && strcmp(fmt2, "%" PRIu64) == 0)) { + (void)va_arg(ap, int64_t); + } else if(strcmp(fmt2, "%.*s") == 0) { + (void)va_arg(ap, int); + (void)va_arg(ap, char*); + } else { + switch(fmt2[n]) { + case 'u': + case 'd': + (void)va_arg(ap, int); + break; + case 'g': + case 'f': + (void)va_arg(ap, double); + break; + case 'p': + (void)va_arg(ap, void*); + break; + default: + /* many types are promoted to int */ + (void)va_arg(ap, int); + } + } + + len += out->printer(out, pbuf, strlen(pbuf)); + skip = n + 1; + + /* If buffer was allocated from heap, free it */ + if(pbuf != buf) { + free(pbuf); + pbuf = NULL; + } + } + fmt += skip; + } else if(*fmt == '_' || json_isalpha(*fmt)) { + len += out->printer(out, quote, 1); + while(*fmt == '_' || json_isalpha(*fmt) || json_isdigit(*fmt)) { + len += out->printer(out, fmt, 1); + fmt++; + } + len += out->printer(out, quote, 1); + } else { + len += out->printer(out, fmt, 1); + fmt++; + } + } + va_end(ap); + + return len; +} + +int json_printf(struct json_out* out, const char* fmt, ...) WEAK; +int json_printf(struct json_out* out, const char* fmt, ...) { + int n; + va_list ap; + va_start(ap, fmt); + n = json_vprintf(out, fmt, ap); + va_end(ap); + return n; +} + +int json_printf_array(struct json_out* out, va_list* ap) WEAK; +int json_printf_array(struct json_out* out, va_list* ap) { + int len = 0; + char* arr = va_arg(*ap, char*); + size_t i, arr_size = va_arg(*ap, size_t); + size_t elem_size = va_arg(*ap, size_t); + const char* fmt = va_arg(*ap, char*); + len += json_printf(out, "[", 1); + for(i = 0; arr != NULL && i < arr_size / elem_size; i++) { + union { + int64_t i; + double d; + } val; + memcpy(&val, arr + i * elem_size, elem_size > sizeof(val) ? sizeof(val) : elem_size); + if(i > 0) len += json_printf(out, ", "); + if(strpbrk(fmt, "efg") != NULL) { + len += json_printf(out, fmt, val.d); + } else { + len += json_printf(out, fmt, val.i); + } + } + len += json_printf(out, "]", 1); + return len; +} + +#ifdef _WIN32 +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) WEAK; +int cs_win_vsnprintf(char* str, size_t size, const char* format, va_list ap) { + int res = _vsnprintf(str, size, format, ap); + va_end(ap); + if(res >= size) { + str[size - 1] = '\0'; + } + return res; +} + +int cs_win_snprintf(char* str, size_t size, const char* format, ...) WEAK; +int cs_win_snprintf(char* str, size_t size, const char* format, ...) { + int res; + va_list ap; + va_start(ap, format); + res = vsnprintf(str, size, format, ap); + va_end(ap); + return res; +} +#endif /* _WIN32 */ + +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data) WEAK; +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data) { + struct frozen frozen; + + memset(&frozen, 0, sizeof(frozen)); + frozen.end = json_string + json_string_length; + frozen.cur = json_string; + frozen.callback_data = callback_data; + frozen.callback = callback; + + TRY(json_doit(&frozen)); + + return frozen.cur - json_string; +} + +struct scan_array_info { + int found; + char path[JSON_MAX_PATH_LEN]; + struct json_token* token; +}; + +static void json_scanf_array_elem_cb( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct scan_array_info* info = (struct scan_array_info*)callback_data; + + (void)name; + (void)name_len; + + if(strcmp(path, info->path) == 0) { + *info->token = *token; + info->found = 1; + } +} + +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int idx, + struct json_token* token) WEAK; +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int idx, + struct json_token* token) { + struct scan_array_info info; + info.token = token; + info.found = 0; + memset(token, 0, sizeof(*token)); + snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx); + json_walk(s, len, json_scanf_array_elem_cb, &info); + return info.found ? token->len : -1; +} + +struct json_scanf_info { + int num_conversions; + char* path; + const char* fmt; + void* target; + void* user_data; + int type; +}; + +int json_unescape(const char* src, int slen, char* dst, int dlen) WEAK; +int json_unescape(const char* src, int slen, char* dst, int dlen) { + char *send = (char*)src + slen, *dend = dst + dlen, *orig_dst = dst, *p; + const char *esc1 = "\"\\/bfnrt", *esc2 = "\"\\/\b\f\n\r\t"; + + while(src < send) { + if(*src == '\\') { + if(++src >= send) return JSON_STRING_INCOMPLETE; + if(*src == 'u') { + if(send - src < 5) return JSON_STRING_INCOMPLETE; + /* Here we go: this is a \u.... escape. Process simple one-byte chars */ + if(src[1] == '0' && src[2] == '0') { + /* This is \u00xx character from the ASCII range */ + if(dst < dend) *dst = hexdec(src + 3); + src += 4; + } else { + /* Complex \uXX XX escapes drag utf8 lib... Do it at some stage */ + return JSON_STRING_INVALID; + } + } else if((p = (char*)strchr(esc1, *src)) != NULL) { + if(dst < dend) *dst = esc2[p - esc1]; + } else { + return JSON_STRING_INVALID; + } + } else { + if(dst < dend) *dst = *src; + } + dst++; + src++; + } + + return dst - orig_dst; +} + +static void json_scanf_cb( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct json_scanf_info* info = (struct json_scanf_info*)callback_data; + char buf[32]; /* Must be enough to hold numbers */ + + (void)name; + (void)name_len; + + if(token->ptr == NULL) { + /* + * We're not interested here in the events for which we have no value; + * namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START + */ + return; + } + + if(strcmp(path, info->path) != 0) { + /* It's not the path we're looking for, so, just ignore this callback */ + return; + } + + switch(info->type) { + case 'B': + info->num_conversions++; + switch(sizeof(bool)) { + case sizeof(char): + *(char*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + case sizeof(int): + *(int*)info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); + break; + default: + /* should never be here */ + abort(); + } + break; + case 'M': { + union { + void* p; + json_scanner_t f; + } u = {info->target}; + info->num_conversions++; + u.f(token->ptr, token->len, info->user_data); + break; + } + case 'Q': { + char** dst = (char**)info->target; + if(token->type == JSON_TYPE_NULL) { + *dst = NULL; + } else { + int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); + if(unescaped_len >= 0 && (*dst = (char*)malloc(unescaped_len + 1)) != NULL) { + info->num_conversions++; + if(json_unescape(token->ptr, token->len, *dst, unescaped_len) == unescaped_len) { + (*dst)[unescaped_len] = '\0'; + } else { + free(*dst); + *dst = NULL; + } + } + } + break; + } + case 'H': { +#if JSON_ENABLE_HEX + char** dst = (char**)info->user_data; + int i, len = token->len / 2; + *(int*)info->target = len; + if((*dst = (char*)malloc(len + 1)) != NULL) { + for(i = 0; i < len; i++) { + (*dst)[i] = hexdec(token->ptr + 2 * i); + } + (*dst)[len] = '\0'; + info->num_conversions++; + } +#endif /* JSON_ENABLE_HEX */ + break; + } + case 'V': { +#if JSON_ENABLE_BASE64 + char** dst = (char**)info->target; + int len = token->len * 4 / 3 + 2; + if((*dst = (char*)malloc(len + 1)) != NULL) { + int n = b64dec(token->ptr, token->len, *dst); + (*dst)[n] = '\0'; + *(int*)info->user_data = n; + info->num_conversions++; + } +#endif /* JSON_ENABLE_BASE64 */ + break; + } + case 'T': + info->num_conversions++; + *(struct json_token*)info->target = *token; + break; + default: + if(token->len >= (int)sizeof(buf)) break; + /* Before converting, copy into tmp buffer in order to 0-terminate it */ + memcpy(buf, token->ptr, token->len); + buf[token->len] = '\0'; + /* NB: Use of base 0 for %d, %ld, %u and %lu is intentional. */ + if(info->fmt[1] == 'd' || (info->fmt[1] == 'l' && info->fmt[2] == 'd') || + info->fmt[1] == 'i') { + char* endptr = NULL; + long r = strtol(buf, &endptr, 0 /* base */); + if(*endptr == '\0') { + if(info->fmt[1] == 'l') { + *((long*)info->target) = r; + } else { + *((int*)info->target) = (int)r; + } + info->num_conversions++; + } + } else if(info->fmt[1] == 'u' || (info->fmt[1] == 'l' && info->fmt[2] == 'u')) { + char* endptr = NULL; + unsigned long r = strtoul(buf, &endptr, 0 /* base */); + if(*endptr == '\0') { + if(info->fmt[1] == 'l') { + *((unsigned long*)info->target) = r; + } else { + *((unsigned int*)info->target) = (unsigned int)r; + } + info->num_conversions++; + } + } else { +#if !JSON_MINIMAL + info->num_conversions += sscanf(buf, info->fmt, info->target); +#endif + } + break; + } +} + +int json_vscanf(const char* s, int len, const char* fmt, va_list ap) WEAK; +int json_vscanf(const char* s, int len, const char* fmt, va_list ap) { + char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20]; + int i = 0; + char* p = NULL; + struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL, 0}; + + while(fmt[i] != '\0') { + if(fmt[i] == '{') { + strcat(path, "."); + i++; + } else if(fmt[i] == '}') { + if((p = strrchr(path, '.')) != NULL) *p = '\0'; + i++; + } else if(fmt[i] == '%') { + info.target = va_arg(ap, void*); + info.type = fmt[i + 1]; + switch(fmt[i + 1]) { + case 'M': + case 'V': + case 'H': + info.user_data = va_arg(ap, void*); + /* FALLTHROUGH */ + case 'B': + case 'Q': + case 'T': + i += 2; + break; + default: { + const char* delims = ", \t\r\n]}"; + int conv_len = strcspn(fmt + i + 1, delims) + 1; + memcpy(fmtbuf, fmt + i, conv_len); + fmtbuf[conv_len] = '\0'; + i += conv_len; + i += strspn(fmt + i, delims); + break; + } + } + json_walk(s, len, json_scanf_cb, &info); + } else if(json_isalpha(fmt[i]) || json_get_utf8_char_len(fmt[i]) > 1) { + char* pe; + const char* delims = ": \r\n\t"; + int key_len = strcspn(&fmt[i], delims); + if((p = strrchr(path, '.')) != NULL) p[1] = '\0'; + pe = path + strlen(path); + memcpy(pe, fmt + i, key_len); + pe[key_len] = '\0'; + i += key_len + strspn(fmt + i + key_len, delims); + } else { + i++; + } + } + return info.num_conversions; +} + +int json_scanf(const char* str, int len, const char* fmt, ...) WEAK; +int json_scanf(const char* str, int len, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vscanf(str, len, fmt, ap); + va_end(ap); + return result; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) WEAK; +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + int res = -1; + FILE* fp = fopen(file_name, "wb"); + if(fp != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_vprintf(&out, fmt, ap); + fputc('\n', fp); + fclose(fp); + } + return res; +} + +int json_fprintf(const char* file_name, const char* fmt, ...) WEAK; +int json_fprintf(const char* file_name, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = json_vfprintf(file_name, fmt, ap); + va_end(ap); + return result; +} + +char* json_fread(const char* path) WEAK; +char* json_fread(const char* path) { + FILE* fp; + char* data = NULL; + if((fp = fopen(path, "rb")) == NULL) { + } else if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + } else { + long size = ftell(fp); + if(size > 0 && (data = (char*)malloc(size + 1)) != NULL) { + fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ + if(fread(data, 1, size, fp) != (size_t)size) { + free(data); + data = NULL; + } else { + data[size] = '\0'; + } + } + fclose(fp); + } + return data; +} + +struct json_setf_data { + const char* json_path; + const char* base; /* Pointer to the source JSON string */ + int matched; /* Matched part of json_path */ + int pos; /* Offset of the mutated value begin */ + int end; /* Offset of the mutated value end */ + int prev; /* Offset of the previous token end */ +}; + +static int get_matched_prefix_len(const char* s1, const char* s2) { + int i = 0; + while(s1[i] && s2[i] && s1[i] == s2[i]) i++; + return i; +} + +static void json_vsetf_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct json_setf_data* data = (struct json_setf_data*)userdata; + int off, len = get_matched_prefix_len(path, data->json_path); + if(t->ptr == NULL) return; + off = t->ptr - data->base; + if(len > data->matched) data->matched = len; + + /* + * If there is no exact path match, set the mutation position to tbe end + * of the object or array + */ + if(len < data->matched && data->pos == 0 && + (t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END)) { + data->pos = data->end = data->prev; + } + + /* Exact path match. Set mutation position to the value of this token */ + if(strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START && + t->type != JSON_TYPE_ARRAY_START) { + data->pos = off; + data->end = off + t->len; + } + + /* + * For deletion, we need to know where the previous value ends, because + * we don't know where matched value key starts. + * When the mutation position is not yet set, remember each value end. + * When the mutation position is already set, but it is at the beginning + * of the object/array, we catch the end of the object/array and see + * whether the object/array start is closer then previously stored prev. + */ + if(data->pos == 0) { + data->prev = off + t->len; /* pos is not yet set */ + } else if((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos && off + 1 > data->prev) { + data->prev = off + 1; + } + (void)name; + (void)name_len; +} + +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap) WEAK; +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap) { + struct json_setf_data data; + memset(&data, 0, sizeof(data)); + data.json_path = json_path; + data.base = s; + data.end = len; + json_walk(s, len, json_vsetf_cb, &data); + if(json_fmt == NULL) { + /* Deletion codepath */ + json_printf(out, "%.*s", data.prev, s); + /* Trim comma after the value that begins at object/array start */ + if(s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { + int i = data.end; + while(i < len && json_isspace(s[i])) i++; + if(s[i] == ',') data.end = i + 1; /* Point after comma */ + } + json_printf(out, "%.*s", len - data.end, s + data.end); + } else { + /* Modification codepath */ + int n, off = data.matched, depth = 0; + + /* Print the unchanged beginning */ + json_printf(out, "%.*s", data.pos, s); + + /* Add missing keys */ + while((n = strcspn(&json_path[off], ".[")) > 0) { + if(s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0) { + json_printf(out, ","); + } + if(off > 0 && json_path[off - 1] != '.') break; + json_printf(out, "%.*Q:", n, json_path + off); + off += n; + if(json_path[off] != '\0') { + json_printf(out, "%c", json_path[off] == '.' ? '{' : '['); + depth++; + off++; + } + } + /* Print the new value */ + json_vprintf(out, json_fmt, ap); + + /* Close brackets/braces of the added missing keys */ + for(; off > data.matched; off--) { + int ch = json_path[off]; + const char* p = ch == '.' ? "}" : ch == '[' ? "]" : ""; + json_printf(out, "%s", p); + } + + /* Print the rest of the unchanged string */ + json_printf(out, "%.*s", len - data.end, s + data.end); + } + return data.end > data.pos ? 1 : 0; +} + +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...) WEAK; +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...) { + int result; + va_list ap; + va_start(ap, json_fmt); + result = json_vsetf(s, len, out, json_path, json_fmt, ap); + va_end(ap); + return result; +} + +struct prettify_data { + struct json_out* out; + int level; + int last_token; +}; + +static void indent(struct json_out* out, int level) { + while(level-- > 0) out->printer(out, " ", 2); +} + +static void print_key(struct prettify_data* pd, const char* path, const char* name, int name_len) { + if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, ",", 1); + } + if(path[0] != '\0') pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + if(path[0] != '\0' && path[strlen(path) - 1] != ']') { + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, name, (int)name_len); + pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, ": ", 2); + } +} + +static void prettify_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct prettify_data* pd = (struct prettify_data*)userdata; + switch(t->type) { + case JSON_TYPE_OBJECT_START: + case JSON_TYPE_ARRAY_START: + print_key(pd, path, name, name_len); + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{", 1); + pd->level++; + break; + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: + pd->level--; + if(pd->last_token != JSON_TYPE_INVALID && pd->last_token != JSON_TYPE_ARRAY_START && + pd->last_token != JSON_TYPE_OBJECT_START) { + pd->out->printer(pd->out, "\n", 1); + indent(pd->out, pd->level); + } + pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1); + break; + case JSON_TYPE_NUMBER: + case JSON_TYPE_NULL: + case JSON_TYPE_TRUE: + case JSON_TYPE_FALSE: + case JSON_TYPE_STRING: + print_key(pd, path, name, name_len); + if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + pd->out->printer(pd->out, t->ptr, t->len); + if(t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); + break; + default: + break; + } + pd->last_token = t->type; +} + +int json_prettify(const char* s, int len, struct json_out* out) WEAK; +int json_prettify(const char* s, int len, struct json_out* out) { + struct prettify_data pd = {out, 0, JSON_TYPE_INVALID}; + return json_walk(s, len, prettify_cb, &pd); +} + +int json_prettify_file(const char* file_name) WEAK; +int json_prettify_file(const char* file_name) { + int res = -1; + char* s = json_fread(file_name); + FILE* fp; + if(s != NULL && (fp = fopen(file_name, "wb")) != NULL) { + struct json_out out = JSON_OUT_FILE(fp); + res = json_prettify(s, strlen(s), &out); + if(res < 0) { + /* On error, restore the old content */ + fclose(fp); + fp = fopen(file_name, "wb"); + fseek(fp, 0, SEEK_SET); + fwrite(s, 1, strlen(s), fp); + } else { + fputc('\n', fp); + } + fclose(fp); + } + free(s); + return res; +} + +struct next_data { + void* handle; // Passed handle. Changed if a next entry is found + const char* path; // Path to the iterated object/array + int path_len; // Path length - optimisation + int found; // Non-0 if found the next entry + struct json_token* key; // Object's key + struct json_token* val; // Object's value + int* idx; // Array index +}; + +static void next_set_key(struct next_data* d, const char* name, int name_len, int is_array) { + if(is_array) { + /* Array. Set index and reset key */ + if(d->key != NULL) { + d->key->len = 0; + d->key->ptr = NULL; + } + if(d->idx != NULL) *d->idx = atoi(name); + } else { + /* Object. Set key and make index -1 */ + if(d->key != NULL) { + d->key->ptr = name; + d->key->len = name_len; + } + if(d->idx != NULL) *d->idx = -1; + } +} + +static void json_next_cb( + void* userdata, + const char* name, + size_t name_len, + const char* path, + const struct json_token* t) { + struct next_data* d = (struct next_data*)userdata; + const char* p = path + d->path_len; + if(d->found) return; + if(d->path_len >= (int)strlen(path)) return; + if(strncmp(d->path, path, d->path_len) != 0) return; + if(strchr(p + 1, '.') != NULL) return; /* More nested objects - skip */ + if(strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */ + // {OBJECT,ARRAY}_END types do not pass name, _START does. Save key. + if(t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) { + next_set_key(d, name, name_len, p[0] == '['); + } else if(d->handle == NULL || d->handle < (void*)t->ptr) { + if(t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) { + next_set_key(d, name, name_len, p[0] == '['); + } + if(d->val != NULL) *d->val = *t; + d->handle = (void*)t->ptr; + d->found = 1; + } +} + +static void* json_next( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val, + int* i) { + struct json_token tmpval, *v = val == NULL ? &tmpval : val; + struct json_token tmpkey, *k = key == NULL ? &tmpkey : key; + int tmpidx, *pidx = i == NULL ? &tmpidx : i; + struct next_data data = {handle, path, (int)strlen(path), 0, k, v, pidx}; + json_walk(s, len, json_next_cb, &data); + return data.found ? data.handle : NULL; +} + +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val) WEAK; +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val) { + return json_next(s, len, handle, path, key, val, NULL); +} + +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val) WEAK; +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val) { + return json_next(s, len, handle, path, NULL, val, idx); +} + +static int json_sprinter(struct json_out* out, const char* str, size_t len) { + size_t old_len = out->u.buf.buf == NULL ? 0 : strlen(out->u.buf.buf); + size_t new_len = len + old_len; + char* p = (char*)realloc(out->u.buf.buf, new_len + 1); + if(p != NULL) { + memcpy(p + old_len, str, len); + p[new_len] = '\0'; + out->u.buf.buf = p; + } + return len; +} + +char* json_vasprintf(const char* fmt, va_list ap) WEAK; +char* json_vasprintf(const char* fmt, va_list ap) { + struct json_out out; + memset(&out, 0, sizeof(out)); + out.printer = json_sprinter; + json_vprintf(&out, fmt, ap); + return out.u.buf.buf; +} + +char* json_asprintf(const char* fmt, ...) WEAK; +char* json_asprintf(const char* fmt, ...) { + char* result = NULL; + va_list ap; + va_start(ap, fmt); + result = json_vasprintf(fmt, ap); + va_end(ap); + return result; +} diff --git a/lib/mjs/common/frozen/frozen.h b/lib/mjs/common/frozen/frozen.h new file mode 100644 index 00000000000..2eda5ae688f --- /dev/null +++ b/lib/mjs/common/frozen/frozen.h @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2004-2013 Sergey Lyubka + * Copyright (c) 2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_FROZEN_FROZEN_H_ +#define CS_FROZEN_FROZEN_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include +#include + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef int bool; +enum { false = 0, true = 1 }; +#else +#include +#endif + +/* JSON token type */ +enum json_token_type { + JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ + JSON_TYPE_STRING, + JSON_TYPE_NUMBER, + JSON_TYPE_TRUE, + JSON_TYPE_FALSE, + JSON_TYPE_NULL, + JSON_TYPE_OBJECT_START, + JSON_TYPE_OBJECT_END, + JSON_TYPE_ARRAY_START, + JSON_TYPE_ARRAY_END, + + JSON_TYPES_CNT +}; + +/* + * Structure containing token type and value. Used in `json_walk()` and + * `json_scanf()` with the format specifier `%T`. + */ +struct json_token { + const char* ptr; /* Points to the beginning of the value */ + int len; /* Value length */ + enum json_token_type type; /* Type of the token, possible values are above */ +}; + +#define JSON_INVALID_TOKEN \ + { 0, 0, JSON_TYPE_INVALID } + +/* Error codes */ +#define JSON_STRING_INVALID -1 +#define JSON_STRING_INCOMPLETE -2 + +/* + * Callback-based SAX-like API. + * + * Property name and length is given only if it's available: i.e. if current + * event is an object's property. In other cases, `name` is `NULL`. For + * example, name is never given: + * - For the first value in the JSON string; + * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END + * + * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, + * the sequence of callback invocations will be as follows: + * + * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL + * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" + * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL + * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" + * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" + * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL + * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": + *true }" + * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { + *\"baz\": true } ]" + * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, + *\"bar\": [ 1, 2, { \"baz\": true } ] }" + */ +typedef void (*json_walk_callback_t)( + void* callback_data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token); + +/* + * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; + * see `json_walk_callback_t`. + * Return number of processed bytes, or a negative error code. + */ +int json_walk( + const char* json_string, + int json_string_length, + json_walk_callback_t callback, + void* callback_data); + +/* + * JSON generation API. + * struct json_out abstracts output, allowing alternative printing plugins. + */ +struct json_out { + int (*printer)(struct json_out*, const char* str, size_t len); + union { + struct { + char* buf; + size_t size; + size_t len; + } buf; + void* data; + FILE* fp; + } u; +}; + +extern int json_printer_buf(struct json_out*, const char*, size_t); +extern int json_printer_file(struct json_out*, const char*, size_t); + +#define JSON_OUT_BUF(buf, len) \ + { \ + json_printer_buf, { \ + { buf, len, 0 } \ + } \ + } +#define JSON_OUT_FILE(fp) \ + { \ + json_printer_file, { \ + { (char*)fp, 0, 0 } \ + } \ + } + +typedef int (*json_printf_callback_t)(struct json_out*, va_list* ap); + +/* + * Generate formatted output into a given sting buffer. + * This is a superset of printf() function, with extra format specifiers: + * - `%B` print json boolean, `true` or `false`. Accepts an `int`. + * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. + * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` + * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. + * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. + * - `%M` invokes a json_printf_callback_t function. That callback function + * can consume more parameters. + * + * Return number of bytes printed. If the return value is bigger than the + * supplied buffer, that is an indicator of overflow. In the overflow case, + * overflown bytes are not printed. + */ +int json_printf(struct json_out*, const char* fmt, ...); +int json_vprintf(struct json_out*, const char* fmt, va_list ap); + +/* + * Same as json_printf, but prints to a file. + * File is created if does not exist. File is truncated if already exists. + */ +int json_fprintf(const char* file_name, const char* fmt, ...); +int json_vfprintf(const char* file_name, const char* fmt, va_list ap); + +/* + * Print JSON into an allocated 0-terminated string. + * Return allocated string, or NULL on error. + * Example: + * + * ```c + * char *str = json_asprintf("{a:%H}", 3, "abc"); + * printf("%s\n", str); // Prints "616263" + * free(str); + * ``` + */ +char* json_asprintf(const char* fmt, ...); +char* json_vasprintf(const char* fmt, va_list ap); + +/* + * Helper %M callback that prints contiguous C arrays. + * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt + * Return number of bytes printed. + */ +int json_printf_array(struct json_out*, va_list* ap); + +/* + * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. + * This is a `scanf()` - like function, with following differences: + * + * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" + * 2. Order of keys in an object is irrelevant. + * 3. Several extra format specifiers are supported: + * - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`), + * expects boolean `true` or `false`. + * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned + * string is malloc-ed, caller must free() the string. + * - %V: consumes `char **`, `int *`. Expects base64-encoded string. + * Result string is base64-decoded, malloced and NUL-terminated. + * The length of result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %H: consumes `int *`, `char **`. + * Expects a hex-encoded string, e.g. "fa014f". + * Result string is hex-decoded, malloced and NUL-terminated. + * The length of the result string is stored in `int *` placeholder. + * Caller must free() the result. + * - %M: consumes custom scanning function pointer and + * `void *user_data` parameter - see json_scanner_t definition. + * - %T: consumes `struct json_token *`, fills it out with matched token. + * + * Return number of elements successfully scanned & converted. + * Negative number means scan error. + */ +int json_scanf(const char* str, int str_len, const char* fmt, ...); +int json_vscanf(const char* str, int str_len, const char* fmt, va_list ap); + +/* json_scanf's %M handler */ +typedef void (*json_scanner_t)(const char* str, int len, void* user_data); + +/* + * Helper function to scan array item with given path and index. + * Fills `token` with the matched JSON token. + * Return -1 if no array element found, otherwise non-negative token length. + */ +int json_scanf_array_elem( + const char* s, + int len, + const char* path, + int index, + struct json_token* token); + +/* + * Unescape JSON-encoded string src,slen into dst, dlen. + * src and dst may overlap. + * If destination buffer is too small (or zero-length), result string is not + * written but the length is counted nevertheless (similar to snprintf). + * Return the length of unescaped string in bytes. + */ +int json_unescape(const char* src, int slen, char* dst, int dlen); + +/* + * Escape a string `str`, `str_len` into the printer `out`. + * Return the number of bytes printed. + */ +int json_escape(struct json_out* out, const char* str, size_t str_len); + +/* + * Read the whole file in memory. + * Return malloc-ed file content, or NULL on error. The caller must free(). + */ +char* json_fread(const char* file_name); + +/* + * Update given JSON string `s,len` by changing the value at given `json_path`. + * The result is saved to `out`. If `json_fmt` == NULL, that deletes the key. + * If path is not present, missing keys are added. Array path without an + * index pushes a value to the end of an array. + * Return 1 if the string was changed, 0 otherwise. + * + * Example: s is a JSON string { "a": 1, "b": [ 2 ] } + * json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] } + * json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 } + * json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] } + * json_setf(s, len, out, ".b", NULL); // { "a": 1 } + */ +int json_setf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + ...); + +int json_vsetf( + const char* s, + int len, + struct json_out* out, + const char* json_path, + const char* json_fmt, + va_list ap); + +/* + * Pretty-print JSON string `s,len` into `out`. + * Return number of processed bytes in `s`. + */ +int json_prettify(const char* s, int len, struct json_out* out); + +/* + * Prettify JSON file `file_name`. + * Return number of processed bytes, or negative number of error. + * On error, file content is not modified. + */ +int json_prettify_file(const char* file_name); + +/* + * Iterate over an object at given JSON `path`. + * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL + * for `key`, or `val`, in which case they won't be populated. + * Return an opaque value suitable for the next iteration, or NULL when done. + * + * Example: + * + * ```c + * void *h = NULL; + * struct json_token key, val; + * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) { + * printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr); + * } + * ``` + */ +void* json_next_key( + const char* s, + int len, + void* handle, + const char* path, + struct json_token* key, + struct json_token* val); + +/* + * Iterate over an array at given JSON `path`. + * Similar to `json_next_key`, but fills array index `idx` instead of `key`. + */ +void* json_next_elem( + const char* s, + int len, + void* handle, + const char* path, + int* idx, + struct json_token* val); + +#ifndef JSON_MAX_PATH_LEN +#define JSON_MAX_PATH_LEN 256 +#endif + +#ifndef JSON_MINIMAL +#define JSON_MINIMAL 0 +#endif + +#ifndef JSON_ENABLE_BASE64 +#define JSON_ENABLE_BASE64 !JSON_MINIMAL +#endif + +#ifndef JSON_ENABLE_HEX +#define JSON_ENABLE_HEX !JSON_MINIMAL +#endif + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CS_FROZEN_FROZEN_H_ */ diff --git a/lib/mjs/common/mbuf.c b/lib/mjs/common/mbuf.c new file mode 100644 index 00000000000..458d8cca0f1 --- /dev/null +++ b/lib/mjs/common/mbuf.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include +#include +#include "mbuf.h" + +#ifndef MBUF_REALLOC +#define MBUF_REALLOC realloc +#endif + +#ifndef MBUF_FREE +#define MBUF_FREE free +#endif + +void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK; +void mbuf_init(struct mbuf *mbuf, size_t initial_size) { + mbuf->len = mbuf->size = 0; + mbuf->buf = NULL; + mbuf_resize(mbuf, initial_size); +} + +void mbuf_free(struct mbuf *mbuf) WEAK; +void mbuf_free(struct mbuf *mbuf) { + if (mbuf->buf != NULL) { + MBUF_FREE(mbuf->buf); + mbuf_init(mbuf, 0); + } +} + +void mbuf_resize(struct mbuf *a, size_t new_size) WEAK; +void mbuf_resize(struct mbuf *a, size_t new_size) { + if (new_size > a->size || (new_size < a->size && new_size >= a->len)) { + char *buf = (char *) MBUF_REALLOC(a->buf, new_size); + /* + * In case realloc fails, there's not much we can do, except keep things as + * they are. Note that NULL is a valid return value from realloc when + * size == 0, but that is covered too. + */ + if (buf == NULL && new_size != 0) return; + a->buf = buf; + a->size = new_size; + } +} + +void mbuf_trim(struct mbuf *mbuf) WEAK; +void mbuf_trim(struct mbuf *mbuf) { + mbuf_resize(mbuf, mbuf->len); +} + +size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK; +size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) { + char *p = NULL; + + assert(a != NULL); + assert(a->len <= a->size); + assert(off <= a->len); + + /* check overflow */ + if (~(size_t) 0 - (size_t) a->buf < len) return 0; + + if (a->len + len <= a->size) { + memmove(a->buf + off + len, a->buf + off, a->len - off); + if (buf != NULL) { + memcpy(a->buf + off, buf, len); + } + a->len += len; + } else { + size_t min_size = (a->len + len); + size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER); + if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) { + new_size = min_size + MBUF_SIZE_MAX_HEADROOM; + } + p = (char *) MBUF_REALLOC(a->buf, new_size); + if (p == NULL && new_size != min_size) { + new_size = min_size; + p = (char *) MBUF_REALLOC(a->buf, new_size); + } + if (p != NULL) { + a->buf = p; + if (off != a->len) { + memmove(a->buf + off + len, a->buf + off, a->len - off); + } + if (buf != NULL) memcpy(a->buf + off, buf, len); + a->len += len; + a->size = new_size; + } else { + len = 0; + } + } + + return len; +} + +size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK; +size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) { + return mbuf_insert(a, a->len, buf, len); +} + +size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK; +size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) { + size_t ret; + /* Optimization: if the buffer is currently empty, + * take over the user-provided buffer. */ + if (a->len == 0) { + if (a->buf != NULL) free(a->buf); + a->buf = (char *) data; + a->len = a->size = len; + return len; + } + ret = mbuf_insert(a, a->len, data, len); + free(data); + return ret; +} + +void mbuf_remove(struct mbuf *mb, size_t n) WEAK; +void mbuf_remove(struct mbuf *mb, size_t n) { + if (n > 0 && n <= mb->len) { + memmove(mb->buf, mb->buf + n, mb->len - n); + mb->len -= n; + } +} + +void mbuf_clear(struct mbuf *mb) WEAK; +void mbuf_clear(struct mbuf *mb) { + mb->len = 0; +} + +void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK; +void mbuf_move(struct mbuf *from, struct mbuf *to) { + memcpy(to, from, sizeof(*to)); + memset(from, 0, sizeof(*from)); +} + +#endif /* EXCLUDE_COMMON */ diff --git a/lib/mjs/common/mbuf.h b/lib/mjs/common/mbuf.h new file mode 100644 index 00000000000..0ca9dd7d174 --- /dev/null +++ b/lib/mjs/common/mbuf.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Mbufs are mutable/growing memory buffers, like C++ strings. + * Mbuf can append data to the end of a buffer or insert data into arbitrary + * position in the middle of a buffer. The buffer grows automatically when + * needed. + */ + +#ifndef CS_COMMON_MBUF_H_ +#define CS_COMMON_MBUF_H_ + +#include +#include "platform.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +#ifndef MBUF_SIZE_MULTIPLIER +#define MBUF_SIZE_MULTIPLIER 1.5 +#endif + +#ifndef MBUF_SIZE_MAX_HEADROOM +#ifdef BUFSIZ +#define MBUF_SIZE_MAX_HEADROOM BUFSIZ +#else +#define MBUF_SIZE_MAX_HEADROOM 1024 +#endif +#endif + +/* Memory buffer descriptor */ +struct mbuf { + char *buf; /* Buffer pointer */ + size_t len; /* Data length. Data is located between offset 0 and len. */ + size_t size; /* Buffer size allocated by realloc(1). Must be >= len */ +}; + +/* + * Initialises an Mbuf. + * `initial_capacity` specifies the initial capacity of the mbuf. + */ +void mbuf_init(struct mbuf *, size_t initial_capacity); + +/* Frees the space allocated for the mbuffer and resets the mbuf structure. */ +void mbuf_free(struct mbuf *); + +/* + * Appends data to the Mbuf. + * + * Returns the number of bytes appended or 0 if out of memory. + */ +size_t mbuf_append(struct mbuf *, const void *data, size_t data_size); + +/* + * Appends data to the Mbuf and frees it (data must be heap-allocated). + * + * Returns the number of bytes appended or 0 if out of memory. + * data is freed irrespective of return value. + */ +size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size); + +/* + * Inserts data at a specified offset in the Mbuf. + * + * Existing data will be shifted forwards and the buffer will + * be grown if necessary. + * Returns the number of bytes inserted. + */ +size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t); + +/* Removes `data_size` bytes from the beginning of the buffer. */ +void mbuf_remove(struct mbuf *, size_t data_size); + +/* + * Resizes an Mbuf. + * + * If `new_size` is smaller than buffer's `len`, the + * resize is not performed. + */ +void mbuf_resize(struct mbuf *, size_t new_size); + +/* Moves the state from one mbuf to the other. */ +void mbuf_move(struct mbuf *from, struct mbuf *to); + +/* Removes all the data from mbuf (if any). */ +void mbuf_clear(struct mbuf *); + +/* Shrinks an Mbuf by resizing its `size` to `len`. */ +void mbuf_trim(struct mbuf *); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* CS_COMMON_MBUF_H_ */ diff --git a/lib/mjs/common/mg_mem.h b/lib/mjs/common/mg_mem.h new file mode 100644 index 00000000000..7fe8381d49e --- /dev/null +++ b/lib/mjs/common/mg_mem.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_MG_MEM_H_ +#define CS_COMMON_MG_MEM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef MG_MALLOC +#define MG_MALLOC malloc +#endif + +#ifndef MG_CALLOC +#define MG_CALLOC calloc +#endif + +#ifndef MG_REALLOC +#define MG_REALLOC realloc +#endif + +#ifndef MG_FREE +#define MG_FREE free +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_MG_MEM_H_ */ diff --git a/lib/mjs/common/mg_str.c b/lib/mjs/common/mg_str.c new file mode 100644 index 00000000000..3ce1f5d2a3b --- /dev/null +++ b/lib/mjs/common/mg_str.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "mg_mem.h" +#include "mg_str.h" +#include "platform.h" + +#include +#include +#include + +int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK; + +struct mg_str mg_mk_str(const char* s) WEAK; +struct mg_str mg_mk_str(const char* s) { + struct mg_str ret = {s, 0}; + if(s != NULL) ret.len = strlen(s); + return ret; +} + +struct mg_str mg_mk_str_n(const char* s, size_t len) WEAK; +struct mg_str mg_mk_str_n(const char* s, size_t len) { + struct mg_str ret = {s, len}; + return ret; +} + +int mg_vcmp(const struct mg_str* str1, const char* str2) WEAK; +int mg_vcmp(const struct mg_str* str1, const char* str2) { + size_t n2 = strlen(str2), n1 = str1->len; + int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2); + if(r == 0) { + return n1 - n2; + } + return r; +} + +int mg_vcasecmp(const struct mg_str* str1, const char* str2) WEAK; +int mg_vcasecmp(const struct mg_str* str1, const char* str2) { + size_t n2 = strlen(str2), n1 = str1->len; + int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2); + if(r == 0) { + return n1 - n2; + } + return r; +} + +static struct mg_str mg_strdup_common(const struct mg_str s, int nul_terminate) { + struct mg_str r = {NULL, 0}; + if(s.len > 0 && s.p != NULL) { + char* sc = (char*)MG_MALLOC(s.len + (nul_terminate ? 1 : 0)); + if(sc != NULL) { + memcpy(sc, s.p, s.len); + if(nul_terminate) sc[s.len] = '\0'; + r.p = sc; + r.len = s.len; + } + } + return r; +} + +struct mg_str mg_strdup(const struct mg_str s) WEAK; +struct mg_str mg_strdup(const struct mg_str s) { + return mg_strdup_common(s, 0 /* NUL-terminate */); +} + +struct mg_str mg_strdup_nul(const struct mg_str s) WEAK; +struct mg_str mg_strdup_nul(const struct mg_str s) { + return mg_strdup_common(s, 1 /* NUL-terminate */); +} + +const char* mg_strchr(const struct mg_str s, int c) WEAK; +const char* mg_strchr(const struct mg_str s, int c) { + size_t i; + for(i = 0; i < s.len; i++) { + if(s.p[i] == c) return &s.p[i]; + } + return NULL; +} + +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK; +int mg_strcmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while(i < str1.len && i < str2.len) { + int c1 = str1.p[i]; + int c2 = str2.p[i]; + if(c1 < c2) return -1; + if(c1 > c2) return 1; + i++; + } + if(i < str1.len) return 1; + if(i < str2.len) return -1; + return 0; +} + +int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK; +int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) { + struct mg_str s1 = str1; + struct mg_str s2 = str2; + + if(s1.len > n) { + s1.len = n; + } + if(s2.len > n) { + s2.len = n; + } + return mg_strcmp(s1, s2); +} + +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) WEAK; +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) { + size_t i = 0; + while(i < str1.len && i < str2.len) { + int c1 = tolower((int)str1.p[i]); + int c2 = tolower((int)str2.p[i]); + if(c1 < c2) return -1; + if(c1 > c2) return 1; + i++; + } + if(i < str1.len) return 1; + if(i < str2.len) return -1; + return 0; +} + +void mg_strfree(struct mg_str* s) WEAK; +void mg_strfree(struct mg_str* s) { + char* sp = (char*)s->p; + s->p = NULL; + s->len = 0; + if(sp != NULL) free(sp); +} + +const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) WEAK; +const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) { + size_t i; + if(needle.len > haystack.len) return NULL; + for(i = 0; i <= haystack.len - needle.len; i++) { + if(memcmp(haystack.p + i, needle.p, needle.len) == 0) { + return haystack.p + i; + } + } + return NULL; +} + +struct mg_str mg_strstrip(struct mg_str s) WEAK; +struct mg_str mg_strstrip(struct mg_str s) { + while(s.len > 0 && isspace((int)*s.p)) { + s.p++; + s.len--; + } + while(s.len > 0 && isspace((int)*(s.p + s.len - 1))) { + s.len--; + } + return s; +} + +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK; +int mg_str_starts_with(struct mg_str s, struct mg_str prefix) { + const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len); + if(s.len < prefix.len) return 0; + return (mg_strcmp(sp, prefix) == 0); +} diff --git a/lib/mjs/common/mg_str.h b/lib/mjs/common/mg_str.h new file mode 100644 index 00000000000..f32f2464784 --- /dev/null +++ b/lib/mjs/common/mg_str.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_MG_STR_H_ +#define CS_COMMON_MG_STR_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Describes chunk of memory */ +struct mg_str { + const char *p; /* Memory chunk pointer */ + size_t len; /* Memory chunk length */ +}; + +/* + * Helper function for creating mg_str struct from plain C string. + * `NULL` is allowed and becomes `{NULL, 0}`. + */ +struct mg_str mg_mk_str(const char *s); + +/* + * Like `mg_mk_str`, but takes string length explicitly. + */ +struct mg_str mg_mk_str_n(const char *s, size_t len); + +/* Macro for initializing mg_str. */ +#define MG_MK_STR(str_literal) \ + { str_literal, sizeof(str_literal) - 1 } +#define MG_MK_STR_N(str_literal, len) \ + { str_literal, len } +#define MG_NULL_STR \ + { NULL, 0 } + +/* + * Cross-platform version of `strcmp()` where where first string is + * specified by `struct mg_str`. + */ +int mg_vcmp(const struct mg_str *str2, const char *str1); + +/* + * Cross-platform version of `strncasecmp()` where first string is + * specified by `struct mg_str`. + */ +int mg_vcasecmp(const struct mg_str *str2, const char *str1); + +/* Creates a copy of s (heap-allocated). */ +struct mg_str mg_strdup(const struct mg_str s); + +/* + * Creates a copy of s (heap-allocated). + * Resulting string is NUL-terminated (but NUL is not included in len). + */ +struct mg_str mg_strdup_nul(const struct mg_str s); + +/* + * Locates character in a string. + */ +const char *mg_strchr(const struct mg_str s, int c); + +/* + * Compare two `mg_str`s; return value is the same as `strcmp`. + */ +int mg_strcmp(const struct mg_str str1, const struct mg_str str2); + +/* + * Like `mg_strcmp`, but compares at most `n` characters. + */ +int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n); + +/* + * Compare two `mg_str`s ignoreing case; return value is the same as `strcmp`. + */ +int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2); + +/* + * Free the string (assuming it was heap allocated). + */ +void mg_strfree(struct mg_str *s); + +/* + * Finds the first occurrence of a substring `needle` in the `haystack`. + */ +const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle); + +/* Strip whitespace at the start and the end of s */ +struct mg_str mg_strstrip(struct mg_str s); + +/* Returns 1 if s starts with the given prefix. */ +int mg_str_starts_with(struct mg_str s, struct mg_str prefix); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_MG_STR_H_ */ diff --git a/lib/mjs/common/platform.h b/lib/mjs/common/platform.h new file mode 100644 index 00000000000..5a2d58d84ff --- /dev/null +++ b/lib/mjs/common/platform.h @@ -0,0 +1,89 @@ +#ifndef CS_COMMON_PLATFORM_H_ +#define CS_COMMON_PLATFORM_H_ + +/* + * For the "custom" platform, includes and dependencies can be + * provided through mg_locals.h. + */ +#define CS_P_CUSTOM 0 +#define CS_P_UNIX 1 +#define CS_P_WINDOWS 2 +#define CS_P_ESP32 15 +#define CS_P_ESP8266 3 +#define CS_P_CC3100 6 +#define CS_P_CC3200 4 +#define CS_P_CC3220 17 +#define CS_P_MSP432 5 +#define CS_P_TM4C129 14 +#define CS_P_MBED 7 +#define CS_P_WINCE 8 +#define CS_P_NXP_LPC 13 +#define CS_P_NXP_KINETIS 9 +#define CS_P_NRF51 12 +#define CS_P_NRF52 10 +#define CS_P_PIC32 11 +#define CS_P_RS14100 18 +#define CS_P_STM32 16 +#define CS_P_FLIPPER 19 +/* Next id: 20 */ + +#ifndef CS_PLATFORM +#define CS_PLATFORM CS_P_FLIPPER +#endif + +#ifndef CS_PLATFORM +#error "CS_PLATFORM is not specified and we couldn't guess it." +#endif + +#define MG_NET_IF_SOCKET 1 +#define MG_NET_IF_SIMPLELINK 2 +#define MG_NET_IF_LWIP_LOW_LEVEL 3 +#define MG_NET_IF_PIC32 4 +#define MG_NET_IF_NULL 5 + +#define MG_SSL_IF_OPENSSL 1 +#define MG_SSL_IF_MBEDTLS 2 +#define MG_SSL_IF_SIMPLELINK 3 + +#if CS_PLATFORM == CS_P_FLIPPER +#include "platforms/platform_flipper.h" +#endif + +/* Common stuff */ + +#if !defined(PRINTF_LIKE) +#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__) +#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a))) +#else +#define PRINTF_LIKE(f, a) +#endif +#endif + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)) && \ + !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifdef __GNUC__ +#define NORETURN __attribute__((noreturn)) +#define NOINLINE __attribute__((noinline)) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#define NOINSTR __attribute__((no_instrument_function)) +#define DO_NOT_WARN_UNUSED __attribute__((unused)) +#else +#define NORETURN +#define NOINLINE +#define WARN_UNUSED_RESULT +#define NOINSTR +#define DO_NOT_WARN_UNUSED +#endif /* __GNUC__ */ + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0])) +#endif + +#endif /* CS_COMMON_PLATFORM_H_ */ \ No newline at end of file diff --git a/lib/mjs/common/platforms/platform_flipper.c b/lib/mjs/common/platforms/platform_flipper.c new file mode 100644 index 00000000000..bbef561d072 --- /dev/null +++ b/lib/mjs/common/platforms/platform_flipper.c @@ -0,0 +1,65 @@ +#include +#include +#include "../cs_dbg.h" +#include "../frozen/frozen.h" + +char* cs_read_file(const char* path, size_t* size) { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = file_stream_alloc(storage); + char* data = NULL; + if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + } else { + *size = stream_size(stream); + data = (char*)malloc(*size + 1); + if(data != NULL) { + stream_rewind(stream); + if(stream_read(stream, (uint8_t*)data, *size) != *size) { + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + free(data); + return NULL; + } + data[*size] = '\0'; + } + } + file_stream_close(stream); + furi_record_close(RECORD_STORAGE); + stream_free(stream); + return data; +} + +char* json_fread(const char* path) { + UNUSED(path); + return NULL; +} + +int json_vfprintf(const char* file_name, const char* fmt, va_list ap) { + UNUSED(file_name); + UNUSED(fmt); + UNUSED(ap); + return 0; +} + +int json_prettify_file(const char* file_name) { + UNUSED(file_name); + return 0; +} + +int json_printer_file(struct json_out* out, const char* buf, size_t len) { + UNUSED(out); + UNUSED(buf); + UNUSED(len); + return 0; +} + +int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) { + (void)level; + (void)file; + (void)ln; + return 0; +} + +void cs_log_printf(const char* fmt, ...) { + (void)fmt; +} diff --git a/lib/mjs/common/platforms/platform_flipper.h b/lib/mjs/common/platforms/platform_flipper.h new file mode 100644 index 00000000000..2a118e0fa91 --- /dev/null +++ b/lib/mjs/common/platforms/platform_flipper.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#if CS_PLATFORM == CS_P_FLIPPER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define to64(x) strtoll(x, NULL, 10) +#define INT64_FMT "lld" +#define SIZE_T_FMT "u" +typedef struct stat cs_stat_t; +#define DIRSEP '/' + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 0 +#endif + +#ifndef MG_ENABLE_FILESYSTEM +#define MG_ENABLE_FILESYSTEM 0 +#endif + +#endif /* CS_PLATFORM == CS_P_FLIPPER */ \ No newline at end of file diff --git a/lib/mjs/common/str_util.c b/lib/mjs/common/str_util.c new file mode 100644 index 00000000000..76e40682f3b --- /dev/null +++ b/lib/mjs/common/str_util.c @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXCLUDE_COMMON + +#include "str_util.h" +#include "mg_mem.h" +#include "platform.h" + +#ifndef C_DISABLE_BUILTIN_SNPRINTF +#define C_DISABLE_BUILTIN_SNPRINTF 1 +#endif + +#include "mg_mem.h" + +size_t c_strnlen(const char* s, size_t maxlen) WEAK; +size_t c_strnlen(const char* s, size_t maxlen) { + size_t l = 0; + for(; l < maxlen && s[l] != '\0'; l++) { + } + return l; +} + +#define C_SNPRINTF_APPEND_CHAR(ch) \ + do { \ + if(i < (int)buf_size) buf[i] = ch; \ + i++; \ + } while(0) + +#define C_SNPRINTF_FLAG_ZERO 1 + +#if C_DISABLE_BUILTIN_SNPRINTF +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK; +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) { + return vsnprintf(buf, buf_size, fmt, ap); +} +#else +static int c_itoa(char* buf, size_t buf_size, int64_t num, int base, int flags, int field_width) { + char tmp[40]; + int i = 0, k = 0, neg = 0; + + if(num < 0) { + neg++; + num = -num; + } + + /* Print into temporary buffer - in reverse order */ + do { + int rem = num % base; + if(rem < 10) { + tmp[k++] = '0' + rem; + } else { + tmp[k++] = 'a' + (rem - 10); + } + num /= base; + } while(num > 0); + + /* Zero padding */ + if(flags && C_SNPRINTF_FLAG_ZERO) { + while(k < field_width && k < (int)sizeof(tmp) - 1) { + tmp[k++] = '0'; + } + } + + /* And sign */ + if(neg) { + tmp[k++] = '-'; + } + + /* Now output */ + while(--k >= 0) { + C_SNPRINTF_APPEND_CHAR(tmp[k]); + } + + return i; +} + +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK; +int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) { + int ch, i = 0, len_mod, flags, precision, field_width; + + while((ch = *fmt++) != '\0') { + if(ch != '%') { + C_SNPRINTF_APPEND_CHAR(ch); + } else { + /* + * Conversion specification: + * zero or more flags (one of: # 0 - + ') + * an optional minimum field width (digits) + * an optional precision (. followed by digits, or *) + * an optional length modifier (one of: hh h l ll L q j z t) + * conversion specifier (one of: d i o u x X e E f F g G a A c s p n) + */ + flags = field_width = precision = len_mod = 0; + + /* Flags. only zero-pad flag is supported. */ + if(*fmt == '0') { + flags |= C_SNPRINTF_FLAG_ZERO; + } + + /* Field width */ + while(*fmt >= '0' && *fmt <= '9') { + field_width *= 10; + field_width += *fmt++ - '0'; + } + /* Dynamic field width */ + if(*fmt == '*') { + field_width = va_arg(ap, int); + fmt++; + } + + /* Precision */ + if(*fmt == '.') { + fmt++; + if(*fmt == '*') { + precision = va_arg(ap, int); + fmt++; + } else { + while(*fmt >= '0' && *fmt <= '9') { + precision *= 10; + precision += *fmt++ - '0'; + } + } + } + + /* Length modifier */ + switch(*fmt) { + case 'h': + case 'l': + case 'L': + case 'I': + case 'q': + case 'j': + case 'z': + case 't': + len_mod = *fmt++; + if(*fmt == 'h') { + len_mod = 'H'; + fmt++; + } + if(*fmt == 'l') { + len_mod = 'q'; + fmt++; + } + break; + } + + ch = *fmt++; + if(ch == 's') { + const char* s = va_arg(ap, const char*); /* Always fetch parameter */ + int j; + int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0); + for(j = 0; j < pad; j++) { + C_SNPRINTF_APPEND_CHAR(' '); + } + + /* `s` may be NULL in case of %.*s */ + if(s != NULL) { + /* Ignore negative and 0 precisions */ + for(j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) { + C_SNPRINTF_APPEND_CHAR(s[j]); + } + } + } else if(ch == 'c') { + ch = va_arg(ap, int); /* Always fetch parameter */ + C_SNPRINTF_APPEND_CHAR(ch); + } else if(ch == 'd' && len_mod == 0) { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags, field_width); + } else if(ch == 'd' && len_mod == 'l') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags, field_width); +#ifdef SSIZE_MAX + } else if(ch == 'd' && len_mod == 'z') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags, field_width); +#endif + } else if(ch == 'd' && len_mod == 'q') { + i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags, field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 0) { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, unsigned), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 'l') { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, unsigned long), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if((ch == 'x' || ch == 'u') && len_mod == 'z') { + i += c_itoa( + buf + i, + buf_size - i, + va_arg(ap, size_t), + ch == 'x' ? 16 : 10, + flags, + field_width); + } else if(ch == 'p') { + unsigned long num = (unsigned long)(uintptr_t)va_arg(ap, void*); + C_SNPRINTF_APPEND_CHAR('0'); + C_SNPRINTF_APPEND_CHAR('x'); + i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0); + } else { +#ifndef NO_LIBC + /* + * TODO(lsm): abort is not nice in a library, remove it + * Also, ESP8266 SDK doesn't have it + */ + abort(); +#endif + } + } + } + + /* Zero-terminate the result */ + if(buf_size > 0) { + buf[i < (int)buf_size ? i : (int)buf_size - 1] = '\0'; + } + + return i; +} +#endif + +int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) WEAK; +int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) { + int result; + va_list ap; + va_start(ap, fmt); + result = c_vsnprintf(buf, buf_size, fmt, ap); + va_end(ap); + return result; +} + +#ifdef _WIN32 +int to_wchar(const char* path, wchar_t* wbuf, size_t wbuf_len) { + int ret; + char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p; + + strncpy(buf, path, sizeof(buf)); + buf[sizeof(buf) - 1] = '\0'; + + /* Trim trailing slashes. Leave backslash for paths like "X:\" */ + p = buf + strlen(buf) - 1; + while(p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0'; + + memset(wbuf, 0, wbuf_len * sizeof(wchar_t)); + ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)wbuf_len); + + /* + * Convert back to Unicode. If doubly-converted string does not match the + * original, something is fishy, reject. + */ + WideCharToMultiByte(CP_UTF8, 0, wbuf, (int)wbuf_len, buf2, sizeof(buf2), NULL, NULL); + if(strcmp(buf, buf2) != 0) { + wbuf[0] = L'\0'; + ret = 0; + } + + return ret; +} +#endif /* _WIN32 */ + +/* The simplest O(mn) algorithm. Better implementation are GPLed */ +const char* c_strnstr(const char* s, const char* find, size_t slen) WEAK; +const char* c_strnstr(const char* s, const char* find, size_t slen) { + size_t find_length = strlen(find); + size_t i; + + for(i = 0; i < slen; i++) { + if(i + find_length > slen) { + return NULL; + } + + if(strncmp(&s[i], find, find_length) == 0) { + return &s[i]; + } + } + + return NULL; +} + +#if CS_ENABLE_STRDUP +char* strdup(const char* src) WEAK; +char* strdup(const char* src) { + size_t len = strlen(src) + 1; + char* ret = MG_MALLOC(len); + if(ret != NULL) { + strcpy(ret, src); + } + return ret; +} +#endif + +void cs_to_hex(char* to, const unsigned char* p, size_t len) WEAK; +void cs_to_hex(char* to, const unsigned char* p, size_t len) { + static const char* hex = "0123456789abcdef"; + + for(; len--; p++) { + *to++ = hex[p[0] >> 4]; + *to++ = hex[p[0] & 0x0f]; + } + *to = '\0'; +} + +static int fourbit(int ch) { + if(ch >= '0' && ch <= '9') { + return ch - '0'; + } else if(ch >= 'a' && ch <= 'f') { + return ch - 'a' + 10; + } else if(ch >= 'A' && ch <= 'F') { + return ch - 'A' + 10; + } + return 0; +} + +void cs_from_hex(char* to, const char* p, size_t len) WEAK; +void cs_from_hex(char* to, const char* p, size_t len) { + size_t i; + + for(i = 0; i < len; i += 2) { + *to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]); + } + *to = '\0'; +} + +#if CS_ENABLE_TO64 +int64_t cs_to64(const char* s) WEAK; +int64_t cs_to64(const char* s) { + int64_t result = 0; + int64_t neg = 1; + while(*s && isspace((unsigned char)*s)) s++; + if(*s == '-') { + neg = -1; + s++; + } + while(isdigit((unsigned char)*s)) { + result *= 10; + result += (*s - '0'); + s++; + } + return result * neg; +} +#endif + +static int str_util_lowercase(const char* s) { + return tolower(*(const unsigned char*)s); +} + +int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK; +int mg_ncasecmp(const char* s1, const char* s2, size_t len) { + int diff = 0; + + if(len > 0) do { + diff = str_util_lowercase(s1++) - str_util_lowercase(s2++); + } while(diff == 0 && s1[-1] != '\0' && --len > 0); + + return diff; +} + +int mg_casecmp(const char* s1, const char* s2) WEAK; +int mg_casecmp(const char* s1, const char* s2) { + return mg_ncasecmp(s1, s2, (size_t)~0); +} + +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) WEAK; +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) { + int ret; + va_list ap; + va_start(ap, fmt); + ret = mg_avprintf(buf, size, fmt, ap); + va_end(ap); + return ret; +} + +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) WEAK; +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) { + va_list ap_copy; + int len; + + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size, fmt, ap_copy); + va_end(ap_copy); + + if(len < 0) { + /* eCos and Windows are not standard-compliant and return -1 when + * the buffer is too small. Keep allocating larger buffers until we + * succeed or out of memory. */ + *buf = NULL; /* LCOV_EXCL_START */ + while(len < 0) { + MG_FREE(*buf); + if(size == 0) { + size = 5; + } + size *= 2; + if((*buf = (char*)MG_MALLOC(size)) == NULL) { + len = -1; + break; + } + va_copy(ap_copy, ap); + len = vsnprintf(*buf, size - 1, fmt, ap_copy); + va_end(ap_copy); + } + + /* + * Microsoft version of vsnprintf() is not always null-terminated, so put + * the terminator manually + */ + (*buf)[len] = 0; + /* LCOV_EXCL_STOP */ + } else if(len >= (int)size) { + /* Standard-compliant code path. Allocate a buffer that is large enough. */ + if((*buf = (char*)MG_MALLOC(len + 1)) == NULL) { + len = -1; /* LCOV_EXCL_LINE */ + } else { /* LCOV_EXCL_LINE */ + va_copy(ap_copy, ap); + len = vsnprintf(*buf, len + 1, fmt, ap_copy); + va_end(ap_copy); + } + } + + return len; +} + +const char* mg_next_comma_list_entry(const char*, struct mg_str*, struct mg_str*) WEAK; +const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val) { + struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val); + return ret.p; +} + +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) WEAK; +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) { + if(list.len == 0) { + /* End of the list */ + list = mg_mk_str(NULL); + } else { + const char* chr = NULL; + *val = list; + + if((chr = mg_strchr(*val, ',')) != NULL) { + /* Comma found. Store length and shift the list ptr */ + val->len = chr - val->p; + chr++; + list.len -= (chr - list.p); + list.p = chr; + } else { + /* This value is the last one */ + list = mg_mk_str_n(list.p + list.len, 0); + } + + if(eq_val != NULL) { + /* Value has form "x=y", adjust pointers and lengths */ + /* so that val points to "x", and eq_val points to "y". */ + eq_val->len = 0; + eq_val->p = (const char*)memchr(val->p, '=', val->len); + if(eq_val->p != NULL) { + eq_val->p++; /* Skip over '=' character */ + eq_val->len = val->p + val->len - eq_val->p; + val->len = (eq_val->p - val->p) - 1; + } + } + } + + return list; +} + +size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK; +size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) { + const char* or_str; + size_t res = 0, len = 0, i = 0, j = 0; + + if((or_str = (const char*)memchr(pattern.p, '|', pattern.len)) != NULL || + (or_str = (const char*)memchr(pattern.p, ',', pattern.len)) != NULL) { + struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)}; + res = mg_match_prefix_n(pstr, str); + if(res > 0) return res; + pstr.p = or_str + 1; + pstr.len = (pattern.p + pattern.len) - (or_str + 1); + return mg_match_prefix_n(pstr, str); + } + + for(; i < pattern.len && j < str.len; i++, j++) { + if(pattern.p[i] == '?') { + continue; + } else if(pattern.p[i] == '*') { + i++; + if(i < pattern.len && pattern.p[i] == '*') { + i++; + len = str.len - j; + } else { + len = 0; + while(j + len < str.len && str.p[j + len] != '/') len++; + } + if(i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1)) return j + len; + do { + const struct mg_str pstr = {pattern.p + i, pattern.len - i}; + const struct mg_str sstr = {str.p + j + len, str.len - j - len}; + res = mg_match_prefix_n(pstr, sstr); + } while(res == 0 && len != 0 && len-- > 0); + return res == 0 ? 0 : j + res + len; + } else if(str_util_lowercase(&pattern.p[i]) != str_util_lowercase(&str.p[j])) { + break; + } + } + if(i < pattern.len && pattern.p[i] == '$') { + return j == str.len ? str.len : 0; + } + return i == pattern.len ? j : 0; +} + +size_t mg_match_prefix(const char*, int, const char*) WEAK; +size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str) { + const struct mg_str pstr = {pattern, (size_t)pattern_len}; + struct mg_str s = {str, 0}; + if(str != NULL) s.len = strlen(str); + return mg_match_prefix_n(pstr, s); +} + +#endif /* EXCLUDE_COMMON */ diff --git a/lib/mjs/common/str_util.h b/lib/mjs/common/str_util.h new file mode 100644 index 00000000000..13873b52c4f --- /dev/null +++ b/lib/mjs/common/str_util.h @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2014-2018 Cesanta Software Limited + * All rights reserved + * + * Licensed under the Apache License, Version 2.0 (the ""License""); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an ""AS IS"" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef CS_COMMON_STR_UTIL_H_ +#define CS_COMMON_STR_UTIL_H_ + +#include +#include + +#include "mg_str.h" +#include "platform.h" + +#ifndef CS_ENABLE_STRDUP +#define CS_ENABLE_STRDUP 0 +#endif + +#ifndef CS_ENABLE_TO64 +#define CS_ENABLE_TO64 0 +#endif + +/* + * Expands to a string representation of its argument: e.g. + * `CS_STRINGIFY_LIT(5) expands to "5"` + */ +#if !defined(_MSC_VER) || _MSC_VER >= 1900 +#define CS_STRINGIFY_LIT(...) #__VA_ARGS__ +#else +#define CS_STRINGIFY_LIT(x) #x +#endif + +/* + * Expands to a string representation of its argument, which is allowed + * to be a macro: e.g. + * + * #define FOO 123 + * CS_STRINGIFY_MACRO(FOO) + * + * expands to 123. + */ +#define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x) + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Equivalent of standard `strnlen()`. + */ +size_t c_strnlen(const char* s, size_t maxlen); + +/* + * Equivalent of standard `snprintf()`. + */ +int c_snprintf(char* buf, size_t buf_size, const char* format, ...) PRINTF_LIKE(3, 4); + +/* + * Equivalent of standard `vsnprintf()`. + */ +int c_vsnprintf(char* buf, size_t buf_size, const char* format, va_list ap); + +/* + * Find the first occurrence of find in s, where the search is limited to the + * first slen characters of s. + */ +const char* c_strnstr(const char* s, const char* find, size_t slen); + +/* + * Stringify binary data. Output buffer size must be 2 * size_of_input + 1 + * because each byte of input takes 2 bytes in string representation + * plus 1 byte for the terminating \0 character. + */ +void cs_to_hex(char* to, const unsigned char* p, size_t len); + +/* + * Convert stringified binary data back to binary. + * Does the reverse of `cs_to_hex()`. + */ +void cs_from_hex(char* to, const char* p, size_t len); + +#if CS_ENABLE_STRDUP +/* + * Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1. + */ +char* strdup(const char* src); +#endif + +#if CS_ENABLE_TO64 +#include +/* + * Simple string -> int64 conversion routine. + */ +int64_t cs_to64(const char* s); +#endif + +/* + * Cross-platform version of `strncasecmp()`. + */ +int mg_ncasecmp(const char* s1, const char* s2, size_t len); + +/* + * Cross-platform version of `strcasecmp()`. + */ +int mg_casecmp(const char* s1, const char* s2); + +/* + * Prints message to the buffer. If the buffer is large enough to hold the + * message, it returns buffer. If buffer is to small, it allocates a large + * enough buffer on heap and returns allocated buffer. + * This is a supposed use case: + * + * ```c + * char buf[5], *p = buf; + * mg_avprintf(&p, sizeof(buf), "%s", "hi there"); + * use_p_somehow(p); + * if (p != buf) { + * free(p); + * } + * ``` + * + * The purpose of this is to avoid malloc-ing if generated strings are small. + */ +int mg_asprintf(char** buf, size_t size, const char* fmt, ...) PRINTF_LIKE(3, 4); + +/* Same as mg_asprintf, but takes varargs list. */ +int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap); + +/* + * A helper function for traversing a comma separated list of values. + * It returns a list pointer shifted to the next value or NULL if the end + * of the list found. + * The value is stored in a val vector. If the value has a form "x=y", then + * eq_val vector is initialised to point to the "y" part, and val vector length + * is adjusted to point only to "x". + * If the list is just a comma separated list of entries, like "aa,bb,cc" then + * `eq_val` will contain zero-length string. + * + * The purpose of this function is to parse comma separated string without + * any copying/memory allocation. + */ +const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val); + +/* + * Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`. + * NB: Test return value's .p, not .len. On last itreation that yields result + * .len will be 0 but .p will not. When finished, .p will be NULL. + */ +struct mg_str + mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val); + +/* + * Matches 0-terminated string (mg_match_prefix) or string with given length + * mg_match_prefix_n against a glob pattern. Glob syntax: + * ``` + * - * matches zero or more characters until a slash character / + * - ** matches zero or more characters + * - ? Matches exactly one character which is not a slash / + * - | or , divides alternative patterns + * - any other character matches itself + * ``` + * Match is case-insensitive. Return number of bytes matched. + * Examples: + * ``` + * mg_match_prefix("a*f", len, "abcdefgh") == 6 + * mg_match_prefix("a*f", len, "abcdexgh") == 0 + * mg_match_prefix("a*f|de*,xy", len, "defgh") == 5 + * mg_match_prefix("?*", len, "abc") == 3 + * mg_match_prefix("?*", len, "") == 0 + * ``` + */ +size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str); + +/* + * Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`. + */ +size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); + +#ifdef __cplusplus +} +#endif + +#endif /* CS_COMMON_STR_UTIL_H_ */ diff --git a/lib/mjs/ffi/ffi.c b/lib/mjs/ffi/ffi.c new file mode 100644 index 00000000000..19d0981aeb0 --- /dev/null +++ b/lib/mjs/ffi/ffi.c @@ -0,0 +1,553 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "ffi.h" + +#define IS_W(arg) ((arg).ctype == FFI_CTYPE_WORD) +#define IS_D(arg) ((arg).ctype == FFI_CTYPE_DOUBLE) +#define IS_F(arg) ((arg).ctype == FFI_CTYPE_FLOAT) + +#define W(arg) ((ffi_word_t)(arg).v.i) +#define D(arg) ((arg).v.d) +#define F(arg) ((arg).v.f) + +void ffi_set_word(struct ffi_arg* arg, ffi_word_t v) { + arg->ctype = FFI_CTYPE_WORD; + arg->v.i = v; +} + +void ffi_set_bool(struct ffi_arg* arg, bool v) { + arg->ctype = FFI_CTYPE_BOOL; + arg->v.i = v; +} + +void ffi_set_ptr(struct ffi_arg* arg, void* v) { + ffi_set_word(arg, (ffi_word_t)v); +} + +void ffi_set_double(struct ffi_arg* arg, double v) { + arg->ctype = FFI_CTYPE_DOUBLE; + arg->v.d = v; +} + +void ffi_set_float(struct ffi_arg* arg, float v) { + arg->ctype = FFI_CTYPE_FLOAT; + arg->v.f = v; +} + +/* + * The ARM ABI uses only 4 32-bit registers for paramter passing. + * Xtensa call0 calling-convention (as used by Espressif) has 6. + * + * Focusing only on implementing FFI with registers means we can simplify a lot. + * + * ARM has some quasi-alignment rules when mixing double and integers as + * arguments. Only: + * a) double, int32_t, int32_t + * b) int32_t, double + * would fit in 4 registers. (the same goes for uint64_t). + * + * In order to simplify further, when a double-width argument is present, we + * allow only two arguments. + */ + +/* + * We need to support x86_64 in order to support local tests. + * x86_64 has more and wider registers, but unlike the two main + * embedded platforms we target it has a separate register file for + * integer values and for floating point values (both for passing args and + * return values). E.g. if a double value is passed as a second argument + * it gets passed in the first available floating point register. + * + * I.e, the compiler generates exactly the same code for: + * + * void foo(int a, double b) {...} + * + * and + * + * void foo(double b, int a) {...} + * + * + */ + +typedef ffi_word_t (*w4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*w5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*w6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); + +typedef ffi_word_t (*wdw_t)(double, ffi_word_t); +typedef ffi_word_t (*wwd_t)(ffi_word_t, double); +typedef ffi_word_t (*wdd_t)(double, double); + +typedef ffi_word_t (*wwwd_t)(ffi_word_t, ffi_word_t, double); +typedef ffi_word_t (*wwdw_t)(ffi_word_t, double, ffi_word_t); +typedef ffi_word_t (*wwdd_t)(ffi_word_t, double, double); +typedef ffi_word_t (*wdww_t)(double, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*wdwd_t)(double, ffi_word_t, double); +typedef ffi_word_t (*wddw_t)(double, double, ffi_word_t); +typedef ffi_word_t (*wddd_t)(double, double, double); + +typedef ffi_word_t (*wfw_t)(float, ffi_word_t); +typedef ffi_word_t (*wwf_t)(ffi_word_t, float); +typedef ffi_word_t (*wff_t)(float, float); + +typedef ffi_word_t (*wwwf_t)(ffi_word_t, ffi_word_t, float); +typedef ffi_word_t (*wwfw_t)(ffi_word_t, float, ffi_word_t); +typedef ffi_word_t (*wwff_t)(ffi_word_t, float, float); +typedef ffi_word_t (*wfww_t)(float, ffi_word_t, ffi_word_t); +typedef ffi_word_t (*wfwf_t)(float, ffi_word_t, float); +typedef ffi_word_t (*wffw_t)(float, float, ffi_word_t); +typedef ffi_word_t (*wfff_t)(float, float, float); + +typedef bool (*b4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*b5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*b6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef bool (*bdw_t)(double, ffi_word_t); +typedef bool (*bwd_t)(ffi_word_t, double); +typedef bool (*bdd_t)(double, double); + +typedef bool (*bwwd_t)(ffi_word_t, ffi_word_t, double); +typedef bool (*bwdw_t)(ffi_word_t, double, ffi_word_t); +typedef bool (*bwdd_t)(ffi_word_t, double, double); +typedef bool (*bdww_t)(double, ffi_word_t, ffi_word_t); +typedef bool (*bdwd_t)(double, ffi_word_t, double); +typedef bool (*bddw_t)(double, double, ffi_word_t); +typedef bool (*bddd_t)(double, double, double); + +typedef bool (*bfw_t)(float, ffi_word_t); +typedef bool (*bwf_t)(ffi_word_t, float); +typedef bool (*bff_t)(float, float); + +typedef bool (*bwwf_t)(ffi_word_t, ffi_word_t, float); +typedef bool (*bwfw_t)(ffi_word_t, float, ffi_word_t); +typedef bool (*bwff_t)(ffi_word_t, float, float); +typedef bool (*bfww_t)(float, ffi_word_t, ffi_word_t); +typedef bool (*bfwf_t)(float, ffi_word_t, float); +typedef bool (*bffw_t)(float, float, ffi_word_t); +typedef bool (*bfff_t)(float, float, float); + +typedef double (*d4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*d5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*d6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef double (*ddw_t)(double, ffi_word_t); +typedef double (*dwd_t)(ffi_word_t, double); +typedef double (*ddd_t)(double, double); + +typedef double (*dwwd_t)(ffi_word_t, ffi_word_t, double); +typedef double (*dwdw_t)(ffi_word_t, double, ffi_word_t); +typedef double (*dwdd_t)(ffi_word_t, double, double); +typedef double (*ddww_t)(double, ffi_word_t, ffi_word_t); +typedef double (*ddwd_t)(double, ffi_word_t, double); +typedef double (*dddw_t)(double, double, ffi_word_t); +typedef double (*dddd_t)(double, double, double); + +typedef float (*f4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*f5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*f6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t); +typedef float (*ffw_t)(float, ffi_word_t); +typedef float (*fwf_t)(ffi_word_t, float); +typedef float (*fff_t)(float, float); + +typedef float (*fwwf_t)(ffi_word_t, ffi_word_t, float); +typedef float (*fwfw_t)(ffi_word_t, float, ffi_word_t); +typedef float (*fwff_t)(ffi_word_t, float, float); +typedef float (*ffww_t)(float, ffi_word_t, ffi_word_t); +typedef float (*ffwf_t)(float, ffi_word_t, float); +typedef float (*fffw_t)(float, float, ffi_word_t); +typedef float (*ffff_t)(float, float, float); + +int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args) { + int i, doubles = 0, floats = 0; + + if(nargs > 6) return -1; + for(i = 0; i < nargs; i++) { + doubles += (IS_D(args[i])); + floats += (IS_F(args[i])); + } + + /* Doubles and floats are not supported together atm */ + if(doubles > 0 && floats > 0) { + return -1; + } + + switch(res->ctype) { + case FFI_CTYPE_WORD: { /* {{{ */ + ffi_word_t r; + if(doubles == 0) { + if(floats == 0) { + /* + * No double and no float args: we currently support up to 6 + * word-sized arguments + */ + if(nargs <= 4) { + w4w_t f = (w4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + w5w_t f = (w5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + w6w_t f = (w6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some floats */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + wff_t f = (wff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + wfw_t f = (wfw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + wwf_t f = (wwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + wwwf_t f = (wwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + wwfw_t f = (wwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + wwff_t f = (wwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + wfww_t f = (wfww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + wfwf_t f = (wfwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + wffw_t f = (wffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + wfff_t f = (wfff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + } else { + /* There are some doubles */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + wdd_t f = (wdd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + wdw_t f = (wdw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + wwd_t f = (wwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + wwwd_t f = (wwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + wwdw_t f = (wwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + wwdd_t f = (wwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + wdww_t f = (wdww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + wdwd_t f = (wdwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + wddw_t f = (wddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + wddd_t f = (wddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.i = (uint64_t)r; + } break; /* }}} */ + case FFI_CTYPE_BOOL: { /* {{{ */ + ffi_word_t r; + if(doubles == 0) { + if(floats == 0) { + /* + * No double and no float args: we currently support up to 6 + * word-sized arguments + */ + if(nargs <= 4) { + b4w_t f = (b4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + b5w_t f = (b5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + b6w_t f = (b6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some floats */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + bff_t f = (bff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + bfw_t f = (bfw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + bwf_t f = (bwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + bwwf_t f = (bwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + bwfw_t f = (bwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + bwff_t f = (bwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + bfww_t f = (bfww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + bfwf_t f = (bfwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + bffw_t f = (bffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + bfff_t f = (bfff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + } else { + /* There are some doubles */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + bdd_t f = (bdd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + bdw_t f = (bdw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + bwd_t f = (bwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + bwwd_t f = (bwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + bwdw_t f = (bwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + bwdd_t f = (bwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + bdww_t f = (bdww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + bdwd_t f = (bdwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + bddw_t f = (bddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + bddd_t f = (bddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.i = (uint64_t)r; + } break; /* }}} */ + case FFI_CTYPE_DOUBLE: { /* {{{ */ + double r; + if(doubles == 0) { + /* No double args: we currently support up to 6 word-sized arguments + */ + if(nargs <= 4) { + d4w_t f = (d4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + d5w_t f = (d5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + d6w_t f = (d6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_D(args[0]) && IS_D(args[1])) { + ddd_t f = (ddd_t)func; + r = f(D(args[0]), D(args[1])); + } else if(IS_D(args[0])) { + ddw_t f = (ddw_t)func; + r = f(D(args[0]), W(args[1])); + } else { + dwd_t f = (dwd_t)func; + r = f(W(args[0]), D(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + dwwd_t f = (dwwd_t)func; + r = f(W(args[0]), W(args[1]), D(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + dwdw_t f = (dwdw_t)func; + r = f(W(args[0]), D(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + dwdd_t f = (dwdd_t)func; + r = f(W(args[0]), D(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + ddww_t f = (ddww_t)func; + r = f(D(args[0]), W(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) { + ddwd_t f = (ddwd_t)func; + r = f(D(args[0]), W(args[1]), D(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) { + dddw_t f = (dddw_t)func; + r = f(D(args[0]), D(args[1]), W(args[2])); + } else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) { + dddd_t f = (dddd_t)func; + r = f(D(args[0]), D(args[1]), D(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.d = r; + } break; /* }}} */ + case FFI_CTYPE_FLOAT: { /* {{{ */ + double r; + if(floats == 0) { + /* No float args: we currently support up to 6 word-sized arguments + */ + if(nargs <= 4) { + f4w_t f = (f4w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3])); + } else if(nargs == 5) { + f5w_t f = (f5w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4])); + } else if(nargs == 6) { + f6w_t f = (f6w_t)func; + r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5])); + } else { + abort(); + } + } else { + /* There are some float args */ + switch(nargs) { + case 0: + case 1: + case 2: + if(IS_F(args[0]) && IS_F(args[1])) { + fff_t f = (fff_t)func; + r = f(F(args[0]), F(args[1])); + } else if(IS_F(args[0])) { + ffw_t f = (ffw_t)func; + r = f(F(args[0]), W(args[1])); + } else { + fwf_t f = (fwf_t)func; + r = f(W(args[0]), F(args[1])); + } + break; + + case 3: + if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + fwwf_t f = (fwwf_t)func; + r = f(W(args[0]), W(args[1]), F(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + fwfw_t f = (fwfw_t)func; + r = f(W(args[0]), F(args[1]), W(args[2])); + } else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + fwff_t f = (fwff_t)func; + r = f(W(args[0]), F(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) { + ffww_t f = (ffww_t)func; + r = f(F(args[0]), W(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) { + ffwf_t f = (ffwf_t)func; + r = f(F(args[0]), W(args[1]), F(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) { + fffw_t f = (fffw_t)func; + r = f(F(args[0]), F(args[1]), W(args[2])); + } else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) { + ffff_t f = (ffff_t)func; + r = f(F(args[0]), F(args[1]), F(args[2])); + } else { + // The above checks should be exhaustive + abort(); + } + break; + default: + return -1; + } + } + res->v.f = r; + } break; /* }}} */ + } + + return 0; +} diff --git a/lib/mjs/ffi/ffi.h b/lib/mjs/ffi/ffi.h new file mode 100644 index 00000000000..2543841ecac --- /dev/null +++ b/lib/mjs/ffi/ffi.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_FFI_H_ +#define MJS_FFI_FFI_H_ + +#include "../common/platform.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Maximum number of word-sized args to ffi-ed function. If at least one + * of the args is double, only 2 args are allowed. + */ +#define FFI_MAX_ARGS_CNT 6 + +typedef void(ffi_fn_t)(void); + +typedef intptr_t ffi_word_t; + +enum ffi_ctype { + FFI_CTYPE_WORD, + FFI_CTYPE_BOOL, + FFI_CTYPE_FLOAT, + FFI_CTYPE_DOUBLE, +}; + +struct ffi_arg { + enum ffi_ctype ctype; + union { + uint64_t i; + double d; + float f; + } v; +}; + +int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args); + +void ffi_set_word(struct ffi_arg* arg, ffi_word_t v); +void ffi_set_bool(struct ffi_arg* arg, bool v); +void ffi_set_ptr(struct ffi_arg* arg, void* v); +void ffi_set_double(struct ffi_arg* arg, double v); +void ffi_set_float(struct ffi_arg* arg, float v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_FFI_H_ */ diff --git a/lib/mjs/mjs_array.c b/lib/mjs/mjs_array.c new file mode 100644 index 00000000000..c74487d65a8 --- /dev/null +++ b/lib/mjs/mjs_array.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include +#include "common/str_util.h" +#include "mjs_array.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +#define SPLICE_NEW_ITEM_IDX 2 + +/* like c_snprintf but returns `size` if write is truncated */ +static int v_sprintf_s(char* buf, size_t size, const char* fmt, ...) { + size_t n; + va_list ap; + va_start(ap, fmt); + n = c_vsnprintf(buf, size, fmt, ap); + if(n > size) { + return size; + } + return n; +} + +mjs_val_t mjs_mk_array(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_object(mjs); + /* change the tag to MJS_TAG_ARRAY */ + ret &= ~MJS_TAG_MASK; + ret |= MJS_TAG_ARRAY; + return ret; +} + +int mjs_is_array(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY; +} + +mjs_val_t mjs_array_get(struct mjs* mjs, mjs_val_t arr, unsigned long index) { + return mjs_array_get2(mjs, arr, index, NULL); +} + +mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has) { + mjs_val_t res = MJS_UNDEFINED; + + if(has != NULL) { + *has = 0; + } + + if(mjs_is_object(arr)) { + struct mjs_property* p; + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + p = mjs_get_own_property(mjs, arr, buf, n); + if(p != NULL) { + if(has != NULL) { + *has = 1; + } + res = p->value; + } + } + + return res; +} + +unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t v) { + struct mjs_property* p; + unsigned long len = 0; + + if(!mjs_is_object(v)) { + len = 0; + goto clean; + } + + for(p = get_object_struct(v)->properties; p != NULL; p = p->next) { + int ok = 0; + unsigned long n = 0; + str_to_ulong(mjs, p->name, &ok, &n); + if(ok && n >= len && n < 0xffffffff) { + len = n + 1; + } + } + +clean: + return len; +} + +mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v) { + mjs_err_t ret = MJS_OK; + + if(mjs_is_object(arr)) { + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + ret = mjs_set(mjs, arr, buf, n, v); + } else { + ret = MJS_TYPE_ERROR; + } + + return ret; +} + +void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index) { + char buf[20]; + int n = v_sprintf_s(buf, sizeof(buf), "%lu", index); + mjs_del(mjs, arr, buf, n); +} + +mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v) { + return mjs_array_set(mjs, arr, mjs_array_length(mjs, arr), v); +} + +MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs) { + mjs_err_t rcode = MJS_OK; + mjs_val_t ret = MJS_UNDEFINED; + int nargs = mjs_nargs(mjs); + int i; + + /* Make sure that `this` is an array */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) { + goto clean; + } + + /* Push all args */ + for(i = 0; i < nargs; i++) { + rcode = mjs_array_push(mjs, mjs->vals.this_obj, mjs_arg(mjs, i)); + if(rcode != MJS_OK) { + mjs_prepend_errorf(mjs, rcode, ""); + goto clean; + } + } + + /* Return the new array length */ + ret = mjs_mk_number(mjs, mjs_array_length(mjs, mjs->vals.this_obj)); + +clean: + mjs_return(mjs, ret); + return; +} + +static void move_item(struct mjs* mjs, mjs_val_t arr, unsigned long from, unsigned long to) { + mjs_val_t cur = mjs_array_get(mjs, arr, from); + mjs_array_set(mjs, arr, to, cur); + mjs_array_del(mjs, arr, from); +} + +MJS_PRIVATE void mjs_array_splice(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_err_t rcode = MJS_OK; + mjs_val_t ret = mjs_mk_array(mjs); + mjs_val_t start_v = MJS_UNDEFINED; + mjs_val_t deleteCount_v = MJS_UNDEFINED; + int start = 0; + int arr_len; + int delete_cnt = 0; + int new_items_cnt = 0; + int delta = 0; + int i; + + /* Make sure that `this` is an array */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) { + goto clean; + } + + /* Get array length */ + arr_len = mjs_array_length(mjs, mjs->vals.this_obj); + + /* get start from arg 0 */ + if(!mjs_check_arg(mjs, 0, "start", MJS_TYPE_NUMBER, &start_v)) { + goto clean; + } + start = mjs_normalize_idx(mjs_get_int(mjs, start_v), arr_len); + + /* Handle deleteCount */ + if(nargs >= SPLICE_NEW_ITEM_IDX) { + /* deleteCount is given; use it */ + if(!mjs_check_arg(mjs, 1, "deleteCount", MJS_TYPE_NUMBER, &deleteCount_v)) { + goto clean; + } + delete_cnt = mjs_get_int(mjs, deleteCount_v); + new_items_cnt = nargs - SPLICE_NEW_ITEM_IDX; + } else { + /* deleteCount is not given; assume the end of the array */ + delete_cnt = arr_len - start; + } + if(delete_cnt > arr_len - start) { + delete_cnt = arr_len - start; + } else if(delete_cnt < 0) { + delete_cnt = 0; + } + + /* delta at which subsequent array items should be moved */ + delta = new_items_cnt - delete_cnt; + + /* + * copy items which are going to be deleted to the separate array (will be + * returned) + */ + for(i = 0; i < delete_cnt; i++) { + mjs_val_t cur = mjs_array_get(mjs, mjs->vals.this_obj, start + i); + rcode = mjs_array_push(mjs, ret, cur); + if(rcode != MJS_OK) { + mjs_prepend_errorf(mjs, rcode, ""); + goto clean; + } + } + + /* If needed, move subsequent items */ + if(delta < 0) { + for(i = start; i < arr_len; i++) { + if(i >= start - delta) { + move_item(mjs, mjs->vals.this_obj, i, i + delta); + } else { + mjs_array_del(mjs, mjs->vals.this_obj, i); + } + } + } else if(delta > 0) { + for(i = arr_len - 1; i >= start; i--) { + move_item(mjs, mjs->vals.this_obj, i, i + delta); + } + } + + /* Set new items to the array */ + for(i = 0; i < nargs - SPLICE_NEW_ITEM_IDX; i++) { + mjs_array_set(mjs, mjs->vals.this_obj, start + i, mjs_arg(mjs, SPLICE_NEW_ITEM_IDX + i)); + } + +clean: + mjs_return(mjs, ret); +} diff --git a/lib/mjs/mjs_array.h b/lib/mjs/mjs_array.h new file mode 100644 index 00000000000..896a83be539 --- /dev/null +++ b/lib/mjs/mjs_array.h @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_ARRAY_H_ +#define MJS_ARRAY_H_ + +#include "mjs_internal.h" +#include "mjs_array_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has); + +MJS_PRIVATE void mjs_array_splice(struct mjs* mjs); + +MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_ARRAY_H_ */ diff --git a/lib/mjs/mjs_array_buf.c b/lib/mjs/mjs_array_buf.c new file mode 100644 index 00000000000..df7760a3d06 --- /dev/null +++ b/lib/mjs/mjs_array_buf.c @@ -0,0 +1,385 @@ +#include "mjs_array_buf.h" +#include "common/cs_varint.h" +#include "common/mg_str.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_object.h" +#include "mjs_array.h" +#include "mjs_util.h" +#include "mjs_exec_public.h" + +#ifndef MJS_ARRAY_BUF_RESERVE +#define MJS_ARRAY_BUF_RESERVE 100 +#endif + +#define IS_SIGNED(type) \ + (type == MJS_DATAVIEW_I8 || type == MJS_DATAVIEW_I16 || type == MJS_DATAVIEW_I32) + +int mjs_is_array_buf(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF; +} + +int mjs_is_data_view(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW; +} + +int mjs_is_typed_array(mjs_val_t v) { + return ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF) || + ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW); +} + +char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen) { + struct mbuf* m = &mjs->array_buffers; + size_t offset = buf & ~MJS_TAG_MASK; + char* ptr = m->buf + offset; + + uint64_t len = 0; + size_t header_len = 0; + if(offset < m->len && cs_varint_decode((uint8_t*)ptr, m->len - offset, &len, &header_len)) { + if(bytelen) { + *bytelen = len; + } + return ptr + header_len; + } + + return NULL; +} + +static size_t mjs_dataview_get_element_len(mjs_dataview_type_t type) { + size_t len = 1; + switch(type) { + case MJS_DATAVIEW_U8: + case MJS_DATAVIEW_I8: + len = 1; + break; + case MJS_DATAVIEW_U16: + case MJS_DATAVIEW_I16: + len = 2; + break; + case MJS_DATAVIEW_U32: + case MJS_DATAVIEW_I32: + len = 4; + break; + default: + break; + } + return len; +} + +static int64_t get_value(char* buf, mjs_dataview_type_t type) { + int64_t value = 0; + switch(type) { + case MJS_DATAVIEW_U8: + value = *(uint8_t*)buf; + break; + case MJS_DATAVIEW_I8: + value = *(int8_t*)buf; + break; + case MJS_DATAVIEW_U16: + value = *(uint16_t*)buf; + break; + case MJS_DATAVIEW_I16: + value = *(int16_t*)buf; + break; + case MJS_DATAVIEW_U32: + value = *(uint32_t*)buf; + break; + case MJS_DATAVIEW_I32: + value = *(int32_t*)buf; + break; + default: + break; + } + return value; +} + +static void set_value(char* buf, int64_t value, mjs_dataview_type_t type) { + switch(type) { + case MJS_DATAVIEW_U8: + *(uint8_t*)buf = (uint8_t)value; + break; + case MJS_DATAVIEW_I8: + *(int8_t*)buf = (int8_t)value; + break; + case MJS_DATAVIEW_U16: + *(uint16_t*)buf = (uint16_t)value; + break; + case MJS_DATAVIEW_I16: + *(int16_t*)buf = (int16_t)value; + break; + case MJS_DATAVIEW_U32: + *(uint32_t*)buf = (uint32_t)value; + break; + case MJS_DATAVIEW_I32: + *(int32_t*)buf = (int32_t)value; + break; + default: + break; + } +} + +static mjs_val_t mjs_dataview_get(struct mjs* mjs, mjs_val_t obj, size_t index) { + mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1); + + size_t byte_len = 0; + char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len); + mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1)); + if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) { + return MJS_UNDEFINED; + } + + buf += mjs_dataview_get_element_len(type) * index; + int64_t value = get_value(buf, type); + + return mjs_mk_number(mjs, value); +} + +static mjs_err_t mjs_dataview_set(struct mjs* mjs, mjs_val_t obj, size_t index, int64_t value) { + mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1); + + size_t byte_len = 0; + char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len); + mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1)); + if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) { + return MJS_TYPE_ERROR; + } + + buf += mjs_dataview_get_element_len(type) * index; + set_value(buf, value, type); + + return MJS_OK; +} + +mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) { + if(!mjs_is_number(key)) { + return MJS_UNDEFINED; + } + int index = mjs_get_int(mjs, key); + return mjs_dataview_get(mjs, obj, index); +} + +mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val) { + if(!mjs_is_number(key)) { + return MJS_TYPE_ERROR; + } + int index = mjs_get_int(mjs, key); + int64_t value = 0; + + if(mjs_is_number(val)) { + value = mjs_get_double(mjs, val); + } else if(mjs_is_boolean(val)) { + value = mjs_get_bool(mjs, val) ? (1) : (0); + } + return mjs_dataview_set(mjs, obj, index, value); +} + +mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj) { + return mjs_get(mjs, obj, "buffer", -1); +} + +mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj) { + size_t bytelen = 0; + mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, obj), &bytelen); + mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1)); + size_t element_len = mjs_dataview_get_element_len(type); + + return mjs_mk_number(mjs, bytelen / element_len); +} + +mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len) { + struct mbuf* m = &mjs->array_buffers; + + if((m->len + buf_len) > m->size) { + char* prev_buf = m->buf; + mbuf_resize(m, m->len + buf_len + MJS_ARRAY_BUF_RESERVE); + + if(data >= prev_buf && data < (prev_buf + m->len)) { + data += m->buf - prev_buf; + } + } + + size_t offset = m->len; + char* prev_buf = m->buf; + + size_t header_len = cs_varint_llen(buf_len); + mbuf_insert(m, offset, NULL, header_len + buf_len); + if(data >= prev_buf && data < (prev_buf + m->len)) { + data += m->buf - prev_buf; + } + + cs_varint_encode(buf_len, (unsigned char*)m->buf + offset, header_len); + + if(data != NULL) { + memcpy(m->buf + offset + header_len, data, buf_len); + } else { + memset(m->buf + offset + header_len, 0, buf_len); + } + + return (offset & ~MJS_TAG_MASK) | MJS_TAG_ARRAY_BUF; +} + +void mjs_array_buf_slice(struct mjs* mjs) { + size_t nargs = mjs_nargs(mjs); + mjs_val_t src = mjs_get_this(mjs); + + size_t start = 0; + size_t end = 0; + char* src_buf = NULL; + size_t src_len = 0; + + bool args_correct = false; + do { + if(!mjs_is_array_buf(src)) { + break; + } + src_buf = mjs_array_buf_get_ptr(mjs, src, &src_len); + + if((nargs == 0) || (nargs > 2)) { + break; + } + + mjs_val_t start_obj = mjs_arg(mjs, 0); + if(!mjs_is_number(start_obj)) { + break; + } + start = mjs_get_int32(mjs, start_obj); + + if(nargs == 2) { + mjs_val_t end_obj = mjs_arg(mjs, 1); + if(!mjs_is_number(end_obj)) { + break; + } + end = mjs_get_int32(mjs, end_obj); + } else { + end = src_len - 1; + } + + if((start >= src_len) || (end >= src_len) || (start >= end)) { + break; + } + + args_correct = true; + } while(0); + + if(!args_correct) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + src_buf += start; + mjs_return(mjs, mjs_mk_array_buf(mjs, src_buf, end - start)); +} + +static mjs_val_t + mjs_mk_dataview_from_buf(struct mjs* mjs, mjs_val_t buf, mjs_dataview_type_t type) { + size_t len = 0; + mjs_array_buf_get_ptr(mjs, buf, &len); + if(len % mjs_dataview_get_element_len(type) != 0) { + mjs_prepend_errorf( + mjs, MJS_BAD_ARGS_ERROR, "Buffer len is not a multiple of element size"); + return MJS_UNDEFINED; + } + mjs_val_t view_obj = mjs_mk_object(mjs); + mjs_set(mjs, view_obj, "_t", ~0, mjs_mk_number(mjs, (double)type)); + mjs_set(mjs, view_obj, "buffer", ~0, buf); + + view_obj &= ~MJS_TAG_MASK; + view_obj |= MJS_TAG_ARRAY_BUF_VIEW; + + mjs_dataview_get(mjs, view_obj, 0); + + return view_obj; +} + +static mjs_val_t + mjs_mk_dataview(struct mjs* mjs, size_t len, mjs_val_t arr, mjs_dataview_type_t type) { + size_t elements_nb = 0; + if(mjs_is_array(arr)) { + if(!mjs_is_number(mjs_array_get(mjs, arr, 0))) { + return MJS_UNDEFINED; + } + elements_nb = mjs_array_length(mjs, arr); + } else { + elements_nb = len; + } + + size_t element_len = mjs_dataview_get_element_len(type); + mjs_val_t buf_obj = mjs_mk_array_buf(mjs, NULL, element_len * elements_nb); + + if(mjs_is_array(arr)) { + char* buf_ptr = mjs_array_buf_get_ptr(mjs, buf_obj, NULL); + for(uint8_t i = 0; i < elements_nb; i++) { + int64_t value = mjs_get_double(mjs, mjs_array_get(mjs, arr, i)); + set_value(buf_ptr, value, type); + buf_ptr += element_len; + } + } + + return mjs_mk_dataview_from_buf(mjs, buf_obj, type); +} + +static void mjs_array_buf_new(struct mjs* mjs) { + mjs_val_t len_arg = mjs_arg(mjs, 0); + mjs_val_t buf_obj = MJS_UNDEFINED; + if(!mjs_is_number(len_arg)) { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } else { + int len = mjs_get_int(mjs, len_arg); + buf_obj = mjs_mk_array_buf(mjs, NULL, len); + } + mjs_return(mjs, buf_obj); +} + +static void mjs_dataview_new(struct mjs* mjs, mjs_dataview_type_t type) { + mjs_val_t view_arg = mjs_arg(mjs, 0); + mjs_val_t view_obj = MJS_UNDEFINED; + + if(mjs_is_array_buf(view_arg)) { // Create a view of existing ArrayBuf + view_obj = mjs_mk_dataview_from_buf(mjs, view_arg, type); + } else if(mjs_is_number(view_arg)) { // Create new typed array + int len = mjs_get_int(mjs, view_arg); + view_obj = mjs_mk_dataview(mjs, len, MJS_UNDEFINED, type); + } else if(mjs_is_array(view_arg)) { // Create new typed array from array + view_obj = mjs_mk_dataview(mjs, 0, view_arg, type); + } else { + mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); + } + + mjs_return(mjs, view_obj); +} + +static void mjs_new_u8_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_U8); +} + +static void mjs_new_i8_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_I8); +} + +static void mjs_new_u16_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_U16); +} + +static void mjs_new_i16_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_I16); +} + +static void mjs_new_u32_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_U32); +} + +static void mjs_new_i32_array(struct mjs* mjs) { + mjs_dataview_new(mjs, MJS_DATAVIEW_I32); +} + +void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj) { + mjs_set(mjs, obj, "ArrayBuffer", ~0, MJS_MK_FN(mjs_array_buf_new)); + mjs_set(mjs, obj, "Uint8Array", ~0, MJS_MK_FN(mjs_new_u8_array)); + mjs_set(mjs, obj, "Int8Array", ~0, MJS_MK_FN(mjs_new_i8_array)); + mjs_set(mjs, obj, "Uint16Array", ~0, MJS_MK_FN(mjs_new_u16_array)); + mjs_set(mjs, obj, "Int16Array", ~0, MJS_MK_FN(mjs_new_i16_array)); + mjs_set(mjs, obj, "Uint32Array", ~0, MJS_MK_FN(mjs_new_u32_array)); + mjs_set(mjs, obj, "Int32Array", ~0, MJS_MK_FN(mjs_new_i32_array)); +} diff --git a/lib/mjs/mjs_array_buf.h b/lib/mjs/mjs_array_buf.h new file mode 100644 index 00000000000..2ff90afdf95 --- /dev/null +++ b/lib/mjs/mjs_array_buf.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#pragma once + +#include "mjs_internal.h" +#include "mjs_array_buf_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val); + +void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj); + +mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj); + +void mjs_array_buf_slice(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ diff --git a/lib/mjs/mjs_array_buf_public.h b/lib/mjs/mjs_array_buf_public.h new file mode 100644 index 00000000000..8b7081e169d --- /dev/null +++ b/lib/mjs/mjs_array_buf_public.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#pragma once + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +typedef enum { + MJS_DATAVIEW_U8, + MJS_DATAVIEW_I8, + MJS_DATAVIEW_U16, + MJS_DATAVIEW_I16, + MJS_DATAVIEW_U32, + MJS_DATAVIEW_I32, +} mjs_dataview_type_t; + +int mjs_is_array_buf(mjs_val_t v); + +int mjs_is_data_view(mjs_val_t v); + +int mjs_is_typed_array(mjs_val_t v); + +mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len); + +char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen); + +mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ diff --git a/lib/mjs/mjs_array_public.h b/lib/mjs/mjs_array_public.h new file mode 100644 index 00000000000..8d043d86edf --- /dev/null +++ b/lib/mjs/mjs_array_public.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +/* + * === Arrays + */ + +#ifndef MJS_ARRAY_PUBLIC_H_ +#define MJS_ARRAY_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* Make an empty array object */ +mjs_val_t mjs_mk_array(struct mjs* mjs); + +/* Returns length on an array. If `arr` is not an array, 0 is returned. */ +unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t arr); + +/* Insert value `v` in array `arr` at the end of the array. */ +mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v); + +/* + * Return array member at index `index`. If `index` is out of bounds, undefined + * is returned. + */ +mjs_val_t mjs_array_get(struct mjs*, mjs_val_t arr, unsigned long index); + +/* Insert value `v` into `arr` at index `index`. */ +mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v); + +/* Returns true if the given value is an array */ +int mjs_is_array(mjs_val_t v); + +/* Delete value in array `arr` at index `index`, if it exists. */ +void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_ARRAY_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_bcode.c b/lib/mjs/mjs_bcode.c new file mode 100644 index 00000000000..48a8ac8c75c --- /dev/null +++ b/lib/mjs/mjs_bcode.c @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" + +#include "mjs_internal.h" +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_tok.h" + +static void add_lineno_map_item(struct pstate* pstate) { + if(pstate->last_emitted_line_no < pstate->line_no) { + int offset = pstate->cur_idx - pstate->start_bcode_idx; + size_t offset_llen = cs_varint_llen(offset); + size_t lineno_llen = cs_varint_llen(pstate->line_no); + mbuf_resize( + &pstate->offset_lineno_map, + pstate->offset_lineno_map.size + offset_llen + lineno_llen); + + /* put offset */ + cs_varint_encode( + offset, + (uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len, + offset_llen); + pstate->offset_lineno_map.len += offset_llen; + + /* put line_no */ + cs_varint_encode( + pstate->line_no, + (uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len, + lineno_llen); + pstate->offset_lineno_map.len += lineno_llen; + + pstate->last_emitted_line_no = pstate->line_no; + } +} + +MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte) { + add_lineno_map_item(pstate); + mbuf_insert(&pstate->mjs->bcode_gen, pstate->cur_idx, &byte, sizeof(byte)); + pstate->cur_idx += sizeof(byte); +} + +MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n) { + struct mbuf* b = &pstate->mjs->bcode_gen; + size_t llen = cs_varint_llen(n); + add_lineno_map_item(pstate); + mbuf_insert(b, pstate->cur_idx, NULL, llen); + cs_varint_encode(n, (uint8_t*)b->buf + pstate->cur_idx, llen); + pstate->cur_idx += llen; +} + +MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len) { + struct mbuf* b = &pstate->mjs->bcode_gen; + size_t llen = cs_varint_llen(len); + add_lineno_map_item(pstate); + mbuf_insert(b, pstate->cur_idx, NULL, llen + len); + cs_varint_encode(len, (uint8_t*)b->buf + pstate->cur_idx, llen); + memcpy(b->buf + pstate->cur_idx + llen, ptr, len); + pstate->cur_idx += llen + len; +} + +MJS_PRIVATE int + mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v) { + int llen = (int)cs_varint_llen(v); + int diff = llen - MJS_INIT_OFFSET_SIZE; + assert(offset < mjs->bcode_gen.len); + if(diff > 0) { + mbuf_resize(&mjs->bcode_gen, mjs->bcode_gen.size + diff); + } + /* + * Offset is going to take more than one was reserved, so, move the data + * forward + */ + memmove( + mjs->bcode_gen.buf + offset + llen, + mjs->bcode_gen.buf + offset + MJS_INIT_OFFSET_SIZE, + mjs->bcode_gen.len - offset - MJS_INIT_OFFSET_SIZE); + mjs->bcode_gen.len += diff; + cs_varint_encode(v, (uint8_t*)mjs->bcode_gen.buf + offset, llen); + + /* + * If current parsing index is after the offset at which we've inserted new + * varint, the index might need to be adjusted + */ + if(p->cur_idx >= (int)offset) { + p->cur_idx += diff; + } + return diff; +} + +MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp) { + mbuf_append(&mjs->bcode_parts, bp, sizeof(*bp)); +} + +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num) { + assert(num < mjs_bcode_parts_cnt(mjs)); + return (struct mjs_bcode_part*)(mjs->bcode_parts.buf + num * sizeof(struct mjs_bcode_part)); +} + +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset) { + int i; + int parts_cnt = mjs_bcode_parts_cnt(mjs); + struct mjs_bcode_part* bp = NULL; + + if(offset >= mjs->bcode_len) { + return NULL; + } + + for(i = 0; i < parts_cnt; i++) { + bp = mjs_bcode_part_get(mjs, i); + if(offset < bp->start_idx + bp->data.len) { + break; + } + } + + /* given the non-corrupted data, the needed part must be found */ + assert(i < parts_cnt); + + return bp; +} + +MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs) { + return mjs->bcode_parts.len / sizeof(struct mjs_bcode_part); +} + +MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs) { + struct mjs_bcode_part bp; + memset(&bp, 0, sizeof(bp)); + + /* Make sure the bcode doesn't occupy any extra space */ + mbuf_trim(&mjs->bcode_gen); + + /* Transfer the ownership of the bcode data */ + bp.data.p = mjs->bcode_gen.buf; + bp.data.len = mjs->bcode_gen.len; + mbuf_init(&mjs->bcode_gen, 0); + + bp.start_idx = mjs->bcode_len; + bp.exec_res = MJS_ERRS_CNT; + + mjs_bcode_part_add(mjs, &bp); + + mjs->bcode_len += bp.data.len; +} diff --git a/lib/mjs/mjs_bcode.h b/lib/mjs/mjs_bcode.h new file mode 100644 index 00000000000..2d077ccecea --- /dev/null +++ b/lib/mjs/mjs_bcode.h @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_BCODE_H_ +#define MJS_BCODE_H_ + +#include "mjs_internal.h" + +#include "mjs_core.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +enum mjs_opcode { + OP_NOP, /* ( -- ) */ + OP_DROP, /* ( a -- ) */ + OP_DUP, /* ( a -- a a ) */ + OP_SWAP, /* ( a b -- b a ) */ + OP_JMP, /* ( -- ) */ + OP_JMP_TRUE, /* ( -- ) */ + OP_JMP_NEUTRAL_TRUE, /* ( -- ) */ + OP_JMP_FALSE, /* ( -- ) */ + OP_JMP_NEUTRAL_FALSE, /* ( -- ) */ + OP_FIND_SCOPE, /* ( a -- a b ) */ + OP_PUSH_SCOPE, /* ( -- a ) */ + OP_PUSH_STR, /* ( -- a ) */ + OP_PUSH_TRUE, /* ( -- a ) */ + OP_PUSH_FALSE, /* ( -- a ) */ + OP_PUSH_INT, /* ( -- a ) */ + OP_PUSH_DBL, /* ( -- a ) */ + OP_PUSH_NULL, /* ( -- a ) */ + OP_PUSH_UNDEF, /* ( -- a ) */ + OP_PUSH_OBJ, /* ( -- a ) */ + OP_PUSH_ARRAY, /* ( -- a ) */ + OP_PUSH_FUNC, /* ( -- a ) */ + OP_PUSH_THIS, /* ( -- a ) */ + OP_GET, /* ( key obj -- obj[key] ) */ + OP_CREATE, /* ( key obj -- ) */ + OP_EXPR, /* ( ... -- a ) */ + OP_APPEND, /* ( a b -- ) */ + OP_SET_ARG, /* ( a -- a ) */ + OP_NEW_SCOPE, /* ( -- ) */ + OP_DEL_SCOPE, /* ( -- ) */ + OP_CALL, /* ( func param1 param2 ... num_params -- result ) */ + OP_RETURN, /* ( -- ) */ + OP_LOOP, /* ( -- ) Push break & continue addresses to loop_labels */ + OP_BREAK, /* ( -- ) */ + OP_CONTINUE, /* ( -- ) */ + OP_SETRETVAL, /* ( a -- ) */ + OP_EXIT, /* ( -- ) */ + OP_BCODE_HEADER, /* ( -- ) */ + OP_ARGS, /* ( -- ) Mark the beginning of function call arguments */ + OP_FOR_IN_NEXT, /* ( name obj iter_ptr -- name obj iter_ptr_next ) */ + OP_MAX +}; + +struct pstate; +struct mjs; + +MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte); +MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n); +MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len); + +/* + * Inserts provided offset `v` at the offset `offset`. + * + * Returns delta at which the code was moved; the delta can be any: 0 or + * positive or negative. + */ +MJS_PRIVATE int + mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v); + +/* + * Adds a new bcode part; does not retain `bp`. + */ +MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp); + +/* + * Returns bcode part by the bcode number + */ +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num); + +/* + * Returns bcode part by the global bcode offset + */ +MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset); + +/* + * Returns a number of bcode parts + */ +MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs); + +/* + * Adds the bcode being generated (mjs->bcode_gen) as a next bcode part + */ +MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_BCODE_H_ */ diff --git a/lib/mjs/mjs_builtin.c b/lib/mjs/mjs_builtin.c new file mode 100644 index 00000000000..afcf9ce6f07 --- /dev/null +++ b/lib/mjs/mjs_builtin.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_dataview.h" +#include "mjs_exec.h" +#include "mjs_gc.h" +#include "mjs_internal.h" +#include "mjs_json.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" +#include "mjs_array_buf.h" + +/* + * If the file with the given filename was already loaded, returns the + * corresponding bcode part; otherwise returns NULL. + */ +static struct mjs_bcode_part* mjs_get_loaded_file_bcode(struct mjs* mjs, const char* filename) { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + + if(filename == NULL) { + return 0; + } + + for(i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + const char* cur_fn = mjs_get_bcode_filename(mjs, bp); + if(strcmp(filename, cur_fn) == 0) { + return bp; + } + } + return NULL; +} + +static void mjs_load(struct mjs* mjs) { + mjs_val_t res = MJS_UNDEFINED; + mjs_val_t arg0 = mjs_arg(mjs, 0); + mjs_val_t arg1 = mjs_arg(mjs, 1); + int custom_global = 0; /* whether the custom global object was provided */ + + if(mjs_is_string(arg0)) { + const char* path = mjs_get_cstring(mjs, &arg0); + struct mjs_bcode_part* bp = NULL; + mjs_err_t ret; + + if(mjs_is_object(arg1)) { + custom_global = 1; + push_mjs_val(&mjs->scopes, arg1); + } + bp = mjs_get_loaded_file_bcode(mjs, path); + if(bp == NULL) { + /* File was not loaded before, so, load */ + ret = mjs_exec_file(mjs, path, &res); + } else { + /* + * File was already loaded before, so if it was evaluated successfully, + * then skip the evaluation at all (and assume MJS_OK); otherwise + * re-evaluate it again. + * + * However, if the custom global object was provided, then reevaluate + * the file in any case. + */ + if(bp->exec_res != MJS_OK || custom_global) { + ret = mjs_execute(mjs, bp->start_idx, &res); + } else { + ret = MJS_OK; + } + } + if(ret != MJS_OK) { + /* + * arg0 and path might be invalidated by executing a file, so refresh + * them + */ + arg0 = mjs_arg(mjs, 0); + path = mjs_get_cstring(mjs, &arg0); + mjs_prepend_errorf(mjs, ret, "failed to exec file \"%s\"", path); + goto clean; + } + + clean: + if(custom_global) { + mjs_pop_val(&mjs->scopes); + } + } + mjs_return(mjs, res); +} + +static void mjs_get_mjs(struct mjs* mjs) { + mjs_return(mjs, mjs_mk_foreign(mjs, mjs)); +} + +static void mjs_chr(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0), res = MJS_NULL; + int n = mjs_get_int(mjs, arg0); + if(mjs_is_number(arg0) && n >= 0 && n <= 255) { + uint8_t s = n; + res = mjs_mk_string(mjs, (const char*)&s, sizeof(s), 1); + } + mjs_return(mjs, res); +} + +static void mjs_do_gc(struct mjs* mjs) { + mjs_val_t arg0 = mjs_arg(mjs, 0); + mjs_gc(mjs, mjs_is_boolean(arg0) ? mjs_get_bool(mjs, arg0) : 0); + mjs_return(mjs, arg0); +} + +static void mjs_s2o(struct mjs* mjs) { + mjs_return( + mjs, + mjs_struct_to_obj( + mjs, + mjs_get_ptr(mjs, mjs_arg(mjs, 0)), + (const struct mjs_c_struct_member*)mjs_get_ptr(mjs, mjs_arg(mjs, 1)))); +} + +void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) { + mjs_val_t v; + + mjs_set(mjs, obj, "global", ~0, obj); + + mjs_set(mjs, obj, "load", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_load)); + mjs_set(mjs, obj, "ffi", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_call)); + mjs_set( + mjs, obj, "ffi_cb_free", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_cb_free)); + mjs_set(mjs, obj, "mkstr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_mkstr)); + mjs_set(mjs, obj, "getMJS", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_get_mjs)); + mjs_set(mjs, obj, "die", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_die)); + mjs_set(mjs, obj, "gc", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_do_gc)); + mjs_set(mjs, obj, "chr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_chr)); + mjs_set(mjs, obj, "s2o", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_s2o)); + + /* + * Populate JSON.parse() and JSON.stringify() + */ + // v = mjs_mk_object(mjs); + // mjs_set( + // mjs, v, "stringify", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_stringify)); + // mjs_set(mjs, v, "parse", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_parse)); + // mjs_set(mjs, obj, "JSON", ~0, v); + + /* + * Populate Object.create() + */ + v = mjs_mk_object(mjs); + mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object)); + mjs_set(mjs, obj, "Object", ~0, v); + + /* + * Populate numeric stuff + */ + mjs_set(mjs, obj, "NaN", ~0, MJS_TAG_NAN); + mjs_set(mjs, obj, "isNaN", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_isnan)); + + mjs_init_builtin_array_buf(mjs, obj); +} diff --git a/lib/mjs/mjs_builtin.h b/lib/mjs/mjs_builtin.h new file mode 100644 index 00000000000..9bbb2b0fe7f --- /dev/null +++ b/lib/mjs/mjs_builtin.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_BUILTIN_H_ +#define MJS_BUILTIN_H_ + +#include "mjs_core_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_BUILTIN_H_ */ diff --git a/lib/mjs/mjs_core.c b/lib/mjs/mjs_core.c new file mode 100644 index 00000000000..aae196599f2 --- /dev/null +++ b/lib/mjs/mjs_core.c @@ -0,0 +1,422 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" +#include "common/str_util.h" + +#include "mjs_bcode.h" +#include "mjs_builtin.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_ffi.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +#ifndef MJS_OBJECT_ARENA_SIZE +#define MJS_OBJECT_ARENA_SIZE 20 +#endif +#ifndef MJS_PROPERTY_ARENA_SIZE +#define MJS_PROPERTY_ARENA_SIZE 20 +#endif +#ifndef MJS_FUNC_FFI_ARENA_SIZE +#define MJS_FUNC_FFI_ARENA_SIZE 20 +#endif + +#ifndef MJS_OBJECT_ARENA_INC_SIZE +#define MJS_OBJECT_ARENA_INC_SIZE 10 +#endif +#ifndef MJS_PROPERTY_ARENA_INC_SIZE +#define MJS_PROPERTY_ARENA_INC_SIZE 10 +#endif +#ifndef MJS_FUNC_FFI_ARENA_INC_SIZE +#define MJS_FUNC_FFI_ARENA_INC_SIZE 10 +#endif + +void mjs_destroy(struct mjs* mjs) { + { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + for(i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + if(!bp->in_rom) { + free((void*)bp->data.p); + } + } + } + + mbuf_free(&mjs->bcode_gen); + mbuf_free(&mjs->bcode_parts); + mbuf_free(&mjs->stack); + mbuf_free(&mjs->call_stack); + mbuf_free(&mjs->arg_stack); + mbuf_free(&mjs->owned_strings); + mbuf_free(&mjs->foreign_strings); + mbuf_free(&mjs->owned_values); + mbuf_free(&mjs->scopes); + mbuf_free(&mjs->loop_addresses); + mbuf_free(&mjs->json_visited_stack); + mbuf_free(&mjs->array_buffers); + free(mjs->error_msg); + free(mjs->stack_trace); + mjs_ffi_args_free_list(mjs); + gc_arena_destroy(mjs, &mjs->object_arena); + gc_arena_destroy(mjs, &mjs->property_arena); + gc_arena_destroy(mjs, &mjs->ffi_sig_arena); + free(mjs); +} + +struct mjs* mjs_create(void* context) { + mjs_val_t global_object; + struct mjs* mjs = calloc(1, sizeof(*mjs)); + mjs->context = context; + mbuf_init(&mjs->stack, 0); + mbuf_init(&mjs->call_stack, 0); + mbuf_init(&mjs->arg_stack, 0); + mbuf_init(&mjs->owned_strings, 0); + mbuf_init(&mjs->foreign_strings, 0); + mbuf_init(&mjs->bcode_gen, 0); + mbuf_init(&mjs->bcode_parts, 0); + mbuf_init(&mjs->owned_values, 0); + mbuf_init(&mjs->scopes, 0); + mbuf_init(&mjs->loop_addresses, 0); + mbuf_init(&mjs->json_visited_stack, 0); + mbuf_init(&mjs->array_buffers, 0); + + mjs->bcode_len = 0; + + /* + * The compacting GC exploits the null terminator of the previous string as a + * marker. + */ + { + char z = 0; + mbuf_append(&mjs->owned_strings, &z, 1); + } + + gc_arena_init( + &mjs->object_arena, + sizeof(struct mjs_object), + MJS_OBJECT_ARENA_SIZE, + MJS_OBJECT_ARENA_INC_SIZE); + gc_arena_init( + &mjs->property_arena, + sizeof(struct mjs_property), + MJS_PROPERTY_ARENA_SIZE, + MJS_PROPERTY_ARENA_INC_SIZE); + gc_arena_init( + &mjs->ffi_sig_arena, + sizeof(struct mjs_ffi_sig), + MJS_FUNC_FFI_ARENA_SIZE, + MJS_FUNC_FFI_ARENA_INC_SIZE); + mjs->ffi_sig_arena.destructor = mjs_ffi_sig_destructor; + + global_object = mjs_mk_object(mjs); + mjs_init_builtin(mjs, global_object); + mjs_set_ffi_resolver(mjs, NULL, NULL); + push_mjs_val(&mjs->scopes, global_object); + mjs->vals.this_obj = MJS_UNDEFINED; + mjs->vals.dataview_proto = MJS_UNDEFINED; + + return mjs; +} + +mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + free(mjs->error_msg); + mjs->error_msg = NULL; + mjs->error = err; + if(fmt != NULL) { + mg_avprintf(&mjs->error_msg, 0, fmt, ap); + } + va_end(ap); + return err; +} + +void mjs_exit(struct mjs* mjs) { + free(mjs->error_msg); + mjs->error_msg = NULL; + mjs->error = MJS_NEED_EXIT; +} + +void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) { + mjs->exec_flags_poller = poller; +} + +void* mjs_get_context(struct mjs* mjs) { + return mjs->context; +} + +mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) { + char* old_error_msg = mjs->error_msg; + char* new_error_msg = NULL; + va_list ap; + va_start(ap, fmt); + + /* err should never be MJS_OK here */ + assert(err != MJS_OK); + + mjs->error_msg = NULL; + /* set error if only it wasn't already set to some error */ + if(mjs->error == MJS_OK) { + mjs->error = err; + } + mg_avprintf(&new_error_msg, 0, fmt, ap); + va_end(ap); + + if(old_error_msg != NULL) { + mg_asprintf(&mjs->error_msg, 0, "%s: %s", new_error_msg, old_error_msg); + free(new_error_msg); + free(old_error_msg); + } else { + mjs->error_msg = new_error_msg; + } + return err; +} + +void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace) { + (void)fp; + + if(print_stack_trace && mjs->stack_trace != NULL) { + // fprintf(fp, "%s", mjs->stack_trace); + } + + if(msg == NULL) { + msg = "MJS error"; + } + + // fprintf(fp, "%s: %s\n", msg, mjs_strerror(mjs, mjs->error)); +} + +MJS_PRIVATE void mjs_die(struct mjs* mjs) { + mjs_val_t msg_v = MJS_UNDEFINED; + const char* msg = NULL; + size_t msg_len = 0; + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "msg", MJS_TYPE_STRING, &msg_v)) { + goto clean; + } + + msg = mjs_get_string(mjs, &msg_v, &msg_len); + + /* TODO(dfrank): take error type as an argument */ + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "%.*s", (int)msg_len, msg); + +clean: + mjs_return(mjs, MJS_UNDEFINED); +} + +const char* mjs_strerror(struct mjs* mjs, enum mjs_err err) { + const char* err_names[] = { + "NO_ERROR", + "SYNTAX_ERROR", + "REFERENCE_ERROR", + "TYPE_ERROR", + "OUT_OF_MEMORY", + "INTERNAL_ERROR", + "NOT_IMPLEMENTED", + "FILE_OPEN_ERROR", + "BAD_ARGUMENTS"}; + return mjs->error_msg == NULL || mjs->error_msg[0] == '\0' ? err_names[err] : mjs->error_msg; +} + +const char* mjs_get_stack_trace(struct mjs* mjs) { + return mjs->stack_trace; +} + +MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v) { + return v & ~MJS_TAG_MASK; +} + +MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v) { + int tag; + if(mjs_is_number(v)) { + return MJS_TYPE_NUMBER; + } + tag = (v & MJS_TAG_MASK) >> 48; + switch(tag) { + case MJS_TAG_FOREIGN >> 48: + return MJS_TYPE_FOREIGN; + case MJS_TAG_UNDEFINED >> 48: + return MJS_TYPE_UNDEFINED; + case MJS_TAG_OBJECT >> 48: + return MJS_TYPE_OBJECT_GENERIC; + case MJS_TAG_ARRAY >> 48: + return MJS_TYPE_OBJECT_ARRAY; + case MJS_TAG_FUNCTION >> 48: + return MJS_TYPE_OBJECT_FUNCTION; + case MJS_TAG_STRING_I >> 48: + case MJS_TAG_STRING_O >> 48: + case MJS_TAG_STRING_F >> 48: + case MJS_TAG_STRING_D >> 48: + case MJS_TAG_STRING_5 >> 48: + return MJS_TYPE_STRING; + case MJS_TAG_BOOLEAN >> 48: + return MJS_TYPE_BOOLEAN; + case MJS_TAG_NULL >> 48: + return MJS_TYPE_NULL; + case MJS_TAG_ARRAY_BUF >> 48: + return MJS_TYPE_ARRAY_BUF; + case MJS_TAG_ARRAY_BUF_VIEW >> 48: + return MJS_TYPE_ARRAY_BUF_VIEW; + default: + abort(); + return MJS_TYPE_UNDEFINED; + } +} + +mjs_val_t mjs_get_global(struct mjs* mjs) { + return *vptr(&mjs->scopes, 0); +} + +static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) { + if(offset != MJS_BCODE_OFFSET_EXIT) { + 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"; + if(filename == NULL) { + // fprintf( + // stderr, + // "ERROR during stack trace generation: wrong bcode offset %d\n", + // (int)offset); + filename = ""; + } + mg_asprintf(&new_line, 0, fmt, filename, line_no); + + if(mjs->stack_trace != NULL) { + char* old = mjs->stack_trace; + mg_asprintf(&mjs->stack_trace, 0, "%s%s", mjs->stack_trace, new_line); + free(old); + free(new_line); + } else { + mjs->stack_trace = new_line; + } + } +} + +MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset) { + mjs_append_stack_trace_line(mjs, offset); + while(mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT) { + int i; + + /* set current offset to it to the offset stored in the frame */ + offset = mjs_get_int(mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETURN_ADDR)); + + /* pop frame from the call stack */ + for(i = 0; i < CALL_STACK_FRAME_ITEMS_CNT; i++) { + mjs_pop_val(&mjs->call_stack); + } + + mjs_append_stack_trace_line(mjs, offset); + } +} + +void mjs_own(struct mjs* mjs, mjs_val_t* v) { + mbuf_append(&mjs->owned_values, &v, sizeof(v)); +} + +int mjs_disown(struct mjs* mjs, mjs_val_t* v) { + mjs_val_t** vp = (mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v)); + + for(; (char*)vp >= mjs->owned_values.buf; vp--) { + if(*vp == v) { + *vp = *(mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v)); + mjs->owned_values.len -= sizeof(v); + return 1; + } + } + + return 0; +} + +/* + * Returns position in the data stack at which the called function is located, + * and which should be later replaced with the returned value. + */ +MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs) { + int pos; + mjs_val_t* ppos = vptr(&mjs->call_stack, -1); + // LOG(LL_INFO, ("ppos: %p %d", ppos, mjs_stack_size(&mjs->call_stack))); + assert(ppos != NULL && mjs_is_number(*ppos)); + pos = mjs_get_int(mjs, *ppos) - 1; + assert(pos < (int)mjs_stack_size(&mjs->stack)); + return pos; +} + +int mjs_nargs(struct mjs* mjs) { + int top = mjs_stack_size(&mjs->stack); + int pos = mjs_getretvalpos(mjs) + 1; + // LOG(LL_INFO, ("top: %d pos: %d", top, pos)); + return pos > 0 && pos < top ? top - pos : 0; +} + +mjs_val_t mjs_arg(struct mjs* mjs, int arg_index) { + mjs_val_t res = MJS_UNDEFINED; + int top = mjs_stack_size(&mjs->stack); + int pos = mjs_getretvalpos(mjs) + 1; + // LOG(LL_INFO, ("idx %d pos: %d", arg_index, pos)); + if(pos > 0 && pos + arg_index < top) { + res = *vptr(&mjs->stack, pos + arg_index); + } + return res; +} + +void mjs_return(struct mjs* mjs, mjs_val_t v) { + int pos = mjs_getretvalpos(mjs); + // LOG(LL_INFO, ("pos: %d", pos)); + mjs->stack.len = sizeof(mjs_val_t) * pos; + mjs_push(mjs, v); +} + +MJS_PRIVATE mjs_val_t vtop(struct mbuf* m) { + size_t size = mjs_stack_size(m); + return size > 0 ? *vptr(m, size - 1) : MJS_UNDEFINED; +} + +MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m) { + return m->len / sizeof(mjs_val_t); +} + +MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx) { + int size = mjs_stack_size(m); + if(idx < 0) idx = size + idx; + return idx >= 0 && idx < size ? &((mjs_val_t*)m->buf)[idx] : NULL; +} + +MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs) { + if(mjs->stack.len == 0) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "stack underflow"); + return MJS_UNDEFINED; + } else { + return mjs_pop_val(&mjs->stack); + } +} + +MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v) { + mbuf_append(m, &v, sizeof(v)); +} + +MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m) { + mjs_val_t v = MJS_UNDEFINED; + assert(m->len >= sizeof(v)); + if(m->len >= sizeof(v)) { + memcpy(&v, m->buf + m->len - sizeof(v), sizeof(v)); + m->len -= sizeof(v); + } + return v; +} + +MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v) { + push_mjs_val(&mjs->stack, v); +} + +void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc) { + mjs->generate_jsc = generate_jsc; +} diff --git a/lib/mjs/mjs_core.h b/lib/mjs/mjs_core.h new file mode 100644 index 00000000000..81372498768 --- /dev/null +++ b/lib/mjs/mjs_core.h @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_CORE_H +#define MJS_CORE_H + +#include "mjs_ffi.h" +#include "mjs_gc.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#define JUMP_INSTRUCTION_SIZE 2 + +enum mjs_call_stack_frame_item { + CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX, /* TOS */ + CALL_STACK_FRAME_ITEM_LOOP_ADDR_IDX, + CALL_STACK_FRAME_ITEM_SCOPE_IDX, + CALL_STACK_FRAME_ITEM_RETURN_ADDR, + CALL_STACK_FRAME_ITEM_THIS, + + CALL_STACK_FRAME_ITEMS_CNT +}; + +struct mjs_vals { + /* Current `this` value */ + mjs_val_t this_obj; + mjs_val_t dataview_proto; + + /* + * The object against which the last `OP_GET` was invoked. Needed for + * "method invocation pattern". + */ + mjs_val_t last_getprop_obj; +}; + +struct mjs_bcode_part { + /* Global index of the bcode part */ + size_t start_idx; + + /* Actual bcode data */ + struct { + const char* p; /* Memory chunk pointer */ + size_t len; /* Memory chunk length */ + } data; + + /* + * Result of evaluation (not parsing: if there is an error during parsing, + * the bcode is not even committed). It is used to determine whether we + * need to evaluate the file: if file was already evaluated, and the result + * was MJS_OK, then we won't evaluate it again. Otherwise, we will. + */ + mjs_err_t exec_res : 4; + + /* If set, bcode data does not need to be freed */ + unsigned in_rom : 1; +}; + +struct mjs { + struct mbuf bcode_gen; + struct mbuf bcode_parts; + size_t bcode_len; + struct mbuf stack; + struct mbuf call_stack; + struct mbuf arg_stack; + struct mbuf scopes; /* Scope objects */ + struct mbuf loop_addresses; /* Addresses for breaks & continues */ + struct mbuf owned_strings; /* Sequence of (varint len, char data[]) */ + struct mbuf foreign_strings; /* Sequence of (varint len, char *data) */ + struct mbuf owned_values; + struct mbuf json_visited_stack; + struct mbuf array_buffers; + struct mjs_vals vals; + char* error_msg; + char* stack_trace; + enum mjs_err error; + mjs_ffi_resolver_t* dlsym; /* Symbol resolver function for FFI */ + void* dlsym_handle; + ffi_cb_args_t* ffi_cb_args; /* List of FFI args descriptors */ + size_t cur_bcode_offset; + mjs_flags_poller_t exec_flags_poller; + void* context; + + struct gc_arena object_arena; + struct gc_arena property_arena; + struct gc_arena ffi_sig_arena; + + unsigned inhibit_gc : 1; + unsigned need_gc : 1; + unsigned generate_jsc : 1; +}; + +/* + * Bcode header: type of the items, and item numbers. + */ +typedef uint32_t mjs_header_item_t; +enum mjs_header_items { + MJS_HDR_ITEM_TOTAL_SIZE, /* Total size of the bcode (not counting the + OP_BCODE_HEADER byte) */ + MJS_HDR_ITEM_BCODE_OFFSET, /* Offset to the start of the actual bcode (not + counting the OP_BCODE_HEADER byte) */ + MJS_HDR_ITEM_MAP_OFFSET, /* Offset to the start of offset-to-line_no mapping + k*/ + + MJS_HDR_ITEMS_CNT +}; + +MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v); + +MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs); + +MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v); + +/* + * Prints stack trace starting from the given bcode offset; other offsets + * (if any) will be fetched from the call_stack. + */ +MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset); + +MJS_PRIVATE mjs_val_t vtop(struct mbuf* m); +MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m); +MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx); +MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v); +MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m); +MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs); +MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v); +MJS_PRIVATE void mjs_die(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_CORE_H */ diff --git a/lib/mjs/mjs_core_public.h b/lib/mjs/mjs_core_public.h new file mode 100644 index 00000000000..2e22c025ffd --- /dev/null +++ b/lib/mjs/mjs_core_public.h @@ -0,0 +1,286 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_CORE_PUBLIC_H_ +#define MJS_CORE_PUBLIC_H_ + +#if !defined(_MSC_VER) || _MSC_VER >= 1700 +#include +#else +typedef unsigned __int64 uint64_t; +typedef int int32_t; +typedef unsigned char uint8_t; +#endif +#include +#include +#include "mjs_features.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#ifndef MJS_ENABLE_DEBUG +#define MJS_ENABLE_DEBUG 0 +#endif + +/* + * Double-precision floating-point number, IEEE 754 + * + * 64 bit (8 bytes) in total + * 1 bit sign + * 11 bits exponent + * 52 bits mantissa + * 7 6 5 4 3 2 1 0 + * seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm + * + * If an exponent is all-1 and mantissa is all-0, then it is an INFINITY: + * 11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000 + * + * If an exponent is all-1 and mantissa's MSB is 1, it is a quiet NaN: + * 11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000 + * + * MJS NaN-packing: + * sign and exponent is 0xfff + * 4 bits specify type (tag), must be non-zero + * 48 bits specify value + * + * 11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv + * NaN marker |type| 48-bit placeholder for values: pointers, strings + * + * On 64-bit platforms, pointers are really 48 bit only, so they can fit, + * provided they are sign extended + */ + +typedef uint64_t mjs_val_t; + +/* + * A tag is made of the sign bit and the 4 lower order bits of byte 6. + * So in total we have 32 possible tags. + * + * Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an + * INFINITY; for simplicity we're just not going to use that combination. + */ +#define MAKE_TAG(s, t) ((uint64_t)(s) << 63 | (uint64_t)0x7ff0 << 48 | (uint64_t)(t) << 48) + +#define MJS_TAG_OBJECT MAKE_TAG(1, 1) +#define MJS_TAG_FOREIGN MAKE_TAG(1, 2) +#define MJS_TAG_UNDEFINED MAKE_TAG(1, 3) +#define MJS_TAG_BOOLEAN MAKE_TAG(1, 4) +#define MJS_TAG_NAN MAKE_TAG(1, 5) +#define MJS_TAG_STRING_I MAKE_TAG(1, 6) /* Inlined string len < 5 */ +#define MJS_TAG_STRING_5 MAKE_TAG(1, 7) /* Inlined string len 5 */ +#define MJS_TAG_STRING_O MAKE_TAG(1, 8) /* Owned string */ +#define MJS_TAG_STRING_F MAKE_TAG(1, 9) /* Foreign string */ +#define MJS_TAG_STRING_C MAKE_TAG(1, 10) /* String chunk */ +#define MJS_TAG_STRING_D MAKE_TAG(1, 11) /* Dictionary string */ +#define MJS_TAG_ARRAY MAKE_TAG(1, 12) +#define MJS_TAG_FUNCTION MAKE_TAG(1, 13) +#define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14) +#define MJS_TAG_NULL MAKE_TAG(1, 15) + +#define MJS_TAG_ARRAY_BUF MAKE_TAG(0, 1) /* ArrayBuffer */ +#define MJS_TAG_ARRAY_BUF_VIEW MAKE_TAG(0, 2) /* DataView */ + +#define MJS_TAG_MASK MAKE_TAG(1, 15) + +/* This if-0 is a dirty workaround to force etags to pick `struct mjs` */ +#if 0 +/* Opaque structure. MJS engine context. */ +struct mjs { + /* ... */ +}; +#endif + +struct mjs; + +enum mjs_type { + /* Primitive types */ + MJS_TYPE_UNDEFINED, + MJS_TYPE_NULL, + MJS_TYPE_BOOLEAN, + MJS_TYPE_NUMBER, + MJS_TYPE_STRING, + MJS_TYPE_FOREIGN, + MJS_TYPE_ARRAY_BUF, + MJS_TYPE_ARRAY_BUF_VIEW, + + /* Different classes of Object type */ + MJS_TYPE_OBJECT_GENERIC, + MJS_TYPE_OBJECT_ARRAY, + MJS_TYPE_OBJECT_FUNCTION, + /* + * TODO(dfrank): if we support prototypes, need to add items for them here + */ + + MJS_TYPES_CNT +}; + +typedef enum mjs_err { + MJS_OK, + MJS_SYNTAX_ERROR, + MJS_REFERENCE_ERROR, + MJS_TYPE_ERROR, + MJS_OUT_OF_MEMORY, + MJS_INTERNAL_ERROR, + MJS_NOT_IMPLEMENTED_ERROR, + MJS_FILE_READ_ERROR, + MJS_BAD_ARGS_ERROR, + + MJS_NEED_EXIT, + + MJS_ERRS_CNT +} mjs_err_t; + +typedef void (*mjs_flags_poller_t)(struct mjs* mjs); + +struct mjs; + +/* Create MJS instance */ +struct mjs* mjs_create(void* context); + +/* Destroy MJS instance */ +void mjs_destroy(struct mjs* mjs); + +mjs_val_t mjs_get_global(struct mjs* mjs); + +/* + * Tells the GC about an MJS value variable/field owned by C code. + * + * The user's C code should own mjs_val_t variables if the value's lifetime + * crosses any invocation of `mjs_exec()` and friends, including `mjs_call()`. + * + * The registration of the variable prevents the GC from mistakenly treat the + * object as garbage. + * + * User code should also explicitly disown the variables with `mjs_disown()` + * once it goes out of scope or the structure containing the mjs_val_t field is + * freed. + * + * Consider the following examples: + * + * Correct (owning is not necessary): + * ```c + * mjs_val_t res; + * mjs_exec(mjs, "....some script", &res); + * // ... use res somehow + * + * mjs_val_t res; + * mjs_exec(mjs, "....some script2", &res); + * // ... use new res somehow + * ``` + * + * WRONG: + * ```c + * mjs_val_t res1; + * mjs_exec(mjs, "....some script", &res1); + * + * mjs_val_t res2; + * mjs_exec(mjs, "....some script2", &res2); + * + * // ... use res1 (WRONG!) and res2 + * ``` + * + * The code above is wrong, because after the second invocation of + * `mjs_exec()`, the value of `res1` is invalidated. + * + * Correct (res1 is owned) + * ```c + * mjs_val_t res1 = MJS_UNDEFINED; + * mjs_own(mjs, &res1); + * mjs_exec(mjs, "....some script", &res1); + * + * mjs_val_t res2 = MJS_UNDEFINED; + * mjs_exec(mjs, "....some script2", &res2); + * + * // ... use res1 and res2 + * mjs_disown(mjs, &res1); + * ``` + * + * NOTE that we explicly initialized `res1` to a valid value before owning it + * (in this case, the value is `MJS_UNDEFINED`). Owning an uninitialized + * variable is an undefined behaviour. + * + * Of course, it's not an error to own a variable even if it's not mandatory: + * e.g. in the last example we could own both `res1` and `res2`. Probably it + * would help us in the future, when we refactor the code so that `res2` has to + * be owned, and we could forget to do that. + * + * Also, if the user code has some C function called from MJS, and in this C + * function some MJS value (`mjs_val_t`) needs to be stored somewhere and to + * stay alive after the C function has returned, it also needs to be properly + * owned. + */ +void mjs_own(struct mjs* mjs, mjs_val_t* v); + +/* + * Disowns the value previously owned by `mjs_own()`. + * + * Returns 1 if value is found, 0 otherwise. + */ +int mjs_disown(struct mjs* mjs, mjs_val_t* v); + +mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); + +void mjs_exit(struct mjs* mjs); + +void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller); + +void* mjs_get_context(struct mjs* mjs); + +/* + * If there is no error message already set, then it's equal to + * `mjs_set_errorf()`. + * + * Otherwise, an old message gets prepended with the new one, followed by a + * colon. (the previously set error code is kept) + */ +mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...); + +/* + * Print the last error details. If print_stack_trace is non-zero, also + * print stack trace. `msg` is the message which gets prepended to the actual + * error message, if it's NULL, then "MJS error" is used. + */ +void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace); + +/* + * return a string representation of an error. + * the error string might be overwritten by calls to `mjs_set_errorf`. + */ +const char* mjs_strerror(struct mjs* mjs, enum mjs_err err); + +const char* mjs_get_stack_trace(struct mjs* mjs); + +/* + * Sets whether *.jsc files are generated when *.js file is executed. By + * default it's 0. + * + * If either `MJS_GENERATE_JSC` or `CS_MMAP` is off, then this function has no + * effect. + */ +void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc); + +/* + * When invoked from a cfunction, returns number of arguments passed to the + * current JS function call. + */ +int mjs_nargs(struct mjs* mjs); + +/* + * When invoked from a cfunction, returns n-th argument to the current JS + * function call. + */ +mjs_val_t mjs_arg(struct mjs* mjs, int n); + +/* + * Sets return value for the current JS function call. + */ +void mjs_return(struct mjs* mjs, mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_CORE_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_dataview.c b/lib/mjs/mjs_dataview.c new file mode 100644 index 00000000000..007f7b00111 --- /dev/null +++ b/lib/mjs/mjs_dataview.c @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_exec_public.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_util.h" + +void* mjs_mem_to_ptr(unsigned val) { + return (void*)(uintptr_t)val; +} + +void* mjs_mem_get_ptr(void* base, int offset) { + return (char*)base + offset; +} + +void mjs_mem_set_ptr(void* ptr, void* val) { + *(void**)ptr = val; +} + +double mjs_mem_get_dbl(void* ptr) { + double v; + memcpy(&v, ptr, sizeof(v)); + return v; +} + +void mjs_mem_set_dbl(void* ptr, double val) { + memcpy(ptr, &val, sizeof(val)); +} + +/* + * TODO(dfrank): add support for unsigned ints to ffi and use + * unsigned int here + */ +double mjs_mem_get_uint(void* ptr, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr; + int i, inc = bigendian ? 1 : -1; + unsigned int res = 0; + p += bigendian ? 0 : size - 1; + for(i = 0; i < size; i++, p += inc) { + res <<= 8; + res |= *p; + } + return res; +} + +/* + * TODO(dfrank): add support for unsigned ints to ffi and use + * unsigned int here + */ +double mjs_mem_get_int(void* ptr, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr; + int i, inc = bigendian ? 1 : -1; + int res = 0; + p += bigendian ? 0 : size - 1; + + for(i = 0; i < size; i++, p += inc) { + res <<= 8; + res |= *p; + } + + /* sign-extend */ + { + int extra = sizeof(res) - size; + for(i = 0; i < extra; i++) res <<= 8; + for(i = 0; i < extra; i++) res >>= 8; + } + + return res; +} + +void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian) { + uint8_t* p = (uint8_t*)ptr + (bigendian ? size - 1 : 0); + int i, inc = bigendian ? -1 : 1; + for(i = 0; i < size; i++, p += inc) { + *p = val & 0xff; + val >>= 8; + } +} + +void mjs_mem_set_int(void* ptr, int val, int size, int bigendian) { + mjs_mem_set_uint(ptr, val, size, bigendian); +} diff --git a/lib/mjs/mjs_dataview.h b/lib/mjs/mjs_dataview.h new file mode 100644 index 00000000000..6bd10425506 --- /dev/null +++ b/lib/mjs/mjs_dataview.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_DATAVIEW_H_ +#define MJS_DATAVIEW_H_ + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Functions for memory introspection. + * These are supposed to be FFI-ed and used from the JS environment. + */ + +void* mjs_mem_to_ptr(unsigned int val); +void* mjs_mem_get_ptr(void* base, int offset); +void mjs_mem_set_ptr(void* ptr, void* val); +double mjs_mem_get_dbl(void* ptr); +void mjs_mem_set_dbl(void* ptr, double val); +double mjs_mem_get_uint(void* ptr, int size, int bigendian); +double mjs_mem_get_int(void* ptr, int size, int bigendian); +void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian); +void mjs_mem_set_int(void* ptr, int val, int size, int bigendian); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_DATAVIEW_H_ */ diff --git a/lib/mjs/mjs_exec.c b/lib/mjs/mjs_exec.c new file mode 100644 index 00000000000..265e7d5c39b --- /dev/null +++ b/lib/mjs/mjs_exec.c @@ -0,0 +1,1252 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_file.h" +#include "common/cs_varint.h" + +#include "mjs_array.h" +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_parser.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_tok.h" +#include "mjs_util.h" +#include "mjs_array_buf.h" + +#if MJS_GENERATE_JSC && defined(CS_MMAP) +#include +#endif + +/* + * Pushes call stack frame. Offset is a global bcode offset. Retval_stack_idx + * is an index in mjs->stack at which return value should be written later. + */ +static void call_stack_push_frame(struct mjs* mjs, size_t offset, mjs_val_t retval_stack_idx) { + /* Pop `this` value, and apply it */ + mjs_val_t this_obj = mjs_pop_val(&mjs->arg_stack); + + /* + * NOTE: the layout is described by enum mjs_call_stack_frame_item + */ + push_mjs_val(&mjs->call_stack, mjs->vals.this_obj); + mjs->vals.this_obj = this_obj; + + push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)offset)); + push_mjs_val(&mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); + push_mjs_val( + &mjs->call_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->loop_addresses))); + push_mjs_val(&mjs->call_stack, retval_stack_idx); +} + +/* + * Restores call stack frame. Returns the return address. + */ +static size_t call_stack_restore_frame(struct mjs* mjs) { + size_t retval_stack_idx, return_address, scope_index, loop_addr_index; + assert(mjs_stack_size(&mjs->call_stack) >= CALL_STACK_FRAME_ITEMS_CNT); + + /* + * NOTE: the layout is described by enum mjs_call_stack_frame_item + */ + retval_stack_idx = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + loop_addr_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + scope_index = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + return_address = mjs_get_int(mjs, mjs_pop_val(&mjs->call_stack)); + mjs->vals.this_obj = mjs_pop_val(&mjs->call_stack); + + /* Remove created scopes */ + while(mjs_stack_size(&mjs->scopes) > scope_index) { + mjs_pop_val(&mjs->scopes); + } + + /* Remove loop addresses */ + while(mjs_stack_size(&mjs->loop_addresses) > loop_addr_index) { + mjs_pop_val(&mjs->loop_addresses); + } + + /* Shrink stack, leave return value on top */ + mjs->stack.len = retval_stack_idx * sizeof(mjs_val_t); + + /* Jump to the return address */ + return return_address; +} + +static mjs_val_t mjs_find_scope(struct mjs* mjs, mjs_val_t key) { + size_t num_scopes = mjs_stack_size(&mjs->scopes); + while(num_scopes > 0) { + mjs_val_t scope = *vptr(&mjs->scopes, num_scopes - 1); + num_scopes--; + if(mjs_get_own_property_v(mjs, scope, key) != NULL) return scope; + } + mjs_set_errorf(mjs, MJS_REFERENCE_ERROR, "[%s] is not defined", mjs_get_cstring(mjs, &key)); + return MJS_UNDEFINED; +} + +mjs_val_t mjs_get_this(struct mjs* mjs) { + return mjs->vals.this_obj; +} + +static double do_arith_op(double da, double db, int op, bool* resnan) { + *resnan = false; + + if(isnan(da) || isnan(db)) { + *resnan = true; + return 0; + } + /* clang-format off */ + switch (op) { + case TOK_MINUS: return da - db; + case TOK_PLUS: return da + db; + case TOK_MUL: return da * db; + case TOK_DIV: + if (db != 0) { + return da / db; + } else { + /* TODO(dfrank): add support for Infinity and return it here */ + *resnan = true; + return 0; + } + case TOK_REM: + /* + * TODO(dfrank): probably support remainder operation as it is in JS + * (which works with non-integer divisor). + */ + db = (int) db; + if (db != 0) { + bool neg = false; + if (da < 0) { + neg = true; + da = -da; + } + if (db < 0) { + db = -db; + } + da = (double) ((int64_t) da % (int64_t) db); + if (neg) { + da = -da; + } + return da; + } else { + *resnan = true; + return 0; + } + case TOK_AND: return (double) ((int64_t) da & (int64_t) db); + case TOK_OR: return (double) ((int64_t) da | (int64_t) db); + case TOK_XOR: return (double) ((int64_t) da ^ (int64_t) db); + case TOK_LSHIFT: return (double) ((int64_t) da << (int64_t) db); + case TOK_RSHIFT: return (double) ((int64_t) da >> (int64_t) db); + case TOK_URSHIFT: return (double) ((uint32_t) da >> (uint32_t) db); + } + /* clang-format on */ + *resnan = true; + return 0; +} + +static void set_no_autoconversion_error(struct mjs* mjs) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "implicit type conversion is prohibited"); +} + +static mjs_val_t do_op(struct mjs* mjs, mjs_val_t a, mjs_val_t b, int op) { + mjs_val_t ret = MJS_UNDEFINED; + bool resnan = false; + if((mjs_is_foreign(a) || mjs_is_number(a)) && (mjs_is_foreign(b) || mjs_is_number(b))) { + int is_result_ptr = 0; + double da, db, result; + + if(mjs_is_foreign(a) && mjs_is_foreign(b)) { + /* When two operands are pointers, only subtraction is supported */ + if(op != TOK_MINUS) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); + } + } else if(mjs_is_foreign(a) || mjs_is_foreign(b)) { + /* + * When one of the operands is a pointer, only + and - are supported, + * and the result is a pointer. + */ + if(op != TOK_MINUS && op != TOK_PLUS) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid operands"); + } + is_result_ptr = 1; + } + da = mjs_is_number(a) ? mjs_get_double(mjs, a) : (double)(uintptr_t)mjs_get_ptr(mjs, a); + db = mjs_is_number(b) ? mjs_get_double(mjs, b) : (double)(uintptr_t)mjs_get_ptr(mjs, b); + result = do_arith_op(da, db, op, &resnan); + if(resnan) { + ret = MJS_TAG_NAN; + } else { + /* + * If at least one of the operands was a pointer, result should also be + * a pointer + */ + ret = is_result_ptr ? mjs_mk_foreign(mjs, (void*)(uintptr_t)result) : + mjs_mk_number(mjs, result); + } + } else if(mjs_is_string(a) && mjs_is_string(b) && (op == TOK_PLUS)) { + ret = s_concat(mjs, a, b); + } else { + set_no_autoconversion_error(mjs); + } + return ret; +} + +static void op_assign(struct mjs* mjs, int op) { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_set_v(mjs, obj, key, do_op(mjs, v, val, op)); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand"); + } +} + +static int check_equal(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + int ret = 0; + if(a == MJS_TAG_NAN && b == MJS_TAG_NAN) { + ret = 0; + } else if(a == b) { + ret = 1; + } else if(mjs_is_number(a) && mjs_is_number(b)) { + /* + * The case of equal numbers is handled above, so here the result is always + * false + */ + ret = 0; + } else if(mjs_is_string(a) && mjs_is_string(b)) { + ret = s_cmp(mjs, a, b) == 0; + } else if(mjs_is_foreign(a) && b == MJS_NULL) { + ret = mjs_get_ptr(mjs, a) == NULL; + } else if(a == MJS_NULL && mjs_is_foreign(b)) { + ret = mjs_get_ptr(mjs, b) == NULL; + } else { + ret = 0; + } + return ret; +} + +static void exec_expr(struct mjs* mjs, int op) { + switch(op) { + case TOK_DOT: + break; + case TOK_MINUS: + case TOK_PLUS: + case TOK_MUL: + case TOK_DIV: + case TOK_REM: + case TOK_XOR: + case TOK_AND: + case TOK_OR: + case TOK_LSHIFT: + case TOK_RSHIFT: + case TOK_URSHIFT: { + mjs_val_t b = mjs_pop(mjs); + mjs_val_t a = mjs_pop(mjs); + mjs_push(mjs, do_op(mjs, a, b, op)); + break; + } + case TOK_UNARY_MINUS: { + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_number(mjs, -a)); + break; + } + case TOK_NOT: { + mjs_val_t val = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, !mjs_is_truthy(mjs, val))); + break; + } + case TOK_TILDA: { + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_number(mjs, (double)(~(int64_t)a))); + break; + } + case TOK_UNARY_PLUS: + break; + case TOK_EQ: + mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use ===, not =="); + break; + case TOK_NE: + mjs_set_errorf(mjs, MJS_NOT_IMPLEMENTED_ERROR, "Use !==, not !="); + break; + case TOK_EQ_EQ: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, check_equal(mjs, a, b))); + break; + } + case TOK_NE_NE: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, mjs_mk_boolean(mjs, !check_equal(mjs, a, b))); + break; + } + case TOK_LT: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a < b)); + break; + } + case TOK_GT: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a > b)); + break; + } + case TOK_LE: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a <= b)); + break; + } + case TOK_GE: { + double b = mjs_get_double(mjs, mjs_pop(mjs)); + double a = mjs_get_double(mjs, mjs_pop(mjs)); + mjs_push(mjs, mjs_mk_boolean(mjs, a >= b)); + break; + } + case TOK_ASSIGN: { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj)) { + mjs_set_v(mjs, obj, key, val); + } else if(mjs_is_data_view(obj)) { + mjs_err_t err = mjs_dataview_set_prop(mjs, obj, key, val); + if(err != MJS_OK) { + mjs_prepend_errorf(mjs, err, ""); + } + } else if(mjs_is_foreign(obj)) { + /* + * We don't have setters, so in order to support properties which behave + * like setters, we have to parse key right here, instead of having real + * built-in prototype objects + */ + + int ikey = mjs_get_int(mjs, key); + int ival = mjs_get_int(mjs, val); + + if(!mjs_is_number(key)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); + val = MJS_UNDEFINED; + } else if(!mjs_is_number(val) || ival < 0 || ival > 0xff) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "only number 0 .. 255 can be assigned"); + val = MJS_UNDEFINED; + } else { + uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, obj); + *(ptr + ikey) = (uint8_t)ival; + } + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "unsupported object type"); + } + mjs_push(mjs, val); + break; + } + case TOK_POSTFIX_PLUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); + mjs_set_v(mjs, obj, key, v1); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); + } + break; + } + case TOK_POSTFIX_MINUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + mjs_val_t v1 = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); + mjs_set_v(mjs, obj, key, v1); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); + } + break; + } + case TOK_MINUS_MINUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_MINUS); + mjs_set_v(mjs, obj, key, v); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for --"); + } + break; + } + case TOK_PLUS_PLUS: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_is_object(obj) && mjs_is_string(key)) { + mjs_val_t v = mjs_get_v(mjs, obj, key); + v = do_op(mjs, v, mjs_mk_number(mjs, 1), TOK_PLUS); + mjs_set_v(mjs, obj, key, v); + mjs_push(mjs, v); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "invalid operand for ++"); + } + break; + } + /* + * NOTE: TOK_LOGICAL_AND and TOK_LOGICAL_OR don't need to be here, because + * they are just naturally handled by the short-circuit evaluation. + * See PARSE_LTR_BINOP() macro in mjs_parser.c. + */ + + /* clang-format off */ + case TOK_MINUS_ASSIGN: op_assign(mjs, TOK_MINUS); break; + case TOK_PLUS_ASSIGN: op_assign(mjs, TOK_PLUS); break; + case TOK_MUL_ASSIGN: op_assign(mjs, TOK_MUL); break; + case TOK_DIV_ASSIGN: op_assign(mjs, TOK_DIV); break; + case TOK_REM_ASSIGN: op_assign(mjs, TOK_REM); break; + case TOK_AND_ASSIGN: op_assign(mjs, TOK_AND); break; + case TOK_OR_ASSIGN: op_assign(mjs, TOK_OR); break; + case TOK_XOR_ASSIGN: op_assign(mjs, TOK_XOR); break; + case TOK_LSHIFT_ASSIGN: op_assign(mjs, TOK_LSHIFT); break; + case TOK_RSHIFT_ASSIGN: op_assign(mjs, TOK_RSHIFT); break; + case TOK_URSHIFT_ASSIGN: op_assign(mjs, TOK_URSHIFT); break; + case TOK_COMMA: break; + /* clang-format on */ + case TOK_KEYWORD_TYPEOF: + mjs_push(mjs, mjs_mk_string(mjs, mjs_typeof(mjs_pop(mjs)), ~0, 1)); + break; + default: + LOG(LL_ERROR, ("Unknown expr: %d", op)); + break; + } +} + +static int getprop_builtin_string( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + int isnum = 0; + int idx = cstr_to_ulong(name, name_len, &isnum); + + if(strcmp(name, "length") == 0) { + size_t val_len; + mjs_get_string(mjs, &val, &val_len); + *res = mjs_mk_number(mjs, (double)val_len); + return 1; + } else if(strcmp(name, "at") == 0 || strcmp(name, "charCodeAt") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_char_code_at); + return 1; + } else if(strcmp(name, "indexOf") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_index_of); + return 1; + } else if(strcmp(name, "slice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_string_slice); + return 1; + } else if(isnum) { + /* + * string subscript: return a new one-byte string if the index + * is not out of bounds + */ + size_t val_len; + const char* str = mjs_get_string(mjs, &val, &val_len); + if(idx >= 0 && idx < (int)val_len) { + *res = mjs_mk_string(mjs, str + idx, 1, 1); + } else { + *res = MJS_UNDEFINED; + } + return 1; + } + return 0; +} + +static int getprop_builtin_array( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "splice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_splice); + return 1; + } else if(strcmp(name, "push") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_push_internal); + return 1; + } else if(strcmp(name, "length") == 0) { + *res = mjs_mk_number(mjs, mjs_array_length(mjs, val)); + return 1; + } + + (void)name_len; + return 0; +} + +static int getprop_builtin_foreign( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + int isnum = 0; + int idx = cstr_to_ulong(name, name_len, &isnum); + + if(!isnum) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "index must be a number"); + } else { + uint8_t* ptr = (uint8_t*)mjs_get_ptr(mjs, val); + *res = mjs_mk_number(mjs, *(ptr + idx)); + } + return 1; +} + +static int getprop_builtin_array_buf( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "byteLength") == 0) { + size_t len = 0; + mjs_array_buf_get_ptr(mjs, val, &len); + *res = mjs_mk_number(mjs, len); + return 1; + } else if(strcmp(name, "getPtr") == 0) { + void* ptr = mjs_array_buf_get_ptr(mjs, val, NULL); + *res = mjs_mk_foreign(mjs, ptr); + return 1; + } else if(strcmp(name, "slice") == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_array_buf_slice); + return 1; + } + + (void)name_len; + return 0; +} + +static int getprop_builtin_data_view( + struct mjs* mjs, + mjs_val_t val, + const char* name, + size_t name_len, + mjs_val_t* res) { + if(strcmp(name, "byteLength") == 0) { + size_t len = 0; + mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, val), &len); + *res = mjs_mk_number(mjs, len); + return 1; + } else if(strcmp(name, "length") == 0) { + *res = mjs_dataview_get_len(mjs, val); + return 1; + } else if(strcmp(name, "buffer") == 0) { + *res = mjs_dataview_get_buf(mjs, val); + return 1; + } + + (void)name_len; + return 0; +} + +static void mjs_apply_(struct mjs* mjs) { + mjs_val_t res = MJS_UNDEFINED, *args = NULL; + mjs_val_t func = mjs->vals.this_obj, v = mjs_arg(mjs, 1); + int i, nargs = 0; + if(mjs_is_array(v)) { + nargs = mjs_array_length(mjs, v); + args = calloc(nargs, sizeof(args[0])); + for(i = 0; i < nargs; i++) args[i] = mjs_array_get(mjs, v, i); + } + mjs_apply(mjs, &res, func, mjs_arg(mjs, 0), nargs, args); + free(args); + mjs_return(mjs, res); +} + +static int getprop_builtin(struct mjs* mjs, mjs_val_t val, mjs_val_t name, mjs_val_t* res) { + size_t n; + char* s = NULL; + int need_free = 0; + int handled = 0; + + mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free); + + if(err == MJS_OK) { + if(mjs_is_string(val)) { + handled = getprop_builtin_string(mjs, val, s, n, res); + } else if(s != NULL && n == 5 && strncmp(s, "apply", n) == 0) { + *res = mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_apply_); + handled = 1; + } else if(mjs_is_array(val)) { + handled = getprop_builtin_array(mjs, val, s, n, res); + } else if(mjs_is_foreign(val)) { + handled = getprop_builtin_foreign(mjs, val, s, n, res); + } else if(mjs_is_array_buf(val)) { + handled = getprop_builtin_array_buf(mjs, val, s, n, res); + } else if(mjs_is_data_view(val)) { + handled = getprop_builtin_data_view(mjs, val, s, n, res); + } + } + + if(need_free) { + free(s); + s = NULL; + } + + return handled; +} + +MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res) { + size_t i; + uint8_t prev_opcode = OP_MAX; + uint8_t opcode = OP_MAX; + + /* + * remember lengths of all stacks, they will be restored in case of an error + */ + int stack_len = mjs->stack.len; + int call_stack_len = mjs->call_stack.len; + int arg_stack_len = mjs->arg_stack.len; + int scopes_len = mjs->scopes.len; + int loop_addresses_len = mjs->loop_addresses.len; + size_t start_off = off; + const uint8_t* code; + + struct mjs_bcode_part bp = *mjs_bcode_part_get_by_offset(mjs, off); + + mjs_set_errorf(mjs, MJS_OK, NULL); + free(mjs->stack_trace); + mjs->stack_trace = NULL; + + off -= bp.start_idx; + + for(i = off; i < bp.data.len; i++) { + mjs->cur_bcode_offset = i; + + if(mjs->need_gc) { + if(maybe_gc(mjs)) { + mjs->need_gc = 0; + } + } +#if MJS_AGGRESSIVE_GC + maybe_gc(mjs); +#endif + + code = (const uint8_t*)bp.data.p; +#if MJS_ENABLE_DEBUG + mjs_disasm_single(code, i); +#endif + prev_opcode = opcode; + opcode = code[i]; + switch(opcode) { + case OP_BCODE_HEADER: { + mjs_header_item_t bcode_offset; + memcpy( + &bcode_offset, + code + i + 1 + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, + sizeof(bcode_offset)); + i += bcode_offset; + } break; + case OP_PUSH_NULL: + mjs_push(mjs, mjs_mk_null()); + break; + case OP_PUSH_UNDEF: + mjs_push(mjs, mjs_mk_undefined()); + break; + case OP_PUSH_FALSE: + mjs_push(mjs, mjs_mk_boolean(mjs, 0)); + break; + case OP_PUSH_TRUE: + mjs_push(mjs, mjs_mk_boolean(mjs, 1)); + break; + case OP_PUSH_OBJ: + mjs_push(mjs, mjs_mk_object(mjs)); + break; + case OP_PUSH_ARRAY: + mjs_push(mjs, mjs_mk_array(mjs)); + break; + case OP_PUSH_FUNC: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_function(mjs, bp.start_idx + i - n)); + i += llen; + break; + } + case OP_PUSH_THIS: + mjs_push(mjs, mjs->vals.this_obj); + break; + case OP_JMP: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += n + llen; + break; + } + case OP_JMP_FALSE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(!mjs_is_truthy(mjs, mjs_pop(mjs))) { + mjs_push(mjs, MJS_UNDEFINED); + i += n; + } + break; + } + /* + * OP_JMP_NEUTRAL_... ops are like as OP_JMP_..., but they are completely + * stack-neutral: they just check the TOS, and increment instruction + * pointer if the TOS is truthy/falsy. + */ + case OP_JMP_NEUTRAL_TRUE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(mjs_is_truthy(mjs, vtop(&mjs->stack))) { + i += n; + } + break; + } + case OP_JMP_NEUTRAL_FALSE: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + i += llen; + if(!mjs_is_truthy(mjs, vtop(&mjs->stack))) { + i += n; + } + break; + } + case OP_FIND_SCOPE: { + mjs_val_t key = vtop(&mjs->stack); + mjs_push(mjs, mjs_find_scope(mjs, key)); + break; + } + case OP_CREATE: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + if(mjs_get_own_property_v(mjs, obj, key) == NULL) { + mjs_set_v(mjs, obj, key, MJS_UNDEFINED); + } + break; + } + case OP_APPEND: { + mjs_val_t val = mjs_pop(mjs); + mjs_val_t arr = mjs_pop(mjs); + mjs_err_t err = mjs_array_push(mjs, arr, val); + if(err != MJS_OK) { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "append to non-array"); + } + break; + } + case OP_GET: { + mjs_val_t obj = mjs_pop(mjs); + mjs_val_t key = mjs_pop(mjs); + mjs_val_t val = MJS_UNDEFINED; + + if(!getprop_builtin(mjs, obj, key, &val)) { + if(mjs_is_object(obj)) { + val = mjs_get_v_proto(mjs, obj, key); + } else if((mjs_is_data_view(obj) && (mjs_is_number(key)))) { + val = mjs_dataview_get_prop(mjs, obj, key); + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "type error"); + } + } + + mjs_push(mjs, val); + if(prev_opcode != OP_FIND_SCOPE) { + /* + * Previous opcode was not OP_FIND_SCOPE, so it's some "custom" + * object which might be used as `this`, so, save it + */ + mjs->vals.last_getprop_obj = obj; + } else { + /* + * Previous opcode was OP_FIND_SCOPE, so we're getting value from + * the scope, and it should *not* be used as `this` + */ + mjs->vals.last_getprop_obj = MJS_UNDEFINED; + } + break; + } + case OP_DEL_SCOPE: + if(mjs->scopes.len <= 1) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "scopes underflow"); + } else { + mjs_pop_val(&mjs->scopes); + } + break; + case OP_NEW_SCOPE: + push_mjs_val(&mjs->scopes, mjs_mk_object(mjs)); + break; + case OP_PUSH_SCOPE: + assert(mjs_stack_size(&mjs->scopes) > 0); + mjs_push(mjs, vtop(&mjs->scopes)); + break; + case OP_PUSH_STR: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_string(mjs, (char*)code + i + 1 + llen, n, 1)); + i += llen + n; + break; + } + case OP_PUSH_INT: { + int llen; + int64_t n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_number(mjs, (double)n)); + i += llen; + break; + } + case OP_PUSH_DBL: { + int llen, n = cs_varint_decode_unsafe(&code[i + 1], &llen); + mjs_push(mjs, mjs_mk_number(mjs, strtod((char*)code + i + 1 + llen, NULL))); + i += llen + n; + break; + } + case OP_FOR_IN_NEXT: { + /* + * Data stack layout: + * ... <-- Bottom of the data stack + * (string) + * (object) + * <-- Top of the data stack + */ + mjs_val_t* iterator = vptr(&mjs->stack, -1); + mjs_val_t obj = *vptr(&mjs->stack, -2); + if(mjs_is_object(obj)) { + mjs_val_t var_name = *vptr(&mjs->stack, -3); + mjs_val_t key = mjs_next(mjs, obj, iterator); + if(key != MJS_UNDEFINED) { + mjs_val_t scope = mjs_find_scope(mjs, var_name); + mjs_set_v(mjs, scope, var_name, key); + } + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "can't iterate over non-object value"); + } + break; + } + case OP_RETURN: { + /* + * Return address is saved as a global bcode offset, so we need to + * convert it to the local offset + */ + size_t off_ret = call_stack_restore_frame(mjs); + if(off_ret != MJS_BCODE_OFFSET_EXIT) { + bp = *mjs_bcode_part_get_by_offset(mjs, off_ret); + code = (const uint8_t*)bp.data.p; + i = off_ret - bp.start_idx; + LOG(LL_VERBOSE_DEBUG, ("RETURNING TO %d", (int)off_ret + 1)); + } else { + goto clean; + } + // mjs_dump(mjs, 0, stdout); + break; + } + case OP_ARGS: { + /* + * If OP_ARGS follows OP_GET, then last_getprop_obj is set to `this` + * value; otherwise, last_getprop_obj is irrelevant and we have to + * reset it to `undefined` + */ + if(prev_opcode != OP_GET) { + mjs->vals.last_getprop_obj = MJS_UNDEFINED; + } + + /* + * Push last_getprop_obj, which is going to be used as `this`, see + * OP_CALL + */ + push_mjs_val(&mjs->arg_stack, mjs->vals.last_getprop_obj); + /* + * Push current size of data stack, it's needed to place arguments + * properly + */ + push_mjs_val(&mjs->arg_stack, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack))); + break; + } + case OP_CALL: { + // LOG(LL_INFO, ("BEFORE CALL")); + // mjs_dump(mjs, 0, stdout); + int func_pos; + mjs_val_t* func; + mjs_val_t retval_stack_idx = vtop(&mjs->arg_stack); + func_pos = mjs_get_int(mjs, retval_stack_idx) - 1; + func = vptr(&mjs->stack, func_pos); + + /* Drop data stack size (pushed by OP_ARGS) */ + mjs_pop_val(&mjs->arg_stack); + + if(mjs_is_function(*func)) { + size_t off_call; + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* + * Function offset is a global bcode offset, so we need to convert it + * to the local offset + */ + off_call = mjs_get_func_addr(*func) - 1; + bp = *mjs_bcode_part_get_by_offset(mjs, off_call); + code = (const uint8_t*)bp.data.p; + i = off_call - bp.start_idx; + + *func = MJS_UNDEFINED; // Return value + // LOG(LL_VERBOSE_DEBUG, ("CALLING %d", i + 1)); + } else if(mjs_is_string(*func) || mjs_is_ffi_sig(*func)) { + /* Call ffi-ed function */ + + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* Perform the ffi-ed function call */ + mjs_ffi_call2(mjs); + + call_stack_restore_frame(mjs); + } else if(mjs_is_foreign(*func)) { + /* Call cfunction */ + + call_stack_push_frame(mjs, bp.start_idx + i, retval_stack_idx); + + /* Perform the cfunction call */ + ((void (*)(struct mjs*))mjs_get_ptr(mjs, *func))(mjs); + + call_stack_restore_frame(mjs); + } else { + mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); + } + break; + } + case OP_SET_ARG: { + int llen1, llen2, n, arg_no = cs_varint_decode_unsafe(&code[i + 1], &llen1); + mjs_val_t obj, key, v; + n = cs_varint_decode_unsafe(&code[i + llen1 + 1], &llen2); + key = mjs_mk_string(mjs, (char*)code + i + 1 + llen1 + llen2, n, 1); + obj = vtop(&mjs->scopes); + v = mjs_arg(mjs, arg_no); + mjs_set_v(mjs, obj, key, v); + i += llen1 + llen2 + n; + break; + } + case OP_SETRETVAL: { + if(mjs_stack_size(&mjs->call_stack) < CALL_STACK_FRAME_ITEMS_CNT) { + mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "cannot return"); + } else { + size_t retval_pos = mjs_get_int( + mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX)); + *vptr(&mjs->stack, retval_pos - 1) = mjs_pop(mjs); + } + // LOG(LL_INFO, ("AFTER SETRETVAL")); + // mjs_dump(mjs, 0, stdout); + break; + } + case OP_EXPR: { + int op = code[i + 1]; + exec_expr(mjs, op); + i++; + break; + } + case OP_DROP: { + mjs_pop(mjs); + break; + } + case OP_DUP: { + mjs_push(mjs, vtop(&mjs->stack)); + break; + } + case OP_SWAP: { + mjs_val_t a = mjs_pop(mjs); + mjs_val_t b = mjs_pop(mjs); + mjs_push(mjs, a); + mjs_push(mjs, b); + break; + } + case OP_LOOP: { + int l1, l2, off = cs_varint_decode_unsafe(&code[i + 1], &l1); + /* push scope index */ + push_mjs_val( + &mjs->loop_addresses, mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->scopes))); + + /* push break offset */ + push_mjs_val( + &mjs->loop_addresses, + mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP */ + l1 + off))); + off = cs_varint_decode_unsafe(&code[i + 1 + l1], &l2); + + /* push continue offset */ + push_mjs_val( + &mjs->loop_addresses, + mjs_mk_number(mjs, (double)(i + 1 /* OP_LOOP*/ + l1 + l2 + off))); + i += l1 + l2; + break; + } + case OP_CONTINUE: { + if(mjs_stack_size(&mjs->loop_addresses) >= 3) { + size_t scopes_len = mjs_get_int(mjs, *vptr(&mjs->loop_addresses, -3)); + assert(mjs_stack_size(&mjs->scopes) >= scopes_len); + mjs->scopes.len = scopes_len * sizeof(mjs_val_t); + + /* jump to "continue" address */ + i = mjs_get_int(mjs, vtop(&mjs->loop_addresses)) - 1; + } else { + mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'continue'"); + } + } break; + case OP_BREAK: { + if(mjs_stack_size(&mjs->loop_addresses) >= 3) { + size_t scopes_len; + /* drop "continue" address */ + mjs_pop_val(&mjs->loop_addresses); + + /* pop "break" address and jump to it */ + i = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)) - 1; + + /* restore scope index */ + scopes_len = mjs_get_int(mjs, mjs_pop_val(&mjs->loop_addresses)); + assert(mjs_stack_size(&mjs->scopes) >= scopes_len); + mjs->scopes.len = scopes_len * sizeof(mjs_val_t); + + LOG(LL_VERBOSE_DEBUG, ("BREAKING TO %d", (int)i + 1)); + } else { + mjs_set_errorf(mjs, MJS_SYNTAX_ERROR, "misplaced 'break'"); + } + } break; + case OP_NOP: + break; + case OP_EXIT: + i = bp.data.len; + break; + default: +#if MJS_ENABLE_DEBUG + mjs_dump(mjs, 1); +#endif + mjs_set_errorf( + mjs, + MJS_INTERNAL_ERROR, + "Unknown opcode: %d, off %d+%d", + (int)opcode, + (int)bp.start_idx, + (int)i); + i = bp.data.len; + break; + } + + if(mjs->exec_flags_poller) { + mjs->exec_flags_poller(mjs); + } + + if(mjs->error != MJS_OK) { + if(mjs->error == MJS_NEED_EXIT) { + mjs->error = MJS_OK; + goto clean; + } + + mjs_gen_stack_trace(mjs, bp.start_idx + i - 1 /* undo the i++ */); + + /* restore stack lenghts */ + mjs->stack.len = stack_len; + mjs->call_stack.len = call_stack_len; + mjs->arg_stack.len = arg_stack_len; + mjs->scopes.len = scopes_len; + mjs->loop_addresses.len = loop_addresses_len; + + /* script will evaluate to `undefined` */ + mjs_push(mjs, MJS_UNDEFINED); + break; + } + } + +clean: + /* Remember result of the evaluation of this bcode part */ + mjs_bcode_part_get_by_offset(mjs, start_off)->exec_res = mjs->error; + + *res = mjs_pop(mjs); + return mjs->error; +} + +MJS_PRIVATE mjs_err_t mjs_exec_internal( + struct mjs* mjs, + const char* path, + const char* src, + int generate_jsc, + mjs_val_t* res) { + size_t off = mjs->bcode_len; + mjs_val_t r = MJS_UNDEFINED; + mjs->error = mjs_parse(path, src, mjs); +#if MJS_ENABLE_DEBUG + if(cs_log_level >= LL_VERBOSE_DEBUG) mjs_dump(mjs, 1); +#endif + if(generate_jsc == -1) generate_jsc = mjs->generate_jsc; + if(mjs->error == MJS_OK) { +#if MJS_GENERATE_JSC && defined(CS_MMAP) + if(generate_jsc && path != NULL) { + const char* jsext = ".js"; + int basename_len = (int)strlen(path) - strlen(jsext); + if(basename_len > 0 && strcmp(path + basename_len, jsext) == 0) { + /* source file has a .js extension: create a .jsc counterpart */ + int rewrite = 1; + int read_mmapped = 1; + + /* construct .jsc filename */ + const char* jscext = ".jsc"; + char filename_jsc[basename_len + strlen(jscext) + 1 /* nul-term */]; + memcpy(filename_jsc, path, basename_len); + strcpy(filename_jsc + basename_len, jscext); + + /* get last bcode part */ + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, mjs_bcode_parts_cnt(mjs) - 1); + + /* + * before writing .jsc file, check if it already exists and has the + * same contents + * + * TODO(dfrank): probably store crc32 before the bcode data, and only + * compare it. + */ + { + size_t size; + char* data = cs_mmap_file(filename_jsc, &size); + if(data != NULL) { + if(size == bp->data.len) { + if(memcmp(data, bp->data.p, size) == 0) { + /* .jsc file is up to date, so don't rewrite it */ + rewrite = 0; + } + } + munmap(data, size); + } + } + + /* try to open .jsc file for writing */ + if(rewrite) { + FILE* fp = fopen(filename_jsc, "wb"); + if(fp != NULL) { + /* write last bcode part to .jsc */ + fwrite(bp->data.p, bp->data.len, 1, fp); + fclose(fp); + } else { + LOG(LL_WARN, ("Failed to open %s for writing", filename_jsc)); + read_mmapped = 0; + } + } + + if(read_mmapped) { + /* free RAM buffer with last bcode part */ + free((void*)bp->data.p); + + /* mmap .jsc file and set last bcode part buffer to it */ + bp->data.p = cs_mmap_file(filename_jsc, &bp->data.len); + bp->in_rom = 1; + } + } + } +#else + (void)generate_jsc; +#endif + + mjs_execute(mjs, off, &r); + } + if(res != NULL) *res = r; + return mjs->error; +} + +mjs_err_t mjs_exec(struct mjs* mjs, const char* src, mjs_val_t* res) { + return mjs_exec_internal(mjs, "", src, 0 /* generate_jsc */, res); +} + +mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res) { + mjs_err_t error = MJS_FILE_READ_ERROR; + mjs_val_t r = MJS_UNDEFINED; + size_t size; + char* source_code = cs_read_file(path, &size); + + if(source_code == NULL) { + error = MJS_FILE_READ_ERROR; + mjs_prepend_errorf(mjs, error, "failed to read file \"%s\"", path); + goto clean; + } + + r = MJS_UNDEFINED; + error = mjs_exec_internal(mjs, path, source_code, -1, &r); + free(source_code); + +clean: + if(res != NULL) *res = r; + return error; +} + +mjs_err_t + mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...) { + va_list ap; + int i; + mjs_err_t ret; + mjs_val_t* args = calloc(nargs, sizeof(mjs_val_t)); + va_start(ap, nargs); + for(i = 0; i < nargs; i++) { + args[i] = va_arg(ap, mjs_val_t); + } + va_end(ap); + + ret = mjs_apply(mjs, res, func, this_val, nargs, args); + + free(args); + return ret; +} + +mjs_err_t mjs_apply( + struct mjs* mjs, + mjs_val_t* res, + mjs_val_t func, + mjs_val_t this_val, + int nargs, + mjs_val_t* args) { + mjs_val_t r, prev_this_val, retval_stack_idx, *resp; + int i; + + if(!mjs_is_function(func) && !mjs_is_foreign(func) && !mjs_is_ffi_sig(func)) { + return mjs_set_errorf(mjs, MJS_TYPE_ERROR, "calling non-callable"); + } + + LOG(LL_VERBOSE_DEBUG, ("applying func %d", (int)mjs_get_func_addr(func))); + + prev_this_val = mjs->vals.this_obj; + + /* Push callable which will be later replaced with the return value */ + mjs_push(mjs, func); + resp = vptr(&mjs->stack, -1); + + /* Remember index by which return value should be written */ + retval_stack_idx = mjs_mk_number(mjs, (double)mjs_stack_size(&mjs->stack)); + + // Push all arguments + for(i = 0; i < nargs; i++) { + mjs_push(mjs, args[i]); + } + + /* Push this value to arg_stack, call_stack_push_frame() expects that */ + push_mjs_val(&mjs->arg_stack, this_val); + + /* Push call stack frame, just like OP_CALL does that */ + call_stack_push_frame(mjs, MJS_BCODE_OFFSET_EXIT, retval_stack_idx); + + if(mjs_is_foreign(func)) { + ((void (*)(struct mjs*))mjs_get_ptr(mjs, func))(mjs); + if(res != NULL) *res = *resp; + } else if(mjs_is_ffi_sig(func)) { + mjs_ffi_call2(mjs); + if(res != NULL) *res = *resp; + } else { + size_t addr = mjs_get_func_addr(func); + mjs_execute(mjs, addr, &r); + if(res != NULL) *res = r; + } + + /* + * If there was an error, we need to restore frame and do the cleanup + * which is otherwise done by OP_RETURN + */ + if(mjs->error != MJS_OK) { + call_stack_restore_frame(mjs); + + // Pop cell at which the returned value should've been written + mjs_pop(mjs); + } + mjs->vals.this_obj = prev_this_val; + + return mjs->error; +} diff --git a/lib/mjs/mjs_exec.h b/lib/mjs/mjs_exec.h new file mode 100644 index 00000000000..07465a5c685 --- /dev/null +++ b/lib/mjs/mjs_exec.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_EXEC_H_ +#define MJS_EXEC_H_ + +#include "mjs_exec_public.h" + +/* + * A special bcode offset value which causes mjs_execute() to exit immediately; + * used in mjs_apply(). + */ +#define MJS_BCODE_OFFSET_EXIT ((size_t)0x7fffffff) + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_EXEC_H_ */ diff --git a/lib/mjs/mjs_exec_public.h b/lib/mjs/mjs_exec_public.h new file mode 100644 index 00000000000..2a25ae5228b --- /dev/null +++ b/lib/mjs/mjs_exec_public.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_EXEC_PUBLIC_H_ +#define MJS_EXEC_PUBLIC_H_ + +#include "mjs_core_public.h" +#include + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +mjs_err_t mjs_exec(struct mjs*, const char* src, mjs_val_t* res); + +mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res); +mjs_err_t mjs_apply( + struct mjs* mjs, + mjs_val_t* res, + mjs_val_t func, + mjs_val_t this_val, + int nargs, + mjs_val_t* args); +mjs_err_t + mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...); +mjs_val_t mjs_get_this(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_EXEC_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_features.h b/lib/mjs/mjs_features.h new file mode 100644 index 00000000000..6706f32389b --- /dev/null +++ b/lib/mjs/mjs_features.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FEATURES_H_ +#define MJS_FEATURES_H_ + +#if !defined(MJS_AGGRESSIVE_GC) +#define MJS_AGGRESSIVE_GC 0 +#endif + +#if !defined(MJS_MEMORY_STATS) +#define MJS_MEMORY_STATS 0 +#endif + +/* + * MJS_GENERATE_JSC: if enabled, and if mmapping is also enabled (CS_MMAP), + * then execution of any .js file will result in creation of a .jsc file with + * precompiled bcode, and this .jsc file will be mmapped, instead of keeping + * bcode in RAM. + * + * By default it's enabled (provided that CS_MMAP is defined) + */ +#if !defined(MJS_GENERATE_JSC) +#if defined(CS_MMAP) +#define MJS_GENERATE_JSC 1 +#else +#define MJS_GENERATE_JSC 0 +#endif +#endif + +#endif /* MJS_FEATURES_H_ */ diff --git a/lib/mjs/mjs_ffi.c b/lib/mjs/mjs_ffi.c new file mode 100644 index 00000000000..13b639d3806 --- /dev/null +++ b/lib/mjs/mjs_ffi.c @@ -0,0 +1,1223 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/mg_str.h" + +#include "ffi/ffi.h" +#include "mjs_core.h" +#include "mjs_exec.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +/* + * on linux this is enabled only if __USE_GNU is defined, but we cannot set it + * because dlfcn could have been included already. + */ +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig); + +/* + * Data of the two related arguments: callback function pointer and the + * userdata for it + */ +struct cbdata { + /* JS callback function */ + mjs_val_t func; + /* JS userdata */ + mjs_val_t userdata; + + /* index of the function pointer param */ + int8_t func_idx; + /* index of the userdata param */ + int8_t userdata_idx; +}; + +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle) { + mjs->dlsym = dlsym; + mjs->dlsym_handle = handle; +} + +void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol) { + if(mjs->dlsym) { + return mjs->dlsym(mjs->dlsym_handle, symbol); + } + return NULL; +} + +static mjs_ffi_ctype_t parse_cval_type(struct mjs* mjs, const char* s, const char* e) { + struct mg_str ms = MG_NULL_STR; + /* Trim leading and trailing whitespace */ + while(s < e && isspace((int)*s)) s++; + while(e > s && isspace((int)e[-1])) e--; + ms.p = s; + ms.len = e - s; + if(mg_vcmp(&ms, "void") == 0) { + return MJS_FFI_CTYPE_NONE; + } else if(mg_vcmp(&ms, "userdata") == 0) { + return MJS_FFI_CTYPE_USERDATA; + } else if(mg_vcmp(&ms, "int") == 0) { + return MJS_FFI_CTYPE_INT; + } else if(mg_vcmp(&ms, "bool") == 0) { + return MJS_FFI_CTYPE_BOOL; + } else if(mg_vcmp(&ms, "double") == 0) { + return MJS_FFI_CTYPE_DOUBLE; + } else if(mg_vcmp(&ms, "float") == 0) { + return MJS_FFI_CTYPE_FLOAT; + } else if(mg_vcmp(&ms, "char*") == 0 || mg_vcmp(&ms, "char *") == 0) { + return MJS_FFI_CTYPE_CHAR_PTR; + } else if(mg_vcmp(&ms, "void*") == 0 || mg_vcmp(&ms, "void *") == 0) { + return MJS_FFI_CTYPE_VOID_PTR; + } else if(mg_vcmp(&ms, "struct mg_str") == 0) { + return MJS_FFI_CTYPE_STRUCT_MG_STR; + } else if(mg_vcmp(&ms, "struct mg_str *") == 0 || mg_vcmp(&ms, "struct mg_str*") == 0) { + return MJS_FFI_CTYPE_STRUCT_MG_STR_PTR; + } else { + mjs_prepend_errorf( + mjs, MJS_TYPE_ERROR, "failed to parse val type \"%.*s\"", (int)ms.len, ms.p); + return MJS_FFI_CTYPE_INVALID; + } +} + +static const char* find_paren(const char* s, const char* e) { + for(; s < e; s++) { + if(*s == '(') return s; + } + return NULL; +} + +static const char* find_closing_paren(const char* s, const char* e) { + int nesting = 1; + while(s < e) { + if(*s == '(') { + nesting++; + } else if(*s == ')') { + if(--nesting == 0) break; + } + s++; + } + return (s < e ? s : NULL); +} + +MJS_PRIVATE mjs_err_t mjs_parse_ffi_signature( + struct mjs* mjs, + const char* s, + int sig_len, + mjs_ffi_sig_t* sig, + enum ffi_sig_type sig_type) { + mjs_err_t ret = MJS_OK; + int vtidx = 0; + const char *cur, *e, *tmp_e, *tmp; + struct mg_str rt = MG_NULL_STR, fn = MG_NULL_STR, args = MG_NULL_STR; + mjs_ffi_ctype_t val_type = MJS_FFI_CTYPE_INVALID; + if(sig_len == ~0) { + sig_len = strlen(s); + } + e = s + sig_len; + + mjs_ffi_sig_init(sig); + + /* Skip leading spaces */ + for(cur = s; cur < e && isspace((int)*cur); cur++) + ; + + /* FInd the first set of parens */ + tmp_e = find_paren(cur, e); + if(tmp_e == NULL || tmp_e - s < 2) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "1"); + goto clean; + } + tmp = find_closing_paren(tmp_e + 1, e); + if(tmp == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "2"); + goto clean; + } + + /* Now see if we have a second set of parens */ + args.p = find_paren(tmp + 1, e); + if(args.p == NULL) { + /* We don't - it's a regular function signature */ + fn.p = tmp_e - 1; + while(fn.p > cur && isspace((int)*fn.p)) fn.p--; + while(fn.p > cur && (isalnum((int)*fn.p) || *fn.p == '_')) { + fn.p--; + fn.len++; + } + fn.p++; + rt.p = cur; + rt.len = fn.p - rt.p; + /* Stuff inside parens is args */ + args.p = tmp_e + 1; + args.len = tmp - args.p; + } else { + /* We do - it's a function pointer, like void (*foo)(...). + * Stuff inside the first pair of parens is the function name */ + fn.p = tmp + 1; + fn.len = args.p - tmp; + rt.p = cur; + rt.len = tmp_e - rt.p; + args.p++; + tmp = find_closing_paren(args.p, e); + if(tmp == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "3"); + goto clean; + } + args.len = tmp - args.p; + /* + * We ignore the name and leave sig->fn NULL here, but it will later be + * set to the appropriate callback implementation. + */ + sig->is_callback = 1; + } + + val_type = parse_cval_type(mjs, rt.p, rt.p + rt.len); + if(val_type == MJS_FFI_CTYPE_INVALID) { + ret = mjs->error; + goto clean; + } + mjs_ffi_sig_set_val_type(sig, vtidx++, val_type); + + /* Parse function name {{{ */ + if(!sig->is_callback) { + char buf[100]; + if(mjs->dlsym == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "resolver is not set, call mjs_set_ffi_resolver"); + goto clean; + } + + snprintf(buf, sizeof(buf), "%.*s", (int)fn.len, fn.p); + sig->fn = (ffi_fn_t*)mjs->dlsym(mjs->dlsym_handle, buf); + if(sig->fn == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "dlsym('%s') failed", buf); + goto clean; + } + } else { + tmp_e = strchr(tmp_e, ')'); + if(tmp_e == NULL) { + ret = MJS_TYPE_ERROR; + goto clean; + } + } + + /* Advance cur to the beginning of the arg list */ + cur = tmp_e = args.p; + + /* Parse all args {{{ */ + while(tmp_e - args.p < (ptrdiff_t)args.len) { + int level = 0; /* nested parens level */ + int is_fp = 0; /* set to 1 is current arg is a callback function ptr */ + tmp_e = cur; + + /* Advance tmp_e until the next arg separator */ + while(*tmp_e && (level > 0 || (*tmp_e != ',' && *tmp_e != ')'))) { + switch(*tmp_e) { + case '(': + level++; + /* + * only function pointer params can have parens, so, set the flag + * that it's going to be a function pointer + */ + is_fp = 1; + break; + case ')': + level--; + break; + } + tmp_e++; + } + + if(tmp_e == cur) break; + + /* Parse current arg */ + if(is_fp) { + /* Current argument is a callback function pointer */ + if(sig->cb_sig != NULL) { + /* + * We already have parsed some callback argument. Currently we don't + * support more than one callback argument, so, return error + * TODO(dfrank): probably improve + */ + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "only one callback is allowed"); + goto clean; + } + + sig->cb_sig = calloc(sizeof(*sig->cb_sig), 1); + ret = mjs_parse_ffi_signature(mjs, cur, tmp_e - cur, sig->cb_sig, FFI_SIG_CALLBACK); + if(ret != MJS_OK) { + mjs_ffi_sig_free(sig->cb_sig); + free(sig->cb_sig); + sig->cb_sig = NULL; + goto clean; + } + val_type = MJS_FFI_CTYPE_CALLBACK; + } else { + /* Some non-function argument */ + val_type = parse_cval_type(mjs, cur, tmp_e); + if(val_type == MJS_FFI_CTYPE_INVALID) { + /* parse_cval_type() has already set error message */ + ret = MJS_TYPE_ERROR; + goto clean; + } + } + + if(!mjs_ffi_sig_set_val_type(sig, vtidx++, val_type)) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "too many callback args"); + goto clean; + } + + if(*tmp_e == ',') { + /* Advance cur to the next argument */ + cur = tmp_e + 1; + while(*cur == ' ') cur++; + } else { + /* No more arguments */ + break; + } + } + /* }}} */ + + /* Analyze the results and see if they are obviously wrong */ + mjs_ffi_sig_validate(mjs, sig, sig_type); + if(!sig->is_valid) { + ret = MJS_TYPE_ERROR; + goto clean; + } + + /* If the signature represents a callback, find the suitable implementation */ + if(sig->is_callback) { + sig->fn = get_cb_impl_by_signature(sig); + if(sig->fn == NULL) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "the callback signature is valid, but there's " + "no existing callback implementation for it"); + goto clean; + } + } + +clean: + if(ret != MJS_OK) { + mjs_prepend_errorf(mjs, ret, "bad ffi signature: \"%.*s\"", sig_len, s); + sig->is_valid = 0; + } + return ret; +} + +/* C callbacks implementation {{{ */ + +/* An argument or a return value for C callback impl */ +union ffi_cb_data_val { + void* p; + uintptr_t w; + double d; + float f; +}; + +struct ffi_cb_data { + union ffi_cb_data_val args[MJS_CB_ARGS_MAX_CNT]; +}; + +static union ffi_cb_data_val ffi_cb_impl_generic(void* param, struct ffi_cb_data* data) { + struct mjs_ffi_cb_args* cbargs = (struct mjs_ffi_cb_args*)param; + mjs_val_t *args, res = MJS_UNDEFINED; + union ffi_cb_data_val ret; + int i; + struct mjs* mjs = cbargs->mjs; + mjs_ffi_ctype_t return_ctype = MJS_FFI_CTYPE_NONE; + mjs_err_t err; + + memset(&ret, 0, sizeof(ret)); + mjs_own(mjs, &res); + + /* There must be at least one argument: a userdata */ + assert(cbargs->sig.args_cnt > 0); + + /* Create JS arguments */ + args = calloc(1, sizeof(mjs_val_t) * cbargs->sig.args_cnt); + for(i = 0; i < cbargs->sig.args_cnt; i++) { + mjs_ffi_ctype_t val_type = + cbargs->sig.val_types[i + 1 /* first val_type is return value type */]; + switch(val_type) { + case MJS_FFI_CTYPE_USERDATA: + args[i] = cbargs->userdata; + break; + case MJS_FFI_CTYPE_INT: + args[i] = mjs_mk_number(mjs, (double)data->args[i].w); + break; + case MJS_FFI_CTYPE_BOOL: + args[i] = mjs_mk_boolean(mjs, !!data->args[i].w); + break; + case MJS_FFI_CTYPE_CHAR_PTR: { + const char* s = (char*)data->args[i].w; + if(s == NULL) s = ""; + args[i] = mjs_mk_string(mjs, s, ~0, 1); + break; + } + case MJS_FFI_CTYPE_VOID_PTR: + args[i] = mjs_mk_foreign(mjs, (void*)data->args[i].w); + break; + case MJS_FFI_CTYPE_DOUBLE: + args[i] = mjs_mk_number(mjs, data->args[i].d); + break; + case MJS_FFI_CTYPE_FLOAT: + args[i] = mjs_mk_number(mjs, data->args[i].f); + break; + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: { + struct mg_str* s = (struct mg_str*)(void*)data->args[i].w; + args[i] = mjs_mk_string(mjs, s->p, s->len, 1); + break; + } + default: + /* should never be here */ + LOG(LL_ERROR, ("unexpected val type for arg #%d: %d\n", i, val_type)); + abort(); + } + } + + /* + * save return ctype outside of `cbargs` before calling the callback, because + * callback might call `ffi_cb_free()`, which will effectively invalidate + * `cbargs` + */ + return_ctype = cbargs->sig.val_types[0]; + + /* Call JS function */ + LOG(LL_VERBOSE_DEBUG, + ("calling JS callback void-void %d from C", mjs_get_int(mjs, cbargs->func))); + err = mjs_apply(mjs, &res, cbargs->func, MJS_UNDEFINED, cbargs->sig.args_cnt, args); + /* + * cbargs might be invalidated by the callback (if it called ffi_cb_free), so + * null it out + */ + cbargs = NULL; + if(err != MJS_OK) { + /* + * There's not much we can do about the error here; let's at least print it + */ + mjs_print_error(mjs, stderr, "MJS callback error", 1 /* print_stack_trace */); + + goto clean; + } + + /* Get return value, if needed */ + switch(return_ctype) { + case MJS_FFI_CTYPE_NONE: + /* do nothing */ + break; + case MJS_FFI_CTYPE_INT: + ret.w = mjs_get_int(mjs, res); + break; + case MJS_FFI_CTYPE_BOOL: + ret.w = mjs_get_bool(mjs, res); + break; + case MJS_FFI_CTYPE_VOID_PTR: + ret.p = mjs_get_ptr(mjs, res); + break; + case MJS_FFI_CTYPE_DOUBLE: + ret.d = mjs_get_double(mjs, res); + break; + case MJS_FFI_CTYPE_FLOAT: + ret.f = (float)mjs_get_double(mjs, res); + break; + default: + /* should never be here */ + LOG(LL_ERROR, ("unexpected return val type %d\n", return_ctype)); + abort(); + } + +clean: + free(args); + mjs_disown(mjs, &res); + return ret; +} + +static void ffi_init_cb_data_wwww( + struct ffi_cb_data* data, + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + memset(data, 0, sizeof(*data)); + data->args[0].w = w0; + data->args[1].w = w1; + data->args[2].w = w2; + data->args[3].w = w3; + data->args[4].w = w4; + data->args[5].w = w5; +} + +static uintptr_t ffi_cb_impl_wpwwwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w0, &data).w; +} + +static uintptr_t ffi_cb_impl_wwpwwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w1, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwpwww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w2, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwpww( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w3, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwwpw( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w4, &data).w; +} + +static uintptr_t ffi_cb_impl_wwwwwwp( + uintptr_t w0, + uintptr_t w1, + uintptr_t w2, + uintptr_t w3, + uintptr_t w4, + uintptr_t w5) { + struct ffi_cb_data data; + ffi_init_cb_data_wwww(&data, w0, w1, w2, w3, w4, w5); + return ffi_cb_impl_generic((void*)w5, &data).w; +} + +static uintptr_t ffi_cb_impl_wpd(uintptr_t w0, double d1) { + struct ffi_cb_data data; + + memset(&data, 0, sizeof(data)); + data.args[0].w = w0; + data.args[1].d = d1; + + return ffi_cb_impl_generic((void*)w0, &data).w; +} + +static uintptr_t ffi_cb_impl_wdp(double d0, uintptr_t w1) { + struct ffi_cb_data data; + + memset(&data, 0, sizeof(data)); + data.args[0].d = d0; + data.args[1].w = w1; + + return ffi_cb_impl_generic((void*)w1, &data).w; +} +/* }}} */ + +static struct mjs_ffi_cb_args** + ffi_get_matching(struct mjs_ffi_cb_args** plist, mjs_val_t func, mjs_val_t userdata) { + for(; *plist != NULL; plist = &((*plist)->next)) { + if((*plist)->func == func && (*plist)->userdata == userdata) { + break; + } + } + return plist; +} + +static ffi_fn_t* get_cb_impl_by_signature(const mjs_ffi_sig_t* sig) { + if(sig->is_valid) { + int i; + int double_cnt = 0; + int float_cnt = 0; + int userdata_idx = 0 /* not a valid value: index 0 means return value */; + + for(i = 1 /*0th item is a return value*/; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) { + mjs_ffi_ctype_t type = sig->val_types[i]; + switch(type) { + case MJS_FFI_CTYPE_DOUBLE: + double_cnt++; + break; + case MJS_FFI_CTYPE_FLOAT: + float_cnt++; + break; + case MJS_FFI_CTYPE_USERDATA: + assert(userdata_idx == 0); /* Otherwise is_valid should be 0 */ + userdata_idx = i; + break; + default: + break; + } + } + + if(float_cnt > 0) { + /* TODO(dfrank): add support for floats in callbacks */ + return NULL; + } + + assert(userdata_idx > 0); /* Otherwise is_valid should be 0 */ + + if(sig->args_cnt <= MJS_CB_ARGS_MAX_CNT) { + if(mjs_ffi_is_regular_word_or_void(sig->val_types[0])) { + /* Return type is a word or void */ + switch(double_cnt) { + case 0: + /* No double arguments */ + switch(userdata_idx) { + case 1: + return (ffi_fn_t*)ffi_cb_impl_wpwwwww; + case 2: + return (ffi_fn_t*)ffi_cb_impl_wwpwwww; + case 3: + return (ffi_fn_t*)ffi_cb_impl_wwwpwww; + case 4: + return (ffi_fn_t*)ffi_cb_impl_wwwwpww; + case 5: + return (ffi_fn_t*)ffi_cb_impl_wwwwwpw; + case 6: + return (ffi_fn_t*)ffi_cb_impl_wwwwwwp; + default: + /* should never be here */ + abort(); + } + break; + case 1: + /* 1 double argument */ + switch(userdata_idx) { + case 1: + return (ffi_fn_t*)ffi_cb_impl_wpd; + case 2: + return (ffi_fn_t*)ffi_cb_impl_wdp; + } + break; + } + } + } else { + /* Too many arguments for the built-in callback impls */ + /* TODO(dfrank): add support for custom app-dependent resolver */ + } + } + + return NULL; +} + +MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig) { + if(psig == NULL) { + return MJS_NULL; + } else { + return mjs_legit_pointer_to_value(psig) | MJS_TAG_FUNCTION_FFI; + } +} + +MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION_FFI; +} + +MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v) { + struct mjs_ffi_sig* ret = NULL; + assert(mjs_is_ffi_sig(v)); + ret = (struct mjs_ffi_sig*)get_ptr(v); + return ret; +} + +MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs) { + struct mjs_ffi_sig* psig = new_ffi_sig(mjs); + mjs_ffi_sig_init(psig); + return mjs_ffi_sig_to_value(psig); +} + +MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig) { + mjs_ffi_sig_free((mjs_ffi_sig_t*)psig); + (void)mjs; +} + +MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs) { + mjs_err_t e = MJS_OK; + const char* sig_str = NULL; + mjs_val_t sig_str_v = mjs_arg(mjs, 0); + mjs_val_t ret_v = MJS_UNDEFINED; + struct mjs_ffi_sig* psig = mjs_get_ffi_sig_struct(mjs_mk_ffi_sig(mjs)); + size_t sig_str_len; + + sig_str = mjs_get_string(mjs, &sig_str_v, &sig_str_len); + e = mjs_parse_ffi_signature(mjs, sig_str, sig_str_len, psig, FFI_SIG_FUNC); + if(e != MJS_OK) goto clean; + ret_v = mjs_ffi_sig_to_value(psig); + +clean: + mjs_return(mjs, ret_v); + return e; +} + +MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs) { + mjs_err_t ret = MJS_OK; + mjs_ffi_sig_t* psig = NULL; + mjs_ffi_ctype_t rtype; + mjs_val_t sig_v = *vptr(&mjs->stack, mjs_getretvalpos(mjs)); + + int i, nargs; + struct ffi_arg res; + struct ffi_arg args[FFI_MAX_ARGS_CNT]; + struct cbdata cbdata; + + /* TODO(dfrank): support multiple callbacks */ + mjs_val_t resv = mjs_mk_undefined(); + + /* + * String arguments, needed to support short strings which are packed into + * mjs_val_t itself + */ + mjs_val_t argvs[FFI_MAX_ARGS_CNT]; + struct mg_str argvmgstr[FFI_MAX_ARGS_CNT]; + + if(mjs_is_ffi_sig(sig_v)) { + psig = mjs_get_ffi_sig_struct(sig_v); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "non-ffi-callable value"); + goto clean; + } + + memset(&cbdata, 0, sizeof(cbdata)); + cbdata.func_idx = -1; + cbdata.userdata_idx = -1; + + rtype = psig->val_types[0]; + + switch(rtype) { + case MJS_FFI_CTYPE_DOUBLE: + res.ctype = FFI_CTYPE_DOUBLE; + break; + case MJS_FFI_CTYPE_FLOAT: + res.ctype = FFI_CTYPE_FLOAT; + break; + case MJS_FFI_CTYPE_BOOL: + res.ctype = FFI_CTYPE_BOOL; + break; + case MJS_FFI_CTYPE_USERDATA: + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_CHAR_PTR: + case MJS_FFI_CTYPE_VOID_PTR: + case MJS_FFI_CTYPE_NONE: + res.ctype = FFI_CTYPE_WORD; + break; + + case MJS_FFI_CTYPE_INVALID: + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "wrong ffi return type"); + goto clean; + } + res.v.i = 0; + + nargs = mjs_stack_size(&mjs->stack) - mjs_get_int(mjs, vtop(&mjs->call_stack)); + + if(nargs != psig->args_cnt) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, ret, "got %d actuals, but function takes %d args", nargs, psig->args_cnt); + goto clean; + } + + for(i = 0; i < nargs; i++) { + mjs_val_t arg = mjs_arg(mjs, i); + + switch(psig->val_types[1 /* retval type */ + i]) { + case MJS_FFI_CTYPE_NONE: + /* + * Void argument: in any case, it's an error, because if C function + * takes no arguments, then the FFI-ed JS function should be called + * without any arguments, and thus we'll not face "void" here. + */ + ret = MJS_TYPE_ERROR; + if(i == 0) { + /* FFI signature is correct, but invocation is wrong */ + mjs_prepend_errorf(mjs, ret, "ffi-ed function takes no arguments"); + } else { + /* + * FFI signature is wrong: we can't have "void" as a non-first + * "argument" + */ + mjs_prepend_errorf(mjs, ret, "bad ffi arg #%d type: \"void\"", i); + } + + goto clean; + case MJS_FFI_CTYPE_USERDATA: + /* Userdata for the callback */ + if(cbdata.userdata_idx != -1) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, ret, "two or more userdata args: #%d and %d", cbdata.userdata_idx, i); + + goto clean; + } + cbdata.userdata = arg; + cbdata.userdata_idx = i; + break; + case MJS_FFI_CTYPE_INT: { + int intval = 0; + if(mjs_is_number(arg)) { + intval = mjs_get_int(mjs, arg); + } else if(mjs_is_boolean(arg)) { + intval = mjs_get_bool(mjs, arg); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not an int (the type idx is: %s)", + i, + mjs_typeof(arg)); + } + ffi_set_word(&args[i], intval); + } break; + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: { + if(!mjs_is_string(arg)) { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a string (the type idx is: %s)", + i, + mjs_typeof(arg)); + goto clean; + } + argvs[i] = arg; + argvmgstr[i].p = mjs_get_string(mjs, &argvs[i], &argvmgstr[i].len); + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + ffi_set_ptr(&args[i], (void*)&argvmgstr[i]); + break; + } + case MJS_FFI_CTYPE_BOOL: { + int intval = 0; + if(mjs_is_number(arg)) { + intval = !!mjs_get_int(mjs, arg); + } else if(mjs_is_boolean(arg)) { + intval = mjs_get_bool(mjs, arg); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a bool (the type idx is: %s)", + i, + mjs_typeof(arg)); + } + ffi_set_word(&args[i], intval); + } break; + case MJS_FFI_CTYPE_DOUBLE: + ffi_set_double(&args[i], mjs_get_double(mjs, arg)); + break; + case MJS_FFI_CTYPE_FLOAT: + ffi_set_float(&args[i], (float)mjs_get_double(mjs, arg)); + break; + case MJS_FFI_CTYPE_CHAR_PTR: { + size_t s; + if(mjs_is_string(arg)) { + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + argvs[i] = arg; + ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &s)); + } else if(mjs_is_null(arg)) { + ffi_set_ptr(&args[i], NULL); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a string (the type idx is: %s)", + i, + mjs_typeof(arg)); + goto clean; + } + } break; + case MJS_FFI_CTYPE_VOID_PTR: + if(mjs_is_string(arg)) { + size_t n; + /* + * String argument should be saved separately in order to support + * short strings (which are packed into mjs_val_t itself) + */ + argvs[i] = arg; + ffi_set_ptr(&args[i], (void*)mjs_get_string(mjs, &argvs[i], &n)); + } else if(mjs_is_foreign(arg)) { + ffi_set_ptr(&args[i], (void*)mjs_get_ptr(mjs, arg)); + } else if(mjs_is_null(arg)) { + ffi_set_ptr(&args[i], NULL); + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "actual arg #%d is not a ptr", i); + goto clean; + } + break; + case MJS_FFI_CTYPE_CALLBACK: + if(mjs_is_function(arg) || mjs_is_foreign(arg) || mjs_is_ffi_sig(arg)) { + /* + * Current argument is a callback function pointer: remember the given + * JS function and the argument index + */ + cbdata.func = arg; + cbdata.func_idx = i; + } else { + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf( + mjs, + ret, + "actual arg #%d is not a function, but %s", + i, + mjs_stringify_type((enum mjs_type)arg)); + goto clean; + } + break; + case MJS_FFI_CTYPE_INVALID: + /* parse_cval_type() has already set a more detailed error */ + ret = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, ret, "wrong arg type"); + goto clean; + default: + abort(); + break; + } + } + + if(cbdata.userdata_idx >= 0 && cbdata.func_idx >= 0) { + struct mjs_ffi_cb_args* cbargs = NULL; + struct mjs_ffi_cb_args** pitem = NULL; + + /* the function takes a callback */ + + /* + * Get cbargs: either reuse the existing one (if the matching item exists), + * or create a new one. + */ + pitem = ffi_get_matching(&mjs->ffi_cb_args, cbdata.func, cbdata.userdata); + if(*pitem == NULL) { + /* No matching cbargs item; we need to add a new one */ + cbargs = calloc(1, sizeof(*cbargs)); + cbargs->mjs = mjs; + cbargs->func = cbdata.func; + cbargs->userdata = cbdata.userdata; + mjs_ffi_sig_copy(&cbargs->sig, psig->cb_sig); + + /* Establish a link to the newly allocated item */ + *pitem = cbargs; + } else { + /* Found matching item: reuse it */ + cbargs = *pitem; + } + + { + union { + ffi_fn_t* fn; + void* p; + } u; + u.fn = psig->cb_sig->fn; + ffi_set_ptr(&args[cbdata.func_idx], u.p); + ffi_set_ptr(&args[cbdata.userdata_idx], cbargs); + } + } else if(!(cbdata.userdata_idx == -1 && cbdata.func_idx == -1)) { + /* + * incomplete signature: it contains either the function pointer or + * userdata. It should contain both or none. + * + * It should be handled in mjs_parse_ffi_signature(). + */ + abort(); + } + + ffi_call_mjs(psig->fn, nargs, &res, args); + + switch(rtype) { + case MJS_FFI_CTYPE_CHAR_PTR: { + const char* s = (const char*)(uintptr_t)res.v.i; + if(s != NULL) { + resv = mjs_mk_string(mjs, s, ~0, 1); + } else { + resv = MJS_NULL; + } + break; + } + case MJS_FFI_CTYPE_VOID_PTR: + resv = mjs_mk_foreign(mjs, (void*)(uintptr_t)res.v.i); + break; + case MJS_FFI_CTYPE_INT: + resv = mjs_mk_number(mjs, (int)res.v.i); + break; + case MJS_FFI_CTYPE_BOOL: + resv = mjs_mk_boolean(mjs, !!res.v.i); + break; + case MJS_FFI_CTYPE_DOUBLE: + resv = mjs_mk_number(mjs, res.v.d); + break; + case MJS_FFI_CTYPE_FLOAT: + resv = mjs_mk_number(mjs, res.v.f); + break; + default: + resv = mjs_mk_undefined(); + break; + } + +clean: + /* + * If there was some error, prepend an error message with the subject + * signature + */ + if(ret != MJS_OK) { + mjs_prepend_errorf(mjs, ret, "failed to call FFIed function"); + /* TODO(dfrank) stringify mjs_ffi_sig_t in some human-readable format */ + } + mjs_return(mjs, resv); + + return ret; +} + +/* + * TODO(dfrank): make it return boolean (when booleans are supported), instead + * of a number + */ +MJS_PRIVATE void mjs_ffi_cb_free(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_number(mjs, 0); + mjs_val_t func = mjs_arg(mjs, 0); + mjs_val_t userdata = mjs_arg(mjs, 1); + + if(mjs_is_function(func)) { + struct mjs_ffi_cb_args** pitem = ffi_get_matching(&mjs->ffi_cb_args, func, userdata); + if(*pitem != NULL) { + /* Found matching item: remove it from the linked list, and free */ + struct mjs_ffi_cb_args* cbargs = *pitem; + *pitem = cbargs->next; + mjs_ffi_sig_free(&cbargs->sig); + free(cbargs); + ret = mjs_mk_number(mjs, 1); + } + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument 'func'"); + } + + mjs_return(mjs, ret); +} + +void mjs_ffi_args_free_list(struct mjs* mjs) { + ffi_cb_args_t* next = mjs->ffi_cb_args; + + while(next != NULL) { + ffi_cb_args_t* cur = next; + next = next->next; + free(cur); + } +} + +MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig) { + memset(sig, 0, sizeof(*sig)); +} + +MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from) { + memcpy(to, from, sizeof(*to)); + if(from->cb_sig != NULL) { + to->cb_sig = calloc(sizeof(*to->cb_sig), 1); + mjs_ffi_sig_copy(to->cb_sig, from->cb_sig); + } +} + +MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig) { + if(sig->cb_sig != NULL) { + free(sig->cb_sig); + sig->cb_sig = NULL; + } +} + +MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type) { + if(idx < MJS_CB_SIGNATURE_MAX_SIZE) { + sig->val_types[idx] = type; + return 1; + } else { + /* Index is too large */ + return 0; + } +} + +MJS_PRIVATE int + mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type) { + int ret = 0; + int i; + int callback_idx = 0; + int userdata_idx = 0; + + sig->is_valid = 0; + + switch(sig_type) { + case FFI_SIG_FUNC: + /* Make sure return type is fine */ + if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT && + sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE && + sig->val_types[0] != MJS_FFI_CTYPE_FLOAT && + sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR && + sig->val_types[0] != MJS_FFI_CTYPE_CHAR_PTR) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type"); + goto clean; + } + break; + case FFI_SIG_CALLBACK: + /* Make sure return type is fine */ + if(sig->val_types[0] != MJS_FFI_CTYPE_NONE && sig->val_types[0] != MJS_FFI_CTYPE_INT && + sig->val_types[0] != MJS_FFI_CTYPE_BOOL && sig->val_types[0] != MJS_FFI_CTYPE_DOUBLE && + sig->val_types[0] != MJS_FFI_CTYPE_FLOAT && + sig->val_types[0] != MJS_FFI_CTYPE_VOID_PTR) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid return value type"); + goto clean; + } + } + + /* Handle argument types */ + for(i = 1; i < MJS_CB_SIGNATURE_MAX_SIZE; i++) { + mjs_ffi_ctype_t type = sig->val_types[i]; + switch(type) { + case MJS_FFI_CTYPE_USERDATA: + if(userdata_idx != 0) { + /* There must be at most one userdata arg, but we have more */ + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "more than one userdata arg: #%d and #%d", + (userdata_idx - 1), + (i - 1)); + goto clean; + } + userdata_idx = i; + break; + case MJS_FFI_CTYPE_CALLBACK: + switch(sig_type) { + case FFI_SIG_FUNC: + break; + case FFI_SIG_CALLBACK: + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "callback can't take another callback"); + goto clean; + } + callback_idx = i; + break; + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_BOOL: + case MJS_FFI_CTYPE_VOID_PTR: + case MJS_FFI_CTYPE_CHAR_PTR: + case MJS_FFI_CTYPE_STRUCT_MG_STR_PTR: + case MJS_FFI_CTYPE_DOUBLE: + case MJS_FFI_CTYPE_FLOAT: + /* Do nothing */ + break; + case MJS_FFI_CTYPE_NONE: + /* No more arguments */ + goto args_over; + default: + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "invalid ffi_ctype: %d", type); + goto clean; + } + + sig->args_cnt++; + } +args_over: + + switch(sig_type) { + case FFI_SIG_FUNC: + if(!((callback_idx > 0 && userdata_idx > 0) || (callback_idx == 0 && userdata_idx == 0))) { + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "callback and userdata should be either both " + "present or both absent"); + goto clean; + } + break; + case FFI_SIG_CALLBACK: + if(userdata_idx == 0) { + /* No userdata arg */ + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "no userdata arg"); + goto clean; + } + break; + } + + ret = 1; + +clean: + if(ret) { + sig->is_valid = 1; + } + return ret; +} + +MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type) { + switch(type) { + case MJS_FFI_CTYPE_INT: + case MJS_FFI_CTYPE_BOOL: + return 1; + default: + return 0; + } +} + +MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type) { + return (type == MJS_FFI_CTYPE_NONE || mjs_ffi_is_regular_word(type)); +} + +#ifdef _WIN32 +void* dlsym(void* handle, const char* name) { + static HANDLE msvcrt_dll; + void* sym = NULL; + if(msvcrt_dll == NULL) msvcrt_dll = GetModuleHandle("msvcrt.dll"); + if((sym = GetProcAddress(GetModuleHandle(NULL), name)) == NULL) { + sym = GetProcAddress(msvcrt_dll, name); + } + return sym; +} +#elif !defined(__unix__) && !defined(__APPLE__) +void* dlsym(void* handle, const char* name) { + (void)handle; + (void)name; + return NULL; +} +#endif diff --git a/lib/mjs/mjs_ffi.h b/lib/mjs/mjs_ffi.h new file mode 100644 index 00000000000..64716cb2485 --- /dev/null +++ b/lib/mjs/mjs_ffi.h @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_H_ +#define MJS_FFI_H_ + +#include "ffi/ffi.h" +#include "mjs_ffi_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +#define MJS_CB_ARGS_MAX_CNT 6 +#define MJS_CB_SIGNATURE_MAX_SIZE (MJS_CB_ARGS_MAX_CNT + 1 /* return type */) + +typedef uint8_t mjs_ffi_ctype_t; + +enum ffi_sig_type { + FFI_SIG_FUNC, + FFI_SIG_CALLBACK, +}; + +/* + * Parsed FFI signature + */ +struct mjs_ffi_sig { + /* + * Callback signature, corresponds to the arg of type MJS_FFI_CTYPE_CALLBACK + * TODO(dfrank): probably we'll need to support multiple callback/userdata + * pairs + * + * NOTE(dfrank): instances of this structure are grouped into GC arenas and + * managed by GC, and for the GC mark to work, the first element should be + * a pointer (so that the two LSBs are not used). + */ + struct mjs_ffi_sig* cb_sig; + + /* + * The first item is the return value type (for `void`, `MJS_FFI_CTYPE_NONE` + * is used); the rest are arguments. If some argument is + * `MJS_FFI_CTYPE_NONE`, it means that there are no more arguments. + */ + mjs_ffi_ctype_t val_types[MJS_CB_SIGNATURE_MAX_SIZE]; + + /* + * Function to call. If `is_callback` is not set, then it's the function + * obtained by dlsym; otherwise it's a pointer to the appropriate callback + * implementation. + */ + ffi_fn_t* fn; + + /* Number of arguments in the signature */ + int8_t args_cnt; + + /* + * If set, then the signature represents the callback (as opposed to a normal + * function), and `fn` points to the suitable callback implementation. + */ + unsigned is_callback : 1; + unsigned is_valid : 1; +}; +typedef struct mjs_ffi_sig mjs_ffi_sig_t; + +/* Initialize new FFI signature */ +MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig); +/* Copy existing FFI signature */ +MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from); +/* Free FFI signature. NOTE: the pointer `sig` itself is not freed */ +MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig); + +/* + * Creates a new FFI signature from the GC arena, and return mjs_val_t which + * wraps it. + */ +MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs); + +/* + * Checks whether the given value is a FFI signature. + */ +MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v); + +/* + * Wraps FFI signature structure into mjs_val_t value. + */ +MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig); + +/* + * Extracts a pointer to the FFI signature struct from the mjs_val_t value. + */ +MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v); + +/* + * A wrapper for mjs_ffi_sig_free() suitable to use as a GC cell destructor. + */ +MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig); + +MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type); +MJS_PRIVATE int + mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type); +MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type); +MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type); + +struct mjs_ffi_cb_args { + struct mjs_ffi_cb_args* next; + struct mjs* mjs; + mjs_ffi_sig_t sig; + mjs_val_t func; + mjs_val_t userdata; +}; +typedef struct mjs_ffi_cb_args ffi_cb_args_t; + +/* + * cfunction: + * Parses the FFI signature string and returns a value wrapping mjs_ffi_sig_t. + */ +MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs); + +/* + * cfunction: + * Performs the FFI signature call. + */ +MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs); + +MJS_PRIVATE void mjs_ffi_cb_free(struct mjs*); +MJS_PRIVATE void mjs_ffi_args_free_list(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_H_ */ diff --git a/lib/mjs/mjs_ffi_public.h b/lib/mjs/mjs_ffi_public.h new file mode 100644 index 00000000000..310e5351245 --- /dev/null +++ b/lib/mjs/mjs_ffi_public.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_FFI_PUBLIC_H_ +#define MJS_FFI_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +enum mjs_ffi_ctype { + MJS_FFI_CTYPE_NONE, + MJS_FFI_CTYPE_USERDATA, + MJS_FFI_CTYPE_CALLBACK, + MJS_FFI_CTYPE_INT, + MJS_FFI_CTYPE_BOOL, + MJS_FFI_CTYPE_DOUBLE, + MJS_FFI_CTYPE_FLOAT, + MJS_FFI_CTYPE_CHAR_PTR, + MJS_FFI_CTYPE_VOID_PTR, + MJS_FFI_CTYPE_STRUCT_MG_STR_PTR, + MJS_FFI_CTYPE_STRUCT_MG_STR, + MJS_FFI_CTYPE_INVALID, +}; + +typedef void*(mjs_ffi_resolver_t)(void* handle, const char* symbol); + +void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle); + +void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_FFI_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_gc.c b/lib/mjs/mjs_gc.c new file mode 100644 index 00000000000..ca0ac06f1f0 --- /dev/null +++ b/lib/mjs/mjs_gc.c @@ -0,0 +1,535 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#include + +#include "common/cs_varint.h" +#include "common/mbuf.h" + +#include "mjs_core.h" +#include "mjs_ffi.h" +#include "mjs_gc.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" + +/* + * Macros for marking reachable things: use bit 0. + */ +#define MARK(p) (((struct gc_cell*)(p))->head.word |= 1) +#define UNMARK(p) (((struct gc_cell*)(p))->head.word &= ~1) +#define MARKED(p) (((struct gc_cell*)(p))->head.word & 1) + +/* + * Similar to `MARK()` / `UNMARK()` / `MARKED()`, but `.._FREE` counterparts + * are intended to mark free cells (as opposed to used ones), so they use + * bit 1. + */ +#define MARK_FREE(p) (((struct gc_cell*)(p))->head.word |= 2) +#define UNMARK_FREE(p) (((struct gc_cell*)(p))->head.word &= ~2) +#define MARKED_FREE(p) (((struct gc_cell*)(p))->head.word & 2) + +/* + * When each arena has that or less free cells, GC will be scheduled + */ +#define GC_ARENA_CELLS_RESERVE 2 + +static struct gc_block* gc_new_block(struct gc_arena* a, size_t size); +static void gc_free_block(struct gc_block* b); +static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf); + +MJS_PRIVATE struct mjs_object* new_object(struct mjs* mjs) { + return (struct mjs_object*)gc_alloc_cell(mjs, &mjs->object_arena); +} + +MJS_PRIVATE struct mjs_property* new_property(struct mjs* mjs) { + return (struct mjs_property*)gc_alloc_cell(mjs, &mjs->property_arena); +} + +MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs) { + return (struct mjs_ffi_sig*)gc_alloc_cell(mjs, &mjs->ffi_sig_arena); +} + +/* Initializes a new arena. */ +MJS_PRIVATE void gc_arena_init( + struct gc_arena* a, + size_t cell_size, + size_t initial_size, + size_t size_increment) { + assert(cell_size >= sizeof(uintptr_t)); + + memset(a, 0, sizeof(*a)); + a->cell_size = cell_size; + a->size_increment = size_increment; + a->blocks = gc_new_block(a, initial_size); +} + +MJS_PRIVATE void gc_arena_destroy(struct mjs* mjs, struct gc_arena* a) { + struct gc_block* b; + + if(a->blocks != NULL) { + gc_sweep(mjs, a, 0); + for(b = a->blocks; b != NULL;) { + struct gc_block* tmp; + tmp = b; + b = b->next; + gc_free_block(tmp); + } + } +} + +static void gc_free_block(struct gc_block* b) { + free(b->base); + free(b); +} + +static struct gc_block* gc_new_block(struct gc_arena* a, size_t size) { + struct gc_cell* cur; + struct gc_block* b; + + b = (struct gc_block*)calloc(1, sizeof(*b)); + if(b == NULL) abort(); + + b->size = size; + b->base = (struct gc_cell*)calloc(a->cell_size, b->size); + if(b->base == NULL) abort(); + + for(cur = GC_CELL_OP(a, b->base, +, 0); cur < GC_CELL_OP(a, b->base, +, b->size); + cur = GC_CELL_OP(a, cur, +, 1)) { + cur->head.link = a->free; + a->free = cur; + } + + return b; +} + +/* + * Returns whether the given arena has GC_ARENA_CELLS_RESERVE or less free + * cells + */ +static int gc_arena_is_gc_needed(struct gc_arena* a) { + struct gc_cell* r = a->free; + int i; + + for(i = 0; i <= GC_ARENA_CELLS_RESERVE; i++, r = r->head.link) { + if(r == NULL) { + return 1; + } + } + + return 0; +} + +MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs) { + struct mbuf* m = &mjs->owned_strings; + return (double)m->len / (double)m->size > (double)0.9; +} + +MJS_PRIVATE void* gc_alloc_cell(struct mjs* mjs, struct gc_arena* a) { + struct gc_cell* r; + + if(a->free == NULL) { + struct gc_block* b = gc_new_block(a, a->size_increment); + b->next = a->blocks; + a->blocks = b; + } + r = a->free; + + UNMARK(r); + + a->free = r->head.link; + +#if MJS_MEMORY_STATS + a->allocations++; + a->alive++; +#endif + + /* Schedule GC if needed */ + if(gc_arena_is_gc_needed(a)) { + mjs->need_gc = 1; + } + + /* + * TODO(mkm): minor opt possible since most of the fields + * are overwritten downstream, but not worth the yak shave time + * when fields are added to GC-able structures */ + memset(r, 0, a->cell_size); + return (void*)r; +} + +/* + * Scans the arena and add all unmarked cells to the free list. + * + * Empty blocks get deallocated. The head of the free list will contais cells + * from the last (oldest) block. Cells will thus be allocated in block order. + */ +void gc_sweep(struct mjs* mjs, struct gc_arena* a, size_t start) { + struct gc_block* b; + struct gc_cell* cur; + struct gc_block** prevp = &a->blocks; +#if MJS_MEMORY_STATS + a->alive = 0; +#endif + + /* + * Before we sweep, we should mark all free cells in a way that is + * distinguishable from marked used cells. + */ + { + struct gc_cell* next; + for(cur = a->free; cur != NULL; cur = next) { + next = cur->head.link; + MARK_FREE(cur); + } + } + + /* + * We'll rebuild the whole `free` list, so initially we just reset it + */ + a->free = NULL; + + for(b = a->blocks; b != NULL;) { + size_t freed_in_block = 0; + /* + * if it turns out that this block is 100% garbage + * we can release the whole block, but the addition + * of it's cells to the free list has to be undone. + */ + struct gc_cell* prev_free = a->free; + + for(cur = GC_CELL_OP(a, b->base, +, start); cur < GC_CELL_OP(a, b->base, +, b->size); + cur = GC_CELL_OP(a, cur, +, 1)) { + if(MARKED(cur)) { + /* The cell is used and marked */ + UNMARK(cur); +#if MJS_MEMORY_STATS + a->alive++; +#endif + } else { + /* + * The cell is either: + * - free + * - garbage that's about to be freed + */ + + if(MARKED_FREE(cur)) { + /* The cell is free, so, just unmark it */ + UNMARK_FREE(cur); + } else { + /* + * The cell is used and should be freed: call the destructor and + * reset the memory + */ + if(a->destructor != NULL) { + a->destructor(mjs, cur); + } + memset(cur, 0, a->cell_size); + } + + /* Add this cell to the `free` list */ + cur->head.link = a->free; + a->free = cur; + freed_in_block++; +#if MJS_MEMORY_STATS + a->garbage++; +#endif + } + } + + /* + * don't free the initial block, which is at the tail + * because it has a special size aimed at reducing waste + * and simplifying initial startup. TODO(mkm): improve + * */ + if(b->next != NULL && freed_in_block == b->size) { + *prevp = b->next; + gc_free_block(b); + b = *prevp; + a->free = prev_free; + } else { + prevp = &b->next; + b = b->next; + } + } +} + +/* Mark an FFI signature */ +static void gc_mark_ffi_sig(struct mjs* mjs, mjs_val_t* v) { + struct mjs_ffi_sig* psig; + + assert(mjs_is_ffi_sig(*v)); + + psig = mjs_get_ffi_sig_struct(*v); + + /* + * we treat all object like things like objects but they might be functions, + * gc_check_val checks the appropriate arena per actual value type. + */ + if(!gc_check_val(mjs, *v)) { + abort(); + } + + if(MARKED(psig)) return; + + MARK(psig); +} + +/* Mark an object */ +static void gc_mark_object(struct mjs* mjs, mjs_val_t* v) { + struct mjs_object* obj_base; + struct mjs_property* prop; + struct mjs_property* next; + + assert(mjs_is_object_based(*v)); + + obj_base = get_object_struct(*v); + + /* + * we treat all object like things like objects but they might be functions, + * gc_check_val checks the appropriate arena per actual value type. + */ + if(!gc_check_val(mjs, *v)) { + abort(); + } + + if(MARKED(obj_base)) return; + + /* mark object itself, and its properties */ + for((prop = obj_base->properties), MARK(obj_base); prop != NULL; prop = next) { + if(!gc_check_ptr(&mjs->property_arena, prop)) { + abort(); + } + + gc_mark(mjs, &prop->name); + gc_mark(mjs, &prop->value); + + next = prop->next; + MARK(prop); + } + + /* mark object's prototype */ + /* + * We dropped support for object prototypes in MJS. + * If we ever bring it back, don't forget to mark it + */ + /* gc_mark(mjs, mjs_get_proto(mjs, v)); */ +} + +/* Mark a string value */ +static void gc_mark_string(struct mjs* mjs, mjs_val_t* v) { + mjs_val_t h, tmp = 0; + char* s; + + /* clang-format off */ + + /* + * If a value points to an unmarked string we shall: + * 1. save the first 6 bytes of the string + * since we need to be able to distinguish real values from + * the saved first 6 bytes of the string, we need to tag the chunk + * as MJS_TAG_STRING_C + * 2. encode value's address (v) into the first 6 bytes of the string. + * 3. put the saved 8 bytes (tag + chunk) back into the value. + * 4. mark the string by putting '\1' in the NUL terminator of the previous + * string chunk. + * + * If a value points to an already marked string we shall: + * (0, <6 bytes of a pointer to a mjs_val_t>), hence we have to skip + * the first byte. We tag the value pointer as a MJS_TAG_FOREIGN + * so that it won't be followed during recursive mark. + * + * ... the rest is the same + * + * Note: 64-bit pointers can be represented with 48-bits + */ + + /* clang-format on */ + + assert((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O); + + s = mjs->owned_strings.buf + gc_string_mjs_val_to_offset(*v); + assert(s < mjs->owned_strings.buf + mjs->owned_strings.len); + if(s[-1] == '\0') { + memcpy(&tmp, s, sizeof(tmp) - 2); + tmp |= MJS_TAG_STRING_C; + } else { + memcpy(&tmp, s, sizeof(tmp) - 2); + tmp |= MJS_TAG_FOREIGN; + } + + h = (mjs_val_t)(uintptr_t)v; + s[-1] = 1; + memcpy(s, &h, sizeof(h) - 2); + memcpy(v, &tmp, sizeof(tmp)); +} + +MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* v) { + if(mjs_is_object_based(*v)) { + gc_mark_object(mjs, v); + } + if(mjs_is_ffi_sig(*v)) { + gc_mark_ffi_sig(mjs, v); + } + if((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O) { + gc_mark_string(mjs, v); + } +} + +MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v) { + return (((uint64_t)(uintptr_t)get_ptr(v)) & ~MJS_TAG_MASK); +} + +MJS_PRIVATE mjs_val_t gc_string_val_from_offset(uint64_t s) { + return s | MJS_TAG_STRING_O; +} + +void gc_compact_strings(struct mjs* mjs) { + char* p = mjs->owned_strings.buf + 1; + uint64_t h, next, head = 1; + int len, llen; + + while(p < mjs->owned_strings.buf + mjs->owned_strings.len) { + if(p[-1] == '\1') { + /* relocate and update ptrs */ + h = 0; + memcpy(&h, p, sizeof(h) - 2); + + /* + * relocate pointers until we find the tail. + * The tail is marked with MJS_TAG_STRING_C, + * while mjs_val_t link pointers are tagged with MJS_TAG_FOREIGN + */ + for(; (h & MJS_TAG_MASK) != MJS_TAG_STRING_C; h = next) { + h &= ~MJS_TAG_MASK; + memcpy(&next, (char*)(uintptr_t)h, sizeof(h)); + + *(mjs_val_t*)(uintptr_t)h = gc_string_val_from_offset(head); + } + h &= ~MJS_TAG_MASK; + + /* + * the tail contains the first 6 bytes we stole from + * the actual string. + */ + len = cs_varint_decode_unsafe((unsigned char*)&h, &llen); + len += llen + 1; + + /* + * restore the saved 6 bytes + * TODO(mkm): think about endianness + */ + memcpy(p, &h, sizeof(h) - 2); + + /* + * and relocate the string data by packing it to the left. + */ + memmove(mjs->owned_strings.buf + head, p, len); + mjs->owned_strings.buf[head - 1] = 0x0; + p += len; + head += len; + } else { + len = cs_varint_decode_unsafe((unsigned char*)p, &llen); + len += llen + 1; + + p += len; + } + } + + mjs->owned_strings.len = head; +} + +MJS_PRIVATE int maybe_gc(struct mjs* mjs) { + if(!mjs->inhibit_gc) { + mjs_gc(mjs, 0); + return 1; + } + return 0; +} + +/* + * mark an array of `mjs_val_t` values (*not pointers* to them) + */ +static void gc_mark_val_array(struct mjs* mjs, mjs_val_t* vals, size_t len) { + mjs_val_t* vp; + for(vp = vals; vp < vals + len; vp++) { + gc_mark(mjs, vp); + } +} + +/* + * mark an mbuf containing *pointers* to `mjs_val_t` values + */ +static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf) { + mjs_val_t** vp; + for(vp = (mjs_val_t**)mbuf->buf; (char*)vp < mbuf->buf + mbuf->len; vp++) { + gc_mark(mjs, *vp); + } +} + +/* + * mark an mbuf containing `mjs_val_t` values (*not pointers* to them) + */ +static void gc_mark_mbuf_val(struct mjs* mjs, const struct mbuf* mbuf) { + gc_mark_val_array(mjs, (mjs_val_t*)mbuf->buf, mbuf->len / sizeof(mjs_val_t)); +} + +static void gc_mark_ffi_cbargs_list(struct mjs* mjs, ffi_cb_args_t* cbargs) { + for(; cbargs != NULL; cbargs = cbargs->next) { + gc_mark(mjs, &cbargs->func); + gc_mark(mjs, &cbargs->userdata); + } +} + +/* Perform garbage collection */ +void mjs_gc(struct mjs* mjs, int full) { + gc_mark_val_array(mjs, (mjs_val_t*)&mjs->vals, sizeof(mjs->vals) / sizeof(mjs_val_t)); + + gc_mark_mbuf_pt(mjs, &mjs->owned_values); + gc_mark_mbuf_val(mjs, &mjs->scopes); + gc_mark_mbuf_val(mjs, &mjs->stack); + gc_mark_mbuf_val(mjs, &mjs->call_stack); + + gc_mark_ffi_cbargs_list(mjs, mjs->ffi_cb_args); + + gc_compact_strings(mjs); + + gc_sweep(mjs, &mjs->object_arena, 0); + gc_sweep(mjs, &mjs->property_arena, 0); + gc_sweep(mjs, &mjs->ffi_sig_arena, 0); + + if(full) { + /* + * In case of full GC, we also resize strings buffer, but we still leave + * some extra space (at most, `_MJS_STRING_BUF_RESERVE`) in order to avoid + * frequent reallocations + */ + size_t trimmed_size = mjs->owned_strings.len + _MJS_STRING_BUF_RESERVE; + if(trimmed_size < mjs->owned_strings.size) { + mbuf_resize(&mjs->owned_strings, trimmed_size); + } + } +} + +MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v) { + if(mjs_is_object_based(v)) { + return gc_check_ptr(&mjs->object_arena, get_object_struct(v)); + } + if(mjs_is_ffi_sig(v)) { + return gc_check_ptr(&mjs->ffi_sig_arena, mjs_get_ffi_sig_struct(v)); + } + return 1; +} + +MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* ptr) { + const struct gc_cell* p = (const struct gc_cell*)ptr; + struct gc_block* b; + for(b = a->blocks; b != NULL; b = b->next) { + if(p >= b->base && p < GC_CELL_OP(a, b->base, +, b->size)) { + return 1; + } + } + return 0; +} diff --git a/lib/mjs/mjs_gc.h b/lib/mjs/mjs_gc.h new file mode 100644 index 00000000000..4f469d05dd2 --- /dev/null +++ b/lib/mjs/mjs_gc.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_GC_H_ +#define MJS_GC_H_ + +#include "mjs_core.h" +#include "mjs_mm.h" +#include "mjs_internal.h" +#include "mjs_gc_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * performs arithmetics on gc_cell pointers as if they were arena->cell_size + * bytes wide + */ +#define GC_CELL_OP(arena, cell, op, arg) \ + ((struct gc_cell*)(((char*)(cell))op((arg) * (arena)->cell_size))) + +struct gc_cell { + union { + struct gc_cell* link; + uintptr_t word; + } head; +}; + +MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs); + +/* perform gc if not inhibited */ +MJS_PRIVATE int maybe_gc(struct mjs* mjs); + +MJS_PRIVATE struct mjs_object* new_object(struct mjs*); +MJS_PRIVATE struct mjs_property* new_property(struct mjs*); +MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs); + +MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* val); + +MJS_PRIVATE void gc_arena_init(struct gc_arena*, size_t, size_t, size_t); +MJS_PRIVATE void gc_arena_destroy(struct mjs*, struct gc_arena* a); +MJS_PRIVATE void gc_sweep(struct mjs*, struct gc_arena*, size_t); +MJS_PRIVATE void* gc_alloc_cell(struct mjs*, struct gc_arena*); + +MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v); + +/* return 0 if v is an object/function with a bad pointer */ +MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v); + +/* checks whether a pointer is within the ranges of an arena */ +MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* p); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_GC_H_ */ diff --git a/lib/mjs/mjs_gc_public.h b/lib/mjs/mjs_gc_public.h new file mode 100644 index 00000000000..4ee2d209f30 --- /dev/null +++ b/lib/mjs/mjs_gc_public.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_GC_PUBLIC_H_ +#define MJS_GC_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Perform garbage collection. + * Pass true to full in order to reclaim unused heap back to the OS. + */ +void mjs_gc(struct mjs* mjs, int full); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_GC_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_internal.h b/lib/mjs/mjs_internal.h new file mode 100644 index 00000000000..eb1bccbd7fe --- /dev/null +++ b/lib/mjs/mjs_internal.h @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_INTERNAL_H_ +#define MJS_INTERNAL_H_ + +#include +#include +#include +#include +#include +#include + +#ifndef FAST +#define FAST +#endif + +#ifndef STATIC +#define STATIC +#endif + +#ifndef ENDL +#define ENDL "\n" +#endif + +#ifndef MJS_EXPOSE_PRIVATE +#define MJS_EXPOSE_PRIVATE 1 +#endif + +#if MJS_EXPOSE_PRIVATE +#define MJS_PRIVATE +#else +#define MJS_PRIVATE static +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) +#endif + +#if !defined(WEAK) +#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) +#define WEAK __attribute__((weak)) +#else +#define WEAK +#endif +#endif + +#ifndef CS_ENABLE_STDIO +#define CS_ENABLE_STDIO 1 +#endif + +#include "common/cs_dbg.h" +#include "common/cs_file.h" +#include "common/mbuf.h" + +#if defined(_WIN32) && _MSC_VER < 1700 +typedef signed char int8_t; +typedef unsigned char uint8_t; +typedef int int32_t; +typedef unsigned int uint32_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef __int64 int64_t; +typedef unsigned long uintptr_t; +#define STRX(x) #x +#define STR(x) STRX(x) +#define __func__ __FILE__ ":" STR(__LINE__) +// #define snprintf _snprintf +#define vsnprintf _vsnprintf +#define isnan(x) _isnan(x) +#define va_copy(x, y) (x) = (y) +#define CS_DEFINE_DIRENT +#include +#else +#if defined(__unix__) || defined(__APPLE__) +#include +#endif +#endif + +/* + * Number of bytes reserved for the jump offset initially. The most practical + * value is 1, but for testing it's useful to set it to 0 and to some large + * value as well (like, 4), to make sure that the code behaves correctly under + * all circumstances. + */ +#ifndef MJS_INIT_OFFSET_SIZE +#define MJS_INIT_OFFSET_SIZE 1 +#endif + +#endif /* MJS_INTERNAL_H_ */ diff --git a/lib/mjs/mjs_json.c b/lib/mjs/mjs_json.c new file mode 100644 index 00000000000..829b3b4c0f8 --- /dev/null +++ b/lib/mjs/mjs_json.c @@ -0,0 +1,523 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "common/str_util.h" +#include "common/frozen/frozen.h" +#include "mjs_array.h" +#include "mjs_internal.h" +#include "mjs_core.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util_public.h" + +#define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0) + +/* + * Returns whether the value of given type should be skipped when generating + * JSON output + * + * So far it always returns 0, but we might add some logic later, if we + * implement some non-jsonnable objects + */ +static int should_skip_for_json(enum mjs_type type) { + int ret; + switch(type) { + /* All permitted values */ + case MJS_TYPE_NULL: + case MJS_TYPE_BOOLEAN: + case MJS_TYPE_NUMBER: + case MJS_TYPE_STRING: + case MJS_TYPE_ARRAY_BUF: + case MJS_TYPE_ARRAY_BUF_VIEW: + case MJS_TYPE_OBJECT_GENERIC: + case MJS_TYPE_OBJECT_ARRAY: + ret = 0; + break; + default: + ret = 1; + break; + } + return ret; +} + +static const char* hex_digits = "0123456789abcdef"; +static char* append_hex(char* buf, char* limit, uint8_t c) { + if(buf < limit) *buf++ = 'u'; + if(buf < limit) *buf++ = '0'; + if(buf < limit) *buf++ = '0'; + if(buf < limit) *buf++ = hex_digits[(int)((c >> 4) % 0xf)]; + if(buf < limit) *buf++ = hex_digits[(int)(c & 0xf)]; + return buf; +} + +/* + * Appends quoted s to buf. Any double quote contained in s will be escaped. + * Returns the number of characters that would have been added, + * like snprintf. + * If size is zero it doesn't output anything but keeps counting. + */ +static int snquote(char* buf, size_t size, const char* s, size_t len) { + char* limit = buf + size; + const char* end; + /* + * String single character escape sequence: + * http://www.ecma-international.org/ecma-262/6.0/index.html#table-34 + * + * 0x8 -> \b + * 0x9 -> \t + * 0xa -> \n + * 0xb -> \v + * 0xc -> \f + * 0xd -> \r + */ + const char* specials = "btnvfr"; + size_t i = 0; + + i++; + if(buf < limit) *buf++ = '"'; + + for(end = s + len; s < end; s++) { + if(*s == '"' || *s == '\\') { + i++; + if(buf < limit) *buf++ = '\\'; + } else if(*s >= '\b' && *s <= '\r') { + i += 2; + if(buf < limit) *buf++ = '\\'; + if(buf < limit) *buf++ = specials[*s - '\b']; + continue; + } else if((unsigned char)*s < '\b' || (*s > '\r' && *s < ' ')) { + i += 6 /* \uXX XX */; + if(buf < limit) *buf++ = '\\'; + buf = append_hex(buf, limit, (uint8_t)*s); + continue; + } + i++; + if(buf < limit) *buf++ = *s; + } + + i++; + if(buf < limit) *buf++ = '"'; + + if(buf < limit) { + *buf = '\0'; + } else if(size != 0) { + /* + * There is no room for the NULL char, but the size wasn't zero, so we can + * safely put NULL in the previous byte + */ + *(buf - 1) = '\0'; + } + return i; +} + +MJS_PRIVATE mjs_err_t to_json_or_debug( + struct mjs* mjs, + mjs_val_t v, + char* buf, + size_t size, + size_t* res_len, + uint8_t is_debug) { + mjs_val_t el; + char* vp; + mjs_err_t rcode = MJS_OK; + size_t len = 0; + /* + * TODO(dfrank) : also push all `mjs_val_t`s that are declared below + */ + + if(size > 0) *buf = '\0'; + + if(!is_debug && should_skip_for_json(mjs_get_type(v))) { + goto clean; + } + + for(vp = mjs->json_visited_stack.buf; + vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len; + vp += sizeof(mjs_val_t)) { + if(*(mjs_val_t*)vp == v) { + strncpy(buf, "[Circular]", size); + len = 10; + goto clean; + } + } + + switch(mjs_get_type(v)) { + case MJS_TYPE_NULL: + case MJS_TYPE_BOOLEAN: + case MJS_TYPE_NUMBER: + case MJS_TYPE_UNDEFINED: + case MJS_TYPE_FOREIGN: + case MJS_TYPE_ARRAY_BUF: + case MJS_TYPE_ARRAY_BUF_VIEW: + /* For those types, regular `mjs_to_string()` works */ + { + /* refactor: mjs_to_string allocates memory every time */ + char* p = NULL; + int need_free = 0; + rcode = mjs_to_string(mjs, &v, &p, &len, &need_free); + c_snprintf(buf, size, "%.*s", (int)len, p); + if(need_free) { + free(p); + } + } + goto clean; + + case MJS_TYPE_STRING: { + /* + * For strings we can't just use `primitive_to_str()`, because we need + * quoted value + */ + size_t n; + const char* str = mjs_get_string(mjs, &v, &n); + len = snquote(buf, size, str, n); + goto clean; + } + + case MJS_TYPE_OBJECT_FUNCTION: + case MJS_TYPE_OBJECT_GENERIC: { + char* b = buf; + struct mjs_property* prop = NULL; + struct mjs_object* o = NULL; + + mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v)); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "{"); + o = get_object_struct(v); + for(prop = o->properties; prop != NULL; prop = prop->next) { + size_t n; + const char* s; + if(!is_debug && should_skip_for_json(mjs_get_type(prop->value))) { + continue; + } + if(b - buf != 1) { /* Not the first property to be printed */ + b += c_snprintf(b, BUF_LEFT(size, b - buf), ","); + } + s = mjs_get_string(mjs, &prop->name, &n); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "\"%.*s\":", (int)n, s); + { + size_t tmp = 0; + rcode = + to_json_or_debug(mjs, prop->value, b, BUF_LEFT(size, b - buf), &tmp, is_debug); + if(rcode != MJS_OK) { + goto clean_iter; + } + b += tmp; + } + } + + b += c_snprintf(b, BUF_LEFT(size, b - buf), "}"); + mjs->json_visited_stack.len -= sizeof(v); + + clean_iter: + len = b - buf; + goto clean; + } + case MJS_TYPE_OBJECT_ARRAY: { + int has; + char* b = buf; + size_t i, alen = mjs_array_length(mjs, v); + mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v)); + b += c_snprintf(b, BUF_LEFT(size, b - buf), "["); + for(i = 0; i < alen; i++) { + el = mjs_array_get2(mjs, v, i, &has); + if(has) { + size_t tmp = 0; + if(!is_debug && should_skip_for_json(mjs_get_type(el))) { + b += c_snprintf(b, BUF_LEFT(size, b - buf), "null"); + } else { + rcode = to_json_or_debug(mjs, el, b, BUF_LEFT(size, b - buf), &tmp, is_debug); + if(rcode != MJS_OK) { + goto clean; + } + } + b += tmp; + } else { + b += c_snprintf(b, BUF_LEFT(size, b - buf), "null"); + } + if(i != alen - 1) { + b += c_snprintf(b, BUF_LEFT(size, b - buf), ","); + } + } + b += c_snprintf(b, BUF_LEFT(size, b - buf), "]"); + mjs->json_visited_stack.len -= sizeof(v); + len = b - buf; + goto clean; + } + + case MJS_TYPES_CNT: + abort(); + } + + abort(); + + len = 0; /* for compilers that don't know about abort() */ + goto clean; + +clean: + if(rcode != MJS_OK) { + len = 0; + } + if(res_len != NULL) { + *res_len = len; + } + return rcode; +} + +MJS_PRIVATE mjs_err_t + mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res) { + mjs_err_t rcode = MJS_OK; + char* p = buf; + size_t len; + + to_json_or_debug(mjs, v, buf, size, &len, 0); + + if(len >= size) { + /* Buffer is not large enough. Allocate a bigger one */ + p = (char*)malloc(len + 1); + rcode = mjs_json_stringify(mjs, v, p, len + 1, res); + assert(*res == p); + goto clean; + } else { + *res = p; + goto clean; + } + +clean: + /* + * If we're going to return an error, and we allocated a buffer, then free + * it. Otherwise, caller should free it. + */ + if(rcode != MJS_OK && p != buf) { + free(p); + } + return rcode; +} + +/* + * JSON parsing frame: a separate frame is allocated for each nested + * object/array during parsing + */ +struct json_parse_frame { + mjs_val_t val; + struct json_parse_frame* up; +}; + +/* + * Context for JSON parsing by means of json_walk() + */ +struct json_parse_ctx { + struct mjs* mjs; + mjs_val_t result; + struct json_parse_frame* frame; + enum mjs_err rcode; +}; + +/* Allocate JSON parse frame */ +static struct json_parse_frame* alloc_json_frame(struct json_parse_ctx* ctx, mjs_val_t v) { + struct json_parse_frame* frame = + (struct json_parse_frame*)calloc(sizeof(struct json_parse_frame), 1); + frame->val = v; + mjs_own(ctx->mjs, &frame->val); + return frame; +} + +/* Free JSON parse frame, return the previous one (which may be NULL) */ +static struct json_parse_frame* + free_json_frame(struct json_parse_ctx* ctx, struct json_parse_frame* frame) { + struct json_parse_frame* up = frame->up; + mjs_disown(ctx->mjs, &frame->val); + free(frame); + return up; +} + +/* Callback for json_walk() */ +static void frozen_cb( + void* data, + const char* name, + size_t name_len, + const char* path, + const struct json_token* token) { + struct json_parse_ctx* ctx = (struct json_parse_ctx*)data; + mjs_val_t v = MJS_UNDEFINED; + + (void)path; + + mjs_own(ctx->mjs, &v); + + switch(token->type) { + case JSON_TYPE_STRING: { + char* dst; + if(token->len > 0 && (dst = malloc(token->len)) != NULL) { + int len = json_unescape(token->ptr, token->len, dst, token->len); + if(len < 0) { + mjs_prepend_errorf(ctx->mjs, MJS_TYPE_ERROR, "invalid JSON string"); + break; + } + v = mjs_mk_string(ctx->mjs, dst, len, 1 /* copy */); + free(dst); + } else { + /* + * This branch is for 0-len strings, and for malloc errors + * TODO(lsm): on malloc error, propagate the error upstream + */ + v = mjs_mk_string(ctx->mjs, "", 0, 1 /* copy */); + } + break; + } + case JSON_TYPE_NUMBER: + v = mjs_mk_number(ctx->mjs, strtod(token->ptr, NULL)); + break; + case JSON_TYPE_TRUE: + v = mjs_mk_boolean(ctx->mjs, 1); + break; + case JSON_TYPE_FALSE: + v = mjs_mk_boolean(ctx->mjs, 0); + break; + case JSON_TYPE_NULL: + v = MJS_NULL; + break; + case JSON_TYPE_OBJECT_START: + v = mjs_mk_object(ctx->mjs); + break; + case JSON_TYPE_ARRAY_START: + v = mjs_mk_array(ctx->mjs); + break; + + case JSON_TYPE_OBJECT_END: + case JSON_TYPE_ARRAY_END: { + /* Object or array has finished: deallocate its frame */ + ctx->frame = free_json_frame(ctx, ctx->frame); + } break; + + default: + LOG(LL_ERROR, ("Wrong token type %d\n", token->type)); + break; + } + + if(!mjs_is_undefined(v)) { + if(name != NULL && name_len != 0) { + /* Need to define a property on the current object/array */ + if(mjs_is_object(ctx->frame->val)) { + mjs_set(ctx->mjs, ctx->frame->val, name, name_len, v); + } else if(mjs_is_array(ctx->frame->val)) { + /* + * TODO(dfrank): consult name_len. Currently it's not a problem due to + * the implementation details of frozen, but it might change + */ + int idx = (int)strtod(name, NULL); + mjs_array_set(ctx->mjs, ctx->frame->val, idx, v); + } else { + LOG(LL_ERROR, ("Current value is neither object nor array\n")); + } + } else { + /* This is a root value */ + assert(ctx->frame == NULL); + + /* + * This value will also be the overall result of JSON parsing + * (it's already owned by the `mjs_alt_json_parse()`) + */ + ctx->result = v; + } + + if(token->type == JSON_TYPE_OBJECT_START || token->type == JSON_TYPE_ARRAY_START) { + /* New object or array has just started, so we need to allocate a frame + * for it */ + struct json_parse_frame* new_frame = alloc_json_frame(ctx, v); + new_frame->up = ctx->frame; + ctx->frame = new_frame; + } + } + + mjs_disown(ctx->mjs, &v); +} + +MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res) { + struct json_parse_ctx* ctx = (struct json_parse_ctx*)calloc(sizeof(struct json_parse_ctx), 1); + int json_res; + enum mjs_err rcode = MJS_OK; + + ctx->mjs = mjs; + ctx->result = MJS_UNDEFINED; + ctx->frame = NULL; + ctx->rcode = MJS_OK; + + mjs_own(mjs, &ctx->result); + + { + /* + * We have to reallocate the buffer before invoking json_walk, because + * frozen_cb can create new strings, which can result in the reallocation + * of mjs string mbuf, invalidating the `str` pointer. + */ + char* stmp = malloc(len); + memcpy(stmp, str, len); + json_res = json_walk(stmp, len, frozen_cb, ctx); + free(stmp); + stmp = NULL; + + /* str might have been invalidated, so null it out */ + str = NULL; + } + + if(ctx->rcode != MJS_OK) { + rcode = ctx->rcode; + mjs_prepend_errorf(mjs, rcode, "invalid JSON string"); + } else if(json_res < 0) { + /* There was an error during parsing */ + rcode = MJS_TYPE_ERROR; + mjs_prepend_errorf(mjs, rcode, "invalid JSON string"); + } else { + /* Expression is parsed successfully */ + *res = ctx->result; + + /* There should be no allocated frames */ + assert(ctx->frame == NULL); + } + + if(rcode != MJS_OK) { + /* There might be some allocated frames in case of malformed JSON */ + while(ctx->frame != NULL) { + ctx->frame = free_json_frame(ctx, ctx->frame); + } + } + + mjs_disown(mjs, &ctx->result); + free(ctx); + + return rcode; +} + +MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t val = mjs_arg(mjs, 0); + + if(mjs_nargs(mjs) < 1) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing a value to stringify"); + } else { + char* p = NULL; + if(mjs_json_stringify(mjs, val, NULL, 0, &p) == MJS_OK) { + ret = mjs_mk_string(mjs, p, ~0, 1 /* copy */); + free(p); + } + } + + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t arg0 = mjs_arg(mjs, 0); + + if(mjs_is_string(arg0)) { + size_t len; + const char* str = mjs_get_string(mjs, &arg0, &len); + mjs_json_parse(mjs, str, len, &ret); + } else { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "string argument required"); + } + + mjs_return(mjs, ret); +} diff --git a/lib/mjs/mjs_json.h b/lib/mjs/mjs_json.h new file mode 100644 index 00000000000..f92a30d1ad9 --- /dev/null +++ b/lib/mjs/mjs_json.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_JSON_H_ +#define MJS_JSON_H_ + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t to_json_or_debug( + struct mjs* mjs, + mjs_val_t v, + char* buf, + size_t size, + size_t* res_len, + uint8_t is_debug); + +MJS_PRIVATE mjs_err_t + mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res); +MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs); +MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs); + +MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_JSON_H_ */ diff --git a/lib/mjs/mjs_license.h b/lib/mjs/mjs_license.h new file mode 100644 index 00000000000..82a5407191a --- /dev/null +++ b/lib/mjs/mjs_license.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + * + * This software is dual-licensed: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. For the terms of this + * license, see . + * + * You are free to use this software under the terms of the GNU General + * Public License, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * Alternatively, you can license this software under a commercial + * license, as set out in . + */ diff --git a/lib/mjs/mjs_mm.h b/lib/mjs/mjs_mm.h new file mode 100644 index 00000000000..07bd423d5af --- /dev/null +++ b/lib/mjs/mjs_mm.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_MM_H_ +#define MJS_MM_H_ + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs; + +typedef void (*gc_cell_destructor_t)(struct mjs* mjs, void*); + +struct gc_block { + struct gc_block* next; + struct gc_cell* base; + size_t size; +}; + +struct gc_arena { + struct gc_block* blocks; + size_t size_increment; + struct gc_cell* free; /* head of free list */ + size_t cell_size; + +#if MJS_MEMORY_STATS + unsigned long allocations; /* cumulative counter of allocations */ + unsigned long garbage; /* cumulative counter of garbage */ + unsigned long alive; /* number of living cells */ +#endif + + gc_cell_destructor_t destructor; +}; + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_MM_H_ */ diff --git a/lib/mjs/mjs_object.c b/lib/mjs/mjs_object.c new file mode 100644 index 00000000000..2aea1bd46ad --- /dev/null +++ b/lib/mjs/mjs_object.c @@ -0,0 +1,399 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_object.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" + +#include "common/mg_str.h" + +MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) { + if(o == NULL) { + return MJS_NULL; + } else { + return mjs_legit_pointer_to_value(o) | MJS_TAG_OBJECT; + } +} + +MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) { + struct mjs_object* ret = NULL; + if(mjs_is_null(v)) { + ret = NULL; + } else { + assert(mjs_is_object_based(v)); + ret = (struct mjs_object*)get_ptr(v); + } + return ret; +} + +mjs_val_t mjs_mk_object(struct mjs* mjs) { + struct mjs_object* o = new_object(mjs); + if(o == NULL) { + return MJS_NULL; + } + (void)mjs; + o->properties = NULL; + return mjs_object_to_value(o); +} + +int mjs_is_object(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_OBJECT || (v & MJS_TAG_MASK) == MJS_TAG_ARRAY; +} + +int mjs_is_object_based(mjs_val_t v) { + return ((v & MJS_TAG_MASK) == MJS_TAG_OBJECT) || ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY) || + ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW); +} + +MJS_PRIVATE struct mjs_property* + mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) { + struct mjs_property* p; + struct mjs_object* o; + + if(!mjs_is_object_based(obj)) { + return NULL; + } + + o = get_object_struct(obj); + + if(len <= 5) { + mjs_val_t ss = mjs_mk_string(mjs, name, len, 1); + for(p = o->properties; p != NULL; p = p->next) { + if(p->name == ss) return p; + } + } else { + for(p = o->properties; p != NULL; p = p->next) { + if(mjs_strcmp(mjs, &p->name, name, len) == 0) return p; + } + return p; + } + + return NULL; +} + +MJS_PRIVATE struct mjs_property* + mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) { + size_t n; + char* s = NULL; + int need_free = 0; + struct mjs_property* p = NULL; + mjs_err_t err = mjs_to_string(mjs, &key, &s, &n, &need_free); + if(err == MJS_OK) { + p = mjs_get_own_property(mjs, obj, s, n); + } + if(need_free) free(s); + return p; +} + +MJS_PRIVATE struct mjs_property* + mjs_mk_property(struct mjs* mjs, mjs_val_t name, mjs_val_t value) { + struct mjs_property* p = new_property(mjs); + p->next = NULL; + p->name = name; + p->value = value; + return p; +} + +mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len) { + struct mjs_property* p; + + if(name_len == (size_t)~0) { + name_len = strlen(name); + } + + p = mjs_get_own_property(mjs, obj, name, name_len); + if(p == NULL) { + return MJS_UNDEFINED; + } else { + return p->value; + } +} + +mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name) { + size_t n; + char* s = NULL; + int need_free = 0; + mjs_val_t ret = MJS_UNDEFINED; + + mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free); + + if(err == MJS_OK) { + /* Successfully converted name value to string: get the property */ + ret = mjs_get(mjs, obj, s, n); + } + + if(need_free) { + free(s); + s = NULL; + } + return ret; +} + +mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) { + struct mjs_property* p; + mjs_val_t pn = mjs_mk_string(mjs, MJS_PROTO_PROP_NAME, ~0, 1); + if((p = mjs_get_own_property_v(mjs, obj, key)) != NULL) return p->value; + if((p = mjs_get_own_property_v(mjs, obj, pn)) == NULL) return MJS_UNDEFINED; + return mjs_get_v_proto(mjs, p->value, key); +} + +mjs_err_t + mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len, mjs_val_t val) { + return mjs_set_internal(mjs, obj, MJS_UNDEFINED, (char*)name, name_len, val); +} + +mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val) { + return mjs_set_internal(mjs, obj, name, NULL, 0, val); +} + +MJS_PRIVATE mjs_err_t mjs_set_internal( + struct mjs* mjs, + mjs_val_t obj, + mjs_val_t name_v, + char* name, + size_t name_len, + mjs_val_t val) { + mjs_err_t rcode = MJS_OK; + + struct mjs_property* p; + + int need_free = 0; + + if(name == NULL) { + /* Pointer was not provided, so obtain one from the name_v. */ + rcode = mjs_to_string(mjs, &name_v, &name, &name_len, &need_free); + if(rcode != MJS_OK) { + goto clean; + } + } else { + /* + * Pointer was provided, so we ignore name_v. Here we set it to undefined, + * and the actual value will be calculated later if needed. + */ + name_v = MJS_UNDEFINED; + } + + p = mjs_get_own_property(mjs, obj, name, name_len); + + if(p == NULL) { + struct mjs_object* o; + if(!mjs_is_object_based(obj)) { + return MJS_REFERENCE_ERROR; + } + + /* + * name_v might be not a string here. In this case, we need to create a new + * `name_v`, which will be a string. + */ + if(!mjs_is_string(name_v)) { + name_v = mjs_mk_string(mjs, name, name_len, 1); + } + + p = mjs_mk_property(mjs, name_v, val); + + o = get_object_struct(obj); + p->next = o->properties; + o->properties = p; + } + + p->value = val; + +clean: + if(need_free) { + free(name); + name = NULL; + } + return rcode; +} + +MJS_PRIVATE void mjs_destroy_property(struct mjs_property** p) { + *p = NULL; +} + +/* + * See comments in `object_public.h` + */ +int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) { + struct mjs_property *prop, *prev; + + if(!mjs_is_object_based(obj)) { + return -1; + } + if(len == (size_t)~0) { + len = strlen(name); + } + for(prev = NULL, prop = get_object_struct(obj)->properties; prop != NULL; + prev = prop, prop = prop->next) { + size_t n; + const char* s = mjs_get_string(mjs, &prop->name, &n); + if(n == len && strncmp(s, name, len) == 0) { + if(prev) { + prev->next = prop->next; + } else { + get_object_struct(obj)->properties = prop->next; + } + mjs_destroy_property(&prop); + return 0; + } + } + return -1; +} + +mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator) { + struct mjs_property* p = NULL; + mjs_val_t key = MJS_UNDEFINED; + + if(*iterator == MJS_UNDEFINED) { + struct mjs_object* o = get_object_struct(obj); + p = o->properties; + } else { + p = ((struct mjs_property*)get_ptr(*iterator))->next; + } + + if(p == NULL) { + *iterator = MJS_UNDEFINED; + } else { + key = p->name; + *iterator = mjs_mk_foreign(mjs, p); + } + + return key; +} + +MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t proto_v = mjs_arg(mjs, 0); + + if(!mjs_check_arg(mjs, 0, "proto", MJS_TYPE_OBJECT_GENERIC, &proto_v)) { + goto clean; + } + + ret = mjs_mk_object(mjs); + mjs_set(mjs, ret, MJS_PROTO_PROP_NAME, ~0, proto_v); + +clean: + mjs_return(mjs, ret); +} + +mjs_val_t + mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) { + mjs_val_t obj; + const struct mjs_c_struct_member* def = defs; + if(base == NULL || def == NULL) return MJS_UNDEFINED; + obj = mjs_mk_object(mjs); + /* Pin the object while it is being built */ + mjs_own(mjs, &obj); + /* + * Because mjs inserts new properties at the head of the list, + * start from the end so the constructed object more closely resembles + * the definition. + */ + while(def->name != NULL) def++; + for(def--; def >= defs; def--) { + mjs_val_t v = MJS_UNDEFINED; + const char* ptr = (const char*)base + def->offset; + switch(def->type) { + case MJS_STRUCT_FIELD_TYPE_STRUCT: { + const void* sub_base = (const void*)ptr; + const struct mjs_c_struct_member* sub_def = + (const struct mjs_c_struct_member*)def->arg; + v = mjs_struct_to_obj(mjs, sub_base, sub_def); + break; + } + case MJS_STRUCT_FIELD_TYPE_STRUCT_PTR: { + const void** sub_base = (const void**)ptr; + const struct mjs_c_struct_member* sub_def = + (const struct mjs_c_struct_member*)def->arg; + if(*sub_base != NULL) { + v = mjs_struct_to_obj(mjs, *sub_base, sub_def); + } else { + v = MJS_NULL; + } + break; + } + case MJS_STRUCT_FIELD_TYPE_INT: { + double value = (double)(*(int*)ptr); + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_BOOL: { + v = mjs_mk_boolean(mjs, *(bool*)ptr); + break; + } + case MJS_STRUCT_FIELD_TYPE_DOUBLE: { + v = mjs_mk_number(mjs, *(double*)ptr); + break; + } + case MJS_STRUCT_FIELD_TYPE_FLOAT: { + float value = *(float*)ptr; + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_CHAR_PTR: { + const char* value = *(const char**)ptr; + v = mjs_mk_string(mjs, value, ~0, 1); + break; + } + case MJS_STRUCT_FIELD_TYPE_VOID_PTR: { + v = mjs_mk_foreign(mjs, *(void**)ptr); + break; + } + case MJS_STRUCT_FIELD_TYPE_MG_STR_PTR: { + const struct mg_str* s = *(const struct mg_str**)ptr; + if(s != NULL) { + v = mjs_mk_string(mjs, s->p, s->len, 1); + } else { + v = MJS_NULL; + } + break; + } + case MJS_STRUCT_FIELD_TYPE_MG_STR: { + const struct mg_str* s = (const struct mg_str*)ptr; + v = mjs_mk_string(mjs, s->p, s->len, 1); + break; + } + case MJS_STRUCT_FIELD_TYPE_DATA: { + const char* dptr = (const char*)ptr; + const intptr_t dlen = (intptr_t)def->arg; + v = mjs_mk_string(mjs, dptr, dlen, 1); + break; + } + case MJS_STRUCT_FIELD_TYPE_INT8: { + double value = (double)(*(int8_t*)ptr); + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_INT16: { + double value = (double)(*(int16_t*)ptr); + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_UINT8: { + double value = (double)(*(uint8_t*)ptr); + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_UINT16: { + double value = (double)(*(uint16_t*)ptr); + v = mjs_mk_number(mjs, value); + break; + } + case MJS_STRUCT_FIELD_TYPE_CUSTOM: { + mjs_val_t (*fptr)(struct mjs*, const void*) = + (mjs_val_t(*)(struct mjs*, const void*))def->arg; + v = fptr(mjs, ptr); + } + default: { + break; + } + } + mjs_set(mjs, obj, def->name, ~0, v); + } + mjs_disown(mjs, &obj); + return obj; +} diff --git a/lib/mjs/mjs_object.h b/lib/mjs/mjs_object.h new file mode 100644 index 00000000000..1c4810385a7 --- /dev/null +++ b/lib/mjs/mjs_object.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_OBJECT_H_ +#define MJS_OBJECT_H_ + +#include "mjs_object_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs; + +struct mjs_property { + struct mjs_property* next; /* Linkage in struct mjs_object::properties */ + mjs_val_t name; /* Property name (a string) */ + mjs_val_t value; /* Property value */ +}; + +struct mjs_object { + struct mjs_property* properties; +}; + +MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v); +MJS_PRIVATE struct mjs_property* + mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); + +MJS_PRIVATE struct mjs_property* + mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +/* + * A worker function for `mjs_set()` and `mjs_set_v()`: it takes name as both + * ptr+len and mjs_val_t. If `name` pointer is not NULL, it takes precedence + * over `name_v`. + */ +MJS_PRIVATE mjs_err_t mjs_set_internal( + struct mjs* mjs, + mjs_val_t obj, + mjs_val_t name_v, + char* name, + size_t name_len, + mjs_val_t val); + +/* + * Implementation of `Object.create(proto)` + */ +MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs); + +#define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */ + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_OBJECT_H_ */ diff --git a/lib/mjs/mjs_object_public.h b/lib/mjs/mjs_object_public.h new file mode 100644 index 00000000000..f9f06c61647 --- /dev/null +++ b/lib/mjs/mjs_object_public.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_OBJECT_PUBLIC_H_ +#define MJS_OBJECT_PUBLIC_H_ + +#include +#include "mjs_core_public.h" +#include "mjs_ffi_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Returns true if the given value is an object or array. + */ +int mjs_is_object(mjs_val_t v); + +/* + * Returns true if the given value type is object-based (object, array, dataview). + */ +int mjs_is_object_based(mjs_val_t v); + +/* Make an empty object */ +mjs_val_t mjs_mk_object(struct mjs* mjs); + +/* Field types for struct-object conversion. */ +enum mjs_struct_field_type { + MJS_STRUCT_FIELD_TYPE_INVALID, + MJS_STRUCT_FIELD_TYPE_STRUCT, /* Struct, arg points to def. */ + MJS_STRUCT_FIELD_TYPE_STRUCT_PTR, /* Ptr to struct, arg points to def. */ + MJS_STRUCT_FIELD_TYPE_INT, + MJS_STRUCT_FIELD_TYPE_BOOL, + MJS_STRUCT_FIELD_TYPE_DOUBLE, + MJS_STRUCT_FIELD_TYPE_FLOAT, + MJS_STRUCT_FIELD_TYPE_CHAR_PTR, /* NUL-terminated string. */ + MJS_STRUCT_FIELD_TYPE_VOID_PTR, /* Converted to foreign ptr. */ + MJS_STRUCT_FIELD_TYPE_MG_STR_PTR, /* Converted to string. */ + MJS_STRUCT_FIELD_TYPE_MG_STR, /* Converted to string. */ + MJS_STRUCT_FIELD_TYPE_DATA, /* Data, arg is length, becomes string. */ + MJS_STRUCT_FIELD_TYPE_INT8, + MJS_STRUCT_FIELD_TYPE_INT16, + MJS_STRUCT_FIELD_TYPE_UINT8, + MJS_STRUCT_FIELD_TYPE_UINT16, + /* + * User-provided function. Arg is a pointer to function that takes void * + * (pointer to field within the struct) and returns mjs_val_t: + * mjs_val_t field_value(struct mjs *mjs, const void *field_ptr) { ... } + */ + MJS_STRUCT_FIELD_TYPE_CUSTOM, +}; + +/* C structure layout descriptor - needed by mjs_struct_to_obj */ +struct mjs_c_struct_member { + const char* name; + int offset; + enum mjs_struct_field_type type; + const void* arg; /* Additional argument, used for some types. */ +}; + +/* Create flat JS object from a C memory descriptor */ +mjs_val_t + mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* members); + +/* + * Lookup property `name` in object `obj`. If `obj` holds no such property, + * an `undefined` value is returned. + * + * If `name_len` is ~0, `name` is assumed to be NUL-terminated and + * `strlen(name)` is used. + */ +mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len); + +/* + * Like mjs_get but with a JS string. + */ +mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name); + +/* + * Like mjs_get_v but lookup the prototype chain. + */ +mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key); + +/* + * Set object property. Behaves just like JavaScript assignment. + */ +mjs_err_t mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len, mjs_val_t val); + +/* + * Like mjs_set but the name is already a JS string. + */ +mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val); + +/* + * Delete own property `name` of the object `obj`. Does not follow the + * prototype chain. + * + * If `name_len` is ~0, `name` is assumed to be NUL-terminated and + * `strlen(name)` is used. + * + * Returns 0 on success, -1 on error. + */ +int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len); + +/* + * Iterate over `obj` properties. + * First call should set `iterator` to MJS_UNDEFINED. + * Return object's key (a string), or MJS_UNDEFINED when no more keys left. + * Do not mutate the object during iteration. + * + * Example: + * mjs_val_t key, iter = MJS_UNDEFINED; + * while ((key = mjs_next(mjs, obj, &iter)) != MJS_UNDEFINED) { + * // Do something with the obj/key ... + * } + */ +mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_OBJECT_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_parser.c b/lib/mjs/mjs_parser.c new file mode 100644 index 00000000000..503b169426a --- /dev/null +++ b/lib/mjs/mjs_parser.c @@ -0,0 +1,1033 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" + +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_parser.h" +#include "mjs_string.h" +#include "mjs_tok.h" + +#ifndef MAX_TOKS_IN_EXPR +#define MAX_TOKS_IN_EXPR 40 +#endif + +#define FAIL_ERR(p, code) \ + do { \ + mjs_set_errorf( \ + p->mjs, code, "parse error at line %d: [%.*s]", p->line_no, 10, p->tok.ptr); \ + return code; \ + } while(0) + +#define pnext1(p) \ + do { \ + LOG(LL_VERBOSE_DEBUG, (" PNEXT %d", __LINE__)); \ + pnext(p); \ + } while(0) + +#define SYNTAX_ERROR(p) FAIL_ERR(p, MJS_SYNTAX_ERROR) +#undef EXPECT +#define EXPECT(p, t) \ + if((p)->tok.tok != (t)) \ + SYNTAX_ERROR(p); \ + else \ + pnext1(p); + +static mjs_err_t parse_statement(struct pstate* p); +static mjs_err_t parse_expr(struct pstate* p); + +static int ptest(struct pstate* p) { + struct pstate saved = *p; + int tok = pnext(p); + *p = saved; + return tok; +} + +static int s_unary_ops[] = { + TOK_NOT, + TOK_TILDA, + TOK_PLUS_PLUS, + TOK_MINUS_MINUS, + TOK_KEYWORD_TYPEOF, + TOK_MINUS, + TOK_PLUS, + TOK_EOF}; +static int s_comparison_ops[] = {TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_EOF}; +static int s_postfix_ops[] = {TOK_PLUS_PLUS, TOK_MINUS_MINUS, TOK_EOF}; +static int s_equality_ops[] = {TOK_EQ, TOK_NE, TOK_EQ_EQ, TOK_NE_NE, TOK_EOF}; +static int s_assign_ops[] = { + TOK_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_REM_ASSIGN, + TOK_LSHIFT_ASSIGN, + TOK_RSHIFT_ASSIGN, + TOK_URSHIFT_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_EOF}; + +static int findtok(int* toks, int tok) { + int i = 0; + while(tok != toks[i] && toks[i] != TOK_EOF) i++; + return toks[i]; +} + +static void emit_op(struct pstate* pstate, int tok) { + assert(tok >= 0 && tok <= 255); + emit_byte(pstate, OP_EXPR); + emit_byte(pstate, (uint8_t)tok); +} + +#define BINOP_STACK_FRAME_SIZE 16 +#define STACK_LIMIT 8192 + +// Intentionally left as macro rather than a function, to let the +// compiler to inline calls and mimimize runtime stack usage. +#define PARSE_LTR_BINOP(p, f1, f2, ops, prev_op) \ + do { \ + mjs_err_t res = MJS_OK; \ + p->depth++; \ + if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { \ + mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); \ + res = MJS_SYNTAX_ERROR; \ + goto binop_clean; \ + } \ + if((res = f1(p, TOK_EOF)) != MJS_OK) goto binop_clean; \ + if(prev_op != TOK_EOF) emit_op(p, prev_op); \ + if(findtok(ops, p->tok.tok) != TOK_EOF) { \ + int op = p->tok.tok; \ + size_t off_if = 0; \ + /* For AND/OR, implement short-circuit evaluation */ \ + if(ops[0] == TOK_LOGICAL_AND || ops[0] == TOK_LOGICAL_OR) { \ + emit_byte( \ + p, \ + (uint8_t)(ops[0] == TOK_LOGICAL_AND ? OP_JMP_NEUTRAL_FALSE : \ + OP_JMP_NEUTRAL_TRUE)); \ + off_if = p->cur_idx; \ + emit_init_offset(p); \ + /* No need to emit TOK_LOGICAL_AND and TOK_LOGICAL_OR: */ \ + /* Just drop the first value, and evaluate the second one. */ \ + emit_byte(p, (uint8_t)OP_DROP); \ + op = TOK_EOF; \ + } \ + pnext1(p); \ + if((res = f2(p, op)) != MJS_OK) goto binop_clean; \ + \ + if(off_if != 0) { \ + mjs_bcode_insert_offset( \ + p, p->mjs, off_if, p->cur_idx - off_if - MJS_INIT_OFFSET_SIZE); \ + } \ + } \ + binop_clean: \ + p->depth--; \ + return res; \ + } while(0) + +#define PARSE_RTL_BINOP(p, f1, f2, ops, prev_op) \ + do { \ + mjs_err_t res = MJS_OK; \ + (void)prev_op; \ + if((res = f1(p, TOK_EOF)) != MJS_OK) return res; \ + if(findtok(ops, p->tok.tok) != TOK_EOF) { \ + int op = p->tok.tok; \ + pnext1(p); \ + if((res = f2(p, TOK_EOF)) != MJS_OK) return res; \ + emit_op(p, op); \ + } \ + return res; \ + } while(0) + +#if MJS_INIT_OFFSET_SIZE > 0 +static void emit_init_offset(struct pstate* p) { + size_t i; + for(i = 0; i < MJS_INIT_OFFSET_SIZE; i++) { + emit_byte(p, 0); + } +} +#else +static void emit_init_offset(struct pstate* p) { + (void)p; +} +#endif + +static mjs_err_t parse_statement_list(struct pstate* p, int et) { + mjs_err_t res = MJS_OK; + int drop = 0; + pnext1(p); + while(res == MJS_OK && p->tok.tok != TOK_EOF && p->tok.tok != et) { + if(drop) emit_byte(p, OP_DROP); + res = parse_statement(p); + drop = 1; + while(p->tok.tok == TOK_SEMICOLON) pnext1(p); + } + + /* + * Client code expects statement list to contain a value, so if the statement + * list was empty, push `undefined`. + */ + if(!drop) { + emit_byte(p, OP_PUSH_UNDEF); + } + return res; +} + +static mjs_err_t parse_block(struct pstate* p, int mkscope) { + mjs_err_t res = MJS_OK; + p->depth++; + if(p->depth > (STACK_LIMIT / BINOP_STACK_FRAME_SIZE)) { + mjs_set_errorf(p->mjs, MJS_SYNTAX_ERROR, "parser stack overflow"); + res = MJS_SYNTAX_ERROR; + return res; + } + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + if(mkscope) emit_byte(p, OP_NEW_SCOPE); + res = parse_statement_list(p, TOK_CLOSE_CURLY); + EXPECT(p, TOK_CLOSE_CURLY); + if(mkscope) emit_byte(p, OP_DEL_SCOPE); + return res; +} + +static mjs_err_t parse_function(struct pstate* p) { + size_t prologue, off; + int arg_no = 0; + int name_provided = 0; + mjs_err_t res = MJS_OK; + + EXPECT(p, TOK_KEYWORD_FUNCTION); + + if(p->tok.tok == TOK_IDENT) { + /* Function name was provided */ + struct tok tmp = p->tok; + name_provided = 1; + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_FIND_SCOPE); + pnext1(p); + } + + emit_byte(p, OP_JMP); + off = p->cur_idx; + emit_init_offset(p); + + prologue = p->cur_idx; + + EXPECT(p, TOK_OPEN_PAREN); + emit_byte(p, OP_NEW_SCOPE); + // Emit names of function arguments + while(p->tok.tok != TOK_CLOSE_PAREN) { + if(p->tok.tok != TOK_IDENT) SYNTAX_ERROR(p); + emit_byte(p, OP_SET_ARG); + emit_int(p, arg_no); + arg_no++; + emit_str(p, p->tok.ptr, p->tok.len); + if(ptest(p) == TOK_COMMA) pnext1(p); + pnext1(p); + } + EXPECT(p, TOK_CLOSE_PAREN); + if((res = parse_block(p, 0)) != MJS_OK) return res; + emit_byte(p, OP_RETURN); + prologue += mjs_bcode_insert_offset(p, p->mjs, off, p->cur_idx - off - MJS_INIT_OFFSET_SIZE); + emit_byte(p, OP_PUSH_FUNC); + emit_int(p, p->cur_idx - 1 /* OP_PUSH_FUNC */ - prologue); + if(name_provided) { + emit_op(p, TOK_ASSIGN); + } + + return res; +} + +static mjs_err_t parse_object_literal(struct pstate* p) { + mjs_err_t res = MJS_OK; + EXPECT(p, TOK_OPEN_CURLY); + emit_byte(p, OP_PUSH_OBJ); + while(p->tok.tok != TOK_CLOSE_CURLY) { + if(p->tok.tok != TOK_IDENT && p->tok.tok != TOK_STR) SYNTAX_ERROR(p); + emit_byte(p, OP_DUP); + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + emit_byte(p, OP_SWAP); + pnext1(p); + EXPECT(p, TOK_COLON); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_op(p, TOK_ASSIGN); + emit_byte(p, OP_DROP); + if(p->tok.tok == TOK_COMMA) { + pnext1(p); + } else if(p->tok.tok != TOK_CLOSE_CURLY) { + SYNTAX_ERROR(p); + } + } + return res; +} + +static mjs_err_t parse_array_literal(struct pstate* p) { + mjs_err_t res = MJS_OK; + EXPECT(p, TOK_OPEN_BRACKET); + emit_byte(p, OP_PUSH_ARRAY); + while(p->tok.tok != TOK_CLOSE_BRACKET) { + emit_byte(p, OP_DUP); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_byte(p, OP_APPEND); + if(p->tok.tok == TOK_COMMA) pnext1(p); + } + return res; +} + +static enum mjs_err parse_literal(struct pstate* p, const struct tok* t) { + struct mbuf* bcode_gen = &p->mjs->bcode_gen; + enum mjs_err res = MJS_OK; + int tok = t->tok; + LOG(LL_VERBOSE_DEBUG, ("[%.*s] %p", p->tok.len, p->tok.ptr, (void*)&t)); + switch(t->tok) { + case TOK_KEYWORD_FALSE: + emit_byte(p, OP_PUSH_FALSE); + break; + case TOK_KEYWORD_TRUE: + emit_byte(p, OP_PUSH_TRUE); + break; + case TOK_KEYWORD_UNDEFINED: + emit_byte(p, OP_PUSH_UNDEF); + break; + case TOK_KEYWORD_NULL: + emit_byte(p, OP_PUSH_NULL); + break; + case TOK_IDENT: { + int prev_tok = p->prev_tok; + int next_tok = ptest(p); + emit_byte(p, OP_PUSH_STR); + emit_str(p, t->ptr, t->len); + emit_byte(p, (uint8_t)(prev_tok == TOK_DOT ? OP_SWAP : OP_FIND_SCOPE)); + if(!findtok(s_assign_ops, next_tok) && !findtok(s_postfix_ops, next_tok) && + /* TODO(dfrank): fix: it doesn't work for prefix ops */ + !findtok(s_postfix_ops, prev_tok)) { + emit_byte(p, OP_GET); + } + break; + } + case TOK_NUM: { + double iv, d = strtod(t->ptr, NULL); + unsigned long uv = strtoul(t->ptr + 2, NULL, 16); + if(t->ptr[0] == '0' && t->ptr[1] == 'x') d = uv; + if(modf(d, &iv) == 0) { + emit_byte(p, OP_PUSH_INT); + emit_int(p, (int64_t)d); + } else { + emit_byte(p, OP_PUSH_DBL); + emit_str(p, t->ptr, t->len); + } + break; + } + case TOK_STR: { + size_t oldlen; + emit_byte(p, OP_PUSH_STR); + oldlen = bcode_gen->len; + embed_string(bcode_gen, p->cur_idx, t->ptr, t->len, EMBSTR_UNESCAPE); + p->cur_idx += bcode_gen->len - oldlen; + } break; + case TOK_OPEN_BRACKET: + res = parse_array_literal(p); + break; + case TOK_OPEN_CURLY: + res = parse_object_literal(p); + break; + case TOK_OPEN_PAREN: + pnext1(p); + res = parse_expr(p); + if(p->tok.tok != TOK_CLOSE_PAREN) SYNTAX_ERROR(p); + break; + case TOK_KEYWORD_FUNCTION: + res = parse_function(p); + break; + case TOK_KEYWORD_THIS: + emit_byte(p, OP_PUSH_THIS); + break; + default: + SYNTAX_ERROR(p); + } + if(tok != TOK_KEYWORD_FUNCTION) pnext1(p); + return res; +} + +static mjs_err_t parse_call_dot_mem(struct pstate* p, int prev_op) { + int ops[] = {TOK_DOT, TOK_OPEN_PAREN, TOK_OPEN_BRACKET, TOK_EOF}; + mjs_err_t res = MJS_OK; + if((res = parse_literal(p, &p->tok)) != MJS_OK) return res; + while(findtok(ops, p->tok.tok) != TOK_EOF) { + if(p->tok.tok == TOK_OPEN_BRACKET) { + int prev_tok = p->prev_tok; + EXPECT(p, TOK_OPEN_BRACKET); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_byte(p, OP_SWAP); + EXPECT(p, TOK_CLOSE_BRACKET); + if(!findtok(s_assign_ops, p->tok.tok) && !findtok(s_postfix_ops, p->tok.tok) && + /* TODO(dfrank): fix: it doesn't work for prefix ops */ + !findtok(s_postfix_ops, prev_tok)) { + emit_byte(p, OP_GET); + } + } else if(p->tok.tok == TOK_OPEN_PAREN) { + EXPECT(p, TOK_OPEN_PAREN); + emit_byte(p, OP_ARGS); + while(p->tok.tok != TOK_CLOSE_PAREN) { + if((res = parse_expr(p)) != MJS_OK) return res; + if(p->tok.tok == TOK_COMMA) pnext1(p); + } + emit_byte(p, OP_CALL); + EXPECT(p, TOK_CLOSE_PAREN); + } else if(p->tok.tok == TOK_DOT) { + EXPECT(p, TOK_DOT); + if((res = parse_call_dot_mem(p, TOK_DOT)) != MJS_OK) return res; + } + } + (void)prev_op; + return res; +} + +static mjs_err_t parse_postfix(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + if((res = parse_call_dot_mem(p, prev_op)) != MJS_OK) return res; + if(p->tok.tok == TOK_PLUS_PLUS || p->tok.tok == TOK_MINUS_MINUS) { + int op = p->tok.tok == TOK_PLUS_PLUS ? TOK_POSTFIX_PLUS : TOK_POSTFIX_MINUS; + emit_op(p, op); + pnext1(p); + } + return res; +} + +static mjs_err_t parse_unary(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + int op = TOK_EOF; + if(findtok(s_unary_ops, p->tok.tok) != TOK_EOF) { + op = p->tok.tok; + pnext1(p); + } + if(findtok(s_unary_ops, p->tok.tok) != TOK_EOF) { + res = parse_unary(p, prev_op); + } else { + res = parse_postfix(p, prev_op); + } + if(res != MJS_OK) return res; + if(op != TOK_EOF) { + if(op == TOK_MINUS) op = TOK_UNARY_MINUS; + if(op == TOK_PLUS) op = TOK_UNARY_PLUS; + emit_op(p, op); + } + return res; +} + +static mjs_err_t parse_mul_div_rem(struct pstate* p, int prev_op) { + int ops[] = {TOK_MUL, TOK_DIV, TOK_REM, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_unary, parse_mul_div_rem, ops, prev_op); +} + +static mjs_err_t parse_plus_minus(struct pstate* p, int prev_op) { + int ops[] = {TOK_PLUS, TOK_MINUS, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_mul_div_rem, parse_plus_minus, ops, prev_op); +} + +static mjs_err_t parse_shifts(struct pstate* p, int prev_op) { + int ops[] = {TOK_LSHIFT, TOK_RSHIFT, TOK_URSHIFT, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_plus_minus, parse_shifts, ops, prev_op); +} + +static mjs_err_t parse_comparison(struct pstate* p, int prev_op) { + PARSE_LTR_BINOP(p, parse_shifts, parse_comparison, s_comparison_ops, prev_op); +} + +static mjs_err_t parse_equality(struct pstate* p, int prev_op) { + PARSE_LTR_BINOP(p, parse_comparison, parse_equality, s_equality_ops, prev_op); +} + +static mjs_err_t parse_bitwise_and(struct pstate* p, int prev_op) { + int ops[] = {TOK_AND, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_equality, parse_bitwise_and, ops, prev_op); +} + +static mjs_err_t parse_bitwise_xor(struct pstate* p, int prev_op) { + int ops[] = {TOK_XOR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_and, parse_bitwise_xor, ops, prev_op); +} + +static mjs_err_t parse_bitwise_or(struct pstate* p, int prev_op) { + int ops[] = {TOK_OR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_xor, parse_bitwise_or, ops, prev_op); +} + +static mjs_err_t parse_logical_and(struct pstate* p, int prev_op) { + int ops[] = {TOK_LOGICAL_AND, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_bitwise_or, parse_logical_and, ops, prev_op); +} + +static mjs_err_t parse_logical_or(struct pstate* p, int prev_op) { + int ops[] = {TOK_LOGICAL_OR, TOK_EOF}; + PARSE_LTR_BINOP(p, parse_logical_and, parse_logical_or, ops, prev_op); +} + +static mjs_err_t parse_ternary(struct pstate* p, int prev_op) { + mjs_err_t res = MJS_OK; + if((res = parse_logical_or(p, TOK_EOF)) != MJS_OK) return res; + if(prev_op != TOK_EOF) emit_op(p, prev_op); + + if(p->tok.tok == TOK_QUESTION) { + size_t off_if, off_endif, off_else; + EXPECT(p, TOK_QUESTION); + + emit_byte(p, OP_JMP_FALSE); + off_if = p->cur_idx; + emit_init_offset(p); + + if((res = parse_ternary(p, TOK_EOF)) != MJS_OK) return res; + + emit_byte(p, OP_JMP); + off_else = p->cur_idx; + emit_init_offset(p); + off_endif = p->cur_idx; + + emit_byte(p, OP_DROP); + + EXPECT(p, TOK_COLON); + if((res = parse_ternary(p, TOK_EOF)) != MJS_OK) return res; + + /* + * NOTE: if inserting offset causes the code to move, off_endif needs to be + * adjusted + */ + off_endif += mjs_bcode_insert_offset( + p, p->mjs, off_else, p->cur_idx - off_else - MJS_INIT_OFFSET_SIZE); + + mjs_bcode_insert_offset(p, p->mjs, off_if, off_endif - off_if - MJS_INIT_OFFSET_SIZE); + } + + return res; +} + +static mjs_err_t parse_assignment(struct pstate* p, int prev_op) { + PARSE_RTL_BINOP(p, parse_ternary, parse_assignment, s_assign_ops, prev_op); +} + +static mjs_err_t parse_expr(struct pstate* p) { + return parse_assignment(p, TOK_EOF); +} + +static mjs_err_t parse_let(struct pstate* p) { + mjs_err_t res = MJS_OK; + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_LET); + for(;;) { + struct tok tmp = p->tok; + EXPECT(p, TOK_IDENT); + + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + + if(p->tok.tok == TOK_ASSIGN) { + pnext1(p); + emit_byte(p, OP_PUSH_STR); + emit_str(p, tmp.ptr, tmp.len); + emit_byte(p, OP_FIND_SCOPE); + if((res = parse_expr(p)) != MJS_OK) return res; + emit_op(p, TOK_ASSIGN); + } else { + emit_byte(p, OP_PUSH_UNDEF); + } + if(p->tok.tok == TOK_COMMA) { + emit_byte(p, OP_DROP); + pnext1(p); + } + if(p->tok.tok == TOK_SEMICOLON || p->tok.tok == TOK_EOF) break; + } + return res; +} + +static mjs_err_t parse_block_or_stmt(struct pstate* p, int cs) { + if(ptest(p) == TOK_OPEN_CURLY) { + return parse_block(p, cs); + } else { + return parse_statement(p); + } +} + +static mjs_err_t parse_for_in(struct pstate* p) { + mjs_err_t res = MJS_OK; + size_t off_b, off_check_end; + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* Put iterator variable name to the stack */ + if(p->tok.tok == TOK_KEYWORD_LET) { + EXPECT(p, TOK_KEYWORD_LET); + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + emit_byte(p, OP_PUSH_SCOPE); + emit_byte(p, OP_CREATE); + } + emit_byte(p, OP_PUSH_STR); + emit_str(p, p->tok.ptr, p->tok.len); + + /* Put object to the stack */ + EXPECT(p, TOK_IDENT); + EXPECT(p, TOK_KEYWORD_IN); + parse_expr(p); + EXPECT(p, TOK_CLOSE_PAREN); + + emit_byte(p, OP_PUSH_UNDEF); /* Push iterator */ + + /* Before parsing condition statement, push break/continue offsets */ + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + emit_byte(p, 0); /* Point OP_CONTINUE to the next instruction */ + + emit_byte(p, OP_FOR_IN_NEXT); + emit_byte(p, OP_DUP); + emit_byte(p, OP_JMP_FALSE); + off_check_end = p->cur_idx; + emit_init_offset(p); + + // Parse loop body + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_check_end, p->cur_idx - off_check_end - MJS_INIT_OFFSET_SIZE); + + /* NOTE: jump C -> cond link is already established, it's constant: zero */ + + emit_byte(p, OP_BREAK); + + /* jump B -> cond */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DROP); + emit_byte(p, OP_DROP); + emit_byte(p, OP_DROP); + emit_byte(p, OP_DEL_SCOPE); + + return res; +} + +static int check_for_in(struct pstate* p) { + struct pstate saved = *p; + int forin = 0; + if(p->tok.tok == TOK_KEYWORD_LET) pnext1(p); + if(p->tok.tok == TOK_IDENT) { + pnext1(p); + if(p->tok.tok == TOK_KEYWORD_IN) forin = 1; + } + *p = saved; + return forin; +} + +static mjs_err_t parse_for(struct pstate* p) { + mjs_err_t res = MJS_OK; + size_t off_b, off_c, off_init_end; + size_t off_incr_begin, off_cond_begin, off_cond_end; + int buf_cur_idx; + + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_FOR); + EXPECT(p, TOK_OPEN_PAREN); + + /* Look forward - is it for..in ? */ + if(check_for_in(p)) return parse_for_in(p); + + /* + * BC is a break+continue offsets (a part of OP_LOOP opcode) + * + * BC init incr cond body break del_scope + * || | ^ ^ | ^ ^ + * || +--|-----+ | | | + * |+-------+ +--------+ | + * +---------------------------------+ + * + * The order to setup links: + * + * cond -> break + * init -> cond + * C -> incr + * B -> del_scope + */ + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* Before parsing condition statement, push break/continue offsets */ + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + off_c = p->cur_idx; + emit_init_offset(p); + + /* Parse init statement */ + if(p->tok.tok == TOK_KEYWORD_LET) { + if((res = parse_let(p)) != MJS_OK) return res; + } else { + if((res = parse_expr(p)) != MJS_OK) return res; + } + EXPECT(p, TOK_SEMICOLON); + emit_byte(p, OP_DROP); + + emit_byte(p, OP_JMP); + off_init_end = p->cur_idx; + emit_init_offset(p); + + off_incr_begin = p->cur_idx; + off_cond_begin = p->cur_idx; + + /* Parse cond statement */ + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_SEMICOLON); + + /* Parse incr statement */ + /* Incr statement should be placed before cond, so, adjust cur_idx */ + buf_cur_idx = p->cur_idx; + p->cur_idx = off_incr_begin; + + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_CLOSE_PAREN); + emit_byte(p, OP_DROP); + + /* + * Now incr is inserted before cond, so we adjust cur_idx back, and set + * off_cond_begin to the correct value + */ + { + int incr_size = p->cur_idx - off_incr_begin; + off_cond_begin += incr_size; + p->cur_idx = buf_cur_idx + incr_size; + } + + /* p->cur_idx is now at the end of "cond" */ + /* Exit the loop if false */ + emit_byte(p, OP_JMP_FALSE); + off_cond_end = p->cur_idx; + emit_init_offset(p); + + /* Parse loop body */ + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* p->cur_idx is at the "break" item now */ + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_cond_end, p->cur_idx - off_cond_end - MJS_INIT_OFFSET_SIZE); + + /* jump init -> cond (and adjust off_incr_begin which may move) */ + off_incr_begin += mjs_bcode_insert_offset( + p, p->mjs, off_init_end, off_cond_begin - off_init_end - MJS_INIT_OFFSET_SIZE); + + /* jump C -> incr */ + mjs_bcode_insert_offset(p, p->mjs, off_c, off_incr_begin - off_c - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_BREAK); + + /* jump B -> del_scope */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DEL_SCOPE); + + return res; +} + +static mjs_err_t parse_while(struct pstate* p) { + size_t off_cond_end, off_b; + mjs_err_t res = MJS_OK; + + EXPECT(p, TOK_KEYWORD_WHILE); + EXPECT(p, TOK_OPEN_PAREN); + + /* new scope should be pushed before OP_LOOP instruction */ + emit_byte(p, OP_NEW_SCOPE); + + /* + * BC is a break+continue offsets (a part of OP_LOOP opcode) + * + * BC cond body break del_scope + * || ^ | ^ ^ + * || | | | | + * |+-+ +------+ | + * +------------------+ + * + * The order to setup links: + * + * cond -> break + * C -> cond + * B -> del_scope + */ + + emit_byte(p, OP_LOOP); + off_b = p->cur_idx; + emit_init_offset(p); + emit_byte(p, 0); /* Point OP_CONTINUE to the next instruction */ + + // parse condition statement + if((res = parse_expr(p)) != MJS_OK) return res; + EXPECT(p, TOK_CLOSE_PAREN); + + // Exit the loop if false + emit_byte(p, OP_JMP_FALSE); + off_cond_end = p->cur_idx; + emit_init_offset(p); + + // Parse loop body + if(p->tok.tok == TOK_OPEN_CURLY) { + if((res = parse_statement_list(p, TOK_CLOSE_CURLY)) != MJS_OK) return res; + pnext1(p); + } else { + if((res = parse_statement(p)) != MJS_OK) return res; + } + emit_byte(p, OP_DROP); + emit_byte(p, OP_CONTINUE); + + /* jump cond -> break */ + mjs_bcode_insert_offset( + p, p->mjs, off_cond_end, p->cur_idx - off_cond_end - MJS_INIT_OFFSET_SIZE); + + /* NOTE: jump C -> cond link is already established, it's constant: zero */ + + emit_byte(p, OP_BREAK); + + /* jump B -> cond */ + mjs_bcode_insert_offset(p, p->mjs, off_b, p->cur_idx - off_b - MJS_INIT_OFFSET_SIZE); + + emit_byte(p, OP_DEL_SCOPE); + return res; +} + +static mjs_err_t parse_if(struct pstate* p) { + size_t off_if, off_endif; + mjs_err_t res = MJS_OK; + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + EXPECT(p, TOK_KEYWORD_IF); + EXPECT(p, TOK_OPEN_PAREN); + if((res = parse_expr(p)) != MJS_OK) return res; + + emit_byte(p, OP_JMP_FALSE); + off_if = p->cur_idx; + emit_init_offset(p); + + EXPECT(p, TOK_CLOSE_PAREN); + if((res = parse_block_or_stmt(p, 1)) != MJS_OK) return res; + + if(p->tok.tok == TOK_KEYWORD_ELSE) { + /* + * Else clause is present, so, if the condition is not true, the jump + * target (off_endif) should be not the current offset, but the offset + * after jump-over-else opcode + */ + size_t off_else, off_endelse; + pnext1(p); + emit_byte(p, OP_JMP); + off_else = p->cur_idx; + emit_init_offset(p); + off_endif = p->cur_idx; + + emit_byte(p, OP_DROP); + if((res = parse_block_or_stmt(p, 1)) != MJS_OK) return res; + off_endelse = p->cur_idx; + + /* + * NOTE: if inserting offset causes the code to move, off_endif needs to be + * adjusted + */ + off_endif += mjs_bcode_insert_offset( + p, p->mjs, off_else, off_endelse - off_else - MJS_INIT_OFFSET_SIZE); + } else { + /* Else clause is not present, so, current offset is a jump target + * (off_endif) */ + off_endif = p->cur_idx; + } + + mjs_bcode_insert_offset(p, p->mjs, off_if, off_endif - off_if - MJS_INIT_OFFSET_SIZE); + + return res; +} + +static void pstate_revert(struct pstate* p, struct pstate* old, int old_bcode_gen_len) { + p->pos = old->pos; + p->line_no = old->line_no; + p->last_emitted_line_no = old->last_emitted_line_no; + p->offset_lineno_map.len = old->offset_lineno_map.len; + p->prev_tok = old->prev_tok; + p->tok = old->tok; + p->mjs->bcode_gen.len = old_bcode_gen_len; + p->cur_idx = old->cur_idx; + p->depth = old->depth; +} + +static mjs_err_t parse_return(struct pstate* p) { + int old_bcode_gen_len; + struct pstate p_saved; + EXPECT(p, TOK_KEYWORD_RETURN); + p_saved = *p; + old_bcode_gen_len = p->mjs->bcode_gen.len; + if(parse_expr(p) != MJS_OK) { + /* + * Failed to parse an expression to return, so return the parser to the + * prior state and push undefined. + */ + pstate_revert(p, &p_saved, old_bcode_gen_len); + emit_byte(p, OP_PUSH_UNDEF); + } + emit_byte(p, OP_SETRETVAL); + emit_byte(p, OP_RETURN); + return MJS_OK; +} + +static mjs_err_t parse_statement(struct pstate* p) { + LOG(LL_VERBOSE_DEBUG, ("[%.*s]", 10, p->tok.ptr)); + switch(p->tok.tok) { + case TOK_SEMICOLON: + emit_byte(p, OP_PUSH_UNDEF); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_LET: + return parse_let(p); + case TOK_OPEN_CURLY: + return parse_block(p, 1); + case TOK_KEYWORD_RETURN: + return parse_return(p); + case TOK_KEYWORD_FOR: + return parse_for(p); + case TOK_KEYWORD_WHILE: + return parse_while(p); + case TOK_KEYWORD_BREAK: + emit_byte(p, OP_PUSH_UNDEF); + emit_byte(p, OP_BREAK); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_CONTINUE: + emit_byte(p, OP_CONTINUE); + pnext1(p); + return MJS_OK; + case TOK_KEYWORD_IF: + return parse_if(p); + case TOK_KEYWORD_CASE: + case TOK_KEYWORD_CATCH: + case TOK_KEYWORD_DELETE: + case TOK_KEYWORD_DO: + case TOK_KEYWORD_INSTANCEOF: + case TOK_KEYWORD_NEW: + case TOK_KEYWORD_SWITCH: + case TOK_KEYWORD_THROW: + case TOK_KEYWORD_TRY: + case TOK_KEYWORD_VAR: + case TOK_KEYWORD_VOID: + case TOK_KEYWORD_WITH: + mjs_set_errorf( + p->mjs, MJS_SYNTAX_ERROR, "[%.*s] is not implemented", p->tok.len, p->tok.ptr); + return MJS_SYNTAX_ERROR; + default: { + mjs_err_t res = MJS_OK; + for(;;) { + if((res = parse_expr(p)) != MJS_OK) return res; + if(p->tok.tok != TOK_COMMA) break; + emit_byte(p, OP_DROP); + pnext1(p); + } + return res; + } + } +} + +MJS_PRIVATE mjs_err_t mjs_parse(const char* path, const char* buf, struct mjs* mjs) { + mjs_err_t res = MJS_OK; + struct pstate p; + size_t start_idx, llen; + int map_len; + mjs_header_item_t bcode_offset, map_offset, total_size; + + pinit(path, buf, &p); + p.mjs = mjs; + p.cur_idx = p.mjs->bcode_gen.len; + emit_byte(&p, OP_BCODE_HEADER); + + /* + * TODO(dfrank): don't access mjs->bcode_gen directly, use emit_... API which + * takes care of p->cur_idx + */ + + /* Remember starting bcode position, and reserve the room for bcode header */ + start_idx = p.mjs->bcode_gen.len; + mbuf_append(&p.mjs->bcode_gen, NULL, sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT); + + /* Append NULL-terminated filename */ + mbuf_append(&p.mjs->bcode_gen, path, strlen(path) + 1 /* null-terminate */); + + bcode_offset = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, + &bcode_offset, + sizeof(mjs_header_item_t)); + + p.start_bcode_idx = p.mjs->bcode_gen.len; + p.cur_idx = p.mjs->bcode_gen.len; + + res = parse_statement_list(&p, TOK_EOF); + emit_byte(&p, OP_EXIT); + + /* remember map offset */ + map_offset = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_MAP_OFFSET, + &map_offset, + sizeof(mjs_header_item_t)); + + /* put map length varint */ + map_len = p.offset_lineno_map.len; + llen = cs_varint_llen(map_len); + mbuf_resize(&p.mjs->bcode_gen, p.mjs->bcode_gen.size + llen); + cs_varint_encode(map_len, (uint8_t*)p.mjs->bcode_gen.buf + p.mjs->bcode_gen.len, llen); + p.mjs->bcode_gen.len += llen; + + /* put the map itself */ + mbuf_append(&p.mjs->bcode_gen, p.offset_lineno_map.buf, p.offset_lineno_map.len); + + total_size = p.mjs->bcode_gen.len - start_idx; + memcpy( + p.mjs->bcode_gen.buf + start_idx + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_TOTAL_SIZE, + &total_size, + sizeof(mjs_header_item_t)); + + mbuf_free(&p.offset_lineno_map); + + /* + * If parsing was successful, commit the bcode; otherwise drop generated + * bcode + */ + if(res == MJS_OK) { + mjs_bcode_commit(mjs); + } else { + mbuf_free(&mjs->bcode_gen); + } + + return res; +} diff --git a/lib/mjs/mjs_parser.h b/lib/mjs/mjs_parser.h new file mode 100644 index 00000000000..8f47099faf9 --- /dev/null +++ b/lib/mjs/mjs_parser.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PARSER_H +#define MJS_PARSER_H + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +MJS_PRIVATE mjs_err_t mjs_parse(const char* path, const char* buf, struct mjs*); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PARSER_H */ diff --git a/lib/mjs/mjs_primitive.c b/lib/mjs/mjs_primitive.c new file mode 100644 index 00000000000..b63a268e567 --- /dev/null +++ b/lib/mjs/mjs_primitive.c @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" + +mjs_val_t mjs_mk_null(void) { + return MJS_NULL; +} + +int mjs_is_null(mjs_val_t v) { + return v == MJS_NULL; +} + +mjs_val_t mjs_mk_undefined(void) { + return MJS_UNDEFINED; +} + +int mjs_is_undefined(mjs_val_t v) { + return v == MJS_UNDEFINED; +} + +mjs_val_t mjs_mk_number(struct mjs* mjs, double v) { + mjs_val_t res; + (void)mjs; + /* not every NaN is a JS NaN */ + if(isnan(v)) { + res = MJS_TAG_NAN; + } else { + union { + double d; + mjs_val_t r; + } u; + u.d = v; + res = u.r; + } + return res; +} + +static double get_double(mjs_val_t v) { + union { + double d; + mjs_val_t v; + } u; + u.v = v; + /* Due to NaN packing, any non-numeric value is already a valid NaN value */ + return u.d; +} + +double mjs_get_double(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + return get_double(v); +} + +int mjs_get_int(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + /* + * NOTE(dfrank): without double cast, all numbers >= 0x80000000 are always + * converted to exactly 0x80000000. + */ + return (int)(unsigned int)get_double(v); +} + +int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + return (int32_t)get_double(v); +} + +int mjs_is_number(mjs_val_t v) { + return v == MJS_TAG_NAN || !isnan(get_double(v)); +} + +mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v) { + (void)mjs; + return (v ? 1 : 0) | MJS_TAG_BOOLEAN; +} + +int mjs_get_bool(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + if(mjs_is_boolean(v)) { + return v & 1; + } else { + return 0; + } +} + +int mjs_is_boolean(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_BOOLEAN; +} + +#define MJS_IS_POINTER_LEGIT(n) \ + (((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK)) + +MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) { + uint64_t n = ((uint64_t)(uintptr_t)p); + + if(!MJS_IS_POINTER_LEGIT(n)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid pointer value: %p", p); + } + return n & ~MJS_TAG_MASK; +} + +MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p) { + uint64_t n = ((uint64_t)(uintptr_t)p); + + assert(MJS_IS_POINTER_LEGIT(n)); + return n & ~MJS_TAG_MASK; +} + +MJS_PRIVATE void* get_ptr(mjs_val_t v) { + return (void*)(uintptr_t)(v & 0xFFFFFFFFFFFFUL); +} + +void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v) { + (void)mjs; + if(!mjs_is_foreign(v)) { + return NULL; + } + return get_ptr(v); +} + +mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* p) { + (void)mjs; + return mjs_pointer_to_value(mjs, p) | MJS_TAG_FOREIGN; +} + +mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn) { + union { + mjs_func_ptr_t fn; + void* p; + } u; + u.fn = fn; + (void)mjs; + return mjs_pointer_to_value(mjs, u.p) | MJS_TAG_FOREIGN; +} + +int mjs_is_foreign(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FOREIGN; +} + +mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off) { + (void)mjs; + return (mjs_val_t)off | MJS_TAG_FUNCTION; +} + +int mjs_is_function(mjs_val_t v) { + return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION; +} + +MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t val = mjs_arg(mjs, 0); + + ret = mjs_mk_boolean(mjs, val == MJS_TAG_NAN); + + mjs_return(mjs, ret); +} diff --git a/lib/mjs/mjs_primitive.h b/lib/mjs/mjs_primitive.h new file mode 100644 index 00000000000..6d6f9178515 --- /dev/null +++ b/lib/mjs/mjs_primitive.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PRIMITIVE_H +#define MJS_PRIMITIVE_H + +#include "mjs_primitive_public.h" +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Convert a pointer to mjs_val_t. If pointer is not valid, mjs crashes. + */ +MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p); + +/* + * Convert a pointer to mjs_val_t. If pointer is not valid, error is set + * in the mjs context. + */ +MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p); + +/* + * Extracts a pointer from the mjs_val_t value. + */ +MJS_PRIVATE void* get_ptr(mjs_val_t v); + +/* + * Implementation for JS isNaN() + */ +MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PRIMITIVE_H */ diff --git a/lib/mjs/mjs_primitive_public.h b/lib/mjs/mjs_primitive_public.h new file mode 100644 index 00000000000..87075d9c9f6 --- /dev/null +++ b/lib/mjs/mjs_primitive_public.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_PRIMITIVE_PUBLIC_H_ +#define MJS_PRIMITIVE_PUBLIC_H_ + +#include "mjs_core_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* JavaScript `null` value */ +#define MJS_NULL MJS_TAG_NULL + +/* JavaScript `undefined` value */ +#define MJS_UNDEFINED MJS_TAG_UNDEFINED + +#define MJS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn) + +/* Function pointer type used in `mjs_mk_foreign_func`. */ +typedef void (*mjs_func_ptr_t)(void); + +/* + * Make `null` primitive value. + * + * NOTE: this function is deprecated and will be removed in future releases. + * Use `MJS_NULL` instead. + */ +mjs_val_t mjs_mk_null(void); + +/* Returns true if given value is a primitive `null` value */ +int mjs_is_null(mjs_val_t v); + +/* + * Make `undefined` primitive value. + * + * NOTE: this function is deprecated and will be removed in future releases. + * Use `MJS_UNDEFINED` instead. + */ +mjs_val_t mjs_mk_undefined(void); + +/* Returns true if given value is a primitive `undefined` value */ +int mjs_is_undefined(mjs_val_t v); + +/* Make numeric primitive value */ +mjs_val_t mjs_mk_number(struct mjs* mjs, double num); + +/* + * Returns number value stored in `mjs_val_t` as `double`. + * + * Returns NaN for non-numbers. + */ +double mjs_get_double(struct mjs* mjs, mjs_val_t v); + +/* + * Returns number value stored in `mjs_val_t` as `int`. If the number value is + * not an integer, the fraction part will be discarded. + * + * If the given value is a non-number, or NaN, the result is undefined. + */ +int mjs_get_int(struct mjs* mjs, mjs_val_t v); + +/* + * Like mjs_get_int but ensures that the returned type + * is a 32-bit signed integer. + */ +int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v); + +/* Returns true if given value is a primitive number value */ +int mjs_is_number(mjs_val_t v); + +/* + * Make JavaScript value that holds C/C++ `void *` pointer. + * + * A foreign value is completely opaque and JS code cannot do anything useful + * with it except holding it in properties and passing it around. + * It behaves like a sealed object with no properties. + * + * NOTE: + * Only valid pointers (as defined by each supported architecture) will fully + * preserved. In particular, all supported 64-bit architectures (x86_64, ARM-64) + * actually define a 48-bit virtual address space. + * Foreign values will be sign-extended as required, i.e creating a foreign + * value of something like `(void *) -1` will work as expected. This is + * important because in some 64-bit OSs (e.g. Solaris) the user stack grows + * downwards from the end of the address space. + * + * If you need to store exactly sizeof(void*) bytes of raw data where + * `sizeof(void*)` >= 8, please use byte arrays instead. + */ +mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* ptr); + +/* + * Make JavaScript value that holds C/C++ function pointer, similarly to + * `mjs_mk_foreign`. + */ +mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn); + +/* + * Returns `void *` pointer stored in `mjs_val_t`. + * + * Returns NULL if the value is not a foreign pointer. + */ +void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v); + +/* Returns true if given value holds `void *` pointer */ +int mjs_is_foreign(mjs_val_t v); + +mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v); +int mjs_get_bool(struct mjs* mjs, mjs_val_t v); +int mjs_is_boolean(mjs_val_t v); + +mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off); +int mjs_is_function(mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_PRIMITIVE_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_string.c b/lib/mjs/mjs_string.c new file mode 100644 index 00000000000..f74bf1074fb --- /dev/null +++ b/lib/mjs/mjs_string.c @@ -0,0 +1,601 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "mjs_string.h" +#include "common/cs_varint.h" +#include "common/mg_str.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_primitive.h" +#include "mjs_util.h" + +// No UTF +typedef unsigned short Rune; +static int chartorune(Rune* rune, const char* str) { + *rune = *(unsigned char*)str; + return 1; +} +static int runetochar(char* str, Rune* rune) { + str[0] = (char)*rune; + return 1; +} + +#ifndef MJS_STRING_BUF_RESERVE +#define MJS_STRING_BUF_RESERVE 100 +#endif + +MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to); + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags); + +/* TODO(lsm): NaN payload location depends on endianness, make crossplatform */ +#define GET_VAL_NAN_PAYLOAD(v) ((char*)&(v)) + +int mjs_is_string(mjs_val_t v) { + uint64_t t = v & MJS_TAG_MASK; + return t == MJS_TAG_STRING_I || t == MJS_TAG_STRING_F || t == MJS_TAG_STRING_O || + t == MJS_TAG_STRING_5 || t == MJS_TAG_STRING_D; +} + +mjs_val_t mjs_mk_string(struct mjs* mjs, const char* p, size_t len, int copy) { + struct mbuf* m; + mjs_val_t offset, tag = MJS_TAG_STRING_F; + if(len == 0) { + /* + * Zero length for foreign string has a special meaning (that the foreign + * string is not inlined into mjs_val_t), so when creating a zero-length + * string, we always assume it'll be owned. Since the length is zero, it + * doesn't matter anyway. + */ + copy = 1; + } + m = copy ? &mjs->owned_strings : &mjs->foreign_strings; + offset = m->len; + + if(len == ~((size_t)0)) len = strlen(p); + + if(copy) { + /* owned string */ + if(len <= 4) { + char* s = GET_VAL_NAN_PAYLOAD(offset) + 1; + offset = 0; + if(p != 0) { + memcpy(s, p, len); + } + s[-1] = len; + tag = MJS_TAG_STRING_I; + } else if(len == 5) { + char* s = GET_VAL_NAN_PAYLOAD(offset); + offset = 0; + if(p != 0) { + memcpy(s, p, len); + } + tag = MJS_TAG_STRING_5; + // } else if ((dict_index = v_find_string_in_dictionary(p, len)) >= 0) { + // offset = 0; + // GET_VAL_NAN_PAYLOAD(offset)[0] = dict_index; + // tag = MJS_TAG_STRING_D; + } else { + if(gc_strings_is_gc_needed(mjs)) { + mjs->need_gc = 1; + } + + /* + * Before embedding new string, check if the reallocation is needed. If + * so, perform the reallocation by calling `mbuf_resize` manually, since + * we need to preallocate some extra space (`MJS_STRING_BUF_RESERVE`) + */ + if((m->len + len) > m->size) { + char* prev_buf = m->buf; + mbuf_resize(m, m->len + len + MJS_STRING_BUF_RESERVE); + + /* + * There is a corner case: when the source pointer is located within + * the mbuf. In this case, we should adjust the pointer, because it + * might have just been reallocated. + */ + if(p >= prev_buf && p < (prev_buf + m->len)) { + p += (m->buf - prev_buf); + } + } + + embed_string(m, m->len, p, len, EMBSTR_ZERO_TERM); + tag = MJS_TAG_STRING_O; + } + } else { + /* foreign string */ + if(sizeof(void*) <= 4 && len <= (1 << 15)) { + /* small foreign strings can fit length and ptr in the mjs_val_t */ + offset = (uint64_t)len << 32 | (uint64_t)(uintptr_t)p; + } else { + /* bigger strings need indirection that uses ram */ + size_t pos = m->len; + size_t llen = cs_varint_llen(len); + + /* allocate space for len and ptr */ + mbuf_insert(m, pos, NULL, llen + sizeof(p)); + + cs_varint_encode(len, (uint8_t*)(m->buf + pos), llen); + memcpy(m->buf + pos + llen, &p, sizeof(p)); + } + tag = MJS_TAG_STRING_F; + } + + /* NOTE(lsm): don't use pointer_to_value, 32-bit ptrs will truncate */ + return (offset & ~MJS_TAG_MASK) | tag; +} + +/* Get a pointer to string and string length. */ +const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* sizep) { + uint64_t tag = v[0] & MJS_TAG_MASK; + const char* p = NULL; + size_t size = 0, llen; + + if(!mjs_is_string(*v)) { + goto clean; + } + + if(tag == MJS_TAG_STRING_I) { + p = GET_VAL_NAN_PAYLOAD(*v) + 1; + size = p[-1]; + } else if(tag == MJS_TAG_STRING_5) { + p = GET_VAL_NAN_PAYLOAD(*v); + size = 5; + // } else if (tag == MJS_TAG_STRING_D) { + // int index = ((unsigned char *) GET_VAL_NAN_PAYLOAD(*v))[0]; + // size = v_dictionary_strings[index].len; + // p = v_dictionary_strings[index].p; + } else if(tag == MJS_TAG_STRING_O) { + size_t offset = (size_t)gc_string_mjs_val_to_offset(*v); + char* s = mjs->owned_strings.buf + offset; + uint64_t v = 0; + if(offset < mjs->owned_strings.len && + cs_varint_decode((uint8_t*)s, mjs->owned_strings.len - offset, &v, &llen)) { + size = v; + p = s + llen; + } else { + goto clean; + } + } else if(tag == MJS_TAG_STRING_F) { + /* + * short foreign strings on <=32-bit machines can be encoded in a compact + * form: + * + * 7 6 5 4 3 2 1 0 + * 11111111|1111tttt|llllllll|llllllll|ssssssss|ssssssss|ssssssss|ssssssss + * + * Strings longer than 2^26 will be indireceted through the foreign_strings + * mbuf. + * + * We don't use a different tag to represent those two cases. Instead, all + * foreign strings represented with the help of the foreign_strings mbuf + * will have the upper 16-bits of the payload set to zero. This allows us to + * represent up to 477 million foreign strings longer than 64k. + */ + uint16_t len = (*v >> 32) & 0xFFFF; + if(sizeof(void*) <= 4 && len != 0) { + size = (size_t)len; + p = (const char*)(uintptr_t)*v; + } else { + size_t offset = (size_t)gc_string_mjs_val_to_offset(*v); + char* s = mjs->foreign_strings.buf + offset; + uint64_t v = 0; + if(offset < mjs->foreign_strings.len && + cs_varint_decode((uint8_t*)s, mjs->foreign_strings.len - offset, &v, &llen)) { + size = v; + memcpy((char**)&p, s + llen, sizeof(p)); + } else { + goto clean; + } + } + } else { + assert(0); + } + +clean: + if(sizep != NULL) { + *sizep = size; + } + return p; +} + +const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* value) { + size_t size; + const char* s = mjs_get_string(mjs, value, &size); + if(s == NULL) return NULL; + if(s[size] != 0 || strlen(s) != size) { + return NULL; + } + return s; +} + +int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len) { + size_t n; + const char* s; + if(len == (size_t)~0) len = strlen(b); + s = mjs_get_string(mjs, a, &n); + if(n != len) { + return n - len; + } + return strncmp(s, b, len); +} + +MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok) { + char* e; + unsigned long res = strtoul(s, &e, 10); + *ok = (e == s + len) && len != 0; + return res; +} + +MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res) { + enum mjs_err ret = MJS_OK; + size_t len = 0; + const char* p = mjs_get_string(mjs, &v, &len); + *res = cstr_to_ulong(p, len, ok); + + return ret; +} + +MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + size_t a_len, b_len; + const char *a_ptr, *b_ptr; + + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + if(a_len == b_len) { + return memcmp(a_ptr, b_ptr, a_len); + } + if(a_len > b_len) { + return 1; + } else if(a_len < b_len) { + return -1; + } else { + return 0; + } +} + +MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) { + size_t a_len, b_len, res_len; + const char *a_ptr, *b_ptr, *res_ptr; + mjs_val_t res; + + /* Find out lengths of both srtings */ + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + /* Create a placeholder string */ + res = mjs_mk_string(mjs, NULL, a_len + b_len, 1); + + /* mjs_mk_string() may have reallocated mbuf - revalidate pointers */ + a_ptr = mjs_get_string(mjs, &a, &a_len); + b_ptr = mjs_get_string(mjs, &b, &b_len); + + /* Copy strings into the placeholder */ + res_ptr = mjs_get_string(mjs, &res, &res_len); + memcpy((char*)res_ptr, a_ptr, a_len); + memcpy((char*)res_ptr + a_len, b_ptr, b_len); + + return res; +} + +MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_val_t ret = mjs_mk_number(mjs, 0); + mjs_val_t beginSlice_v = MJS_UNDEFINED; + mjs_val_t endSlice_v = MJS_UNDEFINED; + int beginSlice = 0; + int endSlice = 0; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "beginSlice", MJS_TYPE_NUMBER, &beginSlice_v)) { + goto clean; + } + beginSlice = mjs_normalize_idx(mjs_get_int(mjs, beginSlice_v), size); + + if(nargs >= 2) { + /* endSlice is given; use it */ + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 1, "endSlice", MJS_TYPE_NUMBER, &endSlice_v)) { + goto clean; + } + endSlice = mjs_normalize_idx(mjs_get_int(mjs, endSlice_v), size); + } else { + /* endSlice is not given; assume the end of the string */ + endSlice = size; + } + + if(endSlice < beginSlice) { + endSlice = beginSlice; + } + + ret = mjs_mk_string(mjs, s + beginSlice, endSlice - beginSlice, 1); + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs) { + mjs_val_t ret = mjs_mk_number(mjs, -1); + mjs_val_t substr_v = MJS_UNDEFINED; + mjs_val_t idx_v = MJS_UNDEFINED; + int idx = 0; + const char *str = NULL, *substr = NULL; + size_t str_len = 0, substr_len = 0; + + if(!mjs_check_arg(mjs, -1 /* this */, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + str = mjs_get_string(mjs, &mjs->vals.this_obj, &str_len); + + if(!mjs_check_arg(mjs, 0, "searchValue", MJS_TYPE_STRING, &substr_v)) { + goto clean; + } + substr = mjs_get_string(mjs, &substr_v, &substr_len); + if(mjs_nargs(mjs) > 1) { + if(!mjs_check_arg(mjs, 1, "fromIndex", MJS_TYPE_NUMBER, &idx_v)) { + goto clean; + } + idx = mjs_get_int(mjs, idx_v); + if(idx < 0) idx = 0; + if((size_t)idx > str_len) idx = str_len; + } + { + const char* substr_p; + struct mg_str mgstr, mgsubstr; + mgstr.p = str + idx; + mgstr.len = str_len - idx; + mgsubstr.p = substr; + mgsubstr.len = substr_len; + substr_p = mg_strstr(mgstr, mgsubstr); + if(substr_p != NULL) { + ret = mjs_mk_number(mjs, (int)(substr_p - str)); + } + } + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs) { + mjs_val_t ret = MJS_UNDEFINED; + mjs_val_t idx_v = MJS_UNDEFINED; + int idx = 0; + size_t size; + const char* s = NULL; + + /* get string from `this` */ + if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) { + goto clean; + } + s = mjs_get_string(mjs, &mjs->vals.this_obj, &size); + + /* get idx from arg 0 */ + if(!mjs_check_arg(mjs, 0, "index", MJS_TYPE_NUMBER, &idx_v)) { + goto clean; + } + idx = mjs_normalize_idx(mjs_get_int(mjs, idx_v), size); + if(idx >= 0 && idx < (int)size) { + ret = mjs_mk_number(mjs, ((unsigned char*)s)[idx]); + } + +clean: + mjs_return(mjs, ret); +} + +MJS_PRIVATE void mjs_mkstr(struct mjs* mjs) { + int nargs = mjs_nargs(mjs); + mjs_val_t ret = MJS_UNDEFINED; + + char* ptr = NULL; + int offset = 0; + int len = 0; + int copy = 0; + + mjs_val_t ptr_v = MJS_UNDEFINED; + mjs_val_t offset_v = MJS_UNDEFINED; + mjs_val_t len_v = MJS_UNDEFINED; + mjs_val_t copy_v = MJS_UNDEFINED; + + if(nargs == 2) { + ptr_v = mjs_arg(mjs, 0); + len_v = mjs_arg(mjs, 1); + } else if(nargs == 3) { + ptr_v = mjs_arg(mjs, 0); + offset_v = mjs_arg(mjs, 1); + len_v = mjs_arg(mjs, 2); + } else if(nargs == 4) { + ptr_v = mjs_arg(mjs, 0); + offset_v = mjs_arg(mjs, 1); + len_v = mjs_arg(mjs, 2); + copy_v = mjs_arg(mjs, 3); + } else { + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "mkstr takes 2, 3 or 4 arguments: (ptr, len), (ptr, " + "offset, len) or (ptr, offset, len, copy)"); + goto clean; + } + + if(!mjs_is_foreign(ptr_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "ptr should be a foreign pointer"); + goto clean; + } + + if(offset_v != MJS_UNDEFINED && !mjs_is_number(offset_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "offset should be a number"); + goto clean; + } + + if(!mjs_is_number(len_v)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "len should be a number"); + goto clean; + } + + copy = mjs_is_truthy(mjs, copy_v); + + /* all arguments are fine */ + + ptr = (char*)mjs_get_ptr(mjs, ptr_v); + if(offset_v != MJS_UNDEFINED) { + offset = mjs_get_int(mjs, offset_v); + } + len = mjs_get_int(mjs, len_v); + + ret = mjs_mk_string(mjs, ptr + offset, len, copy); + +clean: + mjs_return(mjs, ret); +} + +enum unescape_error { + SLRE_INVALID_HEX_DIGIT, + SLRE_INVALID_ESC_CHAR, + SLRE_UNTERM_ESC_SEQ, +}; + +static int hex(int c) { + if(c >= '0' && c <= '9') return c - '0'; + if(c >= 'a' && c <= 'f') return c - 'a' + 10; + if(c >= 'A' && c <= 'F') return c - 'A' + 10; + return -SLRE_INVALID_HEX_DIGIT; +} + +static int nextesc(const char** p) { + const unsigned char* s = (unsigned char*)(*p)++; + switch(*s) { + case 0: + return -SLRE_UNTERM_ESC_SEQ; + case 'c': + ++*p; + return *s & 31; + case 'b': + return '\b'; + case 't': + return '\t'; + case 'n': + return '\n'; + case 'v': + return '\v'; + case 'f': + return '\f'; + case 'r': + return '\r'; + case '\\': + return '\\'; + case 'u': + if(isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4])) { + (*p) += 4; + return hex(s[1]) << 12 | hex(s[2]) << 8 | hex(s[3]) << 4 | hex(s[4]); + } + return -SLRE_INVALID_HEX_DIGIT; + case 'x': + if(isxdigit(s[1]) && isxdigit(s[2])) { + (*p) += 2; + return (hex(s[1]) << 4) | hex(s[2]); + } + return -SLRE_INVALID_HEX_DIGIT; + default: + return -SLRE_INVALID_ESC_CHAR; + } +} + +MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to) { + const char* end = s + len; + size_t n = 0; + char tmp[4]; + Rune r; + + while(s < end) { + s += chartorune(&r, s); + if(r == '\\' && s < end) { + switch(*s) { + case '"': + s++, r = '"'; + break; + case '\'': + s++, r = '\''; + break; + case '\n': + s++, r = '\n'; + break; + default: { + const char* tmp_s = s; + int i = nextesc(&s); + switch(i) { + case -SLRE_INVALID_ESC_CHAR: + r = '\\'; + s = tmp_s; + n += runetochar(to == NULL ? tmp : to + n, &r); + s += chartorune(&r, s); + break; + case -SLRE_INVALID_HEX_DIGIT: + default: + r = i; + } + } + } + } + n += runetochar(to == NULL ? tmp : to + n, &r); + } + + return n; +} + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags) { + char* old_base = m->buf; + uint8_t p_backed_by_mbuf = p >= old_base && p < old_base + m->len; + size_t n = (flags & EMBSTR_UNESCAPE) ? unescape(p, len, NULL) : len; + + /* Calculate how many bytes length takes */ + size_t k = cs_varint_llen(n); + + /* total length: varing length + string len + zero-term */ + size_t tot_len = k + n + !!(flags & EMBSTR_ZERO_TERM); + + /* Allocate buffer */ + mbuf_insert(m, offset, NULL, tot_len); + + /* Fixup p if it was relocated by mbuf_insert() above */ + if(p_backed_by_mbuf) { + p += m->buf - old_base; + } + + /* Write length */ + cs_varint_encode(n, (unsigned char*)m->buf + offset, k); + + /* Write string */ + if(p != 0) { + if(flags & EMBSTR_UNESCAPE) { + unescape(p, len, m->buf + offset + k); + } else { + memcpy(m->buf + offset + k, p, len); + } + } + + /* add NULL-terminator if needed */ + if(flags & EMBSTR_ZERO_TERM) { + m->buf[offset + tot_len - 1] = '\0'; + } +} diff --git a/lib/mjs/mjs_string.h b/lib/mjs/mjs_string.h new file mode 100644 index 00000000000..ba6869b6247 --- /dev/null +++ b/lib/mjs/mjs_string.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_STRING_H_ +#define MJS_STRING_H_ + +#include "mjs_internal.h" +#include "mjs_string_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Size of the extra space for strings mbuf that is needed to avoid frequent + * reallocations + */ +#define _MJS_STRING_BUF_RESERVE 100 + +MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok); +MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res); +MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b); +MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b); + +MJS_PRIVATE void embed_string( + struct mbuf* m, + size_t offset, + const char* p, + size_t len, + uint8_t /*enum embstr_flags*/ flags); + +MJS_PRIVATE void mjs_mkstr(struct mjs* mjs); + +MJS_PRIVATE void mjs_string_slice(struct mjs* mjs); +MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs); +MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs); + +#define EMBSTR_ZERO_TERM 1 +#define EMBSTR_UNESCAPE 2 + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_STRING_H_ */ diff --git a/lib/mjs/mjs_string_public.h b/lib/mjs/mjs_string_public.h new file mode 100644 index 00000000000..8d73aaf7f67 --- /dev/null +++ b/lib/mjs/mjs_string_public.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_STRING_PUBLIC_H_ +#define MJS_STRING_PUBLIC_H_ + +#include "mjs_core_public.h" + +#define MJS_STRING_LITERAL_MAX_LEN 128 + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +/* + * Creates a string primitive value. + * `str` must point to the utf8 string of length `len`. + * If `len` is ~0, `str` is assumed to be NUL-terminated and `strlen(str)` is + * used. + * + * If `copy` is non-zero, the string data is copied and owned by the GC. The + * caller can free the string data afterwards. Otherwise (`copy` is zero), the + * caller owns the string data, and is responsible for not freeing it while it + * is used. + */ +mjs_val_t mjs_mk_string(struct mjs* mjs, const char* str, size_t len, int copy); + +/* Returns true if given value is a primitive string value */ +int mjs_is_string(mjs_val_t v); + +/* + * Returns a pointer to the string stored in `mjs_val_t`. + * + * String length returned in `len`, which is allowed to be NULL. Returns NULL + * if the value is not a string. + * + * JS strings can contain embedded NUL chars and may or may not be NUL + * terminated. + * + * CAUTION: creating new JavaScript object, array, or string may kick in a + * garbage collector, which in turn may relocate string data and invalidate + * pointer returned by `mjs_get_string()`. + * + * Short JS strings are embedded inside the `mjs_val_t` value itself. This + * is why a pointer to a `mjs_val_t` is required. It also means that the string + * data will become invalid once that `mjs_val_t` value goes out of scope. + */ +const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* len); + +/* + * Returns a pointer to the string stored in `mjs_val_t`. + * + * Returns NULL if the value is not a string or if the string is not compatible + * with a C string. + * + * C compatible strings contain exactly one NUL char, in terminal position. + * + * All strings owned by the MJS engine (see `mjs_mk_string()`) are guaranteed to + * be NUL terminated. Out of these, those that don't include embedded NUL chars + * are guaranteed to be C compatible. + */ +const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* v); + +/* + * Returns the standard strcmp comparison code after comparing a JS string a + * with a possibly non null-terminated string b. NOTE: the strings are equal + * only if their length is equal, i.e. the len field doesn't imply strncmp + * behaviour. + */ +int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_STRING_PUBLIC_H_ */ diff --git a/lib/mjs/mjs_tok.c b/lib/mjs/mjs_tok.c new file mode 100644 index 00000000000..bdff5a86a23 --- /dev/null +++ b/lib/mjs/mjs_tok.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include +#include + +#include "common/cs_dbg.h" +#include "mjs_tok.h" + +MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate* p) { + memset(p, 0, sizeof(*p)); + p->line_no = 1; + p->last_emitted_line_no = 1; + p->file_name = file_name; + p->buf = p->pos = buf; + mbuf_init(&p->offset_lineno_map, 0); +} + +// We're not relying on the target libc ctype, as it may incorrectly +// handle negative arguments, e.g. isspace(-1). +static int mjs_is_space(int c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\v'; +} + +MJS_PRIVATE int mjs_is_digit(int c) { + return c >= '0' && c <= '9'; +} + +static int mjs_is_alpha(int c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +MJS_PRIVATE int mjs_is_ident(int c) { + return c == '_' || c == '$' || mjs_is_alpha(c); +} + +// Try to parse a token that can take one or two chars. +static int longtok(struct pstate* p, const char* first_chars, const char* second_chars) { + if(strchr(first_chars, p->pos[0]) == NULL) return TOK_EOF; + if(p->pos[1] != '\0' && strchr(second_chars, p->pos[1]) != NULL) { + p->tok.len++; + p->pos++; + return p->pos[-1] << 8 | p->pos[0]; + } + return p->pos[0]; +} + +// Try to parse a token that takes exactly 3 chars. +static int longtok3(struct pstate* p, char a, char b, char c) { + if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c) { + p->tok.len += 2; + p->pos += 2; + return p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0]; + } + return TOK_EOF; +} + +// Try to parse a token that takes exactly 4 chars. +static int longtok4(struct pstate* p, char a, char b, char c, char d) { + if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c && p->pos[3] == d) { + p->tok.len += 3; + p->pos += 3; + return p->pos[-3] << 24 | p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0]; + } + return TOK_EOF; +} + +static int getnum(struct pstate* p) { + if(p->pos[0] == '0' && p->pos[1] == 'x') { + // MSVC6 strtod cannot parse 0x... numbers, thus this ugly workaround. + strtoul(p->pos + 2, (char**)&p->pos, 16); + } else { + strtod(p->pos, (char**)&p->pos); + } + p->tok.len = p->pos - p->tok.ptr; + p->pos--; + return TOK_NUM; +} + +static int is_reserved_word_token(const char* s, int len) { + const char* reserved[] = {"break", "case", "catch", "continue", "debugger", "default", + "delete", "do", "else", "false", "finally", "for", + "function", "if", "in", "instanceof", "new", "null", + "return", "switch", "this", "throw", "true", "try", + "typeof", "var", "void", "while", "with", "let", + "undefined", NULL}; + int i; + if(!mjs_is_alpha(s[0])) return 0; + for(i = 0; reserved[i] != NULL; i++) { + if(len == (int)strlen(reserved[i]) && strncmp(s, reserved[i], len) == 0) return i + 1; + } + return 0; +} + +static int getident(struct pstate* p) { + while(mjs_is_ident(p->pos[0]) || mjs_is_digit(p->pos[0])) p->pos++; + p->tok.len = p->pos - p->tok.ptr; + p->pos--; + return TOK_IDENT; +} + +static int getstr(struct pstate* p) { + int quote = *p->pos++; + p->tok.ptr++; + while(p->pos[0] != '\0' && p->pos[0] != quote) { + if(p->pos[0] == '\\' && p->pos[1] != '\0' && + (p->pos[1] == quote || strchr("bfnrtv\\", p->pos[1]) != NULL)) { + p->pos += 2; + } else { + p->pos++; + } + } + p->tok.len = p->pos - p->tok.ptr; + return TOK_STR; +} + +static void skip_spaces_and_comments(struct pstate* p) { + const char* pos; + do { + pos = p->pos; + while(mjs_is_space(p->pos[0])) { + if(p->pos[0] == '\n') p->line_no++; + p->pos++; + } + if(p->pos[0] == '/' && p->pos[1] == '/') { + while(p->pos[0] != '\0' && p->pos[0] != '\n') p->pos++; + } + if(p->pos[0] == '/' && p->pos[1] == '*') { + p->pos += 2; + while(p->pos[0] != '\0') { + if(p->pos[0] == '\n') p->line_no++; + if(p->pos[0] == '*' && p->pos[1] == '/') { + p->pos += 2; + break; + } + p->pos++; + } + } + } while(pos < p->pos); +} + +static int ptranslate(int tok) { +#define DT(a, b) ((a) << 8 | (b)) +#define TT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d)) + /* Map token ID produced by mjs_tok.c to token ID produced by lemon */ + /* clang-format off */ + switch (tok) { + case ':': return TOK_COLON; + case ';': return TOK_SEMICOLON; + case ',': return TOK_COMMA; + case '=': return TOK_ASSIGN; + case '{': return TOK_OPEN_CURLY; + case '}': return TOK_CLOSE_CURLY; + case '(': return TOK_OPEN_PAREN; + case ')': return TOK_CLOSE_PAREN; + case '[': return TOK_OPEN_BRACKET; + case ']': return TOK_CLOSE_BRACKET; + case '*': return TOK_MUL; + case '+': return TOK_PLUS; + case '-': return TOK_MINUS; + case '/': return TOK_DIV; + case '%': return TOK_REM; + case '&': return TOK_AND; + case '|': return TOK_OR; + case '^': return TOK_XOR; + case '.': return TOK_DOT; + case '?': return TOK_QUESTION; + case '!': return TOK_NOT; + case '~': return TOK_TILDA; + case '<': return TOK_LT; + case '>': return TOK_GT; + case DT('<','<'): return TOK_LSHIFT; + case DT('>','>'): return TOK_RSHIFT; + case DT('-','-'): return TOK_MINUS_MINUS; + case DT('+','+'): return TOK_PLUS_PLUS; + case DT('+','='): return TOK_PLUS_ASSIGN; + case DT('-','='): return TOK_MINUS_ASSIGN; + case DT('*','='): return TOK_MUL_ASSIGN; + case DT('/','='): return TOK_DIV_ASSIGN; + case DT('&','='): return TOK_AND_ASSIGN; + case DT('|','='): return TOK_OR_ASSIGN; + case DT('%','='): return TOK_REM_ASSIGN; + case DT('^','='): return TOK_XOR_ASSIGN; + case DT('=','='): return TOK_EQ; + case DT('!','='): return TOK_NE; + case DT('<','='): return TOK_LE; + case DT('>','='): return TOK_GE; + case DT('&','&'): return TOK_LOGICAL_AND; + case DT('|','|'): return TOK_LOGICAL_OR; + case TT('=','=','='): return TOK_EQ_EQ; + case TT('!','=','='): return TOK_NE_NE; + case TT('<','<','='): return TOK_LSHIFT_ASSIGN; + case TT('>','>','='): return TOK_RSHIFT_ASSIGN; + case TT('>','>','>'): return TOK_URSHIFT; + case QT('>','>','>','='): return TOK_URSHIFT_ASSIGN; + } + /* clang-format on */ + return tok; +} + +MJS_PRIVATE int pnext(struct pstate* p) { + int tmp, tok = TOK_INVALID; + + skip_spaces_and_comments(p); + p->tok.ptr = p->pos; + p->tok.len = 1; + + if(p->pos[0] == '\0') { + tok = TOK_EOF; + } else if(mjs_is_digit(p->pos[0])) { + tok = getnum(p); + } else if(p->pos[0] == '\'' || p->pos[0] == '"') { + tok = getstr(p); + } else if(mjs_is_ident(p->pos[0])) { + tok = getident(p); + /* + * NOTE: getident() has side effects on `p`, and `is_reserved_word_token()` + * relies on them. Since in C the order of evaluation of the operands is + * undefined, `is_reserved_word_token()` should be called in a separate + * statement. + */ + tok += is_reserved_word_token(p->tok.ptr, p->tok.len); + } else if(strchr(",.:;{}[]()?", p->pos[0]) != NULL) { + tok = p->pos[0]; + } else if( + (tmp = longtok3(p, '<', '<', '=')) != TOK_EOF || + (tmp = longtok3(p, '>', '>', '=')) != TOK_EOF || + (tmp = longtok4(p, '>', '>', '>', '=')) != TOK_EOF || + (tmp = longtok3(p, '>', '>', '>')) != TOK_EOF || + (tmp = longtok3(p, '=', '=', '=')) != TOK_EOF || + (tmp = longtok3(p, '!', '=', '=')) != TOK_EOF || + (tmp = longtok(p, "&", "&=")) != TOK_EOF || (tmp = longtok(p, "|", "|=")) != TOK_EOF || + (tmp = longtok(p, "<", "<=")) != TOK_EOF || (tmp = longtok(p, ">", ">=")) != TOK_EOF || + (tmp = longtok(p, "-", "-=")) != TOK_EOF || (tmp = longtok(p, "+", "+=")) != TOK_EOF) { + tok = tmp; + } else if((tmp = longtok(p, "^~+-%/*<>=!|&", "=")) != TOK_EOF) { + tok = tmp; + } + if(p->pos[0] != '\0') p->pos++; + LOG(LL_VERBOSE_DEBUG, (" --> %d [%.*s]", tok, p->tok.len, p->tok.ptr)); + p->prev_tok = p->tok.tok; + p->tok.tok = ptranslate(tok); + return p->tok.tok; +} diff --git a/lib/mjs/mjs_tok.h b/lib/mjs/mjs_tok.h new file mode 100644 index 00000000000..03d8fe6fa49 --- /dev/null +++ b/lib/mjs/mjs_tok.h @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_TOK_H_ +#define MJS_TOK_H_ + +#include "mjs_internal.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct tok { + int tok; + int len; + const char* ptr; +}; + +struct pstate { + const char* file_name; /* Source code file name */ + const char* buf; /* Nul-terminated source code buffer */ + const char* pos; /* Current position */ + int line_no; /* Line number */ + int last_emitted_line_no; + struct mbuf offset_lineno_map; + int prev_tok; /* Previous token, for prefix increment / decrement */ + struct tok tok; /* Parsed token */ + struct mjs* mjs; + int start_bcode_idx; /* Index in mjs->bcode at which parsing was started */ + int cur_idx; /* Index in mjs->bcode at which newly generated code is inserted + */ + int depth; +}; + +enum { + TOK_EOF, + TOK_INVALID, + + TOK_COLON, + TOK_SEMICOLON, + TOK_COMMA, + TOK_ASSIGN, + TOK_OPEN_CURLY, + TOK_CLOSE_CURLY, + TOK_OPEN_PAREN, + TOK_CLOSE_PAREN, + TOK_OPEN_BRACKET, + TOK_CLOSE_BRACKET, + TOK_MUL, + TOK_PLUS, + TOK_MINUS, + TOK_DIV, + TOK_REM, + TOK_AND, + TOK_OR, + TOK_XOR, + TOK_DOT, + TOK_QUESTION, + TOK_NOT, + TOK_TILDA, + TOK_LT, + TOK_GT, + TOK_LSHIFT, + TOK_RSHIFT, + TOK_MINUS_MINUS, + TOK_PLUS_PLUS, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_AND_ASSIGN, + TOK_OR_ASSIGN, + TOK_REM_ASSIGN, + TOK_XOR_ASSIGN, + TOK_EQ, + TOK_NE, + TOK_LE, + TOK_GE, + TOK_LOGICAL_AND, + TOK_LOGICAL_OR, + TOK_EQ_EQ, + TOK_NE_NE, + TOK_LSHIFT_ASSIGN, + TOK_RSHIFT_ASSIGN, + TOK_URSHIFT, + TOK_URSHIFT_ASSIGN, + + TOK_UNARY_PLUS, + TOK_UNARY_MINUS, + TOK_POSTFIX_PLUS, + TOK_POSTFIX_MINUS, + + TOK_NUM = 200, /* Make sure they don't clash with ascii '+', '{', etc */ + TOK_STR, + TOK_IDENT, + TOK_KEYWORD_BREAK, + TOK_KEYWORD_CASE, + TOK_KEYWORD_CATCH, + TOK_KEYWORD_CONTINUE, + TOK_KEYWORD_DEBUGGER, + TOK_KEYWORD_DEFAULT, + TOK_KEYWORD_DELETE, + TOK_KEYWORD_DO, + TOK_KEYWORD_ELSE, + TOK_KEYWORD_FALSE, + TOK_KEYWORD_FINALLY, + TOK_KEYWORD_FOR, + TOK_KEYWORD_FUNCTION, + TOK_KEYWORD_IF, + TOK_KEYWORD_IN, + TOK_KEYWORD_INSTANCEOF, + TOK_KEYWORD_NEW, + TOK_KEYWORD_NULL, + TOK_KEYWORD_RETURN, + TOK_KEYWORD_SWITCH, + TOK_KEYWORD_THIS, + TOK_KEYWORD_THROW, + TOK_KEYWORD_TRUE, + TOK_KEYWORD_TRY, + TOK_KEYWORD_TYPEOF, + TOK_KEYWORD_VAR, + TOK_KEYWORD_VOID, + TOK_KEYWORD_WHILE, + TOK_KEYWORD_WITH, + TOK_KEYWORD_LET, + TOK_KEYWORD_UNDEFINED, + TOK_MAX +}; + +MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate*); +MJS_PRIVATE int pnext(struct pstate*); +MJS_PRIVATE int mjs_is_ident(int c); +MJS_PRIVATE int mjs_is_digit(int c); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_TOK_H_ */ diff --git a/lib/mjs/mjs_util.c b/lib/mjs/mjs_util.c new file mode 100644 index 00000000000..a31dba6bb5c --- /dev/null +++ b/lib/mjs/mjs_util.c @@ -0,0 +1,567 @@ +/* + * Copyright (c) 2017 Cesanta Software Limited + * All rights reserved + */ + +#include "common/cs_varint.h" +#include "common/frozen/frozen.h" +#include "mjs_array.h" +#include "mjs_bcode.h" +#include "mjs_core.h" +#include "mjs_internal.h" +#include "mjs_object.h" +#include "mjs_primitive.h" +#include "mjs_string.h" +#include "mjs_util.h" +#include "mjs_tok.h" +#include "mjs_array_buf.h" +#include + +const char* mjs_typeof(mjs_val_t v) { + return mjs_stringify_type(mjs_get_type(v)); +} + +MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t) { + switch(t) { + case MJS_TYPE_NUMBER: + return "number"; + case MJS_TYPE_BOOLEAN: + return "boolean"; + case MJS_TYPE_STRING: + return "string"; + case MJS_TYPE_OBJECT_ARRAY: + return "array"; + case MJS_TYPE_OBJECT_GENERIC: + return "object"; + case MJS_TYPE_FOREIGN: + return "foreign_ptr"; + case MJS_TYPE_OBJECT_FUNCTION: + return "function"; + case MJS_TYPE_NULL: + return "null"; + case MJS_TYPE_UNDEFINED: + return "undefined"; + case MJS_TYPE_ARRAY_BUF: + return "array_buf"; + case MJS_TYPE_ARRAY_BUF_VIEW: + return "data_view"; + default: + return "???"; + } +} + +void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out) { + if(mjs_is_number(v)) { + double iv, d = mjs_get_double(mjs, v); + if(modf(d, &iv) == 0) { + json_printf(out, "%" INT64_FMT, (int64_t)d); + } else { + json_printf(out, "%f", mjs_get_double(mjs, v)); + } + } else if(mjs_is_boolean(v)) { + json_printf(out, "%s", mjs_get_bool(mjs, v) ? "true" : "false"); + } else if(mjs_is_string(v)) { + size_t i, size; + const char* s = mjs_get_string(mjs, &v, &size); + for(i = 0; i < size; i++) { + int ch = ((unsigned char*)s)[i]; + if(isprint(ch)) { + json_printf(out, "%c", ch); + } else { + json_printf(out, "%s%02x", "\\x", ch); + } + } + } else if(mjs_is_array(v)) { + json_printf(out, "%s", ""); + } else if(mjs_is_object(v)) { + json_printf(out, "%s", ""); + } else if(mjs_is_foreign(v)) { + json_printf( + out, "%s%lx%s", ""); + } else if(mjs_is_function(v)) { + json_printf(out, "%s%d%s", ""); + } else if(mjs_is_null(v)) { + json_printf(out, "%s", "null"); + } else if(mjs_is_undefined(v)) { + json_printf(out, "%s", "undefined"); + } else { + json_printf(out, "%s%" INT64_FMT "%s", ""); + } +} + +void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t n) { + struct json_out out = JSON_OUT_BUF(buf, n); + mjs_jprintf(v, mjs, &out); +} + +void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp) { + struct json_out out = JSON_OUT_FILE(fp); + mjs_jprintf(v, mjs, &out); +} + +MJS_PRIVATE const char* opcodetostr(uint8_t opcode) { + static const char* names[] = { + "NOP", + "DROP", + "DUP", + "SWAP", + "JMP", + "JMP_TRUE", + "JMP_NEUTRAL_TRUE", + "JMP_FALSE", + "JMP_NEUTRAL_FALSE", + "FIND_SCOPE", + "PUSH_SCOPE", + "PUSH_STR", + "PUSH_TRUE", + "PUSH_FALSE", + "PUSH_INT", + "PUSH_DBL", + "PUSH_NULL", + "PUSH_UNDEF", + "PUSH_OBJ", + "PUSH_ARRAY", + "PUSH_FUNC", + "PUSH_THIS", + "GET", + "CREATE", + "EXPR", + "APPEND", + "SET_ARG", + "NEW_SCOPE", + "DEL_SCOPE", + "CALL", + "RETURN", + "LOOP", + "BREAK", + "CONTINUE", + "SETRETVAL", + "EXIT", + "BCODE_HDR", + "ARGS", + "FOR_IN_NEXT", + }; + const char* name = "???"; + assert(ARRAY_SIZE(names) == OP_MAX); + if(opcode < ARRAY_SIZE(names)) name = names[opcode]; + return name; +} + +MJS_PRIVATE size_t + mjs_disasm_single(const uint8_t* code, size_t i, MjsPrintCallback print_cb, void* print_ctx) { + char buf[40]; + size_t start_i = i; + size_t llen; + uint64_t n; + + furi_assert(print_cb); + + snprintf(buf, sizeof(buf), "%-3u\t%-8s", (unsigned)i, opcodetostr(code[i])); + + switch(code[i]) { + case OP_PUSH_FUNC: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + print_cb(print_ctx, "%s %04u", buf, (unsigned)(i - n)); + i += llen; + break; + } + case OP_PUSH_INT: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + print_cb(print_ctx, "%s\t%lu", buf, (unsigned long)n); + i += llen; + break; + } + case OP_SET_ARG: { + size_t llen2; + uint64_t arg_no; + cs_varint_decode(&code[i + 1], ~0, &arg_no, &llen); + cs_varint_decode(&code[i + llen + 1], ~0, &n, &llen2); + print_cb( + print_ctx, "%s\t[%.*s] %u", buf, (int)n, code + i + 1 + llen + llen2, (unsigned)arg_no); + i += llen + llen2 + n; + break; + } + case OP_PUSH_STR: + case OP_PUSH_DBL: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + print_cb(print_ctx, "%s\t[%.*s]", buf, (int)n, code + i + 1 + llen); + i += llen + n; + break; + } + case OP_JMP: + case OP_JMP_TRUE: + case OP_JMP_NEUTRAL_TRUE: + case OP_JMP_FALSE: + case OP_JMP_NEUTRAL_FALSE: { + cs_varint_decode(&code[i + 1], ~0, &n, &llen); + print_cb( + print_ctx, + "%s\t%u", + buf, + (unsigned)(i + n + llen + 1 /* becaue i will be incremented on the usual terms */)); + i += llen; + break; + } + case OP_LOOP: { + size_t l1, l2; + uint64_t n1, n2; + cs_varint_decode(&code[i + 1], ~0, &n1, &l1); + cs_varint_decode(&code[i + l1 + 1], ~0, &n2, &l2); + print_cb( + print_ctx, + "%s\tB:%lu C:%lu (%d)", + buf, + (unsigned long)(i + 1 /* OP_LOOP */ + l1 + n1), + (unsigned long)(i + 1 /* OP_LOOP */ + l1 + l2 + n2), + (int)i); + i += l1 + l2; + break; + } + case OP_EXPR: { + int op = code[i + 1]; + const char* name = "???"; + /* clang-format off */ + switch (op) { + case TOK_DOT: name = "."; break; + case TOK_MINUS: name = "-"; break; + case TOK_PLUS: name = "+"; break; + case TOK_MUL: name = "*"; break; + case TOK_DIV: name = "/"; break; + case TOK_REM: name = "%"; break; + case TOK_XOR: name = "^"; break; + case TOK_AND: name = "&"; break; + case TOK_OR: name = "|"; break; + case TOK_LSHIFT: name = "<<"; break; + case TOK_RSHIFT: name = ">>"; break; + case TOK_URSHIFT: name = ">>>"; break; + case TOK_UNARY_MINUS: name = "- (unary)"; break; + case TOK_UNARY_PLUS: name = "+ (unary)"; break; + case TOK_NOT: name = "!"; break; + case TOK_TILDA: name = "~"; break; + case TOK_EQ: name = "=="; break; + case TOK_NE: name = "!="; break; + case TOK_EQ_EQ: name = "==="; break; + case TOK_NE_NE: name = "!=="; break; + case TOK_LT: name = "<"; break; + case TOK_GT: name = ">"; break; + case TOK_LE: name = "<="; break; + case TOK_GE: name = ">="; break; + case TOK_ASSIGN: name = "="; break; + case TOK_POSTFIX_PLUS: name = "++ (postfix)"; break; + case TOK_POSTFIX_MINUS: name = "-- (postfix)"; break; + case TOK_MINUS_MINUS: name = "--"; break; + case TOK_PLUS_PLUS: name = "++"; break; + case TOK_LOGICAL_AND: name = "&&"; break; + case TOK_LOGICAL_OR: name = "||"; break; + case TOK_KEYWORD_TYPEOF: name = "typeof"; break; + case TOK_PLUS_ASSIGN: name = "+="; break; + case TOK_MINUS_ASSIGN: name = "-="; break; + case TOK_MUL_ASSIGN: name = "*="; break; + case TOK_DIV_ASSIGN: name = "/="; break; + case TOK_REM_ASSIGN: name = "%="; break; + case TOK_XOR_ASSIGN: name = "^="; break; + case TOK_AND_ASSIGN: name = "&="; break; + case TOK_OR_ASSIGN: name = "|="; break; + case TOK_LSHIFT_ASSIGN: name = "<<="; break; + case TOK_RSHIFT_ASSIGN: name = ">>="; break; + case TOK_URSHIFT_ASSIGN: name = ">>>="; break; + } + /* clang-format on */ + print_cb(print_ctx, "%s\t%s", buf, name); + i++; + break; + } + case OP_BCODE_HEADER: { + size_t start = 0; + mjs_header_item_t map_offset = 0, total_size = 0; + start = i; + memcpy(&total_size, &code[i + 1], sizeof(total_size)); + memcpy( + &map_offset, + &code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)], + sizeof(map_offset)); + i += sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT; + print_cb( + print_ctx, + "%s\t[%s] end:%lu map_offset: %lu", + buf, + &code[i + 1], + (unsigned long)start + total_size, + (unsigned long)start + map_offset); + i += strlen((char*)(code + i + 1)) + 1; + break; + } + default: + print_cb(print_ctx, "%s", buf); + break; + } + return i - start_i; +} + +void mjs_disasm(const uint8_t* code, size_t len, MjsPrintCallback print_cb, void* print_ctx) { + size_t i, start = 0; + mjs_header_item_t map_offset = 0, total_size = 0; + + for(i = 0; i < len; i++) { + size_t delta = mjs_disasm_single(code, i, print_cb, print_ctx); + if(code[i] == OP_BCODE_HEADER) { + start = i; + memcpy(&total_size, &code[i + 1], sizeof(total_size)); + memcpy( + &map_offset, + &code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)], + sizeof(map_offset)); + } + + i += delta; + + if(map_offset > 0 && i == start + map_offset) { + i = start + total_size - 1; + continue; + } + } +} + +void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx) { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + for(int i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx); + } +} + +static void mjs_dump_obj_stack( + struct mjs* mjs, + const char* name, + const struct mbuf* m, + MjsPrintCallback print_cb, + void* print_ctx) { + char buf[50]; + size_t i, n; + n = mjs_stack_size(m); + print_cb(print_ctx, "%12s (%d elems): ", name, (int)n); + for(i = 0; i < n; i++) { + mjs_sprintf(((mjs_val_t*)m->buf)[i], mjs, buf, sizeof(buf)); + print_cb(print_ctx, "%34s", buf); + } +} + +void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx) { + print_cb(print_ctx, "------- MJS VM DUMP BEGIN"); + mjs_dump_obj_stack(mjs, "DATA_STACK", &mjs->stack, print_cb, print_ctx); + mjs_dump_obj_stack(mjs, "CALL_STACK", &mjs->call_stack, print_cb, print_ctx); + mjs_dump_obj_stack(mjs, "SCOPES", &mjs->scopes, print_cb, print_ctx); + mjs_dump_obj_stack(mjs, "LOOP_OFFSETS", &mjs->loop_addresses, print_cb, print_ctx); + mjs_dump_obj_stack(mjs, "ARG_STACK", &mjs->arg_stack, print_cb, print_ctx); + if(do_disasm) { + int parts_cnt = mjs_bcode_parts_cnt(mjs); + int i; + print_cb(print_ctx, "%23s", "CODE:"); + for(i = 0; i < parts_cnt; i++) { + struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i); + mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx); + } + } + print_cb(print_ctx, "------- MJS VM DUMP END"); +} + +MJS_PRIVATE int mjs_check_arg( + struct mjs* mjs, + int arg_num, + const char* arg_name, + enum mjs_type expected_type, + mjs_val_t* parg) { + mjs_val_t arg = MJS_UNDEFINED; + enum mjs_type actual_type; + + if(arg_num >= 0) { + int nargs = mjs_nargs(mjs); + if(nargs < arg_num + 1) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument %s", arg_name); + return 0; + } + + arg = mjs_arg(mjs, arg_num); + } else { + /* use `this` */ + arg = mjs->vals.this_obj; + } + + actual_type = mjs_get_type(arg); + if(actual_type != expected_type) { + mjs_prepend_errorf( + mjs, + MJS_TYPE_ERROR, + "%s should be a %s, %s given", + arg_name, + mjs_stringify_type(expected_type), + mjs_stringify_type(actual_type)); + return 0; + } + + if(parg != NULL) { + *parg = arg; + } + + return 1; +} + +MJS_PRIVATE int mjs_normalize_idx(int idx, int size) { + if(idx < 0) { + idx = size + idx; + if(idx < 0) { + idx = 0; + } + } + if(idx > size) { + idx = size; + } + return idx; +} + +MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp) { + (void)mjs; + return bp->data.p + 1 /* OP_BCODE_HEADER */ + sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT; +} + +const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset) { + const char* ret = NULL; + struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset); + if(bp != NULL) { + ret = mjs_get_bcode_filename(mjs, bp); + } + return ret; +} + +int mjs_get_lineno_by_offset(struct mjs* mjs, int offset) { + size_t llen; + uint64_t map_len; + int prev_line_no, ret = 1; + struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset); + uint8_t *p, *pe; + if(bp != NULL) { + mjs_header_item_t map_offset, bcode_offset; + memcpy( + &map_offset, + bp->data.p + 1 /* OP_BCODE_HEADER */ + + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_MAP_OFFSET, + sizeof(map_offset)); + + memcpy( + &bcode_offset, + bp->data.p + 1 /* OP_BCODE_HEADER */ + + sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET, + sizeof(bcode_offset)); + + offset -= (1 /* OP_BCODE_HEADER */ + bcode_offset) + bp->start_idx; + + /* get pointer to the length of the map followed by the map itself */ + p = (uint8_t*)bp->data.p + 1 /* OP_BCODE_HEADER */ + map_offset; + + cs_varint_decode(p, ~0, &map_len, &llen); + p += llen; + pe = p + map_len; + + prev_line_no = 1; + while(p < pe) { + uint64_t cur_offset, line_no; + cs_varint_decode(p, ~0, &cur_offset, &llen); + p += llen; + cs_varint_decode(p, ~0, &line_no, &llen); + p += llen; + + if(cur_offset >= (uint64_t)offset) { + ret = prev_line_no; + break; + } + prev_line_no = line_no; + } + } + return ret; +} + +int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num) { + int ret = -1; + if(cf_num == 0) { + /* Return current bcode offset */ + ret = mjs->cur_bcode_offset; + } else if( + cf_num > 0 && + mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT * cf_num) { + /* Get offset from the call_stack */ + int pos = CALL_STACK_FRAME_ITEM_RETURN_ADDR + CALL_STACK_FRAME_ITEMS_CNT * (cf_num - 1); + mjs_val_t val = *vptr(&mjs->call_stack, -1 - pos); + ret = mjs_get_int(mjs, val); + } + return ret; +} + +mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free) { + mjs_err_t ret = MJS_OK; + + *p = NULL; + *sizep = 0; + *need_free = 0; + + if(mjs_is_string(*v)) { + *p = (char*)mjs_get_string(mjs, v, sizep); + } else if(mjs_is_number(*v)) { + char buf[50] = ""; + struct json_out out = JSON_OUT_BUF(buf, sizeof(buf)); + mjs_jprintf(*v, mjs, &out); + *sizep = strlen(buf); + *p = malloc(*sizep + 1); + if(*p == NULL) { + ret = MJS_OUT_OF_MEMORY; + goto clean; + } + memmove(*p, buf, *sizep + 1); + *need_free = 1; + } else if(mjs_is_boolean(*v)) { + if(mjs_get_bool(mjs, *v)) { + *p = "true"; + *sizep = 4; + } else { + *p = "false"; + *sizep = 5; + } + } else if(mjs_is_undefined(*v)) { + *p = "undefined"; + *sizep = 9; + } else if(mjs_is_null(*v)) { + *p = "null"; + *sizep = 4; + } else if(mjs_is_object(*v)) { + ret = MJS_TYPE_ERROR; + mjs_set_errorf(mjs, ret, "conversion from object to string is not supported"); + } else if(mjs_is_foreign(*v)) { + *p = "TODO_foreign"; + *sizep = 12; + } else if(mjs_is_typed_array(*v)) { + *p = "TODO_typed_array"; + *sizep = 16; + } else { + ret = MJS_TYPE_ERROR; + mjs_set_errorf(mjs, ret, "unknown type to convert to string"); + } + +clean: + return ret; +} + +mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v) { + size_t len; + int is_truthy; + + is_truthy = ((mjs_is_boolean(v) && mjs_get_bool(mjs, v)) || + (mjs_is_number(v) && mjs_get_double(mjs, v) != (double)0.0) || + (mjs_is_string(v) && mjs_get_string(mjs, &v, &len) && len > 0) || + (mjs_is_function(v)) || (mjs_is_foreign(v)) || (mjs_is_object(v))) && + v != MJS_TAG_NAN; + + return mjs_mk_boolean(mjs, is_truthy); +} + +int mjs_is_truthy(struct mjs* mjs, mjs_val_t v) { + return mjs_get_bool(mjs, mjs_to_boolean_v(mjs, v)); +} diff --git a/lib/mjs/mjs_util.h b/lib/mjs/mjs_util.h new file mode 100644 index 00000000000..ccdf3b5081f --- /dev/null +++ b/lib/mjs/mjs_util.h @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_UTIL_H_ +#define MJS_UTIL_H_ + +#include "common/frozen/frozen.h" +#include "mjs_core.h" +#include "mjs_util_public.h" + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +struct mjs_bcode_part; + +#if MJS_ENABLE_DEBUG +MJS_PRIVATE const char* opcodetostr(uint8_t opcode); +MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i); +#endif + +MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t); + +/* + * Checks that the given argument is provided, and checks its type. If check + * fails, sets error in the mjs context, and returns 0; otherwise returns 1. + * + * If `arg_num` >= 0, checks argument; otherwise (`arg_num` is negative) checks + * `this`. `arg_name` is used for the error message only. If `parg` is not + * NULL, writes resulting value at this location in case of success. + */ +MJS_PRIVATE int mjs_check_arg( + struct mjs* mjs, + int arg_num, + const char* arg_name, + enum mjs_type expected_type, + mjs_val_t* parg); + +/* + * mjs_normalize_idx takes and index in the string and the string size, and + * returns the index which is >= 0 and <= size. Negative index is interpreted + * as size + index. + */ +MJS_PRIVATE int mjs_normalize_idx(int idx, int size); + +MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp); + +/* Print JS value `v` to the JSON stream `out`. */ +void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_UTIL_H_ */ diff --git a/lib/mjs/mjs_util_public.h b/lib/mjs/mjs_util_public.h new file mode 100644 index 00000000000..35c00803f9d --- /dev/null +++ b/lib/mjs/mjs_util_public.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef MJS_UTIL_PUBLIC_H_ +#define MJS_UTIL_PUBLIC_H_ + +#include "mjs_core_public.h" +#include + +#if defined(__cplusplus) +extern "C" { +#endif /* __cplusplus */ + +typedef void (*MjsPrintCallback)(void* ctx, const char* format, ...); + +const char* mjs_typeof(mjs_val_t v); + +void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp); +void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t buflen); + +void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx); +void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx); + +/* + * Returns the filename corresponding to the given bcode offset. + */ +const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset); + +/* + * Returns the line number corresponding to the given bcode offset. + */ +int mjs_get_lineno_by_offset(struct mjs* mjs, int offset); + +/* + * Returns bcode offset of the corresponding call frame cf_num, where 0 means + * the currently executing function, 1 means the first return address, etc. + * + * If given cf_num is too large, -1 is returned. + */ +int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num); + +/* + * Tries to convert `mjs_val_t` to a string, returns MJS_OK if successful. + * String is returned as a pair of pointers: `char **p, size_t *sizep`. + * + * Caller must also provide a non-null `need_free`, and if it is non-zero, + * then the string `*p` should be freed by the caller. + * + * MJS does not support `toString()` and `valueOf()`, so, passing an object + * always results in `MJS_TYPE_ERROR`. + */ +mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free); + +/* + * Converts value to boolean as in the expression `if (v)`. + */ +mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v); + +int mjs_is_truthy(struct mjs* mjs, mjs_val_t v); + +#if defined(__cplusplus) +} +#endif /* __cplusplus */ + +#endif /* MJS_UTIL_PUBLIC_H_ */ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 5a5fa4cfc06..63bc13fd4df 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.1,, +Version,+,55.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -85,6 +85,14 @@ Header,+,lib/mbedtls/include/mbedtls/md.h,, Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/mbedtls/include/mbedtls/sha256.h,, +Header,+,lib/mjs/mjs_array_buf_public.h,, +Header,+,lib/mjs/mjs_array_public.h,, +Header,+,lib/mjs/mjs_core_public.h,, +Header,+,lib/mjs/mjs_exec_public.h,, +Header,+,lib/mjs/mjs_object_public.h,, +Header,+,lib/mjs/mjs_primitive_public.h,, +Header,+,lib/mjs/mjs_string_public.h,, +Header,+,lib/mjs/mjs_util_public.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -1901,6 +1909,88 @@ Function,+,menu_free,void,Menu* Function,+,menu_get_view,View*,Menu* Function,+,menu_reset,void,Menu* Function,+,menu_set_selected_item,void,"Menu*, uint32_t" +Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" +Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_array_buf_get_ptr,char*,"mjs*, mjs_val_t, size_t*" +Function,+,mjs_array_del,void,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_get,mjs_val_t,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_length,unsigned long,"mjs*, mjs_val_t" +Function,+,mjs_array_push,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_array_set,mjs_err_t,"mjs*, mjs_val_t, unsigned long, mjs_val_t" +Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." +Function,+,mjs_create,mjs*,void* +Function,+,mjs_dataview_get_buf,mjs_val_t,"mjs*, mjs_val_t" +Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" +Function,+,mjs_destroy,void,mjs* +Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" +Function,+,mjs_disown,int,"mjs*, mjs_val_t*" +Function,-,mjs_dump,void,"mjs*, int, MjsPrintCallback, void*" +Function,+,mjs_exec,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exec_file,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exit,void,mjs* +Function,+,mjs_ffi_resolve,void*,"mjs*, const char*" +Function,-,mjs_fprintf,void,"mjs_val_t, mjs*, FILE*" +Function,+,mjs_get,mjs_val_t,"mjs*, mjs_val_t, const char*, size_t" +Function,-,mjs_get_bcode_filename_by_offset,const char*,"mjs*, int" +Function,+,mjs_get_bool,int,"mjs*, mjs_val_t" +Function,+,mjs_get_context,void*,mjs* +Function,+,mjs_get_cstring,const char*,"mjs*, mjs_val_t*" +Function,+,mjs_get_double,double,"mjs*, mjs_val_t" +Function,+,mjs_get_global,mjs_val_t,mjs* +Function,+,mjs_get_int,int,"mjs*, mjs_val_t" +Function,+,mjs_get_int32,int32_t,"mjs*, mjs_val_t" +Function,+,mjs_get_lineno_by_offset,int,"mjs*, int" +Function,+,mjs_get_offset_by_call_frame_num,int,"mjs*, int" +Function,+,mjs_get_ptr,void*,"mjs*, mjs_val_t" +Function,+,mjs_get_stack_trace,const char*,mjs* +Function,+,mjs_get_string,const char*,"mjs*, mjs_val_t*, size_t*" +Function,+,mjs_get_this,mjs_val_t,mjs* +Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_is_array,int,mjs_val_t +Function,+,mjs_is_array_buf,int,mjs_val_t +Function,+,mjs_is_boolean,int,mjs_val_t +Function,+,mjs_is_data_view,int,mjs_val_t +Function,+,mjs_is_foreign,int,mjs_val_t +Function,+,mjs_is_function,int,mjs_val_t +Function,+,mjs_is_null,int,mjs_val_t +Function,+,mjs_is_number,int,mjs_val_t +Function,+,mjs_is_object,int,mjs_val_t +Function,+,mjs_is_object_based,int,mjs_val_t +Function,+,mjs_is_string,int,mjs_val_t +Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" +Function,+,mjs_is_typed_array,int,mjs_val_t +Function,+,mjs_is_undefined,int,mjs_val_t +Function,+,mjs_mk_array,mjs_val_t,mjs* +Function,+,mjs_mk_array_buf,mjs_val_t,"mjs*, char*, size_t" +Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" +Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" +Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" +Function,+,mjs_mk_function,mjs_val_t,"mjs*, size_t" +Function,+,mjs_mk_null,mjs_val_t, +Function,+,mjs_mk_number,mjs_val_t,"mjs*, double" +Function,+,mjs_mk_object,mjs_val_t,mjs* +Function,+,mjs_mk_string,mjs_val_t,"mjs*, const char*, size_t, int" +Function,+,mjs_mk_undefined,mjs_val_t, +Function,+,mjs_nargs,int,mjs* +Function,+,mjs_next,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t*" +Function,+,mjs_own,void,"mjs*, mjs_val_t*" +Function,+,mjs_prepend_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,-,mjs_print_error,void,"mjs*, FILE*, const char*, int" +Function,+,mjs_return,void,"mjs*, mjs_val_t" +Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" +Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,+,mjs_set_exec_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" +Function,-,mjs_set_generate_jsc,void,"mjs*, int" +Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" +Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" +Function,+,mjs_strcmp,int,"mjs*, mjs_val_t*, const char*, size_t" +Function,+,mjs_strerror,const char*,"mjs*, mjs_err" +Function,+,mjs_struct_to_obj,mjs_val_t,"mjs*, const void*, const mjs_c_struct_member*" +Function,+,mjs_to_boolean_v,mjs_val_t,"mjs*, mjs_val_t" +Function,+,mjs_to_string,mjs_err_t,"mjs*, mjs_val_t*, char**, size_t*, int*" +Function,+,mjs_typeof,const char*,mjs_val_t Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" diff --git a/targets/f18/target.json b/targets/f18/target.json index e021a5b2296..7345fceec66 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -27,6 +27,7 @@ "assets", "one_wire", "music_worker", + "mjs", "mbedtls", "flipper_application", "toolbox", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index eee91ce1cc9..a42ddc0788b 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.1,, +Version,+,55.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -98,6 +98,14 @@ Header,+,lib/mbedtls/include/mbedtls/md.h,, Header,+,lib/mbedtls/include/mbedtls/md5.h,, Header,+,lib/mbedtls/include/mbedtls/sha1.h,, Header,+,lib/mbedtls/include/mbedtls/sha256.h,, +Header,+,lib/mjs/mjs_array_buf_public.h,, +Header,+,lib/mjs/mjs_array_public.h,, +Header,+,lib/mjs/mjs_core_public.h,, +Header,+,lib/mjs/mjs_exec_public.h,, +Header,+,lib/mjs/mjs_object_public.h,, +Header,+,lib/mjs/mjs_primitive_public.h,, +Header,+,lib/mjs/mjs_string_public.h,, +Header,+,lib/mjs/mjs_util_public.h,, Header,+,lib/mlib/m-algo.h,, Header,+,lib/mlib/m-array.h,, Header,+,lib/mlib/m-bptree.h,, @@ -1988,7 +1996,7 @@ Function,-,isupper,int,int Function,-,isupper_l,int,"int, locale_t" Function,-,isxdigit,int,int Function,-,isxdigit_l,int,"int, locale_t" -Function,-,itoa,char*,"int, char*, int" +Function,+,itoa,char*,"int, char*, int" Function,-,j0,double,double Function,-,j0f,float,float Function,-,j1,double,double @@ -2421,6 +2429,88 @@ Function,+,mf_ultralight_save,_Bool,"const MfUltralightData*, FlipperFormat*" Function,+,mf_ultralight_set_uid,_Bool,"MfUltralightData*, const uint8_t*, size_t" Function,+,mf_ultralight_support_feature,_Bool,"const uint32_t, const uint32_t" Function,+,mf_ultralight_verify,_Bool,"MfUltralightData*, const FuriString*" +Function,+,mjs_apply,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, mjs_val_t*" +Function,+,mjs_arg,mjs_val_t,"mjs*, int" +Function,+,mjs_array_buf_get_ptr,char*,"mjs*, mjs_val_t, size_t*" +Function,+,mjs_array_del,void,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_get,mjs_val_t,"mjs*, mjs_val_t, unsigned long" +Function,+,mjs_array_length,unsigned long,"mjs*, mjs_val_t" +Function,+,mjs_array_push,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_array_set,mjs_err_t,"mjs*, mjs_val_t, unsigned long, mjs_val_t" +Function,+,mjs_call,mjs_err_t,"mjs*, mjs_val_t*, mjs_val_t, mjs_val_t, int, ..." +Function,+,mjs_create,mjs*,void* +Function,+,mjs_dataview_get_buf,mjs_val_t,"mjs*, mjs_val_t" +Function,+,mjs_del,int,"mjs*, mjs_val_t, const char*, size_t" +Function,+,mjs_destroy,void,mjs* +Function,-,mjs_disasm_all,void,"mjs*, MjsPrintCallback, void*" +Function,+,mjs_disown,int,"mjs*, mjs_val_t*" +Function,-,mjs_dump,void,"mjs*, int, MjsPrintCallback, void*" +Function,+,mjs_exec,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exec_file,mjs_err_t,"mjs*, const char*, mjs_val_t*" +Function,+,mjs_exit,void,mjs* +Function,+,mjs_ffi_resolve,void*,"mjs*, const char*" +Function,-,mjs_fprintf,void,"mjs_val_t, mjs*, FILE*" +Function,+,mjs_get,mjs_val_t,"mjs*, mjs_val_t, const char*, size_t" +Function,-,mjs_get_bcode_filename_by_offset,const char*,"mjs*, int" +Function,+,mjs_get_bool,int,"mjs*, mjs_val_t" +Function,+,mjs_get_context,void*,mjs* +Function,+,mjs_get_cstring,const char*,"mjs*, mjs_val_t*" +Function,+,mjs_get_double,double,"mjs*, mjs_val_t" +Function,+,mjs_get_global,mjs_val_t,mjs* +Function,+,mjs_get_int,int,"mjs*, mjs_val_t" +Function,+,mjs_get_int32,int32_t,"mjs*, mjs_val_t" +Function,+,mjs_get_lineno_by_offset,int,"mjs*, int" +Function,+,mjs_get_offset_by_call_frame_num,int,"mjs*, int" +Function,+,mjs_get_ptr,void*,"mjs*, mjs_val_t" +Function,+,mjs_get_stack_trace,const char*,mjs* +Function,+,mjs_get_string,const char*,"mjs*, mjs_val_t*, size_t*" +Function,+,mjs_get_this,mjs_val_t,mjs* +Function,+,mjs_get_v,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_get_v_proto,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t" +Function,+,mjs_is_array,int,mjs_val_t +Function,+,mjs_is_array_buf,int,mjs_val_t +Function,+,mjs_is_boolean,int,mjs_val_t +Function,+,mjs_is_data_view,int,mjs_val_t +Function,+,mjs_is_foreign,int,mjs_val_t +Function,+,mjs_is_function,int,mjs_val_t +Function,+,mjs_is_null,int,mjs_val_t +Function,+,mjs_is_number,int,mjs_val_t +Function,+,mjs_is_object,int,mjs_val_t +Function,+,mjs_is_object_based,int,mjs_val_t +Function,+,mjs_is_string,int,mjs_val_t +Function,+,mjs_is_truthy,int,"mjs*, mjs_val_t" +Function,+,mjs_is_typed_array,int,mjs_val_t +Function,+,mjs_is_undefined,int,mjs_val_t +Function,+,mjs_mk_array,mjs_val_t,mjs* +Function,+,mjs_mk_array_buf,mjs_val_t,"mjs*, char*, size_t" +Function,+,mjs_mk_boolean,mjs_val_t,"mjs*, int" +Function,+,mjs_mk_foreign,mjs_val_t,"mjs*, void*" +Function,+,mjs_mk_foreign_func,mjs_val_t,"mjs*, mjs_func_ptr_t" +Function,+,mjs_mk_function,mjs_val_t,"mjs*, size_t" +Function,+,mjs_mk_null,mjs_val_t, +Function,+,mjs_mk_number,mjs_val_t,"mjs*, double" +Function,+,mjs_mk_object,mjs_val_t,mjs* +Function,+,mjs_mk_string,mjs_val_t,"mjs*, const char*, size_t, int" +Function,+,mjs_mk_undefined,mjs_val_t, +Function,+,mjs_nargs,int,mjs* +Function,+,mjs_next,mjs_val_t,"mjs*, mjs_val_t, mjs_val_t*" +Function,+,mjs_own,void,"mjs*, mjs_val_t*" +Function,+,mjs_prepend_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,-,mjs_print_error,void,"mjs*, FILE*, const char*, int" +Function,+,mjs_return,void,"mjs*, mjs_val_t" +Function,+,mjs_set,mjs_err_t,"mjs*, mjs_val_t, const char*, size_t, mjs_val_t" +Function,+,mjs_set_errorf,mjs_err_t,"mjs*, mjs_err_t, const char*, ..." +Function,+,mjs_set_exec_flags_poller,void,"mjs*, mjs_flags_poller_t" +Function,+,mjs_set_ffi_resolver,void,"mjs*, mjs_ffi_resolver_t*, void*" +Function,-,mjs_set_generate_jsc,void,"mjs*, int" +Function,+,mjs_set_v,mjs_err_t,"mjs*, mjs_val_t, mjs_val_t, mjs_val_t" +Function,+,mjs_sprintf,void,"mjs_val_t, mjs*, char*, size_t" +Function,+,mjs_strcmp,int,"mjs*, mjs_val_t*, const char*, size_t" +Function,+,mjs_strerror,const char*,"mjs*, mjs_err" +Function,+,mjs_struct_to_obj,mjs_val_t,"mjs*, const void*, const mjs_c_struct_member*" +Function,+,mjs_to_boolean_v,mjs_val_t,"mjs*, mjs_val_t" +Function,+,mjs_to_string,mjs_err_t,"mjs*, mjs_val_t*, char**, size_t*, int*" +Function,+,mjs_typeof,const char*,mjs_val_t Function,-,mkdtemp,char*,char* Function,-,mkostemp,int,"char*, int" Function,-,mkostemps,int,"char*, int, int" @@ -2981,7 +3071,7 @@ Function,-,strsep,char*,"char**, const char*" Function,-,strsignal,char*,int Function,+,strspn,size_t,"const char*, const char*" Function,+,strstr,char*,"const char*, const char*" -Function,-,strtod,double,"const char*, char**" +Function,+,strtod,double,"const char*, char**" Function,-,strtod_l,double,"const char*, char**, locale_t" Function,+,strtof,float,"const char*, char**" Function,-,strtof_l,float,"const char*, char**, locale_t" diff --git a/targets/f7/target.json b/targets/f7/target.json index 7a816828c78..14090026947 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -38,6 +38,7 @@ "one_wire", "ibutton", "music_worker", + "mjs", "mbedtls", "lfrfid", "flipper_application", From c8e62ba5e8623eb88e6b8ae2fe1381375af0c728 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 12 Feb 2024 14:16:58 +0000 Subject: [PATCH 30/58] mjs: minor fixes (#3442) * lib: mjs: removed relocation flags * js: fixed api example script * js: private api cleanup * Updated CODEOWNERS & readme for apps --- .github/CODEOWNERS | 3 ++- applications/ReadMe.md | 5 ++++- applications/system/js_app/examples/apps/Scripts/load.js | 2 +- applications/system/js_app/plugin_api/app_api_table_i.h | 4 +--- lib/mjs/SConscript | 3 --- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cf86fb91698..aab3ff35318 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -36,6 +36,7 @@ /applications/services/power_settings_app/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich /applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/applications/system/js_app/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov @gornekich @Astrrra @Skorpionm @@ -58,7 +59,7 @@ /lib/lfrfid/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /lib/libusb_stm32/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /lib/mbedtls/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov -/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov +/lib/mjs/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /lib/nanopb/ @skotopes @DrZlo13 @hedger @gsurkov @nminaylov /lib/nfc/ @skotopes @DrZlo13 @hedger @gsurkov @gornekich @Astrrra /lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov diff --git a/applications/ReadMe.md b/applications/ReadMe.md index de465832aef..44bd8c5d7ba 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -72,7 +72,10 @@ Small applications providing configuration for basic firmware and its services. ## system -Utility apps not visible in other menus. +Utility apps not visible in other menus, plus few external apps pre-packaged with the firmware. +- `hid_app` - BLE & USB HID remote +- `js_app` - JS engine runner +- `snake_game` - Snake game - `storage_move_to_sd` - Data migration tool for internal storage - `updater` - Update service & application diff --git a/applications/system/js_app/examples/apps/Scripts/load.js b/applications/system/js_app/examples/apps/Scripts/load.js index 59d99b94cb7..dfb110ca59a 100644 --- a/applications/system/js_app/examples/apps/Scripts/load.js +++ b/applications/system/js_app/examples/apps/Scripts/load.js @@ -1,3 +1,3 @@ -let math = load("/ext/apps/Scripts/api.js"); +let math = load("/ext/apps/Scripts/load_api.js"); let result = math.add(5, 10); print(result); \ No newline at end of file diff --git a/applications/system/js_app/plugin_api/app_api_table_i.h b/applications/system/js_app/plugin_api/app_api_table_i.h index 8f1ebd5f4b3..d84ae811051 100644 --- a/applications/system/js_app/plugin_api/app_api_table_i.h +++ b/applications/system/js_app/plugin_api/app_api_table_i.h @@ -8,6 +8,4 @@ static constexpr auto app_api_table = sort(create_array_t( API_METHOD(js_delay_with_flags, bool, (struct mjs*, uint32_t)), API_METHOD(js_flags_set, void, (struct mjs*, uint32_t)), - API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)), - API_VARIABLE(I_Certification1_103x56, const Icon), - API_VARIABLE(I_Certification2_46x33, const Icon))); \ No newline at end of file + API_METHOD(js_flags_wait, uint32_t, (struct mjs*, uint32_t, uint32_t)))); diff --git a/lib/mjs/SConscript b/lib/mjs/SConscript index 35b45fc91e6..ddfa91f62b6 100644 --- a/lib/mjs/SConscript +++ b/lib/mjs/SConscript @@ -21,9 +21,6 @@ libenv.ApplyLibFlags() libenv.AppendUnique( CCFLAGS=[ - # Required for lib to be linkable with .faps - "-mword-relocations", - "-mlong-calls", "-Wno-redundant-decls", "-Wno-unused-function", ], From 25a280c818f67bc7b6f34026c2d497d4241ce47a Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Tue, 13 Feb 2024 06:04:27 +0300 Subject: [PATCH 31/58] JS serial module renamed, uart channel selection (#3445) --- applications/system/js_app/application.fam | 6 +- .../js_app/examples/apps/Scripts/bad_uart.js | 22 +- .../js_app/examples/apps/Scripts/uart_echo.js | 8 +- .../js_app/modules/{js_uart.c => js_serial.c} | 241 ++++++++++-------- 4 files changed, 155 insertions(+), 122 deletions(-) rename applications/system/js_app/modules/{js_uart.c => js_serial.c} (66%) diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 29b4ebb5977..5716234dc3b 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -33,9 +33,9 @@ App( ) App( - appid="js_uart", + appid="js_serial", apptype=FlipperAppType.PLUGIN, - entry_point="js_uart_ep", + entry_point="js_serial_ep", requires=["js_app"], - sources=["modules/js_uart.c"], + sources=["modules/js_serial.c"], ) diff --git a/applications/system/js_app/examples/apps/Scripts/bad_uart.js b/applications/system/js_app/examples/apps/Scripts/bad_uart.js index 8166c61f551..9396a346467 100644 --- a/applications/system/js_app/examples/apps/Scripts/bad_uart.js +++ b/applications/system/js_app/examples/apps/Scripts/bad_uart.js @@ -1,19 +1,19 @@ -let uart = require("uart"); -uart.setup(115200); +let serial = require("serial"); +serial.setup("lpuart", 115200); -// uart.write("\n"); -uart.write([0x0a]); -let console_resp = uart.expect("# ", 1000); +// serial.write("\n"); +serial.write([0x0a]); +let console_resp = serial.expect("# ", 1000); if (console_resp === undefined) { print("No CLI response"); } else { - uart.write("uci\n"); - let uci_state = uart.expect([": not found", "Usage: "]); + serial.write("uci\n"); + let uci_state = serial.expect([": not found", "Usage: "]); if (uci_state === 1) { - uart.expect("# "); - uart.write("uci show wireless\n"); - uart.expect(".key="); - print("key:", uart.readln()); + serial.expect("# "); + serial.write("uci show wireless\n"); + serial.expect(".key="); + print("key:", serial.readln()); } else { print("uci cmd not found"); } diff --git a/applications/system/js_app/examples/apps/Scripts/uart_echo.js b/applications/system/js_app/examples/apps/Scripts/uart_echo.js index 2f3957fd19e..60d44d078d7 100644 --- a/applications/system/js_app/examples/apps/Scripts/uart_echo.js +++ b/applications/system/js_app/examples/apps/Scripts/uart_echo.js @@ -1,10 +1,10 @@ -let uart = require("uart"); -uart.setup(115200); +let serial = require("serial"); +serial.setup("usart", 230400); while (1) { - let rx_data = uart.readBytes(1, 0); + let rx_data = serial.readBytes(1, 0); if (rx_data !== undefined) { - uart.write(rx_data); + serial.write(rx_data); let data_view = Uint8Array(rx_data); print("0x" + to_hex_string(data_view[0])); } diff --git a/applications/system/js_app/modules/js_uart.c b/applications/system/js_app/modules/js_serial.c similarity index 66% rename from applications/system/js_app/modules/js_uart.c rename to applications/system/js_app/modules/js_serial.c index 8f251c276eb..4f1b14f98c9 100644 --- a/applications/system/js_app/modules/js_uart.c +++ b/applications/system/js_app/modules/js_serial.c @@ -3,7 +3,7 @@ #include "../js_modules.h" #include -#define TAG "js_uart" +#define TAG "js_serial" #define RX_BUF_LEN 2048 typedef struct { @@ -11,69 +11,99 @@ typedef struct { FuriStreamBuffer* rx_stream; FuriHalSerialHandle* serial_handle; struct mjs* mjs; -} JsUartInst; +} JsSerialInst; typedef struct { size_t len; char* data; } PatternArrayItem; +static const struct { + const char* name; + const FuriHalSerialId value; +} serial_channels[] = { + {"usart", FuriHalSerialIdUsart}, + {"lpuart", FuriHalSerialIdLpuart}, +}; + ARRAY_DEF(PatternArray, PatternArrayItem, M_POD_OPLIST); static void - js_uart_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { - JsUartInst* uart = context; - furi_assert(uart); + js_serial_on_async_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { + JsSerialInst* serial = context; + furi_assert(serial); if(event & FuriHalSerialRxEventData) { uint8_t data = furi_hal_serial_async_rx(handle); - furi_stream_buffer_send(uart->rx_stream, &data, 1, 0); - js_flags_set(uart->mjs, ThreadEventCustomDataRx); + furi_stream_buffer_send(serial->rx_stream, &data, 1, 0); + js_flags_set(serial->mjs, ThreadEventCustomDataRx); } } -static void js_uart_setup(struct mjs* mjs) { +static void js_serial_setup(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); - if(uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is already configured"); + if(serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is already configured"); mjs_return(mjs, MJS_UNDEFINED); return; } bool args_correct = false; + FuriHalSerialId serial_id = FuriHalSerialIdMax; uint32_t baudrate = 0; - if(mjs_nargs(mjs) == 1) { + do { + if(mjs_nargs(mjs) != 2) break; + mjs_val_t arg = mjs_arg(mjs, 0); - if(mjs_is_number(arg)) { - baudrate = mjs_get_int32(mjs, arg); - args_correct = true; + if(!mjs_is_string(arg)) break; + + size_t str_len = 0; + const char* arg_str = mjs_get_string(mjs, &arg, &str_len); + for(size_t i = 0; i < COUNT_OF(serial_channels); i++) { + size_t name_len = strlen(serial_channels[i].name); + if(str_len != name_len) continue; + if(strncmp(arg_str, serial_channels[i].name, str_len) == 0) { + serial_id = serial_channels[i].value; + break; + } } - } + if(serial_id == FuriHalSerialIdMax) { + break; + } + + arg = mjs_arg(mjs, 1); + if(!mjs_is_number(arg)) break; + baudrate = mjs_get_int32(mjs, arg); + + args_correct = true; + } while(0); + if(!args_correct) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); mjs_return(mjs, MJS_UNDEFINED); return; } - uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); - uart->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdLpuart); - if(uart->serial_handle) { - furi_hal_serial_init(uart->serial_handle, baudrate); - furi_hal_serial_async_rx_start(uart->serial_handle, js_uart_on_async_rx, uart, false); - uart->setup_done = true; + serial->rx_stream = furi_stream_buffer_alloc(RX_BUF_LEN, 1); + serial->serial_handle = furi_hal_serial_control_acquire(serial_id); + if(serial->serial_handle) { + furi_hal_serial_init(serial->serial_handle, baudrate); + furi_hal_serial_async_rx_start( + serial->serial_handle, js_serial_on_async_rx, serial, false); + serial->setup_done = true; } } -static void js_uart_write(struct mjs* mjs) { +static void js_serial_write(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); - if(!uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); mjs_return(mjs, MJS_UNDEFINED); return; } @@ -90,14 +120,14 @@ static void js_uart_write(struct mjs* mjs) { args_correct = false; break; } - furi_hal_serial_tx(uart->serial_handle, (uint8_t*)arg_str, str_len); + furi_hal_serial_tx(serial->serial_handle, (uint8_t*)arg_str, str_len); } else if(mjs_is_number(arg)) { uint32_t byte_val = mjs_get_int32(mjs, arg); if(byte_val > 0xFF) { args_correct = false; break; } - furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1); + furi_hal_serial_tx(serial->serial_handle, (uint8_t*)&byte_val, 1); } else if(mjs_is_array(arg)) { size_t array_len = mjs_array_length(mjs, arg); for(size_t i = 0; i < array_len; i++) { @@ -111,7 +141,7 @@ static void js_uart_write(struct mjs* mjs) { args_correct = false; break; } - furi_hal_serial_tx(uart->serial_handle, (uint8_t*)&byte_val, 1); + furi_hal_serial_tx(serial->serial_handle, (uint8_t*)&byte_val, 1); } if(!args_correct) { break; @@ -123,7 +153,7 @@ static void js_uart_write(struct mjs* mjs) { } size_t len = 0; char* buf = mjs_array_buf_get_ptr(mjs, array_buf, &len); - furi_hal_serial_tx(uart->serial_handle, (uint8_t*)buf, len); + furi_hal_serial_tx(serial->serial_handle, (uint8_t*)buf, len); } else { args_correct = false; break; @@ -136,12 +166,12 @@ static void js_uart_write(struct mjs* mjs) { mjs_return(mjs, MJS_UNDEFINED); } -static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t timeout) { +static size_t js_serial_receive(JsSerialInst* serial, char* buf, size_t len, uint32_t timeout) { size_t bytes_read = 0; while(1) { uint32_t flags = ThreadEventCustomDataRx; - if(furi_stream_buffer_is_empty(uart->rx_stream)) { - flags = js_flags_wait(uart->mjs, ThreadEventCustomDataRx, timeout); + if(furi_stream_buffer_is_empty(serial->rx_stream)) { + flags = js_flags_wait(serial->mjs, ThreadEventCustomDataRx, timeout); } if(flags == 0) { // Timeout break; @@ -149,8 +179,8 @@ static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t bytes_read = 0; break; } else if(flags & ThreadEventCustomDataRx) { // New data received - size_t rx_len = - furi_stream_buffer_receive(uart->rx_stream, &buf[bytes_read], len - bytes_read, 0); + size_t rx_len = furi_stream_buffer_receive( + serial->rx_stream, &buf[bytes_read], len - bytes_read, 0); bytes_read += rx_len; if(bytes_read == len) { break; @@ -160,12 +190,12 @@ static size_t js_uart_receive(JsUartInst* uart, char* buf, size_t len, uint32_t return bytes_read; } -static void js_uart_read(struct mjs* mjs) { +static void js_serial_read(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); - if(!uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); mjs_return(mjs, MJS_UNDEFINED); return; } @@ -199,7 +229,7 @@ static void js_uart_read(struct mjs* mjs) { } char* read_buf = malloc(read_len); - size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); mjs_val_t return_obj = MJS_UNDEFINED; if(bytes_read > 0) { @@ -209,12 +239,12 @@ static void js_uart_read(struct mjs* mjs) { free(read_buf); } -static void js_uart_readln(struct mjs* mjs) { +static void js_serial_readln(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); - if(!uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); mjs_return(mjs, MJS_UNDEFINED); return; } @@ -246,7 +276,7 @@ static void js_uart_readln(struct mjs* mjs) { char read_char = 0; while(1) { - size_t read_len = js_uart_receive(uart, &read_char, 1, timeout); + size_t read_len = js_serial_receive(serial, &read_char, 1, timeout); if(read_len != 1) { break; } @@ -266,12 +296,12 @@ static void js_uart_readln(struct mjs* mjs) { furi_string_free(rx_buf); } -static void js_uart_read_bytes(struct mjs* mjs) { +static void js_serial_read_bytes(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); - if(!uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); mjs_return(mjs, MJS_UNDEFINED); return; } @@ -305,7 +335,7 @@ static void js_uart_read_bytes(struct mjs* mjs) { } char* read_buf = malloc(read_len); - size_t bytes_read = js_uart_receive(uart, read_buf, read_len, timeout); + size_t bytes_read = js_serial_receive(serial, read_buf, read_len, timeout); mjs_val_t return_obj = MJS_UNDEFINED; if(bytes_read > 0) { @@ -315,7 +345,8 @@ static void js_uart_read_bytes(struct mjs* mjs) { free(read_buf); } -static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { +static bool + js_serial_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { size_t str_len = 0; const char* arg_str = mjs_get_string(mjs, &arg, &str_len); if((str_len == 0) || (arg_str == NULL)) { @@ -328,7 +359,7 @@ static bool js_uart_expect_parse_string(struct mjs* mjs, mjs_val_t arg, PatternA return true; } -static bool js_uart_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { +static bool js_serial_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternArray_t patterns) { size_t array_len = mjs_array_length(mjs, arg); if(array_len == 0) { return false; @@ -357,7 +388,7 @@ static bool js_uart_expect_parse_array(struct mjs* mjs, mjs_val_t arg, PatternAr } static bool - js_uart_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) { + js_serial_expect_parse_args(struct mjs* mjs, PatternArray_t patterns, uint32_t* timeout) { size_t num_args = mjs_nargs(mjs); if(num_args == 2) { mjs_val_t timeout_arg = mjs_arg(mjs, 1); @@ -370,7 +401,7 @@ static bool } mjs_val_t patterns_arg = mjs_arg(mjs, 0); if(mjs_is_string(patterns_arg)) { // Single string pattern - if(!js_uart_expect_parse_string(mjs, patterns_arg, patterns)) { + if(!js_serial_expect_parse_string(mjs, patterns_arg, patterns)) { return false; } } else if(mjs_is_array(patterns_arg)) { @@ -381,7 +412,7 @@ static bool mjs_val_t array_arg = mjs_array_get(mjs, patterns_arg, 0); if(mjs_is_number(array_arg)) { // Binary array pattern - if(!js_uart_expect_parse_array(mjs, patterns_arg, patterns)) { + if(!js_serial_expect_parse_array(mjs, patterns_arg, patterns)) { return false; } } else if((mjs_is_string(array_arg)) || (mjs_is_array(array_arg))) { // Multiple patterns @@ -389,11 +420,11 @@ static bool mjs_val_t arg = mjs_array_get(mjs, patterns_arg, i); if(mjs_is_string(arg)) { - if(!js_uart_expect_parse_string(mjs, arg, patterns)) { + if(!js_serial_expect_parse_string(mjs, arg, patterns)) { return false; } } else if(mjs_is_array(arg)) { - if(!js_uart_expect_parse_array(mjs, arg, patterns)) { + if(!js_serial_expect_parse_array(mjs, arg, patterns)) { return false; } } @@ -407,8 +438,10 @@ static bool return true; } -static int32_t - js_uart_expect_check_pattern_start(PatternArray_t patterns, char value, int32_t pattern_last) { +static int32_t js_serial_expect_check_pattern_start( + PatternArray_t patterns, + char value, + int32_t pattern_last) { size_t array_len = PatternArray_size(patterns); if((pattern_last + 1) >= (int32_t)array_len) { return (-1); @@ -421,12 +454,12 @@ static int32_t return (-1); } -static void js_uart_expect(struct mjs* mjs) { +static void js_serial_expect(struct mjs* mjs) { mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); - JsUartInst* uart = mjs_get_ptr(mjs, obj_inst); - furi_assert(uart); - if(!uart->setup_done) { - mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "UART is not configured"); + JsSerialInst* serial = mjs_get_ptr(mjs, obj_inst); + furi_assert(serial); + if(!serial->setup_done) { + mjs_prepend_errorf(mjs, MJS_INTERNAL_ERROR, "Serial is not configured"); mjs_return(mjs, MJS_UNDEFINED); return; } @@ -436,7 +469,7 @@ static void js_uart_expect(struct mjs* mjs) { PatternArray_it_t it; PatternArray_init(patterns); - if(!js_uart_expect_parse_args(mjs, patterns, &timeout)) { + if(!js_serial_expect_parse_args(mjs, patterns, &timeout)) { mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, ""); mjs_return(mjs, MJS_UNDEFINED); for(PatternArray_it(it, patterns); !PatternArray_end_p(it); PatternArray_next(it)) { @@ -464,12 +497,12 @@ static void js_uart_expect(struct mjs* mjs) { while(1) { if(buf_len == 0) { // Empty buffer - read by 1 byte to find pattern start - size_t bytes_read = js_uart_receive(uart, &compare_buf[0], 1, timeout); + size_t bytes_read = js_serial_receive(serial, &compare_buf[0], 1, timeout); if(bytes_read != 1) { is_timeout = true; break; } - pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[0], -1); + pattern_candidate = js_serial_expect_check_pattern_start(patterns, compare_buf[0], -1); if(pattern_candidate == -1) { continue; } @@ -482,7 +515,7 @@ static void js_uart_expect(struct mjs* mjs) { pattern_found = pattern_candidate; for(size_t i = 0; i < pattern_cur->len; i++) { if(i >= buf_len) { - size_t bytes_read = js_uart_receive(uart, &compare_buf[i], 1, timeout); + size_t bytes_read = js_serial_receive(serial, &compare_buf[i], 1, timeout); if(bytes_read != 1) { is_timeout = true; break; @@ -500,14 +533,14 @@ static void js_uart_expect(struct mjs* mjs) { // Search other patterns with the same start char pattern_candidate = - js_uart_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate); + js_serial_expect_check_pattern_start(patterns, compare_buf[0], pattern_candidate); if(pattern_candidate >= 0) { continue; } // Look for another pattern start for(size_t i = 1; i < buf_len; i++) { - pattern_candidate = js_uart_expect_check_pattern_start(patterns, compare_buf[i], -1); + pattern_candidate = js_serial_expect_check_pattern_start(patterns, compare_buf[i], -1); if(pattern_candidate >= 0) { memmove(&compare_buf[0], &compare_buf[i], buf_len - i); buf_len -= i; @@ -539,47 +572,47 @@ static void js_uart_expect(struct mjs* mjs) { } } -static void* js_uart_create(struct mjs* mjs, mjs_val_t* object) { - JsUartInst* js_uart = malloc(sizeof(JsUartInst)); - js_uart->mjs = mjs; - mjs_val_t uart_obj = mjs_mk_object(mjs); - mjs_set(mjs, uart_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_uart)); - mjs_set(mjs, uart_obj, "setup", ~0, MJS_MK_FN(js_uart_setup)); - mjs_set(mjs, uart_obj, "write", ~0, MJS_MK_FN(js_uart_write)); - mjs_set(mjs, uart_obj, "read", ~0, MJS_MK_FN(js_uart_read)); - mjs_set(mjs, uart_obj, "readln", ~0, MJS_MK_FN(js_uart_readln)); - mjs_set(mjs, uart_obj, "readBytes", ~0, MJS_MK_FN(js_uart_read_bytes)); - mjs_set(mjs, uart_obj, "expect", ~0, MJS_MK_FN(js_uart_expect)); - *object = uart_obj; - - return js_uart; +static void* js_serial_create(struct mjs* mjs, mjs_val_t* object) { + JsSerialInst* js_serial = malloc(sizeof(JsSerialInst)); + js_serial->mjs = mjs; + mjs_val_t serial_obj = mjs_mk_object(mjs); + mjs_set(mjs, serial_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, js_serial)); + mjs_set(mjs, serial_obj, "setup", ~0, MJS_MK_FN(js_serial_setup)); + mjs_set(mjs, serial_obj, "write", ~0, MJS_MK_FN(js_serial_write)); + mjs_set(mjs, serial_obj, "read", ~0, MJS_MK_FN(js_serial_read)); + mjs_set(mjs, serial_obj, "readln", ~0, MJS_MK_FN(js_serial_readln)); + mjs_set(mjs, serial_obj, "readBytes", ~0, MJS_MK_FN(js_serial_read_bytes)); + mjs_set(mjs, serial_obj, "expect", ~0, MJS_MK_FN(js_serial_expect)); + *object = serial_obj; + + return js_serial; } -static void js_uart_destroy(void* inst) { - JsUartInst* js_uart = inst; - if(js_uart->setup_done) { - furi_hal_serial_async_rx_stop(js_uart->serial_handle); - furi_hal_serial_deinit(js_uart->serial_handle); - furi_hal_serial_control_release(js_uart->serial_handle); - js_uart->serial_handle = NULL; +static void js_serial_destroy(void* inst) { + JsSerialInst* js_serial = inst; + if(js_serial->setup_done) { + furi_hal_serial_async_rx_stop(js_serial->serial_handle); + furi_hal_serial_deinit(js_serial->serial_handle); + furi_hal_serial_control_release(js_serial->serial_handle); + js_serial->serial_handle = NULL; } - furi_stream_buffer_free(js_uart->rx_stream); - free(js_uart); + furi_stream_buffer_free(js_serial->rx_stream); + free(js_serial); } -static const JsModuleDescriptor js_uart_desc = { - "uart", - js_uart_create, - js_uart_destroy, +static const JsModuleDescriptor js_serial_desc = { + "serial", + js_serial_create, + js_serial_destroy, }; static const FlipperAppPluginDescriptor plugin_descriptor = { .appid = PLUGIN_APP_ID, .ep_api_version = PLUGIN_API_VERSION, - .entry_point = &js_uart_desc, + .entry_point = &js_serial_desc, }; -const FlipperAppPluginDescriptor* js_uart_ep(void) { +const FlipperAppPluginDescriptor* js_serial_ep(void) { return &plugin_descriptor; } From 3d9a6a41db1b1e031e349647f5748d4257ed2ca8 Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 14 Feb 2024 02:37:04 +0000 Subject: [PATCH 32/58] FIX ISO15693 emulation (#3444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * furi hal gpio: allow enabling interrupt without handler * signal reader: explicitly enable and disable gpio interrupt Co-authored-by: あく --- lib/signal_reader/signal_reader.c | 3 +++ targets/f7/furi_hal/furi_hal_gpio.c | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/signal_reader/signal_reader.c b/lib/signal_reader/signal_reader.c index 1c08d29f455..c06c7a5c0e5 100644 --- a/lib/signal_reader/signal_reader.c +++ b/lib/signal_reader/signal_reader.c @@ -228,6 +228,7 @@ void signal_reader_start(SignalReader* instance, SignalReaderCallback callback, /* We need the EXTI to be configured as interrupt generating line, but no ISR registered */ furi_hal_gpio_init( instance->pin, GpioModeInterruptRiseFall, instance->pull, GpioSpeedVeryHigh); + furi_hal_gpio_enable_int_callback(instance->pin); /* Set DMAMUX request generation signal ID on specified DMAMUX channel */ LL_DMAMUX_SetRequestSignalID( @@ -309,6 +310,8 @@ void signal_reader_stop(SignalReader* instance) { furi_hal_interrupt_set_isr(SIGNAL_READER_DMA_GPIO_IRQ, NULL, NULL); + furi_hal_gpio_disable_int_callback(instance->pin); + // Deinit DMA Rx pin LL_DMA_DeInit(SIGNAL_READER_DMA_GPIO_DEF); // Deinit DMA Sync timer diff --git a/targets/f7/furi_hal/furi_hal_gpio.c b/targets/f7/furi_hal/furi_hal_gpio.c index 1e703b5d16d..da195bdca05 100644 --- a/targets/f7/furi_hal/furi_hal_gpio.c +++ b/targets/f7/furi_hal/furi_hal_gpio.c @@ -215,11 +215,8 @@ void furi_hal_gpio_enable_int_callback(const GpioPin* gpio) { FURI_CRITICAL_ENTER(); - uint8_t pin_num = furi_hal_gpio_get_pin_num(gpio); - if(gpio_interrupt[pin_num].callback) { - const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); - LL_EXTI_EnableIT_0_31(exti_line); - } + const uint32_t exti_line = GET_EXTI_LINE(gpio->pin); + LL_EXTI_EnableIT_0_31(exti_line); FURI_CRITICAL_EXIT(); } From 0acd3bfc66a0091a8445e4f4ea6c746614e95267 Mon Sep 17 00:00:00 2001 From: hedger Date: Wed, 14 Feb 2024 07:05:59 +0400 Subject: [PATCH 33/58] VSCode integration fixes for new toolchain (#3448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fbtenv: link toolchain to current on *nix * vscode: simplified tool paths * fbtenv: link toolchain to 'current' on Windows Co-authored-by: あく --- .vscode/example/settings.json | 12 +++--------- scripts/toolchain/fbtenv.cmd | 6 +++--- scripts/toolchain/fbtenv.sh | 3 +++ scripts/toolchain/windows-toolchain-download.ps1 | 9 +++++++++ 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index efa08157b62..8199171464b 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -6,15 +6,9 @@ "cortex-debug.enableTelemetry": false, "cortex-debug.variableUseNaturalFormat": true, "cortex-debug.showRTOS": true, - "cortex-debug.armToolchainPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin", - "cortex-debug.armToolchainPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin", - "cortex-debug.armToolchainPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin", - "cortex-debug.openocdPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/openocd/bin/openocd.exe", - "cortex-debug.openocdPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/openocd/bin/openocd", - "cortex-debug.openocdPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/openocd/bin/openocd", - "cortex-debug.gdbPath.windows": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gdb-py.bat", - "cortex-debug.gdbPath.linux": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gdb-py", - "cortex-debug.gdbPath.osx": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gdb-py", + "cortex-debug.armToolchainPath": "${workspaceFolder}/toolchain/current/bin", + "cortex-debug.openocdPath": "${workspaceFolder}/toolchain/current/bin/openocd", + "cortex-debug.gdbPath": "${workspaceFolder}/toolchain/current/bin/arm-none-eabi-gdb-py3", "editor.formatOnSave": true, "files.associations": { "*.scons": "python", diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 186cbf28a53..446f04aa7d8 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -24,17 +24,17 @@ set "FBT_TOOLCHAIN_ROOT=%FBT_TOOLCHAIN_PATH%\toolchain\x86_64-windows" set "FBT_TOOLCHAIN_VERSION_FILE=%FBT_TOOLCHAIN_ROOT%\VERSION" if not exist "%FBT_TOOLCHAIN_ROOT%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" || exit /b ) if not exist "%FBT_TOOLCHAIN_VERSION_FILE%" ( - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" || exit /b ) set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" if not "%REAL_TOOLCHAIN_VERSION%" == "%FLIPPER_TOOLCHAIN_VERSION%" ( echo FBT: starting toolchain upgrade process.. - powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" + powershell -ExecutionPolicy Bypass -File "%FBT_ROOT%\scripts\toolchain\windows-toolchain-download.ps1" %flipper_toolchain_version% "%FBT_TOOLCHAIN_ROOT%" || exit /b set /p REAL_TOOLCHAIN_VERSION=<"%FBT_TOOLCHAIN_VERSION_FILE%" ) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index ea7c9019b8a..27fb8f975b5 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -203,9 +203,12 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; + rm $FBT_TOOLCHAIN_PATH/toolchain/current || true; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; + printf "linking toolchain to 'current'.."; + ln -s "$TOOLCHAIN_ARCH_DIR" "$FBT_TOOLCHAIN_PATH/toolchain/current" || return 1; echo "done"; return 0; } diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 9a4d2e18f5f..11d8a564d30 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -18,6 +18,13 @@ if (Test-Path -LiteralPath "$toolchain_target_path") { Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse Write-Host "done!" } + +if (Test-path -Path "$toolchain_target_path\..\current") { + Write-Host -NoNewline "Unlinking 'current'.." + Remove-Item -LiteralPath "$toolchain_target_path\..\current" -Force + Write-Host "done!" +} + if (!(Test-Path -Path "$toolchain_zip_temp_path" -PathType Leaf)) { Write-Host -NoNewline "Downloading Windows toolchain.." $wc = New-Object net.webclient @@ -42,6 +49,8 @@ Add-Type -Assembly "System.IO.Compression.Filesystem" Write-Host -NoNewline "moving.." Move-Item -LiteralPath "$toolchain_dist_temp_path" -Destination "$toolchain_target_path" -Force +Write-Host -NoNewline "linking to 'current'.." +cmd /c mklink /J "$toolchain_target_path\..\current" "$toolchain_target_path" Write-Host "done!" Write-Host -NoNewline "Cleaning up temporary files.." From 13493916e22b77fd1e817c44301323635416bc75 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Wed, 14 Feb 2024 06:14:29 +0300 Subject: [PATCH 34/58] JS debug disabled, archive and file browser fixes (#3446) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/archive/helpers/archive_files.c | 2 +- applications/services/gui/modules/file_browser_worker.c | 1 + applications/system/js_app/js_thread.c | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index a8bd937c99b..0d32e37bfb9 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -15,7 +15,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder } else { for(size_t i = 0; i < COUNT_OF(known_ext); i++) { if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; - if(furi_string_search(file->path, known_ext[i], 0) != FURI_STRING_FAILURE) { + if(furi_string_end_with(file->path, known_ext[i])) { if(i == ArchiveFileTypeBadUsb) { if(furi_string_search( file->path, archive_get_default_path(ArchiveTabBadUsb)) == 0) { diff --git a/applications/services/gui/modules/file_browser_worker.c b/applications/services/gui/modules/file_browser_worker.c index 2636c9555f1..b5d8aee9ae7 100644 --- a/applications/services/gui/modules/file_browser_worker.c +++ b/applications/services/gui/modules/file_browser_worker.c @@ -80,6 +80,7 @@ static bool browser_path_trim(FuriString* path) { return is_root; } static void browser_parse_ext_filter(ExtFilterArray_t ext_filter, const char* filter_str) { + ExtFilterArray_reset(ext_filter); if(!filter_str) { return; } diff --git a/applications/system/js_app/js_thread.c b/applications/system/js_app/js_thread.c index 22007d013d3..5ca3654041c 100644 --- a/applications/system/js_app/js_thread.c +++ b/applications/system/js_app/js_thread.c @@ -210,6 +210,7 @@ static void js_global_to_hex_string(struct mjs* mjs) { mjs_return(mjs, ret); } +#ifdef JS_DEBUG static void js_dump_write_callback(void* ctx, const char* format, ...) { File* file = ctx; furi_assert(ctx); @@ -225,6 +226,7 @@ static void js_dump_write_callback(void* ctx, const char* format, ...) { storage_file_write(file, furi_string_get_cstr(str), furi_string_size(str)); furi_string_free(str); } +#endif static int32_t js_thread(void* arg) { JsThread* worker = arg; @@ -255,6 +257,7 @@ static int32_t js_thread(void* arg) { mjs_err_t err = mjs_exec_file(mjs, furi_string_get_cstr(worker->path), NULL); +#ifdef JS_DEBUG if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { FuriString* dump_path = furi_string_alloc_set(worker->path); furi_string_cat(dump_path, ".lst"); @@ -273,6 +276,7 @@ static int32_t js_thread(void* arg) { furi_string_free(dump_path); } +#endif if(err != MJS_OK) { FURI_LOG_E(TAG, "Exec error: %s", mjs_strerror(mjs, err)); From 3c77dc79308803a93cc853fee6c52412bc2b1a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6ren?= Date: Wed, 14 Feb 2024 04:20:36 +0100 Subject: [PATCH 35/58] Add Model JTC Genesis 5.5 to IR (#3388) Co-authored-by: Aleksandr Kutuzov --- .../infrared/resources/infrared/assets/tv.ir | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/applications/main/infrared/resources/infrared/assets/tv.ir b/applications/main/infrared/resources/infrared/assets/tv.ir index ff8101b9087..fcbbf28dcc4 100644 --- a/applications/main/infrared/resources/infrared/assets/tv.ir +++ b/applications/main/infrared/resources/infrared/assets/tv.ir @@ -1762,3 +1762,41 @@ type: parsed protocol: RCA address: 0F 00 00 00 command: 34 00 00 00 +# +# Model: JTC Genesis 5.5 +# +name: Mute +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 5C A3 00 00 +# +name: Power +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 1E E1 00 00 +# +name: Vol_up +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 0A F5 00 00 +# +name: Vol_dn +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 06 F9 00 00 +# +name: Ch_next +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 48 B7 00 00 +# +name: Ch_prev +type: parsed +protocol: NECext +address: 01 72 00 00 +command: 44 BB 00 00 From a46038acbf576a3102c6d89c0f142c537e3cae1f Mon Sep 17 00:00:00 2001 From: gornekich Date: Wed, 14 Feb 2024 04:19:05 +0000 Subject: [PATCH 36/58] Gui text box: fix formatted string memory reservation (#3447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * text box: reserve correct memory size to avoid reallocation * Furi: change string reset behavior to release memory, prevent realloc call. Gui: fix NULL-ptr dereference in TextBox. Co-authored-by: あく --- applications/services/gui/modules/text_box.c | 16 ++++++++++++---- furi/core/string.c | 3 ++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/applications/services/gui/modules/text_box.c b/applications/services/gui/modules/text_box.c index 0e4aae9446b..c0110806a73 100644 --- a/applications/services/gui/modules/text_box.c +++ b/applications/services/gui/modules/text_box.c @@ -4,6 +4,9 @@ #include #include +#define TEXT_BOX_MAX_SYMBOL_WIDTH (10) +#define TEXT_BOX_LINE_WIDTH (120) + struct TextBox { View* view; @@ -78,13 +81,11 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { const char* str = model->text; size_t line_num = 0; - const size_t text_width = 120; - while(str[i] != '\0') { char symb = str[i++]; if(symb != '\n') { size_t glyph_width = canvas_glyph_width(canvas, symb); - if(line_width + glyph_width > text_width) { + if(line_width + glyph_width > TEXT_BOX_LINE_WIDTH) { line_num++; line_width = 0; furi_string_push_back(model->text_formatted, '\n'); @@ -116,6 +117,10 @@ static void text_box_insert_endline(Canvas* canvas, TextBoxModel* model) { static void text_box_view_draw_callback(Canvas* canvas, void* _model) { TextBoxModel* model = _model; + if(!model->text) { + return; + } + canvas_clear(canvas); if(model->font == TextBoxFontText) { canvas_set_font(canvas, FontSecondary); @@ -211,6 +216,7 @@ void text_box_reset(TextBox* text_box) { furi_string_set(model->text_formatted, ""); model->font = TextBoxFontText; model->focus = TextBoxFocusStart; + model->formatted = false; }, true); } @@ -218,6 +224,8 @@ void text_box_reset(TextBox* text_box) { void text_box_set_text(TextBox* text_box, const char* text) { furi_assert(text_box); furi_assert(text); + size_t str_length = strlen(text); + size_t formating_margin = str_length * TEXT_BOX_MAX_SYMBOL_WIDTH / TEXT_BOX_LINE_WIDTH; with_view_model( text_box->view, @@ -225,7 +233,7 @@ void text_box_set_text(TextBox* text_box, const char* text) { { model->text = text; furi_string_reset(model->text_formatted); - furi_string_reserve(model->text_formatted, strlen(text)); + furi_string_reserve(model->text_formatted, str_length + formating_margin); model->formatted = false; }, true); diff --git a/furi/core/string.c b/furi/core/string.c index 682c8d40977..a09ff953f92 100644 --- a/furi/core/string.c +++ b/furi/core/string.c @@ -71,7 +71,8 @@ void furi_string_reserve(FuriString* s, size_t alloc) { } void furi_string_reset(FuriString* s) { - string_reset(s->string); + string_clear(s->string); + string_init(s->string); } void furi_string_swap(FuriString* v1, FuriString* v2) { From dd988ba4498403f52648499c32f966e1a5222650 Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:41:42 +0900 Subject: [PATCH 37/58] bit_lib and nfc_util refactor (#3383) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * nfc_util functions for processing bytes moved into bit_lib * bitlib test update * bit_lib moved from lfrfid to standalone lib * Added bit functions for any supported data types * Error fix and api add * Added test for 64 * Added doc * Testcase for 64 rewrited * Realization error fix * API version bump * sync api version, fix after-merge old libs usage * fix build errors * build fix * fbt format Co-authored-by: assasinfil Co-authored-by: gornekich Co-authored-by: あく --- .../{lfrfid => bit_lib}/bit_lib_test.c | 269 +++++++++++++++++- .../main/nfc/helpers/mfkey32_logger.c | 14 +- .../main/nfc/plugins/supported_cards/aime.c | 10 +- .../nfc/plugins/supported_cards/clipper.c | 8 +- .../nfc/plugins/supported_cards/gallagher.c | 7 +- .../main/nfc/plugins/supported_cards/hi.c | 15 +- .../main/nfc/plugins/supported_cards/hid.c | 10 +- .../nfc/plugins/supported_cards/microel.c | 20 +- .../main/nfc/plugins/supported_cards/mizip.c | 15 +- .../nfc/plugins/supported_cards/plantain.c | 11 +- .../main/nfc/plugins/supported_cards/troika.c | 11 +- .../nfc/plugins/supported_cards/two_cities.c | 12 +- .../main/nfc/plugins/supported_cards/umarsh.c | 22 +- .../nfc/plugins/supported_cards/washcity.c | 18 +- .../nfc/scenes/nfc_scene_slix_key_input.c | 5 +- lib/ReadMe.md | 1 + lib/SConscript | 1 + lib/bit_lib/SConscript | 22 ++ lib/{lfrfid/tools => bit_lib}/bit_lib.c | 118 ++++++++ lib/{lfrfid/tools => bit_lib}/bit_lib.h | 57 ++++ lib/lfrfid/SConscript | 1 - lib/lfrfid/lfrfid_dict_file.c | 2 +- lib/lfrfid/lfrfid_worker_modes.c | 2 +- lib/lfrfid/protocols/protocol_awid.c | 2 +- lib/lfrfid/protocols/protocol_fdx_a.c | 2 +- lib/lfrfid/protocols/protocol_fdx_b.c | 2 +- lib/lfrfid/protocols/protocol_gallagher.c | 2 +- .../protocols/protocol_hid_ex_generic.c | 2 +- lib/lfrfid/protocols/protocol_hid_generic.c | 2 +- lib/lfrfid/protocols/protocol_idteck.c | 2 +- lib/lfrfid/protocols/protocol_indala26.c | 2 +- lib/lfrfid/protocols/protocol_io_prox_xsf.c | 2 +- lib/lfrfid/protocols/protocol_jablotron.c | 2 +- lib/lfrfid/protocols/protocol_keri.c | 2 +- lib/lfrfid/protocols/protocol_nexwatch.c | 2 +- lib/lfrfid/protocols/protocol_pac_stanley.c | 2 +- lib/lfrfid/protocols/protocol_paradox.c | 2 +- lib/lfrfid/protocols/protocol_pyramid.c | 2 +- lib/lfrfid/protocols/protocol_viking.c | 2 +- lib/nfc/helpers/nfc_util.c | 35 --- lib/nfc/helpers/nfc_util.h | 6 - lib/nfc/protocols/mf_classic/crypto1.c | 3 +- lib/nfc/protocols/mf_classic/mf_classic.c | 10 +- .../mf_classic/mf_classic_listener.c | 20 +- .../protocols/mf_classic/mf_classic_poller.c | 16 +- .../mf_classic/mf_classic_poller_i.c | 2 +- .../mf_classic/mf_classic_poller_i.h | 2 +- .../protocols/mf_ultralight/mf_ultralight.c | 8 +- .../mf_ultralight/mf_ultralight_poller.c | 6 +- .../mf_ultralight/mf_ultralight_poller_i.h | 2 +- lib/nfc/protocols/slix/slix_poller_i.c | 4 +- targets/f18/api_symbols.csv | 29 +- targets/f18/target.json | 3 +- targets/f7/api_symbols.csv | 13 +- targets/f7/target.json | 3 +- 55 files changed, 661 insertions(+), 184 deletions(-) rename applications/debug/unit_tests/{lfrfid => bit_lib}/bit_lib_test.c (67%) create mode 100644 lib/bit_lib/SConscript rename lib/{lfrfid/tools => bit_lib}/bit_lib.c (69%) rename lib/{lfrfid/tools => bit_lib}/bit_lib.h (81%) diff --git a/applications/debug/unit_tests/lfrfid/bit_lib_test.c b/applications/debug/unit_tests/bit_lib/bit_lib_test.c similarity index 67% rename from applications/debug/unit_tests/lfrfid/bit_lib_test.c rename to applications/debug/unit_tests/bit_lib/bit_lib_test.c index dcb69de7f4f..42b494413e4 100644 --- a/applications/debug/unit_tests/lfrfid/bit_lib_test.c +++ b/applications/debug/unit_tests/bit_lib/bit_lib_test.c @@ -1,6 +1,6 @@ #include #include "../minunit.h" -#include +#include MU_TEST(test_bit_lib_increment_index) { uint32_t index = 0; @@ -218,6 +218,178 @@ MU_TEST(test_bit_lib_get_bits_32) { mu_assert_int_eq(0b00001001101100011000110001100010, bit_lib_get_bits_32(value, 0, 32)); } +MU_TEST(test_bit_lib_get_bits_64) { + uint8_t value[8] = { + 0b00001001, + 0b10110001, + 0b10001100, + 0b01100010, + 0b00001001, + 0b10110001, + 0b10001100, + 0b01100010}; + mu_assert_int_eq(0b0, bit_lib_get_bits_64(value, 0, 1)); + mu_assert_int_eq(0b00, bit_lib_get_bits_64(value, 0, 2)); + mu_assert_int_eq(0b000, bit_lib_get_bits_64(value, 0, 3)); + mu_assert_int_eq(0b0000, bit_lib_get_bits_64(value, 0, 4)); + mu_assert_int_eq(0b00001, bit_lib_get_bits_64(value, 0, 5)); + mu_assert_int_eq(0b000010, bit_lib_get_bits_64(value, 0, 6)); + mu_assert_int_eq(0b0000100, bit_lib_get_bits_64(value, 0, 7)); + mu_assert_int_eq(0b00001001, bit_lib_get_bits_64(value, 0, 8)); + mu_assert_int_eq(0b000010011, bit_lib_get_bits_64(value, 0, 9)); + mu_assert_int_eq(0b0000100110, bit_lib_get_bits_64(value, 0, 10)); + mu_assert_int_eq(0b00001001101, bit_lib_get_bits_64(value, 0, 11)); + mu_assert_int_eq(0b000010011011, bit_lib_get_bits_64(value, 0, 12)); + mu_assert_int_eq(0b0000100110110, bit_lib_get_bits_64(value, 0, 13)); + mu_assert_int_eq(0b00001001101100, bit_lib_get_bits_64(value, 0, 14)); + mu_assert_int_eq(0b000010011011000, bit_lib_get_bits_64(value, 0, 15)); + mu_assert_int_eq(0b0000100110110001, bit_lib_get_bits_64(value, 0, 16)); + mu_assert_int_eq(0b00001001101100011, bit_lib_get_bits_64(value, 0, 17)); + mu_assert_int_eq(0b000010011011000110, bit_lib_get_bits_64(value, 0, 18)); + mu_assert_int_eq(0b0000100110110001100, bit_lib_get_bits_64(value, 0, 19)); + mu_assert_int_eq(0b00001001101100011000, bit_lib_get_bits_64(value, 0, 20)); + mu_assert_int_eq(0b000010011011000110001, bit_lib_get_bits_64(value, 0, 21)); + mu_assert_int_eq(0b0000100110110001100011, bit_lib_get_bits_64(value, 0, 22)); + mu_assert_int_eq(0b00001001101100011000110, bit_lib_get_bits_64(value, 0, 23)); + mu_assert_int_eq(0b000010011011000110001100, bit_lib_get_bits_64(value, 0, 24)); + mu_assert_int_eq(0b0000100110110001100011000, bit_lib_get_bits_64(value, 0, 25)); + mu_assert_int_eq(0b00001001101100011000110001, bit_lib_get_bits_64(value, 0, 26)); + mu_assert_int_eq(0b000010011011000110001100011, bit_lib_get_bits_64(value, 0, 27)); + mu_assert_int_eq(0b0000100110110001100011000110, bit_lib_get_bits_64(value, 0, 28)); + mu_assert_int_eq(0b00001001101100011000110001100, bit_lib_get_bits_64(value, 0, 29)); + mu_assert_int_eq(0b000010011011000110001100011000, bit_lib_get_bits_64(value, 0, 30)); + mu_assert_int_eq(0b0000100110110001100011000110001, bit_lib_get_bits_64(value, 0, 31)); + mu_assert_int_eq(0b00001001101100011000110001100010, bit_lib_get_bits_64(value, 0, 32)); + + uint64_t res = bit_lib_get_bits_64(value, 0, 33); + uint64_t expected = 0b000010011011000110001100011000100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 34); + expected = 0b0000100110110001100011000110001000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 35); + expected = 0b00001001101100011000110001100010000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 36); + expected = 0b000010011011000110001100011000100000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 37); + expected = 0b0000100110110001100011000110001000001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 38); + expected = 0b00001001101100011000110001100010000010; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 39); + expected = 0b000010011011000110001100011000100000100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 40); + expected = 0b0000100110110001100011000110001000001001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 41); + expected = 0b00001001101100011000110001100010000010011; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 42); + expected = 0b000010011011000110001100011000100000100110; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 43); + expected = 0b0000100110110001100011000110001000001001101; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 44); + expected = 0b00001001101100011000110001100010000010011011; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 45); + expected = 0b000010011011000110001100011000100000100110110; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 46); + expected = 0b0000100110110001100011000110001000001001101100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 47); + expected = 0b00001001101100011000110001100010000010011011000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 48); + expected = 0b000010011011000110001100011000100000100110110001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 49); + expected = 0b0000100110110001100011000110001000001001101100011; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 50); + expected = 0b00001001101100011000110001100010000010011011000110; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 51); + expected = 0b000010011011000110001100011000100000100110110001100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 52); + expected = 0b0000100110110001100011000110001000001001101100011000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 53); + expected = 0b00001001101100011000110001100010000010011011000110001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 54); + expected = 0b000010011011000110001100011000100000100110110001100011; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 55); + expected = 0b0000100110110001100011000110001000001001101100011000110; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 56); + expected = 0b00001001101100011000110001100010000010011011000110001100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 57); + expected = 0b000010011011000110001100011000100000100110110001100011000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 58); + expected = 0b0000100110110001100011000110001000001001101100011000110001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 59); + expected = 0b00001001101100011000110001100010000010011011000110001100011; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 60); + expected = 0b000010011011000110001100011000100000100110110001100011000110; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 61); + expected = 0b0000100110110001100011000110001000001001101100011000110001100; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 62); + expected = 0b00001001101100011000110001100010000010011011000110001100011000; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 63); + expected = 0b000010011011000110001100011000100000100110110001100011000110001; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + + res = bit_lib_get_bits_64(value, 0, 64); + expected = 0b0000100110110001100011000110001000001001101100011000110001100010; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); +} + MU_TEST(test_bit_lib_test_parity_u32) { // test even parity mu_assert_int_eq(bit_lib_test_parity_32(0b00000000, BitLibParityEven), 0); @@ -447,6 +619,95 @@ MU_TEST(test_bit_lib_crc16) { mu_assert_int_eq(0x31C3, bit_lib_crc16(data, data_size, 0x1021, 0x0000, false, false, 0x0000)); } +MU_TEST(test_bit_lib_num_to_bytes_be) { + uint8_t src[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + uint8_t dest[8]; + + bit_lib_num_to_bytes_be(0x01, 1, dest); + mu_assert_mem_eq(src, dest, sizeof(src[0])); + + bit_lib_num_to_bytes_be(0x0123456789ABCDEF, 4, dest); + mu_assert_mem_eq(src + 4, dest, 4 * sizeof(src[0])); + + bit_lib_num_to_bytes_be(0x0123456789ABCDEF, 8, dest); + mu_assert_mem_eq(src, dest, 8 * sizeof(src[0])); + + bit_lib_num_to_bytes_be(bit_lib_bytes_to_num_be(src, 8), 8, dest); + mu_assert_mem_eq(src, dest, 8 * sizeof(src[0])); +} + +MU_TEST(test_bit_lib_num_to_bytes_le) { + uint8_t dest[8]; + + uint8_t n2b_le_expected_1[] = {0x01}; + bit_lib_num_to_bytes_le(0x01, 1, dest); + mu_assert_mem_eq(n2b_le_expected_1, dest, sizeof(n2b_le_expected_1[0])); + + uint8_t n2b_le_expected_2[] = {0xEF, 0xCD, 0xAB, 0x89}; + bit_lib_num_to_bytes_le(0x0123456789ABCDEF, 4, dest); + mu_assert_mem_eq(n2b_le_expected_2, dest, 4 * sizeof(n2b_le_expected_2[0])); + + uint8_t n2b_le_expected_3[] = {0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01}; + bit_lib_num_to_bytes_le(0x0123456789ABCDEF, 8, dest); + mu_assert_mem_eq(n2b_le_expected_3, dest, 8 * sizeof(n2b_le_expected_3[0])); + + bit_lib_num_to_bytes_le(bit_lib_bytes_to_num_le(n2b_le_expected_3, 8), 8, dest); + mu_assert_mem_eq(n2b_le_expected_3, dest, 8 * sizeof(n2b_le_expected_3[0])); +} + +MU_TEST(test_bit_lib_bytes_to_num_be) { + uint8_t src[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + uint64_t res; + + res = bit_lib_bytes_to_num_be(src, 1); + mu_assert_int_eq(0x01, res); + + res = bit_lib_bytes_to_num_be(src, 4); + mu_assert_int_eq(0x01234567, res); + + res = bit_lib_bytes_to_num_be(src, 8); + uint64_t expected = 0x0123456789ABCDEF; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); +} + +MU_TEST(test_bit_lib_bytes_to_num_le) { + uint8_t src[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + uint64_t res; + + res = bit_lib_bytes_to_num_le(src, 1); + mu_assert_int_eq(0x01, res); + + res = bit_lib_bytes_to_num_le(src, 4); + mu_assert_int_eq(0x67452301, res); + + res = bit_lib_bytes_to_num_le(src, 8); + uint64_t expected = 0xEFCDAB8967452301; + mu_assert_mem_eq(&expected, &res, sizeof(expected)); +} + +MU_TEST(test_bit_lib_bytes_to_num_bcd) { + uint8_t src[8] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF}; + uint64_t res; + bool is_bcd_res; + + res = bit_lib_bytes_to_num_bcd(src, 1, &is_bcd_res); + mu_assert_int_eq(01, res); + mu_assert_int_eq(true, is_bcd_res); + + res = bit_lib_bytes_to_num_bcd(src, 4, &is_bcd_res); + mu_assert_int_eq(1234567, res); + mu_assert_int_eq(true, is_bcd_res); + + uint8_t digits[5] = {0x98, 0x76, 0x54, 0x32, 0x10}; + uint64_t expected = 9876543210; + res = bit_lib_bytes_to_num_bcd(digits, 5, &is_bcd_res); + mu_assert_mem_eq(&expected, &res, sizeof(expected)); + mu_assert_int_eq(true, is_bcd_res); + + res = bit_lib_bytes_to_num_bcd(src, 8, &is_bcd_res); + mu_assert_int_eq(false, is_bcd_res); +} + MU_TEST_SUITE(test_bit_lib) { MU_RUN_TEST(test_bit_lib_increment_index); MU_RUN_TEST(test_bit_lib_is_set); @@ -457,6 +718,7 @@ MU_TEST_SUITE(test_bit_lib) { MU_RUN_TEST(test_bit_lib_get_bits); MU_RUN_TEST(test_bit_lib_get_bits_16); MU_RUN_TEST(test_bit_lib_get_bits_32); + MU_RUN_TEST(test_bit_lib_get_bits_64); MU_RUN_TEST(test_bit_lib_test_parity_u32); MU_RUN_TEST(test_bit_lib_test_parity); MU_RUN_TEST(test_bit_lib_remove_bit_every_nth); @@ -465,6 +727,11 @@ MU_TEST_SUITE(test_bit_lib) { MU_RUN_TEST(test_bit_lib_get_bit_count); MU_RUN_TEST(test_bit_lib_reverse_16_fast); MU_RUN_TEST(test_bit_lib_crc16); + MU_RUN_TEST(test_bit_lib_num_to_bytes_be); + MU_RUN_TEST(test_bit_lib_num_to_bytes_le); + MU_RUN_TEST(test_bit_lib_bytes_to_num_be); + MU_RUN_TEST(test_bit_lib_bytes_to_num_le); + MU_RUN_TEST(test_bit_lib_bytes_to_num_bcd); } int run_minunit_test_bit_lib() { diff --git a/applications/main/nfc/helpers/mfkey32_logger.c b/applications/main/nfc/helpers/mfkey32_logger.c index 8024179f809..bdd899a3684 100644 --- a/applications/main/nfc/helpers/mfkey32_logger.c +++ b/applications/main/nfc/helpers/mfkey32_logger.c @@ -2,7 +2,7 @@ #include -#include +#include #include #include @@ -63,9 +63,9 @@ static bool mfkey32_logger_add_nonce_to_existing_params( if(params->sector_num != sector_num) continue; if(params->key_type != auth_context->key_type) continue; - params->nt1 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)); - params->nr1 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)); - params->ar1 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)); + params->nt1 = bit_lib_bytes_to_num_be(auth_context->nt.data, sizeof(MfClassicNt)); + params->nr1 = bit_lib_bytes_to_num_be(auth_context->nr.data, sizeof(MfClassicNr)); + params->ar1 = bit_lib_bytes_to_num_be(auth_context->ar.data, sizeof(MfClassicAr)); params->is_filled = true; instance->params_collected++; @@ -90,9 +90,9 @@ void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* aut .cuid = instance->cuid, .sector_num = sector_num, .key_type = auth_context->key_type, - .nt0 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)), - .nr0 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)), - .ar0 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)), + .nt0 = bit_lib_bytes_to_num_be(auth_context->nt.data, sizeof(MfClassicNt)), + .nr0 = bit_lib_bytes_to_num_be(auth_context->nr.data, sizeof(MfClassicNr)), + .ar0 = bit_lib_bytes_to_num_be(auth_context->ar.data, sizeof(MfClassicAr)), }; Mfkey32LoggerParams_push_back(instance->params_arr, params); instance->nonces_saves++; diff --git a/applications/main/nfc/plugins/supported_cards/aime.c b/applications/main/nfc/plugins/supported_cards/aime.c index 50052f0d199..ab122d92b94 100644 --- a/applications/main/nfc/plugins/supported_cards/aime.c +++ b/applications/main/nfc/plugins/supported_cards/aime.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #define TAG "Aime" @@ -19,7 +19,7 @@ bool aime_verify(Nfc* nfc) { FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); MfClassicKey key = {}; - nfc_util_num2bytes(aime_key, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(aime_key, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_ctx = {}; MfClassicError error = @@ -53,9 +53,9 @@ static bool aime_read(Nfc* nfc, NfcDevice* device) { data->type = type; MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(aime_key, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(aime_key, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(aime_key, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -85,7 +85,7 @@ static bool aime_parse(const NfcDevice* device, FuriString* parsed_data) { do { // verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); if(key != aime_key) break; // Aime Magic is stored at block 1, starts from byte 0, len 4 bytes diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index c76fb1cdcf8..2878b6f3c63 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include @@ -194,12 +194,12 @@ static bool dump_ride_event(const uint8_t* record, FuriString* parsed_data); // Unmarshal a 32-bit integer, big endian, unsigned static inline uint32_t get_u32be(const uint8_t* field) { - return nfc_util_bytes2num(field, 4); + return bit_lib_bytes_to_num_be(field, 4); } // Unmarshal a 16-bit integer, big endian, unsigned static uint16_t get_u16be(const uint8_t* field) { - return nfc_util_bytes2num(field, 2); + return bit_lib_bytes_to_num_be(field, 2); } // Unmarshal a 16-bit integer, big endian, signed, two's-complement @@ -330,7 +330,7 @@ static bool decode_id_file(const uint8_t* ef8_data, ClipperCardInfo* info) { // uk ?8?? Unknown, 8-bit byte // card_id U32BE Card identifier // - info->serial_number = nfc_util_bytes2num(&ef8_data[1], 4); + info->serial_number = bit_lib_bytes_to_num_be(&ef8_data[1], 4); return true; } diff --git a/applications/main/nfc/plugins/supported_cards/gallagher.c b/applications/main/nfc/plugins/supported_cards/gallagher.c index 8b6a5898701..5772b46d70b 100644 --- a/applications/main/nfc/plugins/supported_cards/gallagher.c +++ b/applications/main/nfc/plugins/supported_cards/gallagher.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include static bool gallagher_parse(const NfcDevice* device, FuriString* parsed_data) { furi_assert(device); @@ -30,8 +30,9 @@ static bool gallagher_parse(const NfcDevice* device, FuriString* parsed_data) { // Test 1: The first 8 bytes and the second 8 bytes should be bitwise inverses. const uint8_t* credential_block_start_ptr = &data->block[credential_sector_start_block_number].data[0]; - uint64_t cardholder_credential = nfc_util_bytes2num(credential_block_start_ptr, 8); - uint64_t cardholder_credential_inverse = nfc_util_bytes2num(credential_block_start_ptr + 8, 8); + uint64_t cardholder_credential = bit_lib_bytes_to_num_be(credential_block_start_ptr, 8); + uint64_t cardholder_credential_inverse = + bit_lib_bytes_to_num_be(credential_block_start_ptr + 8, 8); // Due to endianness, this is testing the bytes in the wrong order, // but the result still should be correct. if(cardholder_credential != ~cardholder_credential_inverse) { diff --git a/applications/main/nfc/plugins/supported_cards/hi.c b/applications/main/nfc/plugins/supported_cards/hi.c index 6807ab00ca8..9f8b0afae2e 100644 --- a/applications/main/nfc/plugins/supported_cards/hi.c +++ b/applications/main/nfc/plugins/supported_cards/hi.c @@ -1,9 +1,8 @@ #include "nfc_supported_card_plugin.h" #include #include -#include +#include #include -#include #define TAG "HI!" #define KEY_LENGTH 6 @@ -91,7 +90,7 @@ static bool hi_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; - nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = @@ -139,16 +138,16 @@ static bool hi_read(Nfc* nfc, NfcDevice* device) { for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { - cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); - cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + cfg.keys[i].a = bit_lib_bytes_to_num_be(keyA[i], KEY_LENGTH); + cfg.keys[i].b = bit_lib_bytes_to_num_be(keyB[i], KEY_LENGTH); } } MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -184,7 +183,7 @@ static bool hi_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); if(key != cfg.keys[cfg.verify_sector].b) return false; //Get UID diff --git a/applications/main/nfc/plugins/supported_cards/hid.c b/applications/main/nfc/plugins/supported_cards/hid.c index b6b0a406ff8..1865f2e7a27 100644 --- a/applications/main/nfc/plugins/supported_cards/hid.c +++ b/applications/main/nfc/plugins/supported_cards/hid.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #define TAG "HID" @@ -19,7 +19,7 @@ bool hid_verify(Nfc* nfc) { FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); MfClassicKey key = {}; - nfc_util_num2bytes(hid_key, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(hid_key, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_ctx = {}; MfClassicError error = @@ -53,9 +53,9 @@ static bool hid_read(Nfc* nfc, NfcDevice* device) { data->type = type; MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(hid_key, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(hid_key, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(hid_key, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -111,7 +111,7 @@ static bool hid_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t verify_sector = 1; MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, verify_sector); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); if(key != hid_key) break; // Currently doesn't support bit length > 63 diff --git a/applications/main/nfc/plugins/supported_cards/microel.c b/applications/main/nfc/plugins/supported_cards/microel.c index 899ad2a7ea5..8a5129f7dc4 100644 --- a/applications/main/nfc/plugins/supported_cards/microel.c +++ b/applications/main/nfc/plugins/supported_cards/microel.c @@ -1,9 +1,8 @@ #include "nfc_supported_card_plugin.h" #include #include -#include +#include #include -#include #define TAG "Microel" #define KEY_LENGTH 6 @@ -116,7 +115,8 @@ static bool microel_read(Nfc* nfc, NfcDevice* device) { // Check key 0a to verify if it is a microel card MfClassicKey key = {0}; - nfc_util_num2bytes(nfc_util_bytes2num(keyA, KEY_LENGTH), COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be( + bit_lib_bytes_to_num_be(keyA, KEY_LENGTH), COUNT_OF(key.data), key.data); const uint8_t block_num = mf_classic_get_first_block_num_of_sector(0); // This is 0 MfClassicAuthContext auth_context; error = @@ -128,17 +128,19 @@ static bool microel_read(Nfc* nfc, NfcDevice* device) { // Save keys generated to stucture for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { if(microel_1k_keys[i].a == 0x000000000000) { - microel_1k_keys[i].a = nfc_util_bytes2num(keyA, KEY_LENGTH); + microel_1k_keys[i].a = bit_lib_bytes_to_num_be(keyA, KEY_LENGTH); } if(microel_1k_keys[i].b == 0x000000000000) { - microel_1k_keys[i].b = nfc_util_bytes2num(keyB, KEY_LENGTH); + microel_1k_keys[i].b = bit_lib_bytes_to_num_be(keyB, KEY_LENGTH); } } MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be( + microel_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(microel_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be( + microel_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -179,8 +181,8 @@ static bool microel_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, verify_sector); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); - uint64_t key_for_check_from_array = nfc_util_bytes2num(keyA, KEY_LENGTH); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + uint64_t key_for_check_from_array = bit_lib_bytes_to_num_be(keyA, KEY_LENGTH); if(key != key_for_check_from_array) break; //Get credit in block number 8 diff --git a/applications/main/nfc/plugins/supported_cards/mizip.c b/applications/main/nfc/plugins/supported_cards/mizip.c index b76c381e97b..6967c3228b6 100644 --- a/applications/main/nfc/plugins/supported_cards/mizip.c +++ b/applications/main/nfc/plugins/supported_cards/mizip.c @@ -1,9 +1,8 @@ #include "nfc_supported_card_plugin.h" #include #include -#include +#include #include -#include #define TAG "MiZIP" #define KEY_LENGTH 6 @@ -102,7 +101,7 @@ static bool mizip_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Verifying sector %lu", cfg.verify_sector); MfClassicKey key = {0}; - nfc_util_num2bytes(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(cfg.keys[cfg.verify_sector].b, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = @@ -152,16 +151,16 @@ static bool mizip_read(Nfc* nfc, NfcDevice* device) { for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { if(cfg.keys[i].a == 0x000000000000 && cfg.keys[i].b == 0x000000000000) { - cfg.keys[i].a = nfc_util_bytes2num(keyA[i], KEY_LENGTH); - cfg.keys[i].b = nfc_util_bytes2num(keyB[i], KEY_LENGTH); + cfg.keys[i].a = bit_lib_bytes_to_num_be(keyA[i], KEY_LENGTH); + cfg.keys[i].b = bit_lib_bytes_to_num_be(keyB[i], KEY_LENGTH); } } MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -197,7 +196,7 @@ static bool mizip_parse(const NfcDevice* device, FuriString* parsed_data) { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.verify_sector); - uint64_t key = nfc_util_bytes2num(sec_tr->key_b.data, 6); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); if(key != cfg.keys[cfg.verify_sector].b) return false; //Get UID diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index ba8f3429543..2ed02f49c6a 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #define TAG "Plantain" @@ -87,7 +87,7 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); MfClassicKey key = {0}; - nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = @@ -128,9 +128,9 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) { MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -166,7 +166,8 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) { const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); - const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; // Point to block 0 of sector 4, value 0 diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 20bd685eeb8..ba7c3bf5e3b 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include "furi_hal_rtc.h" @@ -221,7 +221,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector); MfClassicKey key = {0}; - nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = @@ -264,9 +264,9 @@ static bool troika_read(Nfc* nfc, NfcDevice* device) { .key_b_mask = 0, }; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -302,7 +302,8 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector); - const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; // Get the block number of the block that contains the data diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index 7e29cd08510..fa1b1c1845b 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #define TAG "TwoCities" @@ -45,7 +45,7 @@ bool two_cities_verify(Nfc* nfc) { FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); MfClassicKey key = {}; - nfc_util_num2bytes(two_cities_4k_keys[verify_sector].a, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be(two_cities_4k_keys[verify_sector].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_ctx = {}; MfClassicError error = @@ -78,9 +78,11 @@ static bool two_cities_read(Nfc* nfc, NfcDevice* device) { data->type = type; MfClassicDeviceKeys keys = {}; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(two_cities_4k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be( + two_cities_4k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(two_cities_4k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be( + two_cities_4k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -110,7 +112,7 @@ static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) { do { // Verify key MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4); - uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); if(key != two_cities_4k_keys[4].a) return false; // ===== diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index bf643d21c15..ca9eb769760 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -30,7 +30,7 @@ #include #include -#include +#include #include #include @@ -63,27 +63,27 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { // Validate specific for Umarsh ticket sector header const uint8_t* block_start_ptr = &data->block[ticket_sector_start_block_number].data[0]; - const uint32_t header_part_0 = nfc_util_bytes2num(block_start_ptr, 4); - const uint32_t header_part_1 = nfc_util_bytes2num(block_start_ptr + 4, 4); + const uint32_t header_part_0 = bit_lib_bytes_to_num_be(block_start_ptr, 4); + const uint32_t header_part_1 = bit_lib_bytes_to_num_be(block_start_ptr + 4, 4); if((header_part_0 + header_part_1) != 0xFFFFFFFF) break; // Data parsing from block 1 block_start_ptr = &data->block[ticket_sector_start_block_number + 1].data[0]; - const uint16_t expiry_date = nfc_util_bytes2num(block_start_ptr + 1, 2); + const uint16_t expiry_date = bit_lib_bytes_to_num_be(block_start_ptr + 1, 2); const uint8_t region_number = (((block_start_ptr[8] >> 5) & 0x07) << 4) | (block_start_ptr[12] & 0x0F); - const uint8_t refill_counter = nfc_util_bytes2num(block_start_ptr + 7, 1); - const uint32_t card_number = nfc_util_bytes2num(block_start_ptr + 8, 4) & 0x3FFFFFFF; + const uint8_t refill_counter = bit_lib_bytes_to_num_be(block_start_ptr + 7, 1); + const uint32_t card_number = bit_lib_bytes_to_num_be(block_start_ptr + 8, 4) & 0x3FFFFFFF; if(card_number == 0) break; // Data parsing from block 2 block_start_ptr = &data->block[ticket_sector_start_block_number + 2].data[0]; - const uint16_t valid_to = nfc_util_bytes2num(block_start_ptr, 2); - const uint32_t terminal_number = nfc_util_bytes2num(block_start_ptr + 3, 3); - const uint16_t last_refill_date = nfc_util_bytes2num(block_start_ptr + 6, 2); - const uint16_t balance_rub = (nfc_util_bytes2num(block_start_ptr + 8, 2)) & 0x7FFF; - const uint8_t balance_kop = nfc_util_bytes2num(block_start_ptr + 10, 1) & 0x7F; + const uint16_t valid_to = bit_lib_bytes_to_num_be(block_start_ptr, 2); + const uint32_t terminal_number = bit_lib_bytes_to_num_be(block_start_ptr + 3, 3); + const uint16_t last_refill_date = bit_lib_bytes_to_num_be(block_start_ptr + 6, 2); + const uint16_t balance_rub = (bit_lib_bytes_to_num_be(block_start_ptr + 8, 2)) & 0x7FFF; + const uint8_t balance_kop = bit_lib_bytes_to_num_be(block_start_ptr + 10, 1) & 0x7F; FuriHalRtcDateTime expiry_datetime; bool is_expiry_datetime_valid = parse_datetime(expiry_date, &expiry_datetime); diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index c90c8abbcd1..2c0ec094c16 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -24,7 +24,7 @@ #include #include -#include +#include #include #define TAG "WashCity" @@ -63,7 +63,8 @@ static bool washcity_verify(Nfc* nfc) { FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); MfClassicKey key = {0}; - nfc_util_num2bytes(washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + bit_lib_num_to_bytes_be( + washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = mf_classic_poller_sync_auth( @@ -101,9 +102,11 @@ static bool washcity_read(Nfc* nfc, NfcDevice* device) { .key_b_mask = 0, }; for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { - nfc_util_num2bytes(washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); + bit_lib_num_to_bytes_be( + washcity_1k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data); FURI_BIT_SET(keys.key_a_mask, i); - nfc_util_num2bytes(washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); + bit_lib_num_to_bytes_be( + washcity_1k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data); FURI_BIT_SET(keys.key_b_mask, i); } @@ -138,7 +141,8 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { const MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number); - const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); + const uint64_t key = + bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != washcity_1k_keys[ticket_sector_number].a) break; // Parse data @@ -148,7 +152,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t* block_start_ptr = &data->block[start_block_num + ticket_block_number].data[0]; - uint32_t balance = nfc_util_bytes2num(block_start_ptr + 2, 2); + uint32_t balance = bit_lib_bytes_to_num_be(block_start_ptr + 2, 2); uint32_t balance_eur = balance / 100; uint8_t balance_cents = balance % 100; @@ -157,7 +161,7 @@ static bool washcity_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t* uid = mf_classic_get_uid(data, &uid_len); // Card Number is printed in HEX (equal to UID) - uint64_t card_number = nfc_util_bytes2num(uid, uid_len); + uint64_t card_number = bit_lib_bytes_to_num_be(uid, uid_len); furi_string_printf( parsed_data, diff --git a/applications/main/nfc/scenes/nfc_scene_slix_key_input.c b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c index 1724eea8df8..6d04ca420f6 100644 --- a/applications/main/nfc/scenes/nfc_scene_slix_key_input.c +++ b/applications/main/nfc/scenes/nfc_scene_slix_key_input.c @@ -1,11 +1,12 @@ #include "../nfc_app_i.h" -#include +#include void nfc_scene_slix_key_input_byte_input_callback(void* context) { NfcApp* instance = context; - SlixPassword password = nfc_util_bytes2num(instance->byte_input_store, sizeof(SlixPassword)); + SlixPassword password = + bit_lib_bytes_to_num_be(instance->byte_input_store, sizeof(SlixPassword)); slix_unlock_set_password(instance->slix_unlock, password); view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone); } diff --git a/lib/ReadMe.md b/lib/ReadMe.md index 3adb7701877..ed0e4ce887c 100644 --- a/lib/ReadMe.md +++ b/lib/ReadMe.md @@ -3,6 +3,7 @@ - `FreeRTOS-Kernel` - FreeRTOS kernel source code - `FreeRTOS-glue` - Extra glue to hold together FreeRTOS kernel and flipper firmware - `app-scened-template` - C++ app library +- `bit_lib` - library for working with bits/bytes directly - `callback-connector` - Callback connector library - `cmsis_core` - CMSIS Core package, contain cortex-m core headers - `cxxheaderparser` - C++ headers parser, used by SDK bundler diff --git a/lib/SConscript b/lib/SConscript index 95f55b37de6..7bd00e4e424 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -42,6 +42,7 @@ libs = env.BuildModules( "nanopb", "update_util", "heatshrink", + "bit_lib", ], ) diff --git a/lib/bit_lib/SConscript b/lib/bit_lib/SConscript new file mode 100644 index 00000000000..bf77708a150 --- /dev/null +++ b/lib/bit_lib/SConscript @@ -0,0 +1,22 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/bit_lib", + ], + SDK_HEADERS=[ + File("bit_lib.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="bit_lib") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/lfrfid/tools/bit_lib.c b/lib/bit_lib/bit_lib.c similarity index 69% rename from lib/lfrfid/tools/bit_lib.c rename to lib/bit_lib/bit_lib.c index e0d0ff40213..e13ca95d48b 100644 --- a/lib/lfrfid/tools/bit_lib.c +++ b/lib/bit_lib/bit_lib.c @@ -78,6 +78,57 @@ uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t lengt return value; } +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length) { + uint64_t value = 0; + if(length <= 8) { + value = bit_lib_get_bits(data, position, length); + } else if(length <= 16) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, length - 8); + } else if(length <= 24) { + value = bit_lib_get_bits(data, position, 8) << (length - 8); + value |= bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= bit_lib_get_bits(data, position + 16, length - 16); + } else if(length <= 32) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= bit_lib_get_bits(data, position + 24, length - 24); + } else if(length <= 40) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= bit_lib_get_bits(data, position + 32, length - 32); + } else if(length <= 48) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= bit_lib_get_bits(data, position + 40, length - 40); + } else if(length <= 56) { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= bit_lib_get_bits(data, position + 48, length - 48); + } else { + value = (uint64_t)bit_lib_get_bits(data, position, 8) << (length - 8); + value |= (uint64_t)bit_lib_get_bits(data, position + 8, 8) << (length - 16); + value |= (uint64_t)bit_lib_get_bits(data, position + 16, 8) << (length - 24); + value |= (uint64_t)bit_lib_get_bits(data, position + 24, 8) << (length - 32); + value |= (uint64_t)bit_lib_get_bits(data, position + 32, 8) << (length - 40); + value |= (uint64_t)bit_lib_get_bits(data, position + 40, 8) << (length - 48); + value |= (uint64_t)bit_lib_get_bits(data, position + 48, 8) << (length - 56); + value |= bit_lib_get_bits(data, position + 56, length - 56); + } + + return value; +} + bool bit_lib_test_parity_32(uint32_t bits, BitLibParity parity) { #if !defined __GNUC__ #error Please, implement parity test for non-GCC compilers @@ -365,3 +416,70 @@ uint16_t bit_lib_crc16( return crc; } + +void bit_lib_num_to_bytes_be(uint64_t src, uint8_t len, uint8_t* dest) { + furi_assert(dest); + furi_assert(len <= 8); + + while(len--) { + dest[len] = (uint8_t)src; + src >>= 8; + } +} + +void bit_lib_num_to_bytes_le(uint64_t src, uint8_t len, uint8_t* dest) { + furi_assert(dest); + furi_assert(len <= 8); + + for(int i = 0; i < len; i++) { + dest[i] = (uint8_t)(src >> (8 * i)); + } +} + +uint64_t bit_lib_bytes_to_num_be(const uint8_t* src, uint8_t len) { + furi_assert(src); + furi_assert(len <= 8); + + uint64_t res = 0; + while(len--) { + res = (res << 8) | (*src); + src++; + } + return res; +} + +uint64_t bit_lib_bytes_to_num_le(const uint8_t* src, uint8_t len) { + furi_assert(src); + furi_assert(len <= 8); + + uint64_t res = 0; + uint8_t shift = 0; + while(len--) { + res |= ((uint64_t)*src) << (8 * shift++); + src++; + } + return res; +} + +uint64_t bit_lib_bytes_to_num_bcd(const uint8_t* src, uint8_t len, bool* is_bcd) { + furi_assert(src); + furi_assert(len <= 9); + + uint64_t res = 0; + uint8_t nibble_1, nibble_2; + *is_bcd = true; + + for(uint8_t i = 0; i < len; i++) { + nibble_1 = src[i] / 16; + nibble_2 = src[i] % 16; + if((nibble_1 > 9) || (nibble_2 > 9)) *is_bcd = false; + + res *= 10; + res += nibble_1; + + res *= 10; + res += nibble_2; + } + + return res; +} \ No newline at end of file diff --git a/lib/lfrfid/tools/bit_lib.h b/lib/bit_lib/bit_lib.h similarity index 81% rename from lib/lfrfid/tools/bit_lib.h rename to lib/bit_lib/bit_lib.h index bae95462d5b..7d23cf06364 100644 --- a/lib/lfrfid/tools/bit_lib.h +++ b/lib/bit_lib/bit_lib.h @@ -90,6 +90,15 @@ uint16_t bit_lib_get_bits_16(const uint8_t* data, size_t position, uint8_t lengt */ uint32_t bit_lib_get_bits_32(const uint8_t* data, size_t position, uint8_t length); +/** + * @brief Get the bits of a data, as uint64_t. + * @param data The data to get the bits from. + * @param position The position of the first bit. + * @param length The length of the bits. + * @return The bits. + */ +uint64_t bit_lib_get_bits_64(const uint8_t* data, size_t position, uint8_t length); + /** * @brief Test parity of given bits * @param bits Bits to test parity of @@ -267,6 +276,54 @@ uint16_t bit_lib_crc16( bool ref_out, uint16_t xor_out); +/** + * @brief Convert number to bytes in big endian order + * + * @param src number to convert + * @param len max used bytes count + * @param dest destination + * @return void + */ +void bit_lib_num_to_bytes_be(uint64_t src, uint8_t len, uint8_t* dest); + +/** + * @brief Convert number to bytes in little endian order + * + * @param src number to convert + * @param len max used bytes count + * @param dest destination + * @return void + */ +void bit_lib_num_to_bytes_le(uint64_t src, uint8_t len, uint8_t* dest); + +/** + * @brief Convert bytes to number in big endian order + * + * @param src byte array + * @param len max used bytes count + * @return uint64_t + */ +uint64_t bit_lib_bytes_to_num_be(const uint8_t* src, uint8_t len); + +/** + * @brief Convert bytes to number in little endian order + * + * @param src byte array + * @param len max used bytes count + * @return uint64_t + */ +uint64_t bit_lib_bytes_to_num_le(const uint8_t* src, uint8_t len); + +/** + * @brief Convert bytes in binary-coded decimal encoding to number + * + * @param src byte array + * @param len max used bytes count + * @param is_bcd will be true if all processed bytes is BCD encoded (no A-F nibbles) + * @return uint64_t + */ +uint64_t bit_lib_bytes_to_num_bcd(const uint8_t* src, uint8_t len, bool* is_bcd); + #ifdef __cplusplus } #endif diff --git a/lib/lfrfid/SConscript b/lib/lfrfid/SConscript index f9431ca75f2..5b2380bd519 100644 --- a/lib/lfrfid/SConscript +++ b/lib/lfrfid/SConscript @@ -12,7 +12,6 @@ env.Append( File("lfrfid_raw_worker.h"), File("lfrfid_raw_file.h"), File("lfrfid_dict_file.h"), - File("tools/bit_lib.h"), File("protocols/lfrfid_protocols.h"), ], ) diff --git a/lib/lfrfid/lfrfid_dict_file.c b/lib/lfrfid/lfrfid_dict_file.c index 18bf505f0f5..33a3b09db5c 100644 --- a/lib/lfrfid/lfrfid_dict_file.c +++ b/lib/lfrfid/lfrfid_dict_file.c @@ -1,7 +1,7 @@ #include "lfrfid_dict_file.h" #include #include -#include +#include #define LFRFID_DICT_FILETYPE "Flipper RFID key" diff --git a/lib/lfrfid/lfrfid_worker_modes.c b/lib/lfrfid/lfrfid_worker_modes.c index 32e2532590c..a017863b36c 100644 --- a/lib/lfrfid/lfrfid_worker_modes.c +++ b/lib/lfrfid/lfrfid_worker_modes.c @@ -5,7 +5,7 @@ #include #include #include "tools/varint_pair.h" -#include "tools/bit_lib.h" +#include #define TAG "LfRfidWorker" diff --git a/lib/lfrfid/protocols/protocol_awid.c b/lib/lfrfid/protocols/protocol_awid.c index 9396277236e..914f2f01b73 100644 --- a/lib/lfrfid/protocols/protocol_awid.c +++ b/lib/lfrfid/protocols/protocol_awid.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define JITTER_TIME (20) diff --git a/lib/lfrfid/protocols/protocol_fdx_a.c b/lib/lfrfid/protocols/protocol_fdx_a.c index 87daa0eb642..667c4f88db4 100644 --- a/lib/lfrfid/protocols/protocol_fdx_a.c +++ b/lib/lfrfid/protocols/protocol_fdx_a.c @@ -3,7 +3,7 @@ #include #include #include "lfrfid_protocols.h" -#include +#include #define JITTER_TIME (20) #define MIN_TIME (64 - JITTER_TIME) diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index a3ab56f25b2..18ccfa70294 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -2,7 +2,7 @@ #include "toolbox/level_duration.h" #include "protocol_fdx_b.h" #include -#include +#include #include "lfrfid_protocols.h" #include diff --git a/lib/lfrfid/protocols/protocol_gallagher.c b/lib/lfrfid/protocols/protocol_gallagher.c index 4720d3a4df0..15bebb90bdb 100644 --- a/lib/lfrfid/protocols/protocol_gallagher.c +++ b/lib/lfrfid/protocols/protocol_gallagher.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define GALLAGHER_CLOCK_PER_BIT (32) diff --git a/lib/lfrfid/protocols/protocol_hid_ex_generic.c b/lib/lfrfid/protocols/protocol_hid_ex_generic.c index 35500ab595b..194e153289e 100644 --- a/lib/lfrfid/protocols/protocol_hid_ex_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_ex_generic.c @@ -3,7 +3,7 @@ #include #include #include "lfrfid_protocols.h" -#include +#include #define JITTER_TIME (20) #define MIN_TIME (64 - JITTER_TIME) diff --git a/lib/lfrfid/protocols/protocol_hid_generic.c b/lib/lfrfid/protocols/protocol_hid_generic.c index e07021403de..e24b5ade71d 100644 --- a/lib/lfrfid/protocols/protocol_hid_generic.c +++ b/lib/lfrfid/protocols/protocol_hid_generic.c @@ -3,7 +3,7 @@ #include #include #include "lfrfid_protocols.h" -#include +#include #define JITTER_TIME (20) #define MIN_TIME (64 - JITTER_TIME) diff --git a/lib/lfrfid/protocols/protocol_idteck.c b/lib/lfrfid/protocols/protocol_idteck.c index 033fcd28c09..6de8de2060f 100644 --- a/lib/lfrfid/protocols/protocol_idteck.c +++ b/lib/lfrfid/protocols/protocol_idteck.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include "lfrfid_protocols.h" // Example: 4944544B 351FBE4B diff --git a/lib/lfrfid/protocols/protocol_indala26.c b/lib/lfrfid/protocols/protocol_indala26.c index 8319f0a93df..7ce83af7fa4 100644 --- a/lib/lfrfid/protocols/protocol_indala26.c +++ b/lib/lfrfid/protocols/protocol_indala26.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include "lfrfid_protocols.h" #define INDALA26_PREAMBLE_BIT_SIZE (33) diff --git a/lib/lfrfid/protocols/protocol_io_prox_xsf.c b/lib/lfrfid/protocols/protocol_io_prox_xsf.c index 71314856840..5cdc9064c78 100644 --- a/lib/lfrfid/protocols/protocol_io_prox_xsf.c +++ b/lib/lfrfid/protocols/protocol_io_prox_xsf.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define JITTER_TIME (20) diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c index d2c7ea79ff8..467002c7e0a 100644 --- a/lib/lfrfid/protocols/protocol_jablotron.c +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -2,7 +2,7 @@ #include "toolbox/level_duration.h" #include "protocol_jablotron.h" #include -#include +#include #include "lfrfid_protocols.h" #define JABLOTRON_ENCODED_BIT_SIZE (64) diff --git a/lib/lfrfid/protocols/protocol_keri.c b/lib/lfrfid/protocols/protocol_keri.c index f0a12863e3b..d994229d99e 100644 --- a/lib/lfrfid/protocols/protocol_keri.c +++ b/lib/lfrfid/protocols/protocol_keri.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include "lfrfid_protocols.h" #define KERI_PREAMBLE_BIT_SIZE (33) diff --git a/lib/lfrfid/protocols/protocol_nexwatch.c b/lib/lfrfid/protocols/protocol_nexwatch.c index 3bbbb42f503..938ef273d0a 100644 --- a/lib/lfrfid/protocols/protocol_nexwatch.c +++ b/lib/lfrfid/protocols/protocol_nexwatch.c @@ -1,6 +1,6 @@ #include #include -#include +#include #include "lfrfid_protocols.h" #define NEXWATCH_PREAMBLE_BIT_SIZE (8) diff --git a/lib/lfrfid/protocols/protocol_pac_stanley.c b/lib/lfrfid/protocols/protocol_pac_stanley.c index 11c642402a1..dc9eaaf493b 100644 --- a/lib/lfrfid/protocols/protocol_pac_stanley.c +++ b/lib/lfrfid/protocols/protocol_pac_stanley.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define PAC_STANLEY_ENCODED_BIT_SIZE (128) diff --git a/lib/lfrfid/protocols/protocol_paradox.c b/lib/lfrfid/protocols/protocol_paradox.c index 26c9b55dcd1..b716acf7a3b 100644 --- a/lib/lfrfid/protocols/protocol_paradox.c +++ b/lib/lfrfid/protocols/protocol_paradox.c @@ -2,7 +2,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define JITTER_TIME (20) diff --git a/lib/lfrfid/protocols/protocol_pyramid.c b/lib/lfrfid/protocols/protocol_pyramid.c index d794bb46ead..f0a506eb39e 100644 --- a/lib/lfrfid/protocols/protocol_pyramid.c +++ b/lib/lfrfid/protocols/protocol_pyramid.c @@ -3,7 +3,7 @@ #include #include #include "lfrfid_protocols.h" -#include +#include #define JITTER_TIME (20) #define MIN_TIME (64 - JITTER_TIME) diff --git a/lib/lfrfid/protocols/protocol_viking.c b/lib/lfrfid/protocols/protocol_viking.c index 8083f6d915f..f5697012af6 100644 --- a/lib/lfrfid/protocols/protocol_viking.c +++ b/lib/lfrfid/protocols/protocol_viking.c @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include "lfrfid_protocols.h" #define VIKING_CLOCK_PER_BIT (32) diff --git a/lib/nfc/helpers/nfc_util.c b/lib/nfc/helpers/nfc_util.c index b7a9f5ec9b8..7bbc9d62156 100644 --- a/lib/nfc/helpers/nfc_util.c +++ b/lib/nfc/helpers/nfc_util.c @@ -13,41 +13,6 @@ static const uint8_t nfc_util_odd_byte_parity[256] = { 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1}; -void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest) { - furi_assert(dest); - furi_assert(len <= 8); - - while(len--) { - dest[len] = (uint8_t)src; - src >>= 8; - } -} - -uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len) { - furi_assert(src); - furi_assert(len <= 8); - - uint64_t res = 0; - while(len--) { - res = (res << 8) | (*src); - src++; - } - return res; -} - -uint64_t nfc_util_bytes2num_little_endian(const uint8_t* src, uint8_t len) { - furi_assert(src); - furi_assert(len <= 8); - - uint64_t res = 0; - uint8_t shift = 0; - while(len--) { - res |= ((uint64_t)*src) << (8 * shift++); - src++; - } - return res; -} - uint8_t nfc_util_even_parity32(uint32_t data) { // data ^= data >> 16; // data ^= data >> 8; diff --git a/lib/nfc/helpers/nfc_util.h b/lib/nfc/helpers/nfc_util.h index 39eb401718e..f8e86d86585 100644 --- a/lib/nfc/helpers/nfc_util.h +++ b/lib/nfc/helpers/nfc_util.h @@ -6,12 +6,6 @@ extern "C" { #endif -void nfc_util_num2bytes(uint64_t src, uint8_t len, uint8_t* dest); - -uint64_t nfc_util_bytes2num(const uint8_t* src, uint8_t len); - -uint64_t nfc_util_bytes2num_little_endian(const uint8_t* src, uint8_t len); - uint8_t nfc_util_even_parity32(uint32_t data); uint8_t nfc_util_odd_parity8(uint8_t data); diff --git a/lib/nfc/protocols/mf_classic/crypto1.c b/lib/nfc/protocols/mf_classic/crypto1.c index 0758b05fb2a..e06eae3707a 100644 --- a/lib/nfc/protocols/mf_classic/crypto1.c +++ b/lib/nfc/protocols/mf_classic/crypto1.c @@ -1,6 +1,7 @@ #include "crypto1.h" #include +#include #include // Algorithm from https://github.com/RfidResearchGroup/proxmark3.git @@ -151,7 +152,7 @@ void crypto1_encrypt_reader_nonce( furi_assert(out); bit_buffer_set_size_bytes(out, 8); - uint32_t nt_num = nfc_util_bytes2num(nt, sizeof(uint32_t)); + uint32_t nt_num = bit_lib_bytes_to_num_be(nt, sizeof(uint32_t)); crypto1_init(crypto, key); if(is_nested) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic.c b/lib/nfc/protocols/mf_classic/mf_classic.c index 27236df4b20..361c53c3d8b 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic.c +++ b/lib/nfc/protocols/mf_classic/mf_classic.c @@ -3,7 +3,7 @@ #include #include -#include +#include #define MF_CLASSIC_PROTOCOL_NAME "Mifare Classic" @@ -121,7 +121,8 @@ static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, u // Load Key A // Key A mask 0b0000000000111111 = 0x003f if((block_unknown_bytes_mask & 0x003f) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_a.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(sec_tr_tmp->key_a.data, sizeof(MfClassicKey)); mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeA, key); } // Load Access Bits @@ -132,7 +133,8 @@ static void mf_classic_parse_block(FuriString* block_str, MfClassicData* data, u // Load Key B // Key B mask 0b1111110000000000 = 0xfc00 if((block_unknown_bytes_mask & 0xfc00) == 0) { - uint64_t key = nfc_util_bytes2num(sec_tr_tmp->key_b.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(sec_tr_tmp->key_b.data, sizeof(MfClassicKey)); mf_classic_set_key_found(data, sector_num, MfClassicKeyTypeB, key); } } else { @@ -493,7 +495,7 @@ void mf_classic_set_key_found( uint8_t key_arr[6] = {}; MfClassicSectorTrailer* sec_trailer = mf_classic_get_sector_trailer_by_sector(data, sector_num); - nfc_util_num2bytes(key, 6, key_arr); + bit_lib_num_to_bytes_be(key, 6, key_arr); if(key_type == MfClassicKeyTypeA) { memcpy(sec_trailer->key_a.data, key_arr, sizeof(MfClassicKey)); FURI_BIT_SET(data->key_a_mask, sector_num); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_listener.c b/lib/nfc/protocols/mf_classic/mf_classic_listener.c index 9f6f1f85c5a..7e4f4725b29 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_listener.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_listener.c @@ -3,7 +3,7 @@ #include #include -#include +#include #include #include @@ -68,14 +68,15 @@ static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler( MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(instance->data, sector_num); MfClassicKey* key = (key_type == MfClassicKeyTypeA) ? &sec_tr->key_a : &sec_tr->key_b; - uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); instance->auth_context.key_type = key_type; instance->auth_context.block_num = block_num; furi_hal_random_fill_buf(instance->auth_context.nt.data, sizeof(MfClassicNt)); - uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t nt_num = + bit_lib_bytes_to_num_be(instance->auth_context.nt.data, sizeof(MfClassicNt)); crypto1_init(instance->crypto, key_num); if(instance->comm_state == MfClassicListenerCommStatePlain) { @@ -88,7 +89,7 @@ static MfClassicListenerCommand mf_classic_listener_auth_first_part_handler( command = MfClassicListenerCommandProcessed; } else { uint8_t key_stream[4] = {}; - nfc_util_num2bytes(nt_num ^ cuid, sizeof(uint32_t), key_stream); + bit_lib_num_to_bytes_be(nt_num ^ cuid, sizeof(uint32_t), key_stream); bit_buffer_copy_bytes( instance->tx_plain_buffer, instance->auth_context.nt.data, sizeof(MfClassicNt)); crypto1_encrypt( @@ -147,11 +148,14 @@ static MfClassicListenerCommand instance->callback(instance->generic_event, instance->context); } - uint32_t nr_num = nfc_util_bytes2num(instance->auth_context.nr.data, sizeof(MfClassicNr)); - uint32_t ar_num = nfc_util_bytes2num(instance->auth_context.ar.data, sizeof(MfClassicAr)); + uint32_t nr_num = + bit_lib_bytes_to_num_be(instance->auth_context.nr.data, sizeof(MfClassicNr)); + uint32_t ar_num = + bit_lib_bytes_to_num_be(instance->auth_context.ar.data, sizeof(MfClassicAr)); crypto1_word(instance->crypto, nr_num, 1); - uint32_t nt_num = nfc_util_bytes2num(instance->auth_context.nt.data, sizeof(MfClassicNt)); + uint32_t nt_num = + bit_lib_bytes_to_num_be(instance->auth_context.nt.data, sizeof(MfClassicNt)); uint32_t secret_poller = ar_num ^ crypto1_word(instance->crypto, 0, 0); if(secret_poller != prng_successor(nt_num, 64)) { FURI_LOG_T( @@ -161,7 +165,7 @@ static MfClassicListenerCommand } uint32_t at_num = prng_successor(nt_num, 96); - nfc_util_num2bytes(at_num, sizeof(uint32_t), instance->auth_context.at.data); + bit_lib_num_to_bytes_be(at_num, sizeof(uint32_t), instance->auth_context.at.data); bit_buffer_copy_bytes( instance->tx_plain_buffer, instance->auth_context.at.data, sizeof(MfClassicAr)); crypto1_encrypt( diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index d846bba69bc..8c50230ca05 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -73,7 +73,7 @@ static void mf_classic_poller_check_key_b_is_readable( break; MfClassicSectorTrailer* sec_tr = (MfClassicSectorTrailer*)data; - uint64_t key_b = nfc_util_bytes2num(sec_tr->key_b.data, sizeof(MfClassicKey)); + uint64_t key_b = bit_lib_bytes_to_num_be(sec_tr->key_b.data, sizeof(MfClassicKey)); uint8_t sector_num = mf_classic_get_sector_by_block(block_num); mf_classic_set_key_found(instance->data, sector_num, MfClassicKeyTypeB, key_b); } while(false); @@ -456,7 +456,7 @@ NfcCommand mf_classic_poller_handler_request_read_sector_blocks(MfClassicPoller* MfClassicError error = MfClassicErrorNone; if(!sec_read_ctx->auth_passed) { - uint64_t key = nfc_util_bytes2num(sec_read_ctx->key.data, sizeof(MfClassicKey)); + uint64_t key = bit_lib_bytes_to_num_be(sec_read_ctx->key.data, sizeof(MfClassicKey)); FURI_LOG_D( TAG, "Auth to block %d with key %c: %06llx", @@ -530,7 +530,8 @@ NfcCommand mf_classic_poller_handler_auth_a(MfClassicPoller* instance) { instance->state = MfClassicPollerStateAuthKeyB; } else { uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); - uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( @@ -568,7 +569,8 @@ NfcCommand mf_classic_poller_handler_auth_b(MfClassicPoller* instance) { } } else { uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->current_sector); - uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( @@ -711,7 +713,8 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_a(MfClassicPoller* insta } else { uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); - uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key A: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( @@ -746,7 +749,8 @@ NfcCommand mf_classic_poller_handler_key_reuse_auth_key_b(MfClassicPoller* insta } else { uint8_t block = mf_classic_get_first_block_num_of_sector(dict_attack_ctx->reuse_key_sector); - uint64_t key = nfc_util_bytes2num(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); + uint64_t key = + bit_lib_bytes_to_num_be(dict_attack_ctx->current_key.data, sizeof(MfClassicKey)); FURI_LOG_D(TAG, "Key attack auth to block %d with key B: %06llx", block, key); MfClassicError error = mf_classic_poller_auth( diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c index 16bfb3f7282..3f8b9348294 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.c @@ -128,7 +128,7 @@ static MfClassicError mf_classic_poller_auth_common( } uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); - uint64_t key_num = nfc_util_bytes2num(key->data, sizeof(MfClassicKey)); + uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); MfClassicNr nr = {}; furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 0be42196f1d..a5af3153077 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -2,7 +2,7 @@ #include "mf_classic_poller.h" #include -#include +#include #include "crypto1.h" #ifdef __cplusplus diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index 3cffa56ff73..d4c5ba103f3 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -1,6 +1,6 @@ #include "mf_ultralight.h" -#include +#include #include #define MF_ULTRALIGHT_PROTOCOL_NAME "NTAG/Ultralight" @@ -603,10 +603,10 @@ bool mf_ultralight_is_all_data_read(const MfUltralightData* data) { } else { MfUltralightConfigPages* config = NULL; if(mf_ultralight_get_config_page(data, &config)) { - uint32_t pass = - nfc_util_bytes2num(config->password.data, sizeof(MfUltralightAuthPassword)); + uint32_t pass = bit_lib_bytes_to_num_be( + config->password.data, sizeof(MfUltralightAuthPassword)); uint16_t pack = - nfc_util_bytes2num(config->pack.data, sizeof(MfUltralightAuthPack)); + bit_lib_bytes_to_num_be(config->pack.data, sizeof(MfUltralightAuthPack)); all_read = ((pass != 0) || (pack != 0)); } } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index f7f814270a8..0c7f9f8038c 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -417,7 +417,7 @@ static NfcCommand mf_ultralight_poller_handler_auth(MfUltralightPoller* instance command = instance->callback(instance->general_event, instance->context); if(!instance->mfu_event.data->auth_context.skip_auth) { instance->auth_context.password = instance->mfu_event.data->auth_context.password; - uint32_t pass = nfc_util_bytes2num( + uint32_t pass = bit_lib_bytes_to_num_be( instance->auth_context.password.data, sizeof(MfUltralightAuthPassword)); FURI_LOG_D(TAG, "Trying to authenticate with password %08lX", pass); instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); @@ -497,14 +497,14 @@ static NfcCommand mf_ultralight_poller_handler_try_default_pass(MfUltralightPoll config->pack = instance->auth_context.pack; } else if(config->access.authlim == 0) { FURI_LOG_D(TAG, "No limits in authentication. Trying default password"); - nfc_util_num2bytes( + bit_lib_num_to_bytes_be( MF_ULTRALIGHT_DEFAULT_PASSWORD, sizeof(MfUltralightAuthPassword), instance->auth_context.password.data); instance->error = mf_ultralight_poller_auth_pwd(instance, &instance->auth_context); if(instance->error == MfUltralightErrorNone) { FURI_LOG_D(TAG, "Default password detected"); - nfc_util_num2bytes( + bit_lib_num_to_bytes_be( MF_ULTRALIGHT_DEFAULT_PASSWORD, sizeof(MfUltralightAuthPassword), config->password.data); diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index 7c7354b1c6d..3f8645fe73a 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -2,7 +2,7 @@ #include "mf_ultralight_poller.h" #include -#include +#include #ifdef __cplusplus extern "C" { diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index 5000efceb04..9b0b5ec5537 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -1,5 +1,5 @@ #include "slix_poller_i.h" -#include +#include #include @@ -107,7 +107,7 @@ SlixError 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] = {}; - nfc_util_num2bytes(xored_password, 4, xored_password_arr); + bit_lib_num_to_bytes_be(xored_password, 4, xored_password_arr); bit_buffer_append_bytes(instance->tx_buffer, xored_password_arr, 4); SlixError error = SlixErrorNone; diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 63bc13fd4df..c6d0c40705f 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.2,, +Version,+,56.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -37,6 +37,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -574,6 +575,32 @@ Function,+,bit_buffer_starts_with_byte,_Bool,"const BitBuffer*, uint8_t" Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" +Function,+,bit_lib_add_parity,size_t,"const uint8_t*, size_t, uint8_t*, size_t, uint8_t, uint8_t, BitLibParity" +Function,+,bit_lib_bytes_to_num_bcd,uint64_t,"const uint8_t*, uint8_t, _Bool*" +Function,+,bit_lib_bytes_to_num_be,uint64_t,"const uint8_t*, uint8_t" +Function,+,bit_lib_bytes_to_num_le,uint64_t,"const uint8_t*, uint8_t" +Function,+,bit_lib_copy_bits,void,"uint8_t*, size_t, size_t, const uint8_t*, size_t" +Function,+,bit_lib_crc16,uint16_t,"const uint8_t*, size_t, uint16_t, uint16_t, _Bool, _Bool, uint16_t" +Function,+,bit_lib_crc8,uint16_t,"const uint8_t*, size_t, uint8_t, uint8_t, _Bool, _Bool, uint8_t" +Function,+,bit_lib_get_bit,_Bool,"const uint8_t*, size_t" +Function,+,bit_lib_get_bit_count,uint8_t,uint32_t +Function,+,bit_lib_get_bits,uint8_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_16,uint16_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_32,uint32_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_64,uint64_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_num_to_bytes_be,void,"uint64_t, uint8_t, uint8_t*" +Function,+,bit_lib_num_to_bytes_le,void,"uint64_t, uint8_t, uint8_t*" +Function,+,bit_lib_print_bits,void,"const uint8_t*, size_t" +Function,+,bit_lib_print_regions,void,"const BitLibRegion*, size_t, const uint8_t*, size_t" +Function,+,bit_lib_push_bit,void,"uint8_t*, size_t, _Bool" +Function,+,bit_lib_remove_bit_every_nth,size_t,"uint8_t*, size_t, uint8_t, uint8_t" +Function,+,bit_lib_reverse_16_fast,uint16_t,uint16_t +Function,+,bit_lib_reverse_8_fast,uint8_t,uint8_t +Function,+,bit_lib_reverse_bits,void,"uint8_t*, size_t, uint8_t" +Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" +Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" +Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" +Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,ble_app_init,_Bool, Function,+,ble_app_thread_stop,void, diff --git a/targets/f18/target.json b/targets/f18/target.json index 7345fceec66..a8669f552ab 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -36,7 +36,8 @@ "update_util", "heatshrink", "flipperformat", - "flipper18" + "flipper18", + "bit_lib" ], "excluded_sources": [ "furi_hal_infrared.c", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index a42ddc0788b..0fd8c24dfa2 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,55.2,, +Version,+,56.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -38,6 +38,7 @@ Header,+,applications/services/notification/notification_messages.h,, Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, +Header,+,lib/bit_lib/bit_lib.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -62,7 +63,6 @@ Header,+,lib/lfrfid/lfrfid_raw_file.h,, Header,+,lib/lfrfid/lfrfid_raw_worker.h,, Header,+,lib/lfrfid/lfrfid_worker.h,, Header,+,lib/lfrfid/protocols/lfrfid_protocols.h,, -Header,+,lib/lfrfid/tools/bit_lib.h,, Header,+,lib/libusb_stm32/inc/hid_usage_button.h,, Header,+,lib/libusb_stm32/inc/hid_usage_consumer.h,, Header,+,lib/libusb_stm32/inc/hid_usage_desktop.h,, @@ -644,6 +644,9 @@ Function,+,bit_buffer_write_bytes,void,"const BitBuffer*, void*, size_t" Function,+,bit_buffer_write_bytes_mid,void,"const BitBuffer*, void*, size_t, size_t" Function,+,bit_buffer_write_bytes_with_parity,void,"const BitBuffer*, void*, size_t, size_t*" Function,+,bit_lib_add_parity,size_t,"const uint8_t*, size_t, uint8_t*, size_t, uint8_t, uint8_t, BitLibParity" +Function,+,bit_lib_bytes_to_num_bcd,uint64_t,"const uint8_t*, uint8_t, _Bool*" +Function,+,bit_lib_bytes_to_num_be,uint64_t,"const uint8_t*, uint8_t" +Function,+,bit_lib_bytes_to_num_le,uint64_t,"const uint8_t*, uint8_t" Function,+,bit_lib_copy_bits,void,"uint8_t*, size_t, size_t, const uint8_t*, size_t" Function,+,bit_lib_crc16,uint16_t,"const uint8_t*, size_t, uint16_t, uint16_t, _Bool, _Bool, uint16_t" Function,+,bit_lib_crc8,uint16_t,"const uint8_t*, size_t, uint8_t, uint8_t, _Bool, _Bool, uint8_t" @@ -652,6 +655,9 @@ Function,+,bit_lib_get_bit_count,uint8_t,uint32_t Function,+,bit_lib_get_bits,uint8_t,"const uint8_t*, size_t, uint8_t" Function,+,bit_lib_get_bits_16,uint16_t,"const uint8_t*, size_t, uint8_t" Function,+,bit_lib_get_bits_32,uint32_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_get_bits_64,uint64_t,"const uint8_t*, size_t, uint8_t" +Function,+,bit_lib_num_to_bytes_be,void,"uint64_t, uint8_t, uint8_t*" +Function,+,bit_lib_num_to_bytes_le,void,"uint64_t, uint8_t, uint8_t*" Function,+,bit_lib_print_bits,void,"const uint8_t*, size_t" Function,+,bit_lib_print_regions,void,"const BitLibRegion*, size_t, const uint8_t*, size_t" Function,+,bit_lib_push_bit,void,"uint8_t*, size_t, _Bool" @@ -2606,10 +2612,7 @@ Function,+,nfc_set_guard_time_us,void,"Nfc*, uint32_t" Function,+,nfc_set_mask_receive_time_fc,void,"Nfc*, uint32_t" Function,+,nfc_start,void,"Nfc*, NfcEventCallback, void*" Function,+,nfc_stop,void,Nfc* -Function,+,nfc_util_bytes2num,uint64_t,"const uint8_t*, uint8_t" -Function,+,nfc_util_bytes2num_little_endian,uint64_t,"const uint8_t*, uint8_t" Function,+,nfc_util_even_parity32,uint8_t,uint32_t -Function,+,nfc_util_num2bytes,void,"uint64_t, uint8_t, uint8_t*" Function,+,nfc_util_odd_parity,void,"const uint8_t*, uint8_t*, uint8_t" Function,+,nfc_util_odd_parity8,uint8_t,uint8_t Function,+,notification_internal_message,void,"NotificationApp*, const NotificationSequence*" diff --git a/targets/f7/target.json b/targets/f7/target.json index 14090026947..d63caf90afc 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -48,6 +48,7 @@ "update_util", "heatshrink", "flipperformat", - "flipper7" + "flipper7", + "bit_lib" ] } \ No newline at end of file From feb45f6645066a283adb0656d2de451786633765 Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:44:41 +0900 Subject: [PATCH 38/58] FuriHalRtc refactor: new datetime lib (#3386) * datetimelib created * datetimelib unit tests added * firmware fixes to new datetimelib * typo fix * merge artifacts fixed, datetimelib renamed to datetime Co-authored-by: Aleksandr Kutuzov --- applications/debug/locale_test/locale_test.c | 2 +- .../unit_tests/datetimelib/datetimelib_test.c | 175 ++++++++++++++++++ .../unit_tests/furi_hal/furi_hal_tests.c | 38 ---- applications/debug/unit_tests/test_index.c | 2 + .../nfc/plugins/supported_cards/clipper.c | 14 +- .../main/nfc/plugins/supported_cards/itso.c | 6 +- .../main/nfc/plugins/supported_cards/opal.c | 14 +- .../main/nfc/plugins/supported_cards/umarsh.c | 10 +- .../subghz/scenes/subghz_scene_save_name.c | 2 +- applications/services/cli/cli_commands.c | 4 +- applications/services/desktop/desktop.c | 2 +- applications/services/dolphin/dolphin.c | 2 +- .../services/dolphin/helpers/dolphin_state.c | 4 +- applications/services/locale/locale.c | 4 +- applications/services/locale/locale.h | 4 +- applications/services/rpc/rpc_system.c | 4 +- lib/SConscript | 1 + lib/datetime/SConscript | 22 +++ lib/datetime/datetime.c | 104 +++++++++++ lib/datetime/datetime.h | 75 ++++++++ lib/toolbox/name_generator.c | 2 +- targets/f18/api_symbols.csv | 23 +-- targets/f18/target.json | 5 +- targets/f7/api_symbols.csv | 23 +-- targets/f7/fatfs/fatfs.c | 2 +- targets/f7/furi_hal/furi_hal_rtc.c | 113 +---------- targets/f7/furi_hal/furi_hal_rtc.h | 69 +------ targets/f7/target.json | 5 +- 28 files changed, 456 insertions(+), 275 deletions(-) create mode 100644 applications/debug/unit_tests/datetimelib/datetimelib_test.c create mode 100644 lib/datetime/SConscript create mode 100644 lib/datetime/datetime.c create mode 100644 lib/datetime/datetime.h diff --git a/applications/debug/locale_test/locale_test.c b/applications/debug/locale_test/locale_test.c index 003df55dca4..a7cbd52f809 100644 --- a/applications/debug/locale_test/locale_test.c +++ b/applications/debug/locale_test/locale_test.c @@ -30,7 +30,7 @@ static void locale_test_view_draw_callback(Canvas* canvas, void* _model) { } canvas_draw_str(canvas, 0, 10, furi_string_get_cstr(tmp_string)); - FuriHalRtcDateTime datetime; + DateTime datetime; furi_hal_rtc_get_datetime(&datetime); locale_format_time(tmp_string, &datetime, locale_get_time_format(), false); diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/datetimelib/datetimelib_test.c new file mode 100644 index 00000000000..42bc7dbed72 --- /dev/null +++ b/applications/debug/unit_tests/datetimelib/datetimelib_test.c @@ -0,0 +1,175 @@ +#include +#include "../minunit.h" + +#include + +MU_TEST(test_datetime_validate_datetime_correct_min) { + DateTime correct_min = {0, 0, 0, 1, 1, 2000, 1}; + bool result = datetime_validate_datetime(&correct_min); + + mu_assert_int_eq(true, result); +} + +MU_TEST(test_datetime_validate_datetime_correct_max) { + DateTime correct_max = {23, 59, 59, 31, 12, 2099, 7}; + bool result = datetime_validate_datetime(&correct_max); + + mu_assert_int_eq(true, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_second) { + DateTime incorrect_sec = {0, 0, 60, 1, 1, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_sec); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_minute) { + DateTime incorrect_min = {0, 60, 0, 1, 1, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_min); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_hour) { + DateTime incorrect_hour = {24, 0, 0, 1, 1, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_hour); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_day_min) { + DateTime incorrect_day_min = {0, 0, 0, 0, 1, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_day_min); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_day_max) { + DateTime incorrect_day_max = {0, 0, 0, 32, 1, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_day_max); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_month_min) { + DateTime incorrect_month_min = {0, 0, 0, 1, 0, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_month_min); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_month_max) { + DateTime incorrect_month_max = {0, 0, 0, 1, 13, 2000, 1}; + bool result = datetime_validate_datetime(&incorrect_month_max); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_year_min) { + DateTime incorrect_year_min = {0, 0, 0, 1, 1, 1999, 1}; + bool result = datetime_validate_datetime(&incorrect_year_min); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_year_max) { + DateTime incorrect_year_max = {0, 0, 0, 1, 1, 2100, 1}; + bool result = datetime_validate_datetime(&incorrect_year_max); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_weekday_min) { + DateTime incorrect_weekday_min = {0, 0, 0, 1, 1, 2000, 0}; + bool result = datetime_validate_datetime(&incorrect_weekday_min); + + mu_assert_int_eq(false, result); +} + +MU_TEST(test_datetime_validate_datetime_incorrect_weekday_max) { + DateTime incorrect_weekday_max = {0, 0, 0, 1, 1, 2000, 8}; + bool result = datetime_validate_datetime(&incorrect_weekday_max); + + mu_assert_int_eq(false, result); +} + +MU_TEST_SUITE(test_datetime_validate_datetime) { + MU_RUN_TEST(test_datetime_validate_datetime_correct_min); + MU_RUN_TEST(test_datetime_validate_datetime_correct_max); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_second); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_minute); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_hour); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_day_min); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_day_max); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_month_min); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_month_max); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_year_min); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_year_max); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_weekday_min); + MU_RUN_TEST(test_datetime_validate_datetime_incorrect_weekday_max); +} + +MU_TEST(test_datetime_timestamp_to_datetime_min) { + uint32_t test_value = 0; + DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; + + DateTime result = {0}; + datetime_timestamp_to_datetime(test_value, &result); + + mu_assert_mem_eq(&min_datetime_expected, &result, sizeof(result)); +} + +MU_TEST(test_datetime_timestamp_to_datetime_max) { + uint32_t test_value = UINT32_MAX; + DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; + + DateTime result = {0}; + datetime_timestamp_to_datetime(test_value, &result); + + mu_assert_mem_eq(&max_datetime_expected, &result, sizeof(result)); +} + +MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) { + uint32_t test_value = random(); + + DateTime datetime = {0}; + datetime_timestamp_to_datetime(test_value, &datetime); + + uint32_t result = datetime_datetime_to_timestamp(&datetime); + + mu_assert_int_eq(test_value, result); +} + +MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) { + MU_RUN_TEST(test_datetime_timestamp_to_datetime_min); + MU_RUN_TEST(test_datetime_timestamp_to_datetime_max); + MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp); +} + +MU_TEST(test_datetime_datetime_to_timestamp_min) { + DateTime min_datetime = {0, 0, 0, 1, 1, 1970, 0}; + + uint32_t result = datetime_datetime_to_timestamp(&min_datetime); + mu_assert_int_eq(0, result); +} + +MU_TEST(test_datetime_datetime_to_timestamp_max) { + DateTime max_datetime = {6, 28, 15, 7, 2, 2106, 0}; + + uint32_t result = datetime_datetime_to_timestamp(&max_datetime); + mu_assert_int_eq(UINT32_MAX, result); +} + +MU_TEST_SUITE(test_datetime_datetime_to_timestamp_suite) { + MU_RUN_TEST(test_datetime_datetime_to_timestamp_min); + MU_RUN_TEST(test_datetime_datetime_to_timestamp_max); +} + +int run_minunit_test_datetime() { + MU_RUN_SUITE(test_datetime_timestamp_to_datetime_suite); + MU_RUN_SUITE(test_datetime_datetime_to_timestamp_suite); + MU_RUN_SUITE(test_datetime_validate_datetime); + + return MU_EXIT_CODE; +} \ No newline at end of file diff --git a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c index e3e44291faa..a75615d0c0f 100644 --- a/applications/debug/unit_tests/furi_hal/furi_hal_tests.c +++ b/applications/debug/unit_tests/furi_hal/furi_hal_tests.c @@ -214,37 +214,6 @@ MU_TEST(furi_hal_i2c_ext_eeprom) { } } -MU_TEST(furi_hal_rtc_timestamp2datetime_min) { - uint32_t test_value = 0; - FuriHalRtcDateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; - - FuriHalRtcDateTime result = {0}; - furi_hal_rtc_timestamp_to_datetime(test_value, &result); - - mu_assert_mem_eq(&min_datetime_expected, &result, sizeof(result)); -} - -MU_TEST(furi_hal_rtc_timestamp2datetime_max) { - uint32_t test_value = UINT32_MAX; - FuriHalRtcDateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; - - FuriHalRtcDateTime result = {0}; - furi_hal_rtc_timestamp_to_datetime(test_value, &result); - - mu_assert_mem_eq(&max_datetime_expected, &result, sizeof(result)); -} - -MU_TEST(furi_hal_rtc_timestamp2datetime2timestamp) { - uint32_t test_value = random(); - - FuriHalRtcDateTime datetime = {0}; - furi_hal_rtc_timestamp_to_datetime(test_value, &datetime); - - uint32_t result = furi_hal_rtc_datetime_to_timestamp(&datetime); - - mu_assert_int_eq(test_value, result); -} - MU_TEST_SUITE(furi_hal_i2c_int_suite) { MU_SUITE_CONFIGURE(&furi_hal_i2c_int_setup, &furi_hal_i2c_int_teardown); MU_RUN_TEST(furi_hal_i2c_int_1b); @@ -258,15 +227,8 @@ MU_TEST_SUITE(furi_hal_i2c_ext_suite) { MU_RUN_TEST(furi_hal_i2c_ext_eeprom); } -MU_TEST_SUITE(furi_hal_rtc_datetime_suite) { - MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_min); - MU_RUN_TEST(furi_hal_rtc_timestamp2datetime_max); - MU_RUN_TEST(furi_hal_rtc_timestamp2datetime2timestamp); -} - int run_minunit_test_furi_hal() { MU_RUN_SUITE(furi_hal_i2c_int_suite); MU_RUN_SUITE(furi_hal_i2c_ext_suite); - MU_RUN_SUITE(furi_hal_rtc_datetime_suite); return MU_EXIT_CODE; } diff --git a/applications/debug/unit_tests/test_index.c b/applications/debug/unit_tests/test_index.c index 7ae9ca03d56..60132a16168 100644 --- a/applications/debug/unit_tests/test_index.c +++ b/applications/debug/unit_tests/test_index.c @@ -26,6 +26,7 @@ int run_minunit_test_protocol_dict(); int run_minunit_test_lfrfid_protocols(); int run_minunit_test_nfc(); int run_minunit_test_bit_lib(); +int run_minunit_test_datetime(); int run_minunit_test_float_tools(); int run_minunit_test_bt(); int run_minunit_test_dialogs_file_browser_options(); @@ -57,6 +58,7 @@ const UnitTest unit_tests[] = { {.name = "protocol_dict", .entry = run_minunit_test_protocol_dict}, {.name = "lfrfid", .entry = run_minunit_test_lfrfid_protocols}, {.name = "bit_lib", .entry = run_minunit_test_bit_lib}, + {.name = "datetime", .entry = run_minunit_test_datetime}, {.name = "float_tools", .entry = run_minunit_test_float_tools}, {.name = "bt", .entry = run_minunit_test_bt}, {.name = "dialogs_file_browser_options", diff --git a/applications/main/nfc/plugins/supported_cards/clipper.c b/applications/main/nfc/plugins/supported_cards/clipper.c index 2878b6f3c63..6560925b047 100644 --- a/applications/main/nfc/plugins/supported_cards/clipper.c +++ b/applications/main/nfc/plugins/supported_cards/clipper.c @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include // @@ -172,7 +172,7 @@ static void furi_string_cat_timestamp( const char* date_hdr, const char* time_hdr, uint32_t tmst_1900); -static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out); +static void epoch_1900_datetime_to_furi(uint32_t seconds, DateTime* out); static bool get_file_contents( const MfDesfireApplication* app, const MfDesfireFileId* id, @@ -527,7 +527,7 @@ static void furi_string_cat_timestamp( const char* date_hdr, const char* time_hdr, uint32_t tmst_1900) { - FuriHalRtcDateTime tm; + DateTime tm; epoch_1900_datetime_to_furi(tmst_1900, &tm); @@ -550,7 +550,7 @@ static void furi_string_cat_timestamp( } // Convert a "1900"-based timestamp to Furi time, assuming a UTC/GMT timezone. -static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* out) { +static void epoch_1900_datetime_to_furi(uint32_t seconds, DateTime* out) { uint16_t year, month, day, hour, minute, second; // Calculate absolute number of days elapsed since the 1900 epoch @@ -568,17 +568,17 @@ static void epoch_1900_datetime_to_furi(uint32_t seconds, FuriHalRtcDateTime* ou // for(year = 1900;; year++) { - uint16_t year_days = furi_hal_rtc_get_days_per_year(year); + uint16_t year_days = datetime_get_days_per_year(year); if(absolute_days >= year_days) absolute_days -= year_days; else break; } - bool is_leap = furi_hal_rtc_is_leap_year(year); + bool is_leap = datetime_is_leap_year(year); for(month = 1;; month++) { - uint8_t days_in_month = furi_hal_rtc_get_days_per_month(is_leap, month); + uint8_t days_in_month = datetime_get_days_per_month(is_leap, month); if(absolute_days >= days_in_month) absolute_days -= days_in_month; else diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index a4e0c8869a4..d9fefb63c9f 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -5,7 +5,7 @@ #include #include -#include +#include static const MfDesfireApplicationId itso_app_id = {.data = {0x16, 0x02, 0xa0}}; static const MfDesfireFileId itso_file_id = 0x0f; @@ -87,8 +87,8 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_push_back(parsed_data, ' '); } - FuriHalRtcDateTime timestamp = {0}; - furi_hal_rtc_timestamp_to_datetime(unixTimestamp, ×tamp); + DateTime timestamp = {0}; + datetime_timestamp_to_datetime(unixTimestamp, ×tamp); FuriString* timestamp_str = furi_string_alloc(); locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-"); diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c index c71635d53eb..64c279ffe38 100644 --- a/applications/main/nfc/plugins/supported_cards/opal.c +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -32,7 +32,7 @@ #include #include -#include +#include #include @@ -78,11 +78,11 @@ typedef struct __attribute__((__packed__)) { static_assert(sizeof(OpalFile) == 16, "OpalFile"); -// Converts an Opal timestamp to FuriHalRtcDateTime. +// Converts an Opal timestamp to DateTime. // // Opal measures days since 1980-01-01 and minutes since midnight, and presumes // all days are 1440 minutes. -static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) { +static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, DateTime* out) { out->year = 1980; out->month = 1; // 1980-01-01 is a Tuesday @@ -93,7 +93,7 @@ static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDa // What year is it? for(;;) { - const uint16_t num_days_in_year = furi_hal_rtc_get_days_per_year(out->year); + const uint16_t num_days_in_year = datetime_get_days_per_year(out->year); if(days < num_days_in_year) break; days -= num_days_in_year; out->year++; @@ -104,8 +104,8 @@ static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDa for(;;) { // What month is it? - const bool is_leap = furi_hal_rtc_is_leap_year(out->year); - const uint8_t num_days_in_month = furi_hal_rtc_get_days_per_month(is_leap, out->month); + const bool is_leap = datetime_is_leap_year(out->year); + const uint8_t num_days_in_month = datetime_get_days_per_month(is_leap, out->month); if(days <= num_days_in_month) break; days -= num_days_in_month; out->month++; @@ -154,7 +154,7 @@ static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) { const uint8_t balance_cents = balance % 100; const int32_t balance_dollars = balance / 100; - FuriHalRtcDateTime timestamp; + DateTime timestamp; opal_date_time_to_furi(opal_file->days, opal_file->minutes, ×tamp); // Usages 4..6 associated with the Manly Ferry, which correspond to diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index ca9eb769760..044820ef390 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -33,11 +33,11 @@ #include #include -#include +#include #define TAG "Umarsh" -bool parse_datetime(uint16_t date, FuriHalRtcDateTime* result) { +bool parse_datetime(uint16_t date, DateTime* result) { result->year = 2000 + (date >> 9); result->month = date >> 5 & 0x0F; result->day = date & 0x1F; @@ -85,13 +85,13 @@ static bool umarsh_parse(const NfcDevice* device, FuriString* parsed_data) { const uint16_t balance_rub = (bit_lib_bytes_to_num_be(block_start_ptr + 8, 2)) & 0x7FFF; const uint8_t balance_kop = bit_lib_bytes_to_num_be(block_start_ptr + 10, 1) & 0x7F; - FuriHalRtcDateTime expiry_datetime; + DateTime expiry_datetime; bool is_expiry_datetime_valid = parse_datetime(expiry_date, &expiry_datetime); - FuriHalRtcDateTime valid_to_datetime; + DateTime valid_to_datetime; bool is_valid_to_datetime_valid = parse_datetime(valid_to, &valid_to_datetime); - FuriHalRtcDateTime last_refill_datetime; + DateTime last_refill_datetime; bool is_last_refill_datetime_valid = parse_datetime(last_refill_date, &last_refill_datetime); diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index 30d0821e158..08ba0ba8252 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -15,7 +15,7 @@ void subghz_scene_save_name_text_input_callback(void* context) { } void subghz_scene_save_name_get_timefilename(FuriString* name) { - FuriHalRtcDateTime datetime = {0}; + DateTime datetime = {0}; furi_hal_rtc_get_datetime(&datetime); furi_string_printf( name, diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 025711fb58f..df54bf3f4d7 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -101,7 +101,7 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { UNUSED(cli); UNUSED(context); - FuriHalRtcDateTime datetime = {0}; + DateTime datetime = {0}; if(furi_string_size(args) > 0) { uint16_t hours, minutes, seconds, month, day, year, weekday; @@ -135,7 +135,7 @@ void cli_command_date(Cli* cli, FuriString* args, void* context) { return; } - if(!furi_hal_rtc_validate_datetime(&datetime)) { + if(!datetime_validate_datetime(&datetime)) { printf("Invalid datetime data"); return; } diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index 7a49dd51e31..5fbd83d5b5e 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -53,7 +53,7 @@ static void desktop_dummy_mode_icon_draw_callback(Canvas* canvas, void* context) static void desktop_clock_update(Desktop* desktop) { furi_assert(desktop); - FuriHalRtcDateTime curr_dt; + DateTime curr_dt; furi_hal_rtc_get_datetime(&curr_dt); bool time_format_12 = locale_get_time_format() == LocaleTimeFormat12h; diff --git a/applications/services/dolphin/dolphin.c b/applications/services/dolphin/dolphin.c index 520a01978ff..e81101ba5b7 100644 --- a/applications/services/dolphin/dolphin.c +++ b/applications/services/dolphin/dolphin.c @@ -128,7 +128,7 @@ static void dolphin_update_clear_limits_timer_period(Dolphin* dolphin) { uint32_t timer_expires_at = furi_timer_get_expire_time(dolphin->clear_limits_timer); if((timer_expires_at - now_ticks) > HOURS_IN_TICKS(0.1)) { - FuriHalRtcDateTime date; + DateTime date; furi_hal_rtc_get_datetime(&date); uint32_t now_time_in_ms = ((date.hour * 60 + date.minute) * 60 + date.second) * 1000; uint32_t time_to_clear_limits = 0; diff --git a/applications/services/dolphin/helpers/dolphin_state.c b/applications/services/dolphin/helpers/dolphin_state.c index 14f080464bf..842d6b71573 100644 --- a/applications/services/dolphin/helpers/dolphin_state.c +++ b/applications/services/dolphin/helpers/dolphin_state.c @@ -74,9 +74,9 @@ bool dolphin_state_load(DolphinState* dolphin_state) { } uint64_t dolphin_state_timestamp() { - FuriHalRtcDateTime datetime; + DateTime datetime; furi_hal_rtc_get_datetime(&datetime); - return furi_hal_rtc_datetime_to_timestamp(&datetime); + return datetime_datetime_to_timestamp(&datetime); } bool dolphin_state_is_levelup(uint32_t icounter) { diff --git a/applications/services/locale/locale.c b/applications/services/locale/locale.c index e8b6a9fc533..4f7eb90ecc9 100644 --- a/applications/services/locale/locale.c +++ b/applications/services/locale/locale.c @@ -36,7 +36,7 @@ float locale_celsius_to_fahrenheit(float temp_c) { void locale_format_time( FuriString* out_str, - const FuriHalRtcDateTime* datetime, + const DateTime* datetime, const LocaleTimeFormat format, const bool show_seconds) { furi_assert(out_str); @@ -69,7 +69,7 @@ void locale_format_time( void locale_format_date( FuriString* out_str, - const FuriHalRtcDateTime* datetime, + const DateTime* datetime, const LocaleDateFormat format, const char* separator) { furi_assert(out_str); diff --git a/applications/services/locale/locale.h b/applications/services/locale/locale.h index 61fb4c60558..c4f8e78e5ae 100644 --- a/applications/services/locale/locale.h +++ b/applications/services/locale/locale.h @@ -73,7 +73,7 @@ void locale_set_time_format(LocaleTimeFormat format); */ void locale_format_time( FuriString* out_str, - const FuriHalRtcDateTime* datetime, + const DateTime* datetime, const LocaleTimeFormat format, const bool show_seconds); @@ -98,7 +98,7 @@ void locale_set_date_format(LocaleDateFormat format); */ void locale_format_date( FuriString* out_str, - const FuriHalRtcDateTime* datetime, + const DateTime* datetime, const LocaleDateFormat format, const char* separator); diff --git a/applications/services/rpc/rpc_system.c b/applications/services/rpc/rpc_system.c index 96a189ad3b6..0b9fd33f955 100644 --- a/applications/services/rpc/rpc_system.c +++ b/applications/services/rpc/rpc_system.c @@ -122,7 +122,7 @@ static void rpc_system_system_get_datetime_process(const PB_Main* request, void* RpcSession* session = (RpcSession*)context; furi_assert(session); - FuriHalRtcDateTime datetime; + DateTime datetime; furi_hal_rtc_get_datetime(&datetime); PB_Main* response = malloc(sizeof(PB_Main)); @@ -157,7 +157,7 @@ static void rpc_system_system_set_datetime_process(const PB_Main* request, void* return; } - FuriHalRtcDateTime datetime; + DateTime datetime; datetime.hour = request->content.system_set_datetime_request.datetime.hour; datetime.minute = request->content.system_set_datetime_request.datetime.minute; datetime.second = request->content.system_set_datetime_request.datetime.second; diff --git a/lib/SConscript b/lib/SConscript index 7bd00e4e424..cf96a1b84b2 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -43,6 +43,7 @@ libs = env.BuildModules( "update_util", "heatshrink", "bit_lib", + "datetime", ], ) diff --git a/lib/datetime/SConscript b/lib/datetime/SConscript new file mode 100644 index 00000000000..99fc0ec5dab --- /dev/null +++ b/lib/datetime/SConscript @@ -0,0 +1,22 @@ +Import("env") + +env.Append( + LINT_SOURCES=[ + Dir("."), + ], + CPPPATH=[ + "#/lib/datetime", + ], + SDK_HEADERS=[ + File("datetime.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="datetime") +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c*") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/datetime/datetime.c b/lib/datetime/datetime.c new file mode 100644 index 00000000000..7b1f73dd0f1 --- /dev/null +++ b/lib/datetime/datetime.c @@ -0,0 +1,104 @@ +#include "datetime.h" + +#define TAG "DateTime" + +#define SECONDS_PER_MINUTE 60 +#define SECONDS_PER_HOUR (SECONDS_PER_MINUTE * 60) +#define SECONDS_PER_DAY (SECONDS_PER_HOUR * 24) +#define MONTHS_COUNT 12 +#define EPOCH_START_YEAR 1970 + +static const uint8_t datetime_days_per_month[2][MONTHS_COUNT] = { + {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; + +static const uint16_t datetime_days_per_year[] = {365, 366}; + +bool datetime_validate_datetime(DateTime* datetime) { + bool invalid = false; + + invalid |= (datetime->second > 59); + invalid |= (datetime->minute > 59); + invalid |= (datetime->hour > 23); + + invalid |= (datetime->year < 2000); + invalid |= (datetime->year > 2099); + + invalid |= (datetime->month == 0); + invalid |= (datetime->month > 12); + + invalid |= (datetime->day == 0); + invalid |= (datetime->day > 31); + + invalid |= (datetime->weekday == 0); + invalid |= (datetime->weekday > 7); + + return !invalid; +} + +uint32_t datetime_datetime_to_timestamp(DateTime* datetime) { + uint32_t timestamp = 0; + uint8_t years = 0; + uint8_t leap_years = 0; + + for(uint16_t y = EPOCH_START_YEAR; y < datetime->year; y++) { + if(datetime_is_leap_year(y)) { + leap_years++; + } else { + years++; + } + } + + timestamp += ((years * datetime_days_per_year[0]) + (leap_years * datetime_days_per_year[1])) * + SECONDS_PER_DAY; + + bool leap_year = datetime_is_leap_year(datetime->year); + + for(uint8_t m = 1; m < datetime->month; m++) { + timestamp += datetime_get_days_per_month(leap_year, m) * SECONDS_PER_DAY; + } + + timestamp += (datetime->day - 1) * SECONDS_PER_DAY; + timestamp += datetime->hour * SECONDS_PER_HOUR; + timestamp += datetime->minute * SECONDS_PER_MINUTE; + timestamp += datetime->second; + + return timestamp; +} + +void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime) { + uint32_t days = timestamp / SECONDS_PER_DAY; + uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY; + + datetime->year = EPOCH_START_YEAR; + + while(days >= datetime_get_days_per_year(datetime->year)) { + days -= datetime_get_days_per_year(datetime->year); + (datetime->year)++; + } + + datetime->month = 1; + while(days >= + datetime_get_days_per_month(datetime_is_leap_year(datetime->year), datetime->month)) { + days -= + datetime_get_days_per_month(datetime_is_leap_year(datetime->year), datetime->month); + (datetime->month)++; + } + + datetime->day = days + 1; + datetime->hour = seconds_in_day / SECONDS_PER_HOUR; + datetime->minute = (seconds_in_day % SECONDS_PER_HOUR) / SECONDS_PER_MINUTE; + datetime->second = seconds_in_day % SECONDS_PER_MINUTE; +} + +uint16_t datetime_get_days_per_year(uint16_t year) { + return datetime_days_per_year[datetime_is_leap_year(year) ? 1 : 0]; +} + +bool datetime_is_leap_year(uint16_t year) { + return (((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0); +} + +uint8_t datetime_get_days_per_month(bool leap_year, uint8_t month) { + return datetime_days_per_month[leap_year ? 1 : 0][month - 1]; +} diff --git a/lib/datetime/datetime.h b/lib/datetime/datetime.h new file mode 100644 index 00000000000..1dabf72408e --- /dev/null +++ b/lib/datetime/datetime.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // Time + uint8_t hour; /**< Hour in 24H format: 0-23 */ + uint8_t minute; /**< Minute: 0-59 */ + uint8_t second; /**< Second: 0-59 */ + // Date + uint8_t day; /**< Current day: 1-31 */ + uint8_t month; /**< Current month: 1-12 */ + uint16_t year; /**< Current year: 2000-2099 */ + uint8_t weekday; /**< Current weekday: 1-7 */ +} DateTime; + +/** Validate Date Time + * + * @param datetime The datetime to validate + * + * @return { description_of_the_return_value } + */ +bool datetime_validate_datetime(DateTime* datetime); + +/** Convert DateTime to UNIX timestamp + * + * @warning Mind timezone when perform conversion + * + * @param datetime The datetime (UTC) + * + * @return UNIX Timestamp in seconds from UNIX epoch start + */ +uint32_t datetime_datetime_to_timestamp(DateTime* datetime); + +/** Convert UNIX timestamp to DateTime + * + * @warning Mind timezone when perform conversion + * + * @param[in] timestamp UNIX Timestamp in seconds from UNIX epoch start + * @param[out] datetime The datetime (UTC) + */ +void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime); + +/** Gets the number of days in the year according to the Gregorian calendar. + * + * @param year Input year. + * + * @return number of days in `year`. + */ +uint16_t datetime_get_days_per_year(uint16_t year); + +/** Check if a year a leap year in the Gregorian calendar. + * + * @param year Input year. + * + * @return true if `year` is a leap year. + */ +bool datetime_is_leap_year(uint16_t year); + +/** Get the number of days in the month. + * + * @param leap_year true to calculate based on leap years + * @param month month to check, where 1 = January + * @return the number of days in the month + */ +uint8_t datetime_get_days_per_month(bool leap_year, uint8_t month); + +#ifdef __cplusplus +} +#endif diff --git a/lib/toolbox/name_generator.c b/lib/toolbox/name_generator.c index 36366485acc..04fcab07cf7 100644 --- a/lib/toolbox/name_generator.c +++ b/lib/toolbox/name_generator.c @@ -59,7 +59,7 @@ void name_generator_make_detailed(char* name, size_t max_name_size, const char* furi_assert(max_name_size); furi_assert(prefix); - FuriHalRtcDateTime dateTime; + DateTime dateTime; furi_hal_rtc_get_datetime(&dateTime); snprintf( diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index c6d0c40705f..6d994653bdf 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,56.0,, +Version,+,57.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -38,6 +38,7 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -730,6 +731,12 @@ Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* +Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* +Function,+,datetime_get_days_per_month,uint8_t,"_Bool, uint8_t" +Function,+,datetime_get_days_per_year,uint16_t,uint16_t +Function,+,datetime_is_leap_year,_Bool,uint16_t +Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" +Function,+,datetime_validate_datetime,_Bool,DateTime* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -1263,12 +1270,9 @@ Function,-,furi_hal_resources_deinit_early,void, Function,+,furi_hal_resources_get_ext_pin_number,int32_t,const GpioPin* Function,-,furi_hal_resources_init,void, Function,-,furi_hal_resources_init_early,void, -Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, -Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* -Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t" -Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t +Function,+,furi_hal_rtc_get_datetime,void,DateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, @@ -1283,11 +1287,10 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag -Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_reset_registers,void, Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode -Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_set_datetime,void,DateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode @@ -1300,8 +1303,6 @@ Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, -Function,+,furi_hal_rtc_timestamp_to_datetime,void,"uint32_t, FuriHalRtcDateTime*" -Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_sd_get_card_state,FuriStatus, Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* Function,+,furi_hal_sd_init,FuriStatus,_Bool @@ -1698,8 +1699,8 @@ Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* Function,+,locale_celsius_to_fahrenheit,float,float Function,+,locale_fahrenheit_to_celsius,float,float -Function,+,locale_format_date,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleDateFormat, const char*" -Function,+,locale_format_time,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleTimeFormat, const _Bool" +Function,+,locale_format_date,void,"FuriString*, const DateTime*, const LocaleDateFormat, const char*" +Function,+,locale_format_time,void,"FuriString*, const DateTime*, const LocaleTimeFormat, const _Bool" Function,+,locale_get_date_format,LocaleDateFormat, Function,+,locale_get_measurement_unit,LocaleMeasurementUnits, Function,+,locale_get_time_format,LocaleTimeFormat, diff --git a/targets/f18/target.json b/targets/f18/target.json index a8669f552ab..43e9254cd46 100644 --- a/targets/f18/target.json +++ b/targets/f18/target.json @@ -37,7 +37,8 @@ "heatshrink", "flipperformat", "flipper18", - "bit_lib" + "bit_lib", + "datetime" ], "excluded_sources": [ "furi_hal_infrared.c", @@ -67,4 +68,4 @@ "ibutton", "infrared" ] -} \ No newline at end of file +} diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0fd8c24dfa2..6921cecdc34 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,56.0,, +Version,+,57.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -39,6 +39,7 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, Header,+,lib/drivers/cc1101_regs.h,, @@ -798,6 +799,12 @@ Function,+,crc32_calc_buffer,uint32_t,"uint32_t, const void*, size_t" Function,+,crc32_calc_file,uint32_t,"File*, const FileCrcProgressCb, void*" Function,-,ctermid,char*,char* Function,-,cuserid,char*,char* +Function,+,datetime_datetime_to_timestamp,uint32_t,DateTime* +Function,+,datetime_get_days_per_month,uint8_t,"_Bool, uint8_t" +Function,+,datetime_get_days_per_year,uint16_t,uint16_t +Function,+,datetime_is_leap_year,_Bool,uint16_t +Function,+,datetime_timestamp_to_datetime,void,"uint32_t, DateTime*" +Function,+,datetime_validate_datetime,_Bool,DateTime* Function,+,dialog_ex_alloc,DialogEx*, Function,+,dialog_ex_disable_extended_events,void,DialogEx* Function,+,dialog_ex_enable_extended_events,void,DialogEx* @@ -1408,12 +1415,9 @@ Function,+,furi_hal_rfid_tim_read_continue,void, Function,+,furi_hal_rfid_tim_read_pause,void, Function,+,furi_hal_rfid_tim_read_start,void,"float, float" Function,+,furi_hal_rfid_tim_read_stop,void, -Function,+,furi_hal_rtc_datetime_to_timestamp,uint32_t,FuriHalRtcDateTime* Function,-,furi_hal_rtc_deinit_early,void, Function,+,furi_hal_rtc_get_boot_mode,FuriHalRtcBootMode, -Function,+,furi_hal_rtc_get_datetime,void,FuriHalRtcDateTime* -Function,+,furi_hal_rtc_get_days_per_month,uint8_t,"_Bool, uint8_t" -Function,+,furi_hal_rtc_get_days_per_year,uint16_t,uint16_t +Function,+,furi_hal_rtc_get_datetime,void,DateTime* Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_heap_track_mode,FuriHalRtcHeapTrackMode, Function,+,furi_hal_rtc_get_locale_dateformat,FuriHalRtcLocaleDateFormat, @@ -1428,11 +1432,10 @@ Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag -Function,+,furi_hal_rtc_is_leap_year,_Bool,uint16_t Function,+,furi_hal_rtc_reset_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_reset_registers,void, Function,+,furi_hal_rtc_set_boot_mode,void,FuriHalRtcBootMode -Function,+,furi_hal_rtc_set_datetime,void,FuriHalRtcDateTime* +Function,+,furi_hal_rtc_set_datetime,void,DateTime* Function,+,furi_hal_rtc_set_fault_data,void,uint32_t Function,+,furi_hal_rtc_set_flag,void,FuriHalRtcFlag Function,+,furi_hal_rtc_set_heap_track_mode,void,FuriHalRtcHeapTrackMode @@ -1445,8 +1448,6 @@ Function,+,furi_hal_rtc_set_log_level,void,uint8_t Function,+,furi_hal_rtc_set_pin_fails,void,uint32_t Function,+,furi_hal_rtc_set_register,void,"FuriHalRtcRegister, uint32_t" Function,+,furi_hal_rtc_sync_shadow,void, -Function,+,furi_hal_rtc_timestamp_to_datetime,void,"uint32_t, FuriHalRtcDateTime*" -Function,+,furi_hal_rtc_validate_datetime,_Bool,FuriHalRtcDateTime* Function,+,furi_hal_sd_get_card_state,FuriStatus, Function,+,furi_hal_sd_info,FuriStatus,FuriHalSdInfo* Function,+,furi_hal_sd_init,FuriStatus,_Bool @@ -2076,8 +2077,8 @@ Function,+,loading_free,void,Loading* Function,+,loading_get_view,View*,Loading* Function,+,locale_celsius_to_fahrenheit,float,float Function,+,locale_fahrenheit_to_celsius,float,float -Function,+,locale_format_date,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleDateFormat, const char*" -Function,+,locale_format_time,void,"FuriString*, const FuriHalRtcDateTime*, const LocaleTimeFormat, const _Bool" +Function,+,locale_format_date,void,"FuriString*, const DateTime*, const LocaleDateFormat, const char*" +Function,+,locale_format_time,void,"FuriString*, const DateTime*, const LocaleTimeFormat, const _Bool" Function,+,locale_get_date_format,LocaleDateFormat, Function,+,locale_get_measurement_unit,LocaleMeasurementUnits, Function,+,locale_get_time_format,LocaleTimeFormat, diff --git a/targets/f7/fatfs/fatfs.c b/targets/f7/fatfs/fatfs.c index 5a8912cbdb7..0e17f75c16d 100644 --- a/targets/f7/fatfs/fatfs.c +++ b/targets/f7/fatfs/fatfs.c @@ -15,7 +15,7 @@ void fatfs_init(void) { * @return Time in DWORD (toasters per square washing machine) */ DWORD get_fattime() { - FuriHalRtcDateTime furi_time; + DateTime furi_time; furi_hal_rtc_get_datetime(&furi_time); return ((uint32_t)(furi_time.year - 1980) << 25) | furi_time.month << 21 | diff --git a/targets/f7/furi_hal/furi_hal_rtc.c b/targets/f7/furi_hal/furi_hal_rtc.c index 88aad6858ef..cd4fa8b9b30 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.c +++ b/targets/f7/furi_hal/furi_hal_rtc.c @@ -42,18 +42,6 @@ typedef struct { _Static_assert(sizeof(SystemReg) == 4, "SystemReg size mismatch"); -#define FURI_HAL_RTC_SECONDS_PER_MINUTE 60 -#define FURI_HAL_RTC_SECONDS_PER_HOUR (FURI_HAL_RTC_SECONDS_PER_MINUTE * 60) -#define FURI_HAL_RTC_SECONDS_PER_DAY (FURI_HAL_RTC_SECONDS_PER_HOUR * 24) -#define FURI_HAL_RTC_MONTHS_COUNT 12 -#define FURI_HAL_RTC_EPOCH_START_YEAR 1970 - -static const uint8_t furi_hal_rtc_days_per_month[2][FURI_HAL_RTC_MONTHS_COUNT] = { - {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, - {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; - -static const uint16_t furi_hal_rtc_days_per_year[] = {365, 366}; - static const FuriHalSerialId furi_hal_rtc_log_devices[] = { [FuriHalRtcLogDeviceUsart] = FuriHalSerialIdUsart, [FuriHalRtcLogDeviceLpuart] = FuriHalSerialIdLpuart, @@ -103,7 +91,7 @@ static bool furi_hal_rtc_start_clock_and_switch() { } static void furi_hal_rtc_recover() { - FuriHalRtcDateTime datetime = {0}; + DateTime datetime = {0}; // Handle fixable LSE failure if(LL_RCC_LSE_IsCSSDetected()) { @@ -350,7 +338,7 @@ FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat() { return data->locale_dateformat; } -void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { +void furi_hal_rtc_set_datetime(DateTime* datetime) { furi_check(!FURI_IS_IRQ_MODE()); furi_assert(datetime); @@ -389,7 +377,7 @@ void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime) { FURI_CRITICAL_EXIT(); } -void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime) { +void furi_hal_rtc_get_datetime(DateTime* datetime) { furi_check(!FURI_IS_IRQ_MODE()); furi_assert(datetime); @@ -407,28 +395,6 @@ void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime) { datetime->weekday = __LL_RTC_CONVERT_BCD2BIN((date >> 24) & 0xFF); } -bool furi_hal_rtc_validate_datetime(FuriHalRtcDateTime* datetime) { - bool invalid = false; - - invalid |= (datetime->second > 59); - invalid |= (datetime->minute > 59); - invalid |= (datetime->hour > 23); - - invalid |= (datetime->year < 2000); - invalid |= (datetime->year > 2099); - - invalid |= (datetime->month == 0); - invalid |= (datetime->month > 12); - - invalid |= (datetime->day == 0); - invalid |= (datetime->day > 31); - - invalid |= (datetime->weekday == 0); - invalid |= (datetime->weekday > 7); - - return !invalid; -} - void furi_hal_rtc_set_fault_data(uint32_t value) { furi_hal_rtc_set_register(FuriHalRtcRegisterFaultData, value); } @@ -446,76 +412,7 @@ uint32_t furi_hal_rtc_get_pin_fails() { } uint32_t furi_hal_rtc_get_timestamp() { - FuriHalRtcDateTime datetime = {0}; + DateTime datetime = {0}; furi_hal_rtc_get_datetime(&datetime); - return furi_hal_rtc_datetime_to_timestamp(&datetime); -} - -uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { - uint32_t timestamp = 0; - uint8_t years = 0; - uint8_t leap_years = 0; - - for(uint16_t y = FURI_HAL_RTC_EPOCH_START_YEAR; y < datetime->year; y++) { - if(furi_hal_rtc_is_leap_year(y)) { - leap_years++; - } else { - years++; - } - } - - timestamp += - ((years * furi_hal_rtc_days_per_year[0]) + (leap_years * furi_hal_rtc_days_per_year[1])) * - FURI_HAL_RTC_SECONDS_PER_DAY; - - bool leap_year = furi_hal_rtc_is_leap_year(datetime->year); - - for(uint8_t m = 1; m < datetime->month; m++) { - timestamp += furi_hal_rtc_get_days_per_month(leap_year, m) * FURI_HAL_RTC_SECONDS_PER_DAY; - } - - timestamp += (datetime->day - 1) * FURI_HAL_RTC_SECONDS_PER_DAY; - timestamp += datetime->hour * FURI_HAL_RTC_SECONDS_PER_HOUR; - timestamp += datetime->minute * FURI_HAL_RTC_SECONDS_PER_MINUTE; - timestamp += datetime->second; - - return timestamp; -} - -void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime) { - uint32_t days = timestamp / FURI_HAL_RTC_SECONDS_PER_DAY; - uint32_t seconds_in_day = timestamp % FURI_HAL_RTC_SECONDS_PER_DAY; - - datetime->year = FURI_HAL_RTC_EPOCH_START_YEAR; - - while(days >= furi_hal_rtc_get_days_per_year(datetime->year)) { - days -= furi_hal_rtc_get_days_per_year(datetime->year); - (datetime->year)++; - } - - datetime->month = 1; - while(days >= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month)) { - days -= furi_hal_rtc_get_days_per_month( - furi_hal_rtc_is_leap_year(datetime->year), datetime->month); - (datetime->month)++; - } - - datetime->day = days + 1; - datetime->hour = seconds_in_day / FURI_HAL_RTC_SECONDS_PER_HOUR; - datetime->minute = - (seconds_in_day % FURI_HAL_RTC_SECONDS_PER_HOUR) / FURI_HAL_RTC_SECONDS_PER_MINUTE; - datetime->second = seconds_in_day % FURI_HAL_RTC_SECONDS_PER_MINUTE; -} - -uint16_t furi_hal_rtc_get_days_per_year(uint16_t year) { - return furi_hal_rtc_days_per_year[furi_hal_rtc_is_leap_year(year) ? 1 : 0]; -} - -bool furi_hal_rtc_is_leap_year(uint16_t year) { - return (((year) % 4 == 0) && ((year) % 100 != 0)) || ((year) % 400 == 0); -} - -uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month) { - return furi_hal_rtc_days_per_month[leap_year ? 1 : 0][month - 1]; + return datetime_datetime_to_timestamp(&datetime); } diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index 0a5023131f8..c871d5060a7 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -8,22 +8,12 @@ #include #include +#include + #ifdef __cplusplus extern "C" { #endif -typedef struct { - // Time - uint8_t hour; /**< Hour in 24H format: 0-23 */ - uint8_t minute; /**< Minute: 0-59 */ - uint8_t second; /**< Second: 0-59 */ - // Date - uint8_t day; /**< Current day: 1-31 */ - uint8_t month; /**< Current month: 1-12 */ - uint16_t year; /**< Current year: 2000-2099 */ - uint8_t weekday; /**< Current weekday: 1-7 */ -} FuriHalRtcDateTime; - typedef enum { FuriHalRtcFlagDebug = (1 << 0), FuriHalRtcFlagStorageFormatInternal = (1 << 1), @@ -247,21 +237,13 @@ FuriHalRtcLocaleDateFormat furi_hal_rtc_get_locale_dateformat(void); * * @param datetime The date time to set */ -void furi_hal_rtc_set_datetime(FuriHalRtcDateTime* datetime); +void furi_hal_rtc_set_datetime(DateTime* datetime); /** Get RTC Date Time * * @param datetime The datetime */ -void furi_hal_rtc_get_datetime(FuriHalRtcDateTime* datetime); - -/** Validate Date Time - * - * @param datetime The datetime to validate - * - * @return { description_of_the_return_value } - */ -bool furi_hal_rtc_validate_datetime(FuriHalRtcDateTime* datetime); +void furi_hal_rtc_get_datetime(DateTime* datetime); /** Set RTC Fault Data * @@ -293,49 +275,6 @@ uint32_t furi_hal_rtc_get_pin_fails(void); */ uint32_t furi_hal_rtc_get_timestamp(void); -/** Convert DateTime to UNIX timestamp - * - * @warning Mind timezone when perform conversion - * - * @param datetime The datetime (UTC) - * - * @return UNIX Timestamp in seconds from UNIX epoch start - */ -uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); - -/** Convert UNIX timestamp to DateTime - * - * @warning Mind timezone when perform conversion - * - * @param[in] timestamp UNIX Timestamp in seconds from UNIX epoch start - * @param[out] datetime The datetime (UTC) - */ -void furi_hal_rtc_timestamp_to_datetime(uint32_t timestamp, FuriHalRtcDateTime* datetime); - -/** Gets the number of days in the year according to the Gregorian calendar. - * - * @param year Input year. - * - * @return number of days in `year`. - */ -uint16_t furi_hal_rtc_get_days_per_year(uint16_t year); - -/** Check if a year a leap year in the Gregorian calendar. - * - * @param year Input year. - * - * @return true if `year` is a leap year. - */ -bool furi_hal_rtc_is_leap_year(uint16_t year); - -/** Get the number of days in the month. - * - * @param leap_year true to calculate based on leap years - * @param month month to check, where 1 = January - * @return the number of days in the month - */ -uint8_t furi_hal_rtc_get_days_per_month(bool leap_year, uint8_t month); - #ifdef __cplusplus } #endif diff --git a/targets/f7/target.json b/targets/f7/target.json index d63caf90afc..0f0161fe4e3 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -49,6 +49,7 @@ "heatshrink", "flipperformat", "flipper7", - "bit_lib" + "bit_lib", + "datetime" ] -} \ No newline at end of file +} From c3c54f672c10799500be2ca7896d1bbeb1a76856 Mon Sep 17 00:00:00 2001 From: Nikolay Minaylov Date: Thu, 15 Feb 2024 06:53:34 +0300 Subject: [PATCH 39/58] Archive: fixed Apps tab ext filter (#3450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- applications/main/archive/helpers/archive_browser.h | 3 ++- applications/main/archive/helpers/archive_files.h | 1 + applications/main/archive/views/archive_browser_view.c | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 30548284bdd..0a977b647cb 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -33,6 +33,7 @@ static const char* known_ext[] = { [ArchiveFileTypeUpdateManifest] = ".fuf", [ArchiveFileTypeFolder] = "?", [ArchiveFileTypeUnknown] = "*", + [ArchiveFileTypeAppOrJs] = ".fap|.js", }; static const ArchiveFileTypeEnum known_type[] = { @@ -44,7 +45,7 @@ static const ArchiveFileTypeEnum known_type[] = { [ArchiveTabInfrared] = ArchiveFileTypeInfrared, [ArchiveTabBadUsb] = ArchiveFileTypeBadUsb, [ArchiveTabU2f] = ArchiveFileTypeU2f, - [ArchiveTabApplications] = ArchiveFileTypeApplication, + [ArchiveTabApplications] = ArchiveFileTypeAppOrJs, [ArchiveTabBrowser] = ArchiveFileTypeUnknown, }; diff --git a/applications/main/archive/helpers/archive_files.h b/applications/main/archive/helpers/archive_files.h index 0c7386c922e..ad766234269 100644 --- a/applications/main/archive/helpers/archive_files.h +++ b/applications/main/archive/helpers/archive_files.h @@ -19,6 +19,7 @@ typedef enum { ArchiveFileTypeJS, ArchiveFileTypeFolder, ArchiveFileTypeUnknown, + ArchiveFileTypeAppOrJs, ArchiveFileTypeLoading, } ArchiveFileTypeEnum; diff --git a/applications/main/archive/views/archive_browser_view.c b/applications/main/archive/views/archive_browser_view.c index 2b65486cd72..447941504cd 100644 --- a/applications/main/archive/views/archive_browser_view.c +++ b/applications/main/archive/views/archive_browser_view.c @@ -35,6 +35,7 @@ static const Icon* ArchiveItemIcons[] = { [ArchiveFileTypeLoading] = &I_loading_10px, [ArchiveFileTypeApplication] = &I_unknown_10px, [ArchiveFileTypeJS] = &I_js_script_10px, + [ArchiveFileTypeAppOrJs] = &I_unknown_10px, }; void archive_browser_set_callback( From 26ac455fb93974eb001535539ee5a6b3c8331d85 Mon Sep 17 00:00:00 2001 From: Leptopt1los <53914086+Leptopt1los@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:10:48 +0900 Subject: [PATCH 40/58] T5577 lib: write with mask function added (#3362) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * T5577 lib: write with mask function added * t5577_write_with_mask: with_password param added Co-authored-by: あく --- lib/lfrfid/tools/t5577.c | 60 ++++++++++++++++++++++++++++++++++---- lib/lfrfid/tools/t5577.h | 5 ++++ targets/f7/api_symbols.csv | 2 ++ 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/lib/lfrfid/tools/t5577.c b/lib/lfrfid/tools/t5577.c index 00c9ddfb627..9b078fe3a15 100644 --- a/lib/lfrfid/tools/t5577.c +++ b/lib/lfrfid/tools/t5577.c @@ -13,6 +13,9 @@ #define T5577_OPCODE_PAGE_1 0b11 #define T5577_OPCODE_RESET 0b00 +#define T5577_BLOCKS_IN_PAGE_0 8 +#define T5577_BLOCKS_IN_PAGE_1 4 + static void t5577_start() { furi_hal_rfid_tim_read_start(125000, 0.5); @@ -51,14 +54,27 @@ static void t5577_write_reset() { t5577_write_bit(0); } -static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) { +static void t5577_write_block_pass( + uint8_t page, + uint8_t block, + bool lock_bit, + uint32_t data, + bool with_pass, + uint32_t password) { furi_delay_us(T5577_TIMING_WAIT_TIME * 8); // start gap t5577_write_gap(T5577_TIMING_START_GAP); - // opcode for page 0 - t5577_write_opcode(T5577_OPCODE_PAGE_0); + // opcode for page + t5577_write_opcode((page == 1) ? T5577_OPCODE_PAGE_1 : T5577_OPCODE_PAGE_0); + + // password + if(with_pass) { + for(uint8_t i = 0; i < 32; i++) { + t5577_write_bit((password >> (31 - i)) & 1); + } + } // lock bit t5577_write_bit(lock_bit); @@ -79,13 +95,47 @@ static void t5577_write_block(uint8_t block, bool lock_bit, uint32_t data) { t5577_write_reset(); } +static void t5577_write_block_simple(uint8_t block, bool lock_bit, uint32_t data) { + t5577_write_block_pass(0, block, lock_bit, data, false, 0); +} + void t5577_write(LFRFIDT5577* data) { t5577_start(); FURI_CRITICAL_ENTER(); for(size_t i = 0; i < data->blocks_to_write; i++) { - t5577_write_block(i, false, data->block[i]); + t5577_write_block_simple(i, false, data->block[i]); + } + t5577_write_reset(); + FURI_CRITICAL_EXIT(); + t5577_stop(); +} + +void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password) { + t5577_start(); + FURI_CRITICAL_ENTER(); + for(size_t i = 0; i < data->blocks_to_write; i++) { + t5577_write_block_pass(0, i, false, data->block[i], true, password); } t5577_write_reset(); FURI_CRITICAL_EXIT(); t5577_stop(); -} \ No newline at end of file +} + +void t5577_write_with_mask(LFRFIDT5577* data, uint8_t page, bool with_pass, uint32_t password) { + t5577_start(); + FURI_CRITICAL_ENTER(); + + uint8_t mask = data->mask; + + size_t pages_total = (page == 0) ? T5577_BLOCKS_IN_PAGE_0 : T5577_BLOCKS_IN_PAGE_1; + + for(size_t i = 0; i < pages_total; i++) { + bool need_to_write = mask & 1; + mask >>= 1; + if(!need_to_write) continue; + t5577_write_block_pass(page, i, false, data->block[i], with_pass, password); + } + t5577_write_reset(); + FURI_CRITICAL_EXIT(); + t5577_stop(); +} diff --git a/lib/lfrfid/tools/t5577.h b/lib/lfrfid/tools/t5577.h index 6d53b5dc74b..f7b5cc4f5a5 100644 --- a/lib/lfrfid/tools/t5577.h +++ b/lib/lfrfid/tools/t5577.h @@ -42,6 +42,7 @@ extern "C" { typedef struct { uint32_t block[LFRFID_T5577_BLOCK_COUNT]; uint32_t blocks_to_write; + uint8_t mask; } LFRFIDT5577; /** @@ -51,6 +52,10 @@ typedef struct { */ void t5577_write(LFRFIDT5577* data); +void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password); + +void t5577_write_with_mask(LFRFIDT5577* data, uint8_t page, bool with_pass, uint32_t password); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 6921cecdc34..12b8fe685f9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3258,6 +3258,8 @@ Function,+,submenu_set_header,void,"Submenu*, const char*" Function,+,submenu_set_selected_item,void,"Submenu*, uint32_t" Function,-,system,int,const char* Function,+,t5577_write,void,LFRFIDT5577* +Function,+,t5577_write_with_mask,void,"LFRFIDT5577*, uint8_t, _Bool, uint32_t" +Function,+,t5577_write_with_pass,void,"LFRFIDT5577*, uint32_t" Function,-,tan,double,double Function,-,tanf,float,float Function,-,tanh,double,double From 87804374c501779c041482f3d076a92ff6a953c8 Mon Sep 17 00:00:00 2001 From: Radek Pilar Date: Thu, 15 Feb 2024 09:31:04 +0100 Subject: [PATCH 41/58] lfrfid/em4100: added support for different bit rates (#3410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * lfrfid/em4100: added support for different bit rates * Format Sources Co-authored-by: Radek Pilar Co-authored-by: あく --- lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 2 + lib/lfrfid/protocols/protocol_em4100.c | 140 +++++++++++++++++++++--- lib/lfrfid/protocols/protocol_em4100.h | 6 +- 4 files changed, 133 insertions(+), 17 deletions(-) diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index f07218d7f30..a8d0ff28049 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -20,6 +20,8 @@ const ProtocolBase* lfrfid_protocols[] = { [LFRFIDProtocolEM4100] = &protocol_em4100, + [LFRFIDProtocolEM410032] = &protocol_em4100_32, + [LFRFIDProtocolEM410016] = &protocol_em4100_16, [LFRFIDProtocolH10301] = &protocol_h10301, [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index 0cb7cbc8440..64a9fcba2e8 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -9,6 +9,8 @@ typedef enum { typedef enum { LFRFIDProtocolEM4100, + LFRFIDProtocolEM410032, + LFRFIDProtocolEM410016, LFRFIDProtocolH10301, LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index 4b720dffdab..38452007f3b 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -24,16 +24,9 @@ typedef uint64_t EM4100DecodedData; #define EM4100_DECODED_DATA_SIZE (5) #define EM4100_ENCODED_DATA_SIZE (sizeof(EM4100DecodedData)) -#define EM4100_CLOCK_PER_BIT (64) - -#define EM_READ_SHORT_TIME (256) -#define EM_READ_LONG_TIME (512) -#define EM_READ_JITTER_TIME (100) - -#define EM_READ_SHORT_TIME_LOW (EM_READ_SHORT_TIME - EM_READ_JITTER_TIME) -#define EM_READ_SHORT_TIME_HIGH (EM_READ_SHORT_TIME + EM_READ_JITTER_TIME) -#define EM_READ_LONG_TIME_LOW (EM_READ_LONG_TIME - EM_READ_JITTER_TIME) -#define EM_READ_LONG_TIME_HIGH (EM_READ_LONG_TIME + EM_READ_JITTER_TIME) +#define EM_READ_SHORT_TIME_BASE (256) +#define EM_READ_LONG_TIME_BASE (512) +#define EM_READ_JITTER_TIME_BASE (100) typedef struct { uint8_t data[EM4100_DECODED_DATA_SIZE]; @@ -43,10 +36,70 @@ typedef struct { bool encoded_polarity; ManchesterState decoder_manchester_state; + uint8_t clock_per_bit; } ProtocolEM4100; +uint16_t protocol_em4100_get_time_divisor(ProtocolEM4100* proto) { + switch(proto->clock_per_bit) { + case 64: + return 1; + case 32: + return 2; + case 16: + return 4; + default: + return 1; + } +} + +uint32_t protocol_em4100_get_t5577_bitrate(ProtocolEM4100* proto) { + switch(proto->clock_per_bit) { + case 64: + return LFRFID_T5577_BITRATE_RF_64; + case 32: + return LFRFID_T5577_BITRATE_RF_32; + case 16: + return LFRFID_T5577_BITRATE_RF_16; + default: + return LFRFID_T5577_BITRATE_RF_64; + } +} + +uint16_t protocol_em4100_get_short_time_low(ProtocolEM4100* proto) { + return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) - + EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); +} + +uint16_t protocol_em4100_get_short_time_high(ProtocolEM4100* proto) { + return EM_READ_SHORT_TIME_BASE / protocol_em4100_get_time_divisor(proto) + + EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); +} + +uint16_t protocol_em4100_get_long_time_low(ProtocolEM4100* proto) { + return EM_READ_LONG_TIME_BASE / protocol_em4100_get_time_divisor(proto) - + EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); +} + +uint16_t protocol_em4100_get_long_time_high(ProtocolEM4100* proto) { + return EM_READ_LONG_TIME_BASE / protocol_em4100_get_time_divisor(proto) + + EM_READ_JITTER_TIME_BASE / protocol_em4100_get_time_divisor(proto); +} + ProtocolEM4100* protocol_em4100_alloc(void) { ProtocolEM4100* proto = malloc(sizeof(ProtocolEM4100)); + proto->clock_per_bit = 64; + return (void*)proto; +}; + +ProtocolEM4100* protocol_em4100_16_alloc(void) { + ProtocolEM4100* proto = malloc(sizeof(ProtocolEM4100)); + proto->clock_per_bit = 16; + return (void*)proto; +}; + +ProtocolEM4100* protocol_em4100_32_alloc(void) { + ProtocolEM4100* proto = malloc(sizeof(ProtocolEM4100)); + proto->clock_per_bit = 32; return (void*)proto; }; @@ -145,13 +198,16 @@ bool protocol_em4100_decoder_feed(ProtocolEM4100* proto, bool level, uint32_t du ManchesterEvent event = ManchesterEventReset; - if(duration > EM_READ_SHORT_TIME_LOW && duration < EM_READ_SHORT_TIME_HIGH) { + if(duration > protocol_em4100_get_short_time_low(proto) && + duration < protocol_em4100_get_short_time_high(proto)) { if(!level) { event = ManchesterEventShortHigh; } else { event = ManchesterEventShortLow; } - } else if(duration > EM_READ_LONG_TIME_LOW && duration < EM_READ_LONG_TIME_HIGH) { + } else if( + duration > protocol_em4100_get_long_time_low(proto) && + duration < protocol_em4100_get_long_time_high(proto)) { if(!level) { event = ManchesterEventLongHigh; } else { @@ -227,7 +283,7 @@ bool protocol_em4100_encoder_start(ProtocolEM4100* proto) { LevelDuration protocol_em4100_encoder_yield(ProtocolEM4100* proto) { bool level = (proto->encoded_data >> (63 - proto->encoded_data_index)) & 1; - uint32_t duration = EM4100_CLOCK_PER_BIT / 2; + uint32_t duration = proto->clock_per_bit / 2; if(proto->encoded_polarity) { proto->encoded_polarity = false; @@ -260,7 +316,7 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { if(request->write_type == LFRFIDWriteTypeT5577) { request->t5577.block[0] = - (LFRFID_T5577_MODULATION_MANCHESTER | LFRFID_T5577_BITRATE_RF_64 | + (LFRFID_T5577_MODULATION_MANCHESTER | protocol_em4100_get_t5577_bitrate(protocol) | (2 << LFRFID_T5577_MAXBLOCK_SHIFT)); request->t5577.block[1] = protocol->encoded_data; request->t5577.block[2] = protocol->encoded_data >> 32; @@ -273,7 +329,11 @@ bool protocol_em4100_write_data(ProtocolEM4100* protocol, void* data) { void protocol_em4100_render_data(ProtocolEM4100* protocol, FuriString* result) { uint8_t* data = protocol->data; furi_string_printf( - result, "FC: %03u, Card: %05u", data[2], (uint16_t)((data[3] << 8) | (data[4]))); + result, + "FC: %03u, Card: %05u (RF/%u)", + data[2], + (uint16_t)((data[3] << 8) | (data[4])), + protocol->clock_per_bit); }; const ProtocolBase protocol_em4100 = { @@ -298,4 +358,52 @@ const ProtocolBase protocol_em4100 = { .render_data = (ProtocolRenderData)protocol_em4100_render_data, .render_brief_data = (ProtocolRenderData)protocol_em4100_render_data, .write_data = (ProtocolWriteData)protocol_em4100_write_data, -}; \ No newline at end of file +}; + +const ProtocolBase protocol_em4100_32 = { + .name = "EM4100/32", + .manufacturer = "EM-Micro", + .data_size = EM4100_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_em4100_32_alloc, + .free = (ProtocolFree)protocol_em4100_free, + .get_data = (ProtocolGetData)protocol_em4100_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_em4100_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_em4100_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_em4100_encoder_start, + .yield = (ProtocolEncoderYield)protocol_em4100_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_em4100_render_data, + .render_brief_data = (ProtocolRenderData)protocol_em4100_render_data, + .write_data = (ProtocolWriteData)protocol_em4100_write_data, +}; + +const ProtocolBase protocol_em4100_16 = { + .name = "EM4100/16", + .manufacturer = "EM-Micro", + .data_size = EM4100_DECODED_DATA_SIZE, + .features = LFRFIDFeatureASK | LFRFIDFeaturePSK, + .validate_count = 3, + .alloc = (ProtocolAlloc)protocol_em4100_16_alloc, + .free = (ProtocolFree)protocol_em4100_free, + .get_data = (ProtocolGetData)protocol_em4100_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_em4100_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_em4100_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_em4100_encoder_start, + .yield = (ProtocolEncoderYield)protocol_em4100_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_em4100_render_data, + .render_brief_data = (ProtocolRenderData)protocol_em4100_render_data, + .write_data = (ProtocolWriteData)protocol_em4100_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_em4100.h b/lib/lfrfid/protocols/protocol_em4100.h index 6e1e25b9371..23af66e279a 100644 --- a/lib/lfrfid/protocols/protocol_em4100.h +++ b/lib/lfrfid/protocols/protocol_em4100.h @@ -1,4 +1,8 @@ #pragma once #include -extern const ProtocolBase protocol_em4100; \ No newline at end of file +extern const ProtocolBase protocol_em4100; + +extern const ProtocolBase protocol_em4100_32; + +extern const ProtocolBase protocol_em4100_16; From 60a9d7e6cf37f1cc9a57d5665bb1a8844a9b0baa Mon Sep 17 00:00:00 2001 From: hedger Date: Fri, 16 Feb 2024 11:20:45 +0400 Subject: [PATCH 42/58] ble: profile rework (#3272) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ble: profile rework, initial * apps: hid: fix for pairing cleanup * app: hid: select transport based on #define * fixing PVS warnings * ble: serial service: fixed uid naming * bt service: on-demand dialog init; ble profiles: docs; battery svc: proper update * Added shci_cmd_resp_wait/shci_cmd_resp_release impl with semaphore * app: hid: separated transport code * ble: fixed service init order for serial svc; moved hardfault check to ble_glue * cli: ps: added thread prio to output, fixed heap display * ble_glue: naming changes; separate thread for event processing; * furi: added runtime stats; cli: added cpu% to `ps` * cli: fixed thread time calculation * furi: added getter for thread priority * fixing pvs warnings * hid profile: fixed naming * more naming fixes * hal: ble init small cleanup * cleanup & draft beacon api * f18: api sync * apps: moved example_custom_font from debug to examples * BLE extra beacon demo app * naming fix * UI fixes for demo app (wip) * desktop, ble svc: added statusbar icon for beacon * minor cleanup * Minor cleanup & naming fixes * api sync * Removed stale header * hal: added FURI_BLE_EXTRA_LOG for extra logging; comments & code cleanup * naming & macro fixes * quick fixes from review * Eliminated stock svc_ctl * cli: ps: removed runtime stats * minor include fixes * (void) * naming fixes * More naming fixes * fbt: always build all libs * fbt: explicitly globbing libs; dist: logging SDK path * scripts: fixed lib path precedence * hal: bt: profiles: naming changes, support for passing params to a profile; include cleanup * ble: hid: added parameter processing for profile template * api sync * BLE HID: long name trim * Removed unused check * desktop: updated beacon status icon; ble: hid: cleaner device name management * desktop: updated status icon Co-authored-by: あく Co-authored-by: nminaylov --- .../example_ble_beacon/application.fam | 11 + .../example_ble_beacon/ble_beacon_app.c | 149 ++++++ .../example_ble_beacon/ble_beacon_app.h | 50 ++ .../example_ble_beacon_10px.png | Bin 0 -> 8727 bytes .../images/lighthouse_35x44.png | Bin 0 -> 8910 bytes .../example_ble_beacon/scenes/scene_config.h | 4 + .../scenes/scene_input_beacon_data.c | 44 ++ .../scenes/scene_input_mac_addr.c | 44 ++ .../example_ble_beacon/scenes/scene_menu.c | 56 +++ .../scenes/scene_run_beacon.c | 79 ++++ .../example_ble_beacon/scenes/scenes.c | 30 ++ .../example_ble_beacon/scenes/scenes.h | 29 ++ .../example_custom_font/application.fam | 4 +- .../example_custom_font/example_custom_font.c | 0 .../main/archive/helpers/archive_apps.h | 2 + .../main/nfc/plugins/supported_cards/opal.c | 2 +- .../main/nfc/plugins/supported_cards/umarsh.c | 1 - applications/services/bt/bt_cli.c | 9 +- applications/services/bt/bt_service/bt.c | 175 ++++--- applications/services/bt/bt_service/bt.h | 24 +- applications/services/bt/bt_service/bt_api.c | 23 +- applications/services/bt/bt_service/bt_i.h | 11 +- applications/services/cli/cli_command_gpio.c | 1 - applications/services/cli/cli_commands.c | 14 +- .../widget_elements/widget_element_i.h | 4 +- applications/services/loader/loader.c | 1 - applications/services/rpc/rpc.c | 3 +- applications/services/rpc/rpc.h | 1 + .../scenes/bt_settings_scene_start.c | 2 +- applications/system/hid_app/application.fam | 5 + applications/system/hid_app/hid.c | 188 ++------ applications/system/hid_app/hid.h | 4 +- applications/system/hid_app/transport_ble.c | 60 +++ applications/system/hid_app/transport_usb.c | 61 +++ applications/system/hid_app/views/hid_media.c | 2 +- assets/icons/StatusBar/BLE_beacon_7x8.png | Bin 0 -> 117 bytes firmware.scons | 4 + furi/core/check.c | 2 +- furi/core/thread.c | 16 +- furi/core/thread.h | 9 +- lib/SConscript | 1 + lib/ble_profile/SConscript | 27 ++ lib/ble_profile/extra_profiles/hid_profile.c | 427 ++++++++++++++++++ lib/ble_profile/extra_profiles/hid_profile.h | 105 +++++ .../ble_profile/extra_services}/hid_service.c | 220 ++++----- lib/ble_profile/extra_services/hid_service.h | 29 ++ lib/nfc/protocols/st25tb/st25tb.c | 1 - lib/stm32wb.scons | 1 - scripts/fbt_tools/fbt_hwtarget.py | 7 +- scripts/sconsdist.py | 15 +- targets/f18/api_symbols.csv | 128 ++++-- targets/f7/api_symbols.csv | 130 ++++-- targets/f7/ble_glue/app_common.h | 2 +- targets/f7/ble_glue/app_conf.h | 2 +- targets/f7/ble_glue/ble_app.c | 99 ++-- targets/f7/ble_glue/ble_app.h | 10 +- targets/f7/ble_glue/ble_conf.h | 9 +- targets/f7/ble_glue/ble_event_thread.c | 96 ++++ targets/f7/ble_glue/ble_event_thread.h | 15 + targets/f7/ble_glue/ble_glue.c | 235 ++++------ targets/f7/ble_glue/ble_glue.h | 46 +- targets/f7/ble_glue/ble_tl_hooks.c | 40 ++ targets/f7/ble_glue/extra_beacon.c | 161 +++++++ targets/f7/ble_glue/extra_beacon.h | 98 ++++ .../f7/ble_glue/furi_ble/event_dispatcher.c | 97 ++++ .../f7/ble_glue/furi_ble/event_dispatcher.h | 50 ++ .../{services/gatt_char.c => furi_ble/gatt.c} | 51 ++- targets/f7/ble_glue/furi_ble/gatt.h | 110 +++++ .../f7/ble_glue/furi_ble/profile_interface.h | 39 ++ targets/f7/ble_glue/gap.c | 34 +- targets/f7/ble_glue/gap.h | 16 +- targets/f7/ble_glue/profiles/serial_profile.c | 114 +++++ targets/f7/ble_glue/profiles/serial_profile.h | 61 +++ .../f7/ble_glue/services/battery_service.c | 171 ++++--- .../f7/ble_glue/services/battery_service.h | 22 +- .../f7/ble_glue/services/dev_info_service.c | 121 ++--- .../f7/ble_glue/services/dev_info_service.h | 11 +- targets/f7/ble_glue/services/gatt_char.h | 96 ---- targets/f7/ble_glue/services/hid_service.h | 29 -- targets/f7/ble_glue/services/serial_service.c | 135 +++--- targets/f7/ble_glue/services/serial_service.h | 32 +- .../ble_glue/services/serial_service_uuid.inc | 8 +- targets/f7/furi_hal/furi_hal_bt.c | 221 ++++----- targets/f7/furi_hal/furi_hal_bt_hid.c | 289 ------------ targets/f7/furi_hal/furi_hal_bt_serial.c | 64 --- targets/f7/furi_hal/furi_hal_cortex.c | 2 +- targets/f7/inc/FreeRTOSConfig.h | 10 +- targets/f7/target.json | 5 +- targets/furi_hal_include/furi_hal_bt.h | 116 +++-- targets/furi_hal_include/furi_hal_bt_hid.h | 91 ---- targets/furi_hal_include/furi_hal_bt_serial.h | 59 --- 91 files changed, 3352 insertions(+), 1730 deletions(-) create mode 100644 applications/examples/example_ble_beacon/application.fam create mode 100644 applications/examples/example_ble_beacon/ble_beacon_app.c create mode 100644 applications/examples/example_ble_beacon/ble_beacon_app.h create mode 100644 applications/examples/example_ble_beacon/example_ble_beacon_10px.png create mode 100644 applications/examples/example_ble_beacon/images/lighthouse_35x44.png create mode 100644 applications/examples/example_ble_beacon/scenes/scene_config.h create mode 100644 applications/examples/example_ble_beacon/scenes/scene_input_beacon_data.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_menu.c create mode 100644 applications/examples/example_ble_beacon/scenes/scene_run_beacon.c create mode 100644 applications/examples/example_ble_beacon/scenes/scenes.c create mode 100644 applications/examples/example_ble_beacon/scenes/scenes.h rename applications/{debug => examples}/example_custom_font/application.fam (71%) rename applications/{debug => examples}/example_custom_font/example_custom_font.c (100%) create mode 100644 applications/system/hid_app/transport_ble.c create mode 100644 applications/system/hid_app/transport_usb.c create mode 100644 assets/icons/StatusBar/BLE_beacon_7x8.png create mode 100644 lib/ble_profile/SConscript create mode 100644 lib/ble_profile/extra_profiles/hid_profile.c create mode 100644 lib/ble_profile/extra_profiles/hid_profile.h rename {targets/f7/ble_glue/services => lib/ble_profile/extra_services}/hid_service.c (53%) create mode 100644 lib/ble_profile/extra_services/hid_service.h create mode 100644 targets/f7/ble_glue/ble_event_thread.c create mode 100644 targets/f7/ble_glue/ble_event_thread.h create mode 100644 targets/f7/ble_glue/ble_tl_hooks.c create mode 100644 targets/f7/ble_glue/extra_beacon.c create mode 100644 targets/f7/ble_glue/extra_beacon.h create mode 100644 targets/f7/ble_glue/furi_ble/event_dispatcher.c create mode 100644 targets/f7/ble_glue/furi_ble/event_dispatcher.h rename targets/f7/ble_glue/{services/gatt_char.c => furi_ble/gatt.c} (73%) create mode 100644 targets/f7/ble_glue/furi_ble/gatt.h create mode 100644 targets/f7/ble_glue/furi_ble/profile_interface.h create mode 100644 targets/f7/ble_glue/profiles/serial_profile.c create mode 100644 targets/f7/ble_glue/profiles/serial_profile.h delete mode 100644 targets/f7/ble_glue/services/gatt_char.h delete mode 100644 targets/f7/ble_glue/services/hid_service.h delete mode 100644 targets/f7/furi_hal/furi_hal_bt_hid.c delete mode 100644 targets/f7/furi_hal/furi_hal_bt_serial.c delete mode 100644 targets/furi_hal_include/furi_hal_bt_hid.h delete mode 100644 targets/furi_hal_include/furi_hal_bt_serial.h diff --git a/applications/examples/example_ble_beacon/application.fam b/applications/examples/example_ble_beacon/application.fam new file mode 100644 index 00000000000..fc5a911ab1b --- /dev/null +++ b/applications/examples/example_ble_beacon/application.fam @@ -0,0 +1,11 @@ +App( + appid="example_ble_beacon", + name="Example: BLE Beacon", + apptype=FlipperAppType.EXTERNAL, + entry_point="ble_beacon_app", + requires=["gui"], + stack_size=1 * 1024, + fap_icon="example_ble_beacon_10px.png", + fap_category="Examples", + fap_icon_assets="images", +) diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.c b/applications/examples/example_ble_beacon/ble_beacon_app.c new file mode 100644 index 00000000000..20e3e307ab9 --- /dev/null +++ b/applications/examples/example_ble_beacon/ble_beacon_app.c @@ -0,0 +1,149 @@ +#include "ble_beacon_app.h" + +#include +#include + +#include + +#define TAG "ble_beacon_app" + +static bool ble_beacon_app_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + BleBeaconApp* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool ble_beacon_app_back_event_callback(void* context) { + furi_assert(context); + BleBeaconApp* app = context; + return scene_manager_handle_back_event(app->scene_manager); +} + +static void ble_beacon_app_tick_event_callback(void* context) { + furi_assert(context); + BleBeaconApp* app = context; + scene_manager_handle_tick_event(app->scene_manager); +} + +static void ble_beacon_app_restore_beacon_state(BleBeaconApp* app) { + // Restore beacon data from service + GapExtraBeaconConfig* local_config = &app->beacon_config; + const GapExtraBeaconConfig* config = furi_hal_bt_extra_beacon_get_config(); + if(config) { + // We have a config, copy it + memcpy(local_config, config, sizeof(app->beacon_config)); + } else { + // No config, set up default values - they will stay until overriden or device is reset + local_config->min_adv_interval_ms = 50; + local_config->max_adv_interval_ms = 150; + + local_config->adv_channel_map = GapAdvChannelMapAll; + local_config->adv_power_level = GapAdvPowerLevel_0dBm; + + local_config->address_type = GapAddressTypePublic; + memcpy( + local_config->address, furi_hal_version_get_ble_mac(), sizeof(local_config->address)); + // Modify MAC address to make it different from the one used by the main app + local_config->address[0] ^= 0xFF; + local_config->address[3] ^= 0xFF; + + furi_check(furi_hal_bt_extra_beacon_set_config(local_config)); + } + + // Get beacon state + app->is_beacon_active = furi_hal_bt_extra_beacon_is_active(); + + // Restore last beacon data + app->beacon_data_len = furi_hal_bt_extra_beacon_get_data(app->beacon_data); +} + +static BleBeaconApp* ble_beacon_app_alloc() { + BleBeaconApp* app = malloc(sizeof(BleBeaconApp)); + + app->gui = furi_record_open(RECORD_GUI); + + app->scene_manager = scene_manager_alloc(&ble_beacon_app_scene_handlers, app); + app->view_dispatcher = view_dispatcher_alloc(); + + app->status_string = furi_string_alloc(); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback( + app->view_dispatcher, ble_beacon_app_custom_event_callback); + view_dispatcher_set_navigation_event_callback( + app->view_dispatcher, ble_beacon_app_back_event_callback); + view_dispatcher_set_tick_event_callback( + app->view_dispatcher, ble_beacon_app_tick_event_callback, 100); + view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_enable_queue(app->view_dispatcher); + + app->submenu = submenu_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewSubmenu, submenu_get_view(app->submenu)); + + app->dialog_ex = dialog_ex_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewDialog, dialog_ex_get_view(app->dialog_ex)); + + app->byte_input = byte_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, BleBeaconAppViewByteInput, byte_input_get_view(app->byte_input)); + + ble_beacon_app_restore_beacon_state(app); + + return app; +} + +static void ble_beacon_app_free(BleBeaconApp* app) { + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewByteInput); + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewSubmenu); + view_dispatcher_remove_view(app->view_dispatcher, BleBeaconAppViewDialog); + + free(app->byte_input); + free(app->submenu); + free(app->dialog_ex); + + free(app->scene_manager); + free(app->view_dispatcher); + + free(app->status_string); + + furi_record_close(RECORD_NOTIFICATION); + furi_record_close(RECORD_GUI); + app->gui = NULL; + + free(app); +} + +int32_t ble_beacon_app(void* args) { + UNUSED(args); + + BleBeaconApp* app = ble_beacon_app_alloc(); + + scene_manager_next_scene(app->scene_manager, BleBeaconAppSceneRunBeacon); + + view_dispatcher_run(app->view_dispatcher); + + ble_beacon_app_free(app); + return 0; +} + +void ble_beacon_app_update_state(BleBeaconApp* app) { + furi_hal_bt_extra_beacon_stop(); + + furi_check(furi_hal_bt_extra_beacon_set_config(&app->beacon_config)); + + app->beacon_data_len = 0; + while((app->beacon_data[app->beacon_data_len] != 0) && + (app->beacon_data_len < sizeof(app->beacon_data))) { + app->beacon_data_len++; + } + + FURI_LOG_I(TAG, "beacon_data_len: %d", app->beacon_data_len); + + furi_check(furi_hal_bt_extra_beacon_set_data(app->beacon_data, app->beacon_data_len)); + + if(app->is_beacon_active) { + furi_check(furi_hal_bt_extra_beacon_start()); + } +} diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.h b/applications/examples/example_ble_beacon/ble_beacon_app.h new file mode 100644 index 00000000000..563bd5bedae --- /dev/null +++ b/applications/examples/example_ble_beacon/ble_beacon_app.h @@ -0,0 +1,50 @@ +#pragma once + +#include "extra_beacon.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include "scenes/scenes.h" +#include + +typedef struct { + Gui* gui; + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + + Submenu* submenu; + ByteInput* byte_input; + DialogEx* dialog_ex; + + FuriString* status_string; + + GapExtraBeaconConfig beacon_config; + uint8_t beacon_data[EXTRA_BEACON_MAX_DATA_SIZE]; + uint8_t beacon_data_len; + bool is_beacon_active; +} BleBeaconApp; + +typedef enum { + BleBeaconAppViewSubmenu, + BleBeaconAppViewByteInput, + BleBeaconAppViewDialog, +} BleBeaconAppView; + +typedef enum { + BleBeaconAppCustomEventDataEditResult = 100, +} BleBeaconAppCustomEvent; + +void ble_beacon_app_update_state(BleBeaconApp* app); diff --git a/applications/examples/example_ble_beacon/example_ble_beacon_10px.png b/applications/examples/example_ble_beacon/example_ble_beacon_10px.png new file mode 100644 index 0000000000000000000000000000000000000000..7060e893db14944726f2d5141ca6fb29f5333bc8 GIT binary patch literal 8727 zcmV+yBIw3>X1L!Heg+=e&2$yZ5hGbGoXgr@E@9`_}*nH$?_UCBQ5IL`5gYn>!hLxO;fg zF^hlz@PG)YK*K*UAyz6nEEW8(vhf5!|LlzHzg+)&-2W)ZAZ9|~uI=3&R|8|?69Ir? zcLy~kF?QF7^LOXjBV3$}cl~Mr=*0hy|0i_wc>gq66IBISW@IE&)>TP1AwDD^F+NZ( zEHLo@sv!i-K|JsWX+Rd309Bx{YndQ&_hIgOu0Uy5C+tRqfB=xVYXgBC2;0?x|4+C7 zpOVN-P5ft#V`9_d!$Lw6=}HQUsxoxb-S+9ui80Ykx?~D7AR#P~sYMS>OpMi%myZsM zW(F}s;+f2Z0A^%NidXjlT>(%6_D8y^!K zwp(WRH8L!a8J)lkq9;WMG2`j}bmMrYe_~iNlWrUn6%`YmKsQWGj1LP)N(_sMmURyG zk7pW2hD9*x%5nOZT^#mF8Qy~gxyX9|2s?If0D?>#D~cLcc}#VfHc_z zfBD@6@{!CC|H#0YASOfZ-(LLV?LWKoe>e(EickK}9Lqli0T>_w3ZMZ7;C8tr?2<_a zl--Z4fDNz%8sGq2fE(}tUcd(gfFPjnp1BAR1!6!PNCF0s0@Ax@EC=Lw&t3^A1C?D0 z)PV-j1X@5F=mI^U4-9}2Fy7_Ebe9$jU{fAa0kVgk74FK?+FSB`+OhfK0F-WP==#3-Uk# zH~S}a2UK$?&?qyy&p6K`zi9$PMy< zJRxt$7upN?LxB(z3V}kQa3~UrhGL*NC;>`>lA%;+ACv*@hq9m?C=V)t4njrHA?Pqv z0v&@&p>n7as)9~HwNO3O05w8qpk}BQIt!hHI-v7V7t{mwLjBMHbOpK!4M8K&D0CAV zhi*d?&^>4pnu4aGN6-^!4tfU7LkrL=XbE}?Ekmo&2j~;@8Ttx+ht{E=&=&Lu+JONK z!$=qd<6r_zf+;W+W`{XoE|>@AhXr9FSOgY>C1EL829|>rVP#kq)_}EO9as-GgpFZS z*aEhKZD4!Y5q5@MVRzUQ_JRFie>eyZhQr_pI0}x1+mML1#iQD5ikOUz#{Mn5`qQ6hM*z15WENh1RWuQ z5JxZ&G6*?@B0>eBj?hBrBJ>eP2vdXw!WvBEJ_ijg3>_gpbSvP zD07rG${yu}az%Nfd{F_YU{n|?3KfS+LZzWHQ8}o5R3Yjx>KLjVRgJ1cokBIC+E5*+ z3#f~zOQ=EAFzN>C7V0kQ0cr*{i+YY)L@l9~Q6EsBQ9n>WQGd_?jYMP7L^KPU9nFR2 zLkpqB&^)1Tmr*NsKH;5u=9D!sug+F%}qGj3dSs~d5u}de8haktYfw?f3Zj` z4okvPv7A^wtPoZlD~*-Ms$ey-dRSwu1=bepgmuGuWBswg*a&PaHVL~An}yBC7GX=U zW!P$LJ@z!V72ARB#`a?eu_M?q>;(1!_7V0eb`kpqyNX@Ie#idAZsQO*ERKYu;y7{q zIANRwP6nrlQ^)Dx3~^>S8=ND~73YQX#|7geaB;X~TskfXcK}z6JC3Ww)#4g)Ex2>I zZd^a^D(*UN9Cr`*5I2jP$Gyfa<38cO;eO(_@h~2XC*j%f+;{=JD4v0r$E)DA@cMXD zycOO7?~3=r`{RT0k@$Fg3O*B`hcCpJ;LGtR@D2E8d^^4i-;ckFzmC6!zmK26&*2yF zZ}9K&pYiMX-vo$&ArJ{v0vADmAWD!T$P-iv+5|&_Il-3TOzs!e_!d;WrT?Vu&On8%`wAh=e7PNi-5KNr)s#k|U{-v`I!J3z9u)56PPpKnf$pkdjFmq&!j) z=@_Y!R8MLmwUfF@mq2GMP*x^OA+h46-~~jjT&H zAzP6h$?jx7axgiPoIu`3&LJ0)OUM=EI`SEEJGq-YKprBGk?)ab$j`{H$SdT}{kiX+9H;ztRlL{Snc>6Bbb5#<=An$kdNp>$GuDT9y0QAPhOkDnCbMR;7O);+EoZG`ZDKvgdXaUIb(D32b(-}l>nqk()-S9-sep>5 zQmC9%L8=5*j;co0qnc6esC%eBR3U?Z@xvGKBrut~EivuU%Lu-UM=uz9luu|={avSqO4vmItDXRBvxX6t0@ zW4p#S#&)0W3EKkOJGM2p4Yt4R78l3jsagWZtblHHNrlRbbvoIQa(ojs5J zFnc+B9eXo-Cwo8p5c@d$1NK?=m+UL-U)g`rAR3-VrSZ~4XfiYvnl8ocx^PobsF+ zoQ9lMoX(uyoJ`JW&J@mU&LYlI&N|L!&hwm?I7c||a87eR=X}HYiF1<+aN)S9T)bSO zTyk9MTn1d0TuxlxTuiQLt`x2uu0vd9Ty)PQV`M>G81wX@)ima ziWk~1R3ua`)F9L@)F(6|bWdnj=(W%%p`XG?VTv%1u$Zudu#T{~u#>Q_aF}qSaF%ef zaHVjgaHsHq@D1Sy!q0@?34ay-BZ3uS6A=`V5>XW~6tNL;7YP)J5!oklK;*bcy-1r# zugI{-U6CguuSM2Geu<()Sw#6nB}J7*^+l~kT}1;#qeat13q+5L)`_->_KFUR-W8n{ zT@qar-4a8KQN;wr7-FhohGMp29%4+fSg{PTLa}nOQ(_%r17bJDro`sOmc_n{{S_yO zbBc?I%ZqD^n~OV(`-w-0r-;-17)iMJA8CAKB;k{psElJb(;k`|IKl6xhiB-11h zNR~>TlsqSSN%E%Tl;jJ^Rmlwo%%CuM8IlYYh9Se2;mHVLBrviWhZ!drEsP$lM0fGlggAjBvmEVB-Jf-O=?1FR_cw^SE(Ioyfmk@ zsI;QAp0u^JyEIceUOG$qu=EM(X6YX3Vd;C)Po>{Uf0uz|$TGY#k}|3?Ml$v?-ZEh_ z$ufB|$7D{*oRb-l8IzfoS(N!Cvn7j>WtSC_m6z3#wUl+04U&zM-7i}#dqTEF_M+@{ z*-6>wva7Nia!5H=IRQBtIZZipITty9xfr<&xkGYQa?Nr*awBs0<(|u}$Zg0YQYBrbNTph(MWs*W zhRU?cOO?+m+p0uW9#w{_x~iF~i)x^1ylS@UQPq0Y4%I=`JF0W4%c|>YNHsP!AvFax zeKk8ZZ?y=ueQHH&RcbA2eQGz=X4GD(eNp?XPFCkvmr>VNw^Vmm4^dB6FHkR6Kcn8G zeqDV^eNlZ)eOrU5!K)#qp{Ze^;ieI+k))BYQKoTPqg&&;#+1gQ#+t^qCQ*}DQ(99? z(^Au2Gek2*^MGcBW|L;G<_*nh%~zUVwSX2yOHfNrOIOQQ%Udf#D_!f5R*lwKtpTmu zTC-ZqS{vGEZJM^2wu-iiwzGDicD#13_Hpe-?Jn&R?J4a=?a$gfI%FLI9a$Y+9a|l5 zok*PwonoC@opU-@bS8A3>ActZrHj+$)@A5w>RRZ!>xSy4>K5u&>$d7%(!HfStGleb zsfW?y)RWLt*E831(+kl{(L1PDrPr!=N$-~4tlqNTranfWQ(r<~UEf^aO+Q3GRliWb zTE9(yK>xP>Q~g!_Uj{e>ZUZR;Edwh9PlE`9bc14pI)e^_s|NQCUKo5b*fAs<3K+^8 z>Kob{?lp`x%rQJ}c-pYX@P^@x;TyvrMkphik+_kXk-3qZQK(U>QIS!NQM=Kg(Osi? zqfbWL#$;nbV|il(V@G3u<9Opd<8tFB<38hY<5}ZnK1HXAjYF?(aSVU989GG~};o7X8y!{#r&5A z-h$6U)dwTiWwwYzngb-MKt z>jvv?>l@aOt(UET+2C#XY~*YVY@BR@Y?5sXZE9>fY=&&6Y+l>^utnQ)+e+K&+S=Rt z+a}l+*jCw|wY_RPY5UUln;p`Q(~e=MZD(ueXBTgmZ&zt|)^5=5zTKkTH+!T#hdsky z+uqK8uYJ6Ifqj*IyZu%BN&8p!-yP5nTn^F>x(*Hw0S-wH2OVl0Ivs``rXAimY&zl` z`5fgP4INz^Lmbl_4?8wEc01m5oON7v{NqG%5_VE?GI#QHigLVk;^7kMlI2qB((E$ea@S?S z<=YdAPEpR>I+Tl9v zI^+7zb<2(9Mt4(jGk5cHi+0O(t8hE(cGYdlZOLuZ9q%sSuIO&+?(QDxp6y=d-s*nE zebW84`??3tgWp5J!^FegBf=xgqs*h#iAUgln2UNK&IUR7S_yoSAIyq3LwdsDncyw$y}z4v-2dKY@vd3Sl= z^q%wn=mYuCd>B5uK8`+2pM5?hKBs;9eeU`!`h54r`ttcI_?q~7_(u8W`d0e3`wsig z_%8eY@ni85^V9UR^9%4x@hkQ_<=5+X$M1#Tx4r1SynE&Mn(TGo8?`rgZ{^-|dx!Tv z+Pk`U+n?$$;jiuQ;Lr5m=U?J~#(%*7zW*!#jQ~ObJwP?UGQc+=A)qjzKA1Rs zFM+5)ocGyx(ZJcjk3monN04-oLC~I{@Sv=qilFwO;h;xBt3f+V zHYS6q$8=_fGBcTF%r@pV<}`DKxgAUmmI&4jb_xy&&I~RMZVkQ`JRQ6eyd6Rfkqprd zaSjO$*&k9CayDcrsOE@~5FI*|yBHSlDA-pKOA-peqBK&3eMg%cJI6@=BE+QymU&OJ9 zmWZnn(-A8XJCW>>QjrFcu91T^ZdOeIxp5^p_ZP3}1|LjAhK;nBXI6fnwwga+MRkQ^=0bMG)kI8nqHb~ zT69`LT76n?+P$=;wBP&K_DStC+UL11ZeP*9#(e|(ruMC*gLKYx`E>Jizx3qvlJu7J zq4X!|YZ<5v{tVR&+YDw#W=2IuXU15@e8!JVVy0-OPNqv{WM+P5U1o3Q{mi$S+xuzz zW%rxz_t~Gc|H%I4{X_eo>|e`5X9;AfXW3_kWMySlWp!oU&U%^kE1NZ&k!_gmnH`^f zDEmzIVD_Wzk2%O3{v6dDyPV*htemQxuAJLBuX29nQgfwpjdQ(n6LSyeHs=oIKFR%@ zhsmSoY34cRh3Dnv)#ml)-OqcQx0BDAuaIw%@1MUfzbwBa|7QM+{EY%ifn4g=A=L>HYzAW4-Vk?pB`-_1j5no>8Kcak%(pDHtgNiF>~`7fvOndV<%;Dt<-z4S z<+bJgl3hjzL6|ohEDw-=sDxOuWS5hjaDorc>D)&`ZRCZNPRKBeORlHTI zRSs1VRR^jXs|Kr{RDG=`R!dYHRr^$@RF_tFR^P5(s@^%leM04g{fY1s1t(6O7(DUh z#Mc^PjYN%6jc-j_O?l0Qnu(gXwV;--R=w7#HmbI;wyAcw_F3(E9c!IToq1hg-Tt~0 zb$xYHbsy?6^}_YK^&a(!^~dVZ)sNS|uHQb%eNyG5!^wz~2Tz_pIdt;b$@K=-2AKwn zhQNlbhT4YyhUtd2Q@B%NrwmVdpGrMdeyZ!#-BZhrh(^Iitwz_z_{NgP_QvtXSB=}J zxlgN}c03(*y6ANC>FcLooc?u&c1H1x?U}GM1!o%1Ts!mh%z6`RlWdb^6SFC&>15ND zrYB9`n#s*l&F0O4%~{R0%>&Jkn!mIVTNo{-E&eV0TWVVRTV`55w-Q<~tdB|xfZs_RH`Jwwm zABOS6jA8TP;NgPdrs2`yS0i9Va71s!dnA3NX5{k7+{os2+I5xd&e!9wAHUvp{lWFM zQPQZ)sP$<0Xwhif=&jLrH&8c3Zy4VQxRG2)*xX3fpZH|K6{ zj&Y8ujqMpr8Y>^WI5s`@ZJc#nVccOnX8h>*`SJVXpKcLv$=tHO6>;m(t@c}YZmr(N z-j=*=emms$f!i&&$8Nv9gSsPn$K+1XoxD3|?%cTZdIFvho-my7pU9bLoEVvSc^BNJ z-!-`FcQ@;9!`>cRB~ucn|Wp(&%OfT`T6(^EI5mL4J=ias=X z$b4Avu=(Nm!*|n|X^Cm`>CoxI>9f;!rr*!tXQXFrW+G>f%yiC7&a6G6JW_b%@F?z4 z>7$-UGmm~eW`C^u*!6MBphM&EB z4m}rsZt|S@{J`_J=XajJpC`=A&fCq$%pafcnSV6D{(|F$#tV-Z=`ZSD482%ffEI)o zOct062N&8FCKf&}k{0C`9Twvk%NP3=XBU6HCUcP;WekJ+J>Q&^c zBd;#Jdid)5Yuan|*B-C;y{>yb^!nuzyd=70wiLQ_XsKgqa_P$(>Km0eu5VJ`)V#U+ zX5lULR^+Ye+mN?KZ_mBG|Mtr}>N}NpuJ6*`)xNv-ZgCl27F{-54qGl>?p&T){lD=|sWn^V(6}2k4YPA}*dUUmC_0j6)d+zr-?|t9ryl;9x{(kiX;e*@<#}5e~ zDnDHQF#loaqtHi_kHH^{K6ZS3@bTLx_D|}cJU?ZAI`!$sr?+d^HR(0GwYatNwM%Qy z*0w*>KO27z{#^9A(|y_ZXh=#Hmo;dHcB`8H=b^6ZwhUiY=&+g-t5|(+1&ie`%~{{z|R9e+kZ~}{Q8Ub zOY@h{ubf}azwZ3{xW%%ivgN*&v2|+e=GMw@;%|lDF27TM*Zm&({pJt$kIWy3KS_U1 z{JHk$)i!FIv2D8@zg@X~WqV-<-jUd`-ig^M-x=7M{|o+#{k8lX^|$nI|KI0-|Na*( zv2KeqRZ6%3000SaNLh0L04^f{04^f|c%?sf0000oNkl9001G2X+uL$X=7sm z0BC`wlXp}UL9^(4W^;yxC5I)m3>X1L!Heg+=e&2$yZ5hGbGoXgr@E@9`_}*nH$?_UCBQ5IL`5gYn>!hLxO;fg zF^hlz@PG)YK*K*UAyz6nEEW8(vhf5!|LlzHzg+)&-2W)ZAZ9|~uI=3&R|8|?69Ir? zcLy~kF?QF7^LOXjBV3$}cl~Mr=*0hy|0i_wc>gq66IBISW@IE&)>TP1AwDD^F+NZ( zEHLo@sv!i-K|JsWX+Rd309Bx{YndQ&_hIgOu0Uy5C+tRqfB=xVYXgBC2;0?x|4+C7 zpOVN-P5ft#V`9_d!$Lw6=}HQUsxoxb-S+9ui80Ykx?~D7AR#P~sYMS>OpMi%myZsM zW(F}s;+f2Z0A^%NidXjlT>(%6_D8y^!K zwp(WRH8L!a8J)lkq9;WMG2`j}bmMrYe_~iNlWrUn6%`YmKsQWGj1LP)N(_sMmURyG zk7pW2hD9*x%5nOZT^#mF8Qy~gxyX9|2s?If0D?>#D~cLcc}#VfHc_z zfBD@6@{!CC|H#0YASOfZ-(LLV?LWKoe>e(EickK}9Lqli0T>_w3ZMZ7;C8tr?2<_a zl--Z4fDNz%8sGq2fE(}tUcd(gfFPjnp1BAR1!6!PNCF0s0@Ax@EC=Lw&t3^A1C?D0 z)PV-j1X@5F=mI^U4-9}2Fy7_Ebe9$jU{fAa0kVgk74FK?+FSB`+OhfK0F-WP==#3-Uk# zH~S}a2UK$?&?qyy&p6K`zi9$PMy< zJRxt$7upN?LxB(z3V}kQa3~UrhGL*NC;>`>lA%;+ACv*@hq9m?C=V)t4njrHA?Pqv z0v&@&p>n7as)9~HwNO3O05w8qpk}BQIt!hHI-v7V7t{mwLjBMHbOpK!4M8K&D0CAV zhi*d?&^>4pnu4aGN6-^!4tfU7LkrL=XbE}?Ekmo&2j~;@8Ttx+ht{E=&=&Lu+JONK z!$=qd<6r_zf+;W+W`{XoE|>@AhXr9FSOgY>C1EL829|>rVP#kq)_}EO9as-GgpFZS z*aEhKZD4!Y5q5@MVRzUQ_JRFie>eyZhQr_pI0}x1+mML1#iQD5ikOUz#{Mn5`qQ6hM*z15WENh1RWuQ z5JxZ&G6*?@B0>eBj?hBrBJ>eP2vdXw!WvBEJ_ijg3>_gpbSvP zD07rG${yu}az%Nfd{F_YU{n|?3KfS+LZzWHQ8}o5R3Yjx>KLjVRgJ1cokBIC+E5*+ z3#f~zOQ=EAFzN>C7V0kQ0cr*{i+YY)L@l9~Q6EsBQ9n>WQGd_?jYMP7L^KPU9nFR2 zLkpqB&^)1Tmr*NsKH;5u=9D!sug+F%}qGj3dSs~d5u}de8haktYfw?f3Zj` z4okvPv7A^wtPoZlD~*-Ms$ey-dRSwu1=bepgmuGuWBswg*a&PaHVL~An}yBC7GX=U zW!P$LJ@z!V72ARB#`a?eu_M?q>;(1!_7V0eb`kpqyNX@Ie#idAZsQO*ERKYu;y7{q zIANRwP6nrlQ^)Dx3~^>S8=ND~73YQX#|7geaB;X~TskfXcK}z6JC3Ww)#4g)Ex2>I zZd^a^D(*UN9Cr`*5I2jP$Gyfa<38cO;eO(_@h~2XC*j%f+;{=JD4v0r$E)DA@cMXD zycOO7?~3=r`{RT0k@$Fg3O*B`hcCpJ;LGtR@D2E8d^^4i-;ckFzmC6!zmK26&*2yF zZ}9K&pYiMX-vo$&ArJ{v0vADmAWD!T$P-iv+5|&_Il-3TOzs!e_!d;WrT?Vu&On8%`wAh=e7PNi-5KNr)s#k|U{-v`I!J3z9u)56PPpKnf$pkdjFmq&!j) z=@_Y!R8MLmwUfF@mq2GMP*x^OA+h46-~~jjT&H zAzP6h$?jx7axgiPoIu`3&LJ0)OUM=EI`SEEJGq-YKprBGk?)ab$j`{H$SdT}{kiX+9H;ztRlL{Snc>6Bbb5#<=An$kdNp>$GuDT9y0QAPhOkDnCbMR;7O);+EoZG`ZDKvgdXaUIb(D32b(-}l>nqk()-S9-sep>5 zQmC9%L8=5*j;co0qnc6esC%eBR3U?Z@xvGKBrut~EivuU%Lu-UM=uz9luu|={avSqO4vmItDXRBvxX6t0@ zW4p#S#&)0W3EKkOJGM2p4Yt4R78l3jsagWZtblHHNrlRbbvoIQa(ojs5J zFnc+B9eXo-Cwo8p5c@d$1NK?=m+UL-U)g`rAR3-VrSZ~4XfiYvnl8ocx^PobsF+ zoQ9lMoX(uyoJ`JW&J@mU&LYlI&N|L!&hwm?I7c||a87eR=X}HYiF1<+aN)S9T)bSO zTyk9MTn1d0TuxlxTuiQLt`x2uu0vd9Ty)PQV`M>G81wX@)ima ziWk~1R3ua`)F9L@)F(6|bWdnj=(W%%p`XG?VTv%1u$Zudu#T{~u#>Q_aF}qSaF%ef zaHVjgaHsHq@D1Sy!q0@?34ay-BZ3uS6A=`V5>XW~6tNL;7YP)J5!oklK;*bcy-1r# zugI{-U6CguuSM2Geu<()Sw#6nB}J7*^+l~kT}1;#qeat13q+5L)`_->_KFUR-W8n{ zT@qar-4a8KQN;wr7-FhohGMp29%4+fSg{PTLa}nOQ(_%r17bJDro`sOmc_n{{S_yO zbBc?I%ZqD^n~OV(`-w-0r-;-17)iMJA8CAKB;k{psElJb(;k`|IKl6xhiB-11h zNR~>TlsqSSN%E%Tl;jJ^Rmlwo%%CuM8IlYYh9Se2;mHVLBrviWhZ!drEsP$lM0fGlggAjBvmEVB-Jf-O=?1FR_cw^SE(Ioyfmk@ zsI;QAp0u^JyEIceUOG$qu=EM(X6YX3Vd;C)Po>{Uf0uz|$TGY#k}|3?Ml$v?-ZEh_ z$ufB|$7D{*oRb-l8IzfoS(N!Cvn7j>WtSC_m6z3#wUl+04U&zM-7i}#dqTEF_M+@{ z*-6>wva7Nia!5H=IRQBtIZZipITty9xfr<&xkGYQa?Nr*awBs0<(|u}$Zg0YQYBrbNTph(MWs*W zhRU?cOO?+m+p0uW9#w{_x~iF~i)x^1ylS@UQPq0Y4%I=`JF0W4%c|>YNHsP!AvFax zeKk8ZZ?y=ueQHH&RcbA2eQGz=X4GD(eNp?XPFCkvmr>VNw^Vmm4^dB6FHkR6Kcn8G zeqDV^eNlZ)eOrU5!K)#qp{Ze^;ieI+k))BYQKoTPqg&&;#+1gQ#+t^qCQ*}DQ(99? z(^Au2Gek2*^MGcBW|L;G<_*nh%~zUVwSX2yOHfNrOIOQQ%Udf#D_!f5R*lwKtpTmu zTC-ZqS{vGEZJM^2wu-iiwzGDicD#13_Hpe-?Jn&R?J4a=?a$gfI%FLI9a$Y+9a|l5 zok*PwonoC@opU-@bS8A3>ActZrHj+$)@A5w>RRZ!>xSy4>K5u&>$d7%(!HfStGleb zsfW?y)RWLt*E831(+kl{(L1PDrPr!=N$-~4tlqNTranfWQ(r<~UEf^aO+Q3GRliWb zTE9(yK>xP>Q~g!_Uj{e>ZUZR;Edwh9PlE`9bc14pI)e^_s|NQCUKo5b*fAs<3K+^8 z>Kob{?lp`x%rQJ}c-pYX@P^@x;TyvrMkphik+_kXk-3qZQK(U>QIS!NQM=Kg(Osi? zqfbWL#$;nbV|il(V@G3u<9Opd<8tFB<38hY<5}ZnK1HXAjYF?(aSVU989GG~};o7X8y!{#r&5A z-h$6U)dwTiWwwYzngb-MKt z>jvv?>l@aOt(UET+2C#XY~*YVY@BR@Y?5sXZE9>fY=&&6Y+l>^utnQ)+e+K&+S=Rt z+a}l+*jCw|wY_RPY5UUln;p`Q(~e=MZD(ueXBTgmZ&zt|)^5=5zTKkTH+!T#hdsky z+uqK8uYJ6Ifqj*IyZu%BN&8p!-yP5nTn^F>x(*Hw0S-wH2OVl0Ivs``rXAimY&zl` z`5fgP4INz^Lmbl_4?8wEc01m5oON7v{NqG%5_VE?GI#QHigLVk;^7kMlI2qB((E$ea@S?S z<=YdAPEpR>I+Tl9v zI^+7zb<2(9Mt4(jGk5cHi+0O(t8hE(cGYdlZOLuZ9q%sSuIO&+?(QDxp6y=d-s*nE zebW84`??3tgWp5J!^FegBf=xgqs*h#iAUgln2UNK&IUR7S_yoSAIyq3LwdsDncyw$y}z4v-2dKY@vd3Sl= z^q%wn=mYuCd>B5uK8`+2pM5?hKBs;9eeU`!`h54r`ttcI_?q~7_(u8W`d0e3`wsig z_%8eY@ni85^V9UR^9%4x@hkQ_<=5+X$M1#Tx4r1SynE&Mn(TGo8?`rgZ{^-|dx!Tv z+Pk`U+n?$$;jiuQ;Lr5m=U?J~#(%*7zW*!#jQ~ObJwP?UGQc+=A)qjzKA1Rs zFM+5)ocGyx(ZJcjk3monN04-oLC~I{@Sv=qilFwO;h;xBt3f+V zHYS6q$8=_fGBcTF%r@pV<}`DKxgAUmmI&4jb_xy&&I~RMZVkQ`JRQ6eyd6Rfkqprd zaSjO$*&k9CayDcrsOE@~5FI*|yBHSlDA-pKOA-peqBK&3eMg%cJI6@=BE+QymU&OJ9 zmWZnn(-A8XJCW>>QjrFcu91T^ZdOeIxp5^p_ZP3}1|LjAhK;nBXI6fnwwga+MRkQ^=0bMG)kI8nqHb~ zT69`LT76n?+P$=;wBP&K_DStC+UL11ZeP*9#(e|(ruMC*gLKYx`E>Jizx3qvlJu7J zq4X!|YZ<5v{tVR&+YDw#W=2IuXU15@e8!JVVy0-OPNqv{WM+P5U1o3Q{mi$S+xuzz zW%rxz_t~Gc|H%I4{X_eo>|e`5X9;AfXW3_kWMySlWp!oU&U%^kE1NZ&k!_gmnH`^f zDEmzIVD_Wzk2%O3{v6dDyPV*htemQxuAJLBuX29nQgfwpjdQ(n6LSyeHs=oIKFR%@ zhsmSoY34cRh3Dnv)#ml)-OqcQx0BDAuaIw%@1MUfzbwBa|7QM+{EY%ifn4g=A=L>HYzAW4-Vk?pB`-_1j5no>8Kcak%(pDHtgNiF>~`7fvOndV<%;Dt<-z4S z<+bJgl3hjzL6|ohEDw-=sDxOuWS5hjaDorc>D)&`ZRCZNPRKBeORlHTI zRSs1VRR^jXs|Kr{RDG=`R!dYHRr^$@RF_tFR^P5(s@^%leM04g{fY1s1t(6O7(DUh z#Mc^PjYN%6jc-j_O?l0Qnu(gXwV;--R=w7#HmbI;wyAcw_F3(E9c!IToq1hg-Tt~0 zb$xYHbsy?6^}_YK^&a(!^~dVZ)sNS|uHQb%eNyG5!^wz~2Tz_pIdt;b$@K=-2AKwn zhQNlbhT4YyhUtd2Q@B%NrwmVdpGrMdeyZ!#-BZhrh(^Iitwz_z_{NgP_QvtXSB=}J zxlgN}c03(*y6ANC>FcLooc?u&c1H1x?U}GM1!o%1Ts!mh%z6`RlWdb^6SFC&>15ND zrYB9`n#s*l&F0O4%~{R0%>&Jkn!mIVTNo{-E&eV0TWVVRTV`55w-Q<~tdB|xfZs_RH`Jwwm zABOS6jA8TP;NgPdrs2`yS0i9Va71s!dnA3NX5{k7+{os2+I5xd&e!9wAHUvp{lWFM zQPQZ)sP$<0Xwhif=&jLrH&8c3Zy4VQxRG2)*xX3fpZH|K6{ zj&Y8ujqMpr8Y>^WI5s`@ZJc#nVccOnX8h>*`SJVXpKcLv$=tHO6>;m(t@c}YZmr(N z-j=*=emms$f!i&&$8Nv9gSsPn$K+1XoxD3|?%cTZdIFvho-my7pU9bLoEVvSc^BNJ z-!-`FcQ@;9!`>cRB~ucn|Wp(&%OfT`T6(^EI5mL4J=ias=X z$b4Avu=(Nm!*|n|X^Cm`>CoxI>9f;!rr*!tXQXFrW+G>f%yiC7&a6G6JW_b%@F?z4 z>7$-UGmm~eW`C^u*!6MBphM&EB z4m}rsZt|S@{J`_J=XajJpC`=A&fCq$%pafcnSV6D{(|F$#tV-Z=`ZSD482%ffEI)o zOct062N&8FCKf&}k{0C`9Twvk%NP3=XBU6HCUcP;WekJ+J>Q&^c zBd;#Jdid)5Yuan|*B-C;y{>yb^!nuzyd=70wiLQ_XsKgqa_P$(>Km0eu5VJ`)V#U+ zX5lULR^+Ye+mN?KZ_mBG|Mtr}>N}NpuJ6*`)xNv-ZgCl27F{-54qGl>?p&T){lD=|sWn^V(6}2k4YPA}*dUUmC_0j6)d+zr-?|t9ryl;9x{(kiX;e*@<#}5e~ zDnDHQF#loaqtHi_kHH^{K6ZS3@bTLx_D|}cJU?ZAI`!$sr?+d^HR(0GwYatNwM%Qy z*0w*>KO27z{#^9A(|y_ZXh=#Hmo;dHcB`8H=b^6ZwhUiY=&+g-t5|(+1&ie`%~{{z|R9e+kZ~}{Q8Ub zOY@h{ubf}azwZ3{xW%%ivgN*&v2|+e=GMw@;%|lDF27TM*Zm&({pJt$kIWy3KS_U1 z{JHk$)i!FIv2D8@zg@X~WqV-<-jUd`-ig^M-x=7M{|o+#{k8lX^|$nI|KI0-|Na*( zv2KeqRZ6%3000SaNLh0L04^f{04^f|c%?sf0002#NklRb5*kK}hz3Xiq3i;o>NpzeCNtiP-T<0#5RkvzramJ3Dr73fZ$#HD z%7w5X&AABu>`P7+Z3t}>b8MK~&}rRAIN_732NQ{VXVo2EVBbZ3P!J*M6t!czTp{~y zAview_dispatcher, BleBeaconAppCustomEventDataEditResult); +} + +void ble_beacon_app_scene_input_beacon_data_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + byte_input_set_header_text(ble_beacon->byte_input, "Enter beacon data"); + + byte_input_set_result_callback( + ble_beacon->byte_input, + ble_beacon_app_scene_add_type_byte_input_callback, + NULL, + context, + ble_beacon->beacon_data, + sizeof(ble_beacon->beacon_data)); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewByteInput); +} + +bool ble_beacon_app_scene_input_beacon_data_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BleBeaconAppCustomEventDataEditResult) { + ble_beacon_app_update_state(ble_beacon); + scene_manager_previous_scene(scene_manager); + return true; + } + } + + return false; +} + +void ble_beacon_app_scene_input_beacon_data_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + + byte_input_set_result_callback(ble_beacon->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(ble_beacon->byte_input, NULL); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c b/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c new file mode 100644 index 00000000000..003934bbad5 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_input_mac_addr.c @@ -0,0 +1,44 @@ +#include "../ble_beacon_app.h" + +static void ble_beacon_app_scene_add_type_byte_input_callback(void* context) { + BleBeaconApp* ble_beacon = context; + view_dispatcher_send_custom_event( + ble_beacon->view_dispatcher, BleBeaconAppCustomEventDataEditResult); +} + +void ble_beacon_app_scene_input_mac_addr_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + byte_input_set_header_text(ble_beacon->byte_input, "Enter MAC (reversed)"); + + byte_input_set_result_callback( + ble_beacon->byte_input, + ble_beacon_app_scene_add_type_byte_input_callback, + NULL, + context, + ble_beacon->beacon_config.address, + sizeof(ble_beacon->beacon_config.address)); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewByteInput); +} + +bool ble_beacon_app_scene_input_mac_addr_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == BleBeaconAppCustomEventDataEditResult) { + ble_beacon_app_update_state(ble_beacon); + scene_manager_previous_scene(scene_manager); + return true; + } + } + + return false; +} + +void ble_beacon_app_scene_input_mac_addr_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + + byte_input_set_result_callback(ble_beacon->byte_input, NULL, NULL, NULL, NULL, 0); + byte_input_set_header_text(ble_beacon->byte_input, NULL); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_menu.c b/applications/examples/example_ble_beacon/scenes/scene_menu.c new file mode 100644 index 00000000000..83223e93ca2 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_menu.c @@ -0,0 +1,56 @@ +#include "../ble_beacon_app.h" + +enum SubmenuIndex { + SubmenuIndexSetMac, + SubmenuIndexSetData, +}; + +static void ble_beacon_app_scene_menu_submenu_callback(void* context, uint32_t index) { + BleBeaconApp* ble_beacon = context; + view_dispatcher_send_custom_event(ble_beacon->view_dispatcher, index); +} + +void ble_beacon_app_scene_menu_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + Submenu* submenu = ble_beacon->submenu; + + submenu_add_item( + submenu, + "Set MAC", + SubmenuIndexSetMac, + ble_beacon_app_scene_menu_submenu_callback, + ble_beacon); + submenu_add_item( + submenu, + "Set Data", + SubmenuIndexSetData, + ble_beacon_app_scene_menu_submenu_callback, + ble_beacon); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewSubmenu); +} + +bool ble_beacon_app_scene_menu_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const uint32_t submenu_index = event.event; + if(submenu_index == SubmenuIndexSetMac) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneInputMacAddress); + consumed = true; + } else if(submenu_index == SubmenuIndexSetData) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneInputBeaconData); + consumed = true; + } + } + + return consumed; +} + +void ble_beacon_app_scene_menu_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + submenu_reset(ble_beacon->submenu); +} diff --git a/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c b/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c new file mode 100644 index 00000000000..121001e0ee7 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scene_run_beacon.c @@ -0,0 +1,79 @@ +#include "../ble_beacon_app.h" +#include + +static void + ble_beacon_app_scene_run_beacon_confirm_dialog_callback(DialogExResult result, void* context) { + BleBeaconApp* ble_beacon = context; + + view_dispatcher_send_custom_event(ble_beacon->view_dispatcher, result); +} + +static void update_status_text(BleBeaconApp* ble_beacon) { + DialogEx* dialog_ex = ble_beacon->dialog_ex; + + dialog_ex_set_header(dialog_ex, "BLE Beacon Demo", 64, 0, AlignCenter, AlignTop); + + FuriString* status = ble_beacon->status_string; + + furi_string_reset(status); + + furi_string_cat_str(status, "Status: "); + if(ble_beacon->is_beacon_active) { + furi_string_cat_str(status, "Running\n"); + } else { + furi_string_cat_str(status, "Stopped\n"); + } + + // Output MAC in reverse order + for(int i = sizeof(ble_beacon->beacon_config.address) - 1; i >= 0; i--) { + furi_string_cat_printf(status, "%02X", ble_beacon->beacon_config.address[i]); + if(i > 0) { + furi_string_cat_str(status, ":"); + } + } + + furi_string_cat_printf(status, "\nData length: %d", ble_beacon->beacon_data_len); + + dialog_ex_set_text(dialog_ex, furi_string_get_cstr(status), 0, 29, AlignLeft, AlignCenter); + + dialog_ex_set_icon(dialog_ex, 93, 20, &I_lighthouse_35x44); + + dialog_ex_set_left_button_text(dialog_ex, "Config"); + + dialog_ex_set_center_button_text(dialog_ex, ble_beacon->is_beacon_active ? "Stop" : "Start"); + + dialog_ex_set_result_callback( + dialog_ex, ble_beacon_app_scene_run_beacon_confirm_dialog_callback); + dialog_ex_set_context(dialog_ex, ble_beacon); +} + +void ble_beacon_app_scene_run_beacon_on_enter(void* context) { + BleBeaconApp* ble_beacon = context; + + update_status_text(ble_beacon); + + view_dispatcher_switch_to_view(ble_beacon->view_dispatcher, BleBeaconAppViewDialog); +} + +bool ble_beacon_app_scene_run_beacon_on_event(void* context, SceneManagerEvent event) { + BleBeaconApp* ble_beacon = context; + SceneManager* scene_manager = ble_beacon->scene_manager; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == DialogExResultLeft) { + scene_manager_next_scene(scene_manager, BleBeaconAppSceneMenu); + return true; + } else if(event.event == DialogExResultCenter) { + ble_beacon->is_beacon_active = !ble_beacon->is_beacon_active; + ble_beacon_app_update_state(ble_beacon); + update_status_text(ble_beacon); + return true; + } + } + return false; +} + +void ble_beacon_app_scene_run_beacon_on_exit(void* context) { + BleBeaconApp* ble_beacon = context; + UNUSED(ble_beacon); +} diff --git a/applications/examples/example_ble_beacon/scenes/scenes.c b/applications/examples/example_ble_beacon/scenes/scenes.c new file mode 100644 index 00000000000..13e7ac8323a --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scenes.c @@ -0,0 +1,30 @@ +#include "scenes.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const ble_beacon_app_on_enter_handlers[])(void*) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const ble_beacon_app_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const ble_beacon_app_on_exit_handlers[])(void* context) = { +#include "scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers ble_beacon_app_scene_handlers = { + .on_enter_handlers = ble_beacon_app_on_enter_handlers, + .on_event_handlers = ble_beacon_app_on_event_handlers, + .on_exit_handlers = ble_beacon_app_on_exit_handlers, + .scene_num = BleBeaconAppSceneNum, +}; diff --git a/applications/examples/example_ble_beacon/scenes/scenes.h b/applications/examples/example_ble_beacon/scenes/scenes.h new file mode 100644 index 00000000000..64d15350d72 --- /dev/null +++ b/applications/examples/example_ble_beacon/scenes/scenes.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) BleBeaconAppScene##id, +typedef enum { +#include "scene_config.h" + BleBeaconAppSceneNum, +} BleBeaconAppScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers ble_beacon_app_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "scene_config.h" +#undef ADD_SCENE diff --git a/applications/debug/example_custom_font/application.fam b/applications/examples/example_custom_font/application.fam similarity index 71% rename from applications/debug/example_custom_font/application.fam rename to applications/examples/example_custom_font/application.fam index 06c0a7f6183..45cc08d4420 100644 --- a/applications/debug/example_custom_font/application.fam +++ b/applications/examples/example_custom_font/application.fam @@ -1,9 +1,9 @@ App( appid="example_custom_font", name="Example: custom font", - apptype=FlipperAppType.DEBUG, + apptype=FlipperAppType.EXTERNAL, entry_point="example_custom_font_main", requires=["gui"], stack_size=1 * 1024, - fap_category="Debug", + fap_category="Examples", ) diff --git a/applications/debug/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c similarity index 100% rename from applications/debug/example_custom_font/example_custom_font.c rename to applications/examples/example_custom_font/example_custom_font.c diff --git a/applications/main/archive/helpers/archive_apps.h b/applications/main/archive/helpers/archive_apps.h index 8bc904587bd..d9d1dec345d 100644 --- a/applications/main/archive/helpers/archive_apps.h +++ b/applications/main/archive/helpers/archive_apps.h @@ -1,5 +1,7 @@ #pragma once +#include "archive_files.h" + typedef enum { ArchiveAppTypeU2f, ArchiveAppTypeUnknown, diff --git a/applications/main/nfc/plugins/supported_cards/opal.c b/applications/main/nfc/plugins/supported_cards/opal.c index 64c279ffe38..f9c386326d4 100644 --- a/applications/main/nfc/plugins/supported_cards/opal.c +++ b/applications/main/nfc/plugins/supported_cards/opal.c @@ -61,7 +61,7 @@ static const char* opal_usages[14] = { }; // Opal file 0x7 structure. Assumes a little-endian CPU. -typedef struct __attribute__((__packed__)) { +typedef struct FURI_PACKED { uint32_t serial : 32; uint8_t check_digit : 4; bool blocked : 1; diff --git a/applications/main/nfc/plugins/supported_cards/umarsh.c b/applications/main/nfc/plugins/supported_cards/umarsh.c index 044820ef390..ba8e481be10 100644 --- a/applications/main/nfc/plugins/supported_cards/umarsh.c +++ b/applications/main/nfc/plugins/supported_cards/umarsh.c @@ -23,7 +23,6 @@ * along with this program. If not, see . */ -#include "core/core_defines.h" #include "nfc_supported_card_plugin.h" #include "protocols/mf_classic/mf_classic.h" diff --git a/applications/services/bt/bt_cli.c b/applications/services/bt/bt_cli.c index 02bf6cee4a3..e8ba215bfe9 100644 --- a/applications/services/bt/bt_cli.c +++ b/applications/services/bt/bt_cli.c @@ -6,6 +6,7 @@ #include #include "bt_settings.h" #include "bt_service/bt.h" +#include static void bt_cli_command_hci_info(Cli* cli, FuriString* args, void* context) { UNUSED(cli); @@ -45,7 +46,7 @@ static void bt_cli_command_carrier_tx(Cli* cli, FuriString* args, void* context) } furi_hal_bt_stop_tone_tx(); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -76,7 +77,7 @@ static void bt_cli_command_carrier_rx(Cli* cli, FuriString* args, void* context) furi_hal_bt_stop_packet_test(); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -124,7 +125,7 @@ static void bt_cli_command_packet_tx(Cli* cli, FuriString* args, void* context) furi_hal_bt_stop_packet_test(); printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets()); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } @@ -159,7 +160,7 @@ static void bt_cli_command_packet_rx(Cli* cli, FuriString* args, void* context) uint16_t packets_received = furi_hal_bt_stop_packet_test(); printf("Received %hu packets", packets_received); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); } while(false); } diff --git a/applications/services/bt/bt_service/bt.c b/applications/services/bt/bt_service/bt.c index 36409fe5cd6..4ef3a15953a 100644 --- a/applications/services/bt/bt_service/bt.c +++ b/applications/services/bt/bt_service/bt.c @@ -1,10 +1,13 @@ #include "bt_i.h" #include "bt_keys_storage.h" +#include +#include #include #include #include #include +#include #define TAG "BtSrv" @@ -12,14 +15,21 @@ #define BT_RPC_EVENT_DISCONNECTED (1UL << 1) #define BT_RPC_EVENT_ALL (BT_RPC_EVENT_BUFF_SENT | BT_RPC_EVENT_DISCONNECTED) +#define ICON_SPACER 2 + static void bt_draw_statusbar_callback(Canvas* canvas, void* context) { furi_assert(context); Bt* bt = context; + uint8_t draw_offset = 0; + if(bt->beacon_active) { + canvas_draw_icon(canvas, 0, 0, &I_BLE_beacon_7x8); + draw_offset += icon_get_width(&I_BLE_beacon_7x8) + ICON_SPACER; + } if(bt->status == BtStatusAdvertising) { - canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Idle_5x8); + canvas_draw_icon(canvas, draw_offset, 0, &I_Bluetooth_Idle_5x8); } else if(bt->status == BtStatusConnected) { - canvas_draw_icon(canvas, 0, 0, &I_Bluetooth_Connected_16x8); + canvas_draw_icon(canvas, draw_offset, 0, &I_Bluetooth_Connected_16x8); } } @@ -61,6 +71,11 @@ static ViewPort* bt_pin_code_view_port_alloc(Bt* bt) { static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { bt->pin_code = pin_code; + if(!bt->pin_code_view_port) { + // Pin code view port + bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt); + gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); + } notification_message(bt->notification, &sequence_display_backlight_on); gui_view_port_send_to_front(bt->gui, bt->pin_code_view_port); view_port_enabled_set(bt->pin_code_view_port, true); @@ -68,7 +83,7 @@ static void bt_pin_code_show(Bt* bt, uint32_t pin_code) { static void bt_pin_code_hide(Bt* bt) { bt->pin_code = 0; - if(view_port_is_enabled(bt->pin_code_view_port)) { + if(bt->pin_code_view_port && view_port_is_enabled(bt->pin_code_view_port)) { view_port_enabled_set(bt->pin_code_view_port, false); } } @@ -77,6 +92,9 @@ static bool bt_pin_code_verify_event_handler(Bt* bt, uint32_t pin) { furi_assert(bt); notification_message(bt->notification, &sequence_display_backlight_on); FuriString* pin_str; + if(!bt->dialog_message) { + bt->dialog_message = dialog_message_alloc(); + } dialog_message_set_icon(bt->dialog_message, &I_BLE_Pairing_128x64, 0, 0); pin_str = furi_string_alloc_printf("Verify code\n%06lu", pin); dialog_message_set_text( @@ -94,25 +112,32 @@ static void bt_battery_level_changed_callback(const void* _event, void* context) Bt* bt = context; BtMessage message = {}; const PowerEvent* event = _event; - if(event->type == PowerEventTypeBatteryLevelChanged) { + bool is_charging = false; + switch(event->type) { + case PowerEventTypeBatteryLevelChanged: message.type = BtMessageTypeUpdateBatteryLevel; message.data.battery_level = event->data.battery_level; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); - } else if( - event->type == PowerEventTypeStartCharging || event->type == PowerEventTypeFullyCharged || - event->type == PowerEventTypeStopCharging) { + break; + case PowerEventTypeStartCharging: + is_charging = true; + /* fallthrough */ + case PowerEventTypeFullyCharged: + case PowerEventTypeStopCharging: message.type = BtMessageTypeUpdatePowerState; + message.data.power_state_charging = is_charging; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + break; } } Bt* bt_alloc() { Bt* bt = malloc(sizeof(Bt)); // Init default maximum packet size - bt->max_packet_size = FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX; - bt->profile = BtProfileSerial; + bt->max_packet_size = BLE_PROFILE_SERIAL_PACKET_SIZE_MAX; + bt->current_profile = NULL; // Load settings if(!bt_settings_load(&bt->bt_settings)) { bt_settings_save(&bt->bt_settings); @@ -124,18 +149,14 @@ Bt* bt_alloc() { // Setup statusbar view port bt->statusbar_view_port = bt_statusbar_view_port_alloc(bt); - // Pin code view port - bt->pin_code_view_port = bt_pin_code_view_port_alloc(bt); // Notification bt->notification = furi_record_open(RECORD_NOTIFICATION); // Gui bt->gui = furi_record_open(RECORD_GUI); gui_add_view_port(bt->gui, bt->statusbar_view_port, GuiLayerStatusBarLeft); - gui_add_view_port(bt->gui, bt->pin_code_view_port, GuiLayerFullscreen); // Dialogs bt->dialogs = furi_record_open(RECORD_DIALOGS); - bt->dialog_message = dialog_message_alloc(); // Power bt->power = furi_record_open(RECORD_POWER); @@ -170,7 +191,11 @@ static uint16_t bt_serial_event_callback(SerialServiceEvent event, void* context furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_BUFF_SENT); } else if(event.event == SerialServiceEventTypesBleResetRequest) { FURI_LOG_I(TAG, "BLE restart request received"); - BtMessage message = {.type = BtMessageTypeSetProfile, .data.profile = BtProfileSerial}; + BtMessage message = { + .type = BtMessageTypeSetProfile, + .data.profile.params = NULL, + .data.profile.template = ble_profile_serial, + }; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } @@ -191,10 +216,10 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt while(bytes_sent < bytes_len) { size_t bytes_remain = bytes_len - bytes_sent; if(bytes_remain > bt->max_packet_size) { - furi_hal_bt_serial_tx(&bytes[bytes_sent], bt->max_packet_size); + ble_profile_serial_tx(bt->current_profile, &bytes[bytes_sent], bt->max_packet_size); bytes_sent += bt->max_packet_size; } else { - furi_hal_bt_serial_tx(&bytes[bytes_sent], bytes_remain); + ble_profile_serial_tx(bt->current_profile, &bytes[bytes_sent], bytes_remain); bytes_sent += bytes_remain; } // We want BT_RPC_EVENT_DISCONNECTED to stick, so don't clear @@ -209,32 +234,42 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt } } +static void bt_serial_buffer_is_empty_callback(void* context) { + furi_assert(context); + Bt* bt = context; + furi_check(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial)); + ble_profile_serial_notify_buffer_is_empty(bt->current_profile); +} + // Called from GAP thread static bool bt_on_gap_event_callback(GapEvent event, void* context) { furi_assert(context); Bt* bt = context; bool ret = false; + bool do_update_status = false; + bool current_profile_is_serial = + furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial); if(event.type == GapEventTypeConnected) { // Update status bar bt->status = BtStatusConnected; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; // Clear BT_RPC_EVENT_DISCONNECTED because it might be set from previous session furi_event_flag_clear(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); - if(bt->profile == BtProfileSerial) { + + if(current_profile_is_serial) { // Open RPC session bt->rpc_session = rpc_session_open(bt->rpc, RpcOwnerBle); if(bt->rpc_session) { FURI_LOG_I(TAG, "Open RPC connection"); rpc_session_set_send_bytes_callback(bt->rpc_session, bt_rpc_send_bytes_callback); rpc_session_set_buffer_is_empty_callback( - bt->rpc_session, furi_hal_bt_serial_notify_buffer_is_empty); + bt->rpc_session, bt_serial_buffer_is_empty_callback); rpc_session_set_context(bt->rpc_session, bt); - furi_hal_bt_serial_set_event_callback( - RPC_BUFFER_SIZE, bt_serial_event_callback, bt); - furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusActive); + ble_profile_serial_set_event_callback( + bt->current_profile, RPC_BUFFER_SIZE, bt_serial_event_callback, bt); + ble_profile_serial_set_rpc_active( + bt->current_profile, FuriHalBtSerialRpcStatusActive); } else { FURI_LOG_W(TAG, "RPC is busy, failed to open new session"); } @@ -242,32 +277,30 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { // Update battery level PowerInfo info; power_get_info(bt->power, &info); + BtMessage message = {.type = BtMessageTypeUpdateStatus}; message.type = BtMessageTypeUpdateBatteryLevel; message.data.battery_level = info.charge; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); ret = true; } else if(event.type == GapEventTypeDisconnected) { - if(bt->profile == BtProfileSerial && bt->rpc_session) { + if(current_profile_is_serial && bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); - furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatusNotActive); + ble_profile_serial_set_rpc_active( + bt->current_profile, FuriHalBtSerialRpcStatusNotActive); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + ble_profile_serial_set_event_callback(bt->current_profile, 0, NULL, NULL); bt->rpc_session = NULL; } ret = true; } else if(event.type == GapEventTypeStartAdvertising) { bt->status = BtStatusAdvertising; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; ret = true; } else if(event.type == GapEventTypeStopAdvertising) { bt->status = BtStatusOff; - BtMessage message = {.type = BtMessageTypeUpdateStatus}; - furi_check( - furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); + do_update_status = true; ret = true; } else if(event.type == GapEventTypePinCodeShow) { BtMessage message = { @@ -280,6 +313,20 @@ static bool bt_on_gap_event_callback(GapEvent event, void* context) { } else if(event.type == GapEventTypeUpdateMTU) { bt->max_packet_size = event.data.max_packet_size; ret = true; + } else if(event.type == GapEventTypeBeaconStart) { + bt->beacon_active = true; + do_update_status = true; + ret = true; + } else if(event.type == GapEventTypeBeaconStop) { + bt->beacon_active = false; + do_update_status = true; + ret = true; + } + + if(do_update_status) { + BtMessage message = {.type = BtMessageTypeUpdateStatus}; + furi_check( + furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); } return ret; } @@ -296,11 +343,18 @@ static void bt_on_key_storage_change_callback(uint8_t* addr, uint16_t size, void } static void bt_statusbar_update(Bt* bt) { + uint8_t active_icon_width = 0; + if(bt->beacon_active) { + active_icon_width = icon_get_width(&I_BLE_beacon_7x8) + ICON_SPACER; + } if(bt->status == BtStatusAdvertising) { - view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Idle_5x8)); - view_port_enabled_set(bt->statusbar_view_port, true); + active_icon_width += icon_get_width(&I_Bluetooth_Idle_5x8); } else if(bt->status == BtStatusConnected) { - view_port_set_width(bt->statusbar_view_port, icon_get_width(&I_Bluetooth_Connected_16x8)); + active_icon_width += icon_get_width(&I_Bluetooth_Connected_16x8); + } + + if(active_icon_width > 0) { + view_port_set_width(bt->statusbar_view_port, active_icon_width); view_port_enabled_set(bt->statusbar_view_port, true); } else { view_port_enabled_set(bt->statusbar_view_port, false); @@ -308,56 +362,61 @@ static void bt_statusbar_update(Bt* bt) { } static void bt_show_warning(Bt* bt, const char* text) { + if(!bt->dialog_message) { + bt->dialog_message = dialog_message_alloc(); + } dialog_message_set_text(bt->dialog_message, text, 64, 28, AlignCenter, AlignCenter); dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL); dialog_message_show(bt->dialogs, bt->dialog_message); } static void bt_close_rpc_connection(Bt* bt) { - if(bt->profile == BtProfileSerial && bt->rpc_session) { + if(furi_hal_bt_check_profile_type(bt->current_profile, ble_profile_serial) && + bt->rpc_session) { FURI_LOG_I(TAG, "Close RPC connection"); furi_event_flag_set(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED); rpc_session_close(bt->rpc_session); - furi_hal_bt_serial_set_event_callback(0, NULL, NULL); + ble_profile_serial_set_event_callback(bt->current_profile, 0, NULL, NULL); bt->rpc_session = NULL; } } static void bt_change_profile(Bt* bt, BtMessage* message) { - if(furi_hal_bt_is_ble_gatt_gap_supported()) { + if(furi_hal_bt_is_gatt_gap_supported()) { bt_settings_load(&bt->bt_settings); bt_close_rpc_connection(bt); - FuriHalBtProfile furi_profile; - if(message->data.profile == BtProfileHidKeyboard) { - furi_profile = FuriHalBtProfileHidKeyboard; - } else { - furi_profile = FuriHalBtProfileSerial; - } - bt_keys_storage_load(bt->keys_storage); - if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) { + bt->current_profile = furi_hal_bt_change_app( + message->data.profile.template, + message->data.profile.params, + bt_on_gap_event_callback, + bt); + if(bt->current_profile) { FURI_LOG_I(TAG, "Bt App started"); if(bt->bt_settings.enabled) { furi_hal_bt_start_advertising(); } furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt); - bt->profile = message->data.profile; - if(message->result) { - *message->result = true; - } } else { FURI_LOG_E(TAG, "Failed to start Bt App"); - if(message->result) { - *message->result = false; - } } + if(message->profile_instance) { + *message->profile_instance = bt->current_profile; + } + if(message->result) { + *message->result = bt->current_profile != NULL; + } + } else { bt_show_warning(bt, "Radio stack doesn't support this app"); if(message->result) { *message->result = false; } + if(message->profile_instance) { + *message->profile_instance = NULL; + } } if(message->lock) api_lock_unlock(message->lock); } @@ -389,8 +448,10 @@ int32_t bt_srv(void* p) { FURI_LOG_E(TAG, "Radio stack start failed"); } - if(furi_hal_bt_is_ble_gatt_gap_supported()) { - if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) { + if(furi_hal_bt_is_gatt_gap_supported()) { + bt->current_profile = + furi_hal_bt_start_app(ble_profile_serial, NULL, bt_on_gap_event_callback, bt); + if(!bt->current_profile) { FURI_LOG_E(TAG, "BLE App start failed"); } else { if(bt->bt_settings.enabled) { @@ -420,7 +481,7 @@ int32_t bt_srv(void* p) { // Update battery level furi_hal_bt_update_battery_level(message.data.battery_level); } else if(message.type == BtMessageTypeUpdatePowerState) { - furi_hal_bt_update_power_state(); + furi_hal_bt_update_power_state(message.data.power_state_charging); } else if(message.type == BtMessageTypePinCodeShow) { // Display PIN code bt_pin_code_show(bt, message.data.pin_code); diff --git a/applications/services/bt/bt_service/bt.h b/applications/services/bt/bt_service/bt.h index ca47936dbbb..270922543e0 100644 --- a/applications/services/bt/bt_service/bt.h +++ b/applications/services/bt/bt_service/bt.h @@ -2,6 +2,8 @@ #include #include +#include +#include #ifdef __cplusplus extern "C" { @@ -18,22 +20,30 @@ typedef enum { BtStatusConnected, } BtStatus; -typedef enum { - BtProfileSerial, - BtProfileHidKeyboard, -} BtProfile; - typedef void (*BtStatusChangedCallback)(BtStatus status, void* context); /** Change BLE Profile + * @note Call of this function leads to 2nd core restart + * + * @param bt Bt instance + * @param profile_template Profile template to change to + * @param params Profile parameters. Can be NULL + * + * @return true on success + */ +FURI_WARN_UNUSED FuriHalBleProfileBase* bt_profile_start( + Bt* bt, + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params); + +/** Stop current BLE Profile and restore default profile * @note Call of this function leads to 2nd core restart * * @param bt Bt instance - * @param profile BtProfile * * @return true on success */ -bool bt_set_profile(Bt* bt, BtProfile profile); +bool bt_profile_restore_default(Bt* bt); /** Disconnect from Central * diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index e31031783b6..ab5d2012814 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -1,21 +1,34 @@ #include "bt_i.h" +#include -bool bt_set_profile(Bt* bt, BtProfile profile) { +FuriHalBleProfileBase* bt_profile_start( + Bt* bt, + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params) { furi_assert(bt); // Send message - bool result = false; + FuriHalBleProfileBase* profile_instance = NULL; + BtMessage message = { .lock = api_lock_alloc_locked(), .type = BtMessageTypeSetProfile, - .data.profile = profile, - .result = &result}; + .profile_instance = &profile_instance, + .data.profile.params = params, + .data.profile.template = profile_template, + }; furi_check( furi_message_queue_put(bt->message_queue, &message, FuriWaitForever) == FuriStatusOk); // Wait for unlock api_lock_wait_unlock_and_free(message.lock); - return result; + bt->current_profile = profile_instance; + return profile_instance; +} + +bool bt_profile_restore_default(Bt* bt) { + bt->current_profile = bt_profile_start(bt, ble_profile_serial, NULL); + return bt->current_profile != NULL; } void bt_disconnect(Bt* bt) { diff --git a/applications/services/bt/bt_service/bt_i.h b/applications/services/bt/bt_service/bt_i.h index 55bae76f3bf..04c1734b750 100644 --- a/applications/services/bt/bt_service/bt_i.h +++ b/applications/services/bt/bt_service/bt_i.h @@ -42,7 +42,12 @@ typedef struct { typedef union { uint32_t pin_code; uint8_t battery_level; - BtProfile profile; + bool power_state_charging; + struct { + const FuriHalBleProfileTemplate* template; + FuriHalBleProfileParams params; + } profile; + FuriHalBleProfileParams profile_params; BtKeyStorageUpdateData key_storage_data; } BtMessageData; @@ -51,6 +56,7 @@ typedef struct { BtMessageType type; BtMessageData data; bool* result; + FuriHalBleProfileBase** profile_instance; } BtMessage; struct Bt { @@ -60,7 +66,8 @@ struct Bt { BtSettings bt_settings; BtKeysStorage* keys_storage; BtStatus status; - BtProfile profile; + bool beacon_active; + FuriHalBleProfileBase* current_profile; FuriMessageQueue* message_queue; NotificationApp* notification; Gui* gui; diff --git a/applications/services/cli/cli_command_gpio.c b/applications/services/cli/cli_command_gpio.c index d0246273494..67511a194f7 100644 --- a/applications/services/cli/cli_command_gpio.c +++ b/applications/services/cli/cli_command_gpio.c @@ -1,6 +1,5 @@ #include "cli_command_gpio.h" -#include "core/string.h" #include #include #include diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index df54bf3f4d7..dd60e820ed7 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -1,6 +1,7 @@ #include "cli_commands.h" #include "cli_command_gpio.h" +#include #include #include #include @@ -388,27 +389,30 @@ void cli_command_ps(Cli* cli, FuriString* args, void* context) { const uint8_t threads_num_max = 32; FuriThreadId threads_ids[threads_num_max]; - uint8_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); + uint32_t thread_num = furi_thread_enumerate(threads_ids, threads_num_max); printf( - "%-20s %-20s %-14s %-8s %-8s %s\r\n", + "%-17s %-20s %-5s %-13s %-6s %-8s %s\r\n", "AppID", "Name", + "Prio", "Stack start", "Heap", "Stack", "Stack min free"); for(uint8_t i = 0; i < thread_num; i++) { TaskControlBlock* tcb = (TaskControlBlock*)threads_ids[i]; + size_t thread_heap = memmgr_heap_get_thread_memory(threads_ids[i]); printf( - "%-20s %-20s 0x%-12lx %-8zu %-8lu %-8lu\r\n", + "%-17s %-20s %-5d 0x%-11lx %-6zu %-8lu %-8lu\r\n", furi_thread_get_appid(threads_ids[i]), furi_thread_get_name(threads_ids[i]), + furi_thread_get_priority(threads_ids[i]), (uint32_t)tcb->pxStack, - memmgr_heap_get_thread_memory(threads_ids[i]), + thread_heap == MEMMGR_HEAP_UNKNOWN ? 0u : thread_heap, (uint32_t)(tcb->pxEndOfStack - tcb->pxStack + 1) * sizeof(StackType_t), furi_thread_get_stack_space(threads_ids[i])); } - printf("\r\nTotal: %d", thread_num); + printf("\r\nTotal: %lu", thread_num); } void cli_command_free(Cli* cli, FuriString* args, void* context) { diff --git a/applications/services/gui/modules/widget_elements/widget_element_i.h b/applications/services/gui/modules/widget_elements/widget_element_i.h index 67dea4b1f69..456a8317290 100644 --- a/applications/services/gui/modules/widget_elements/widget_element_i.h +++ b/applications/services/gui/modules/widget_elements/widget_element_i.h @@ -4,10 +4,12 @@ */ #pragma once + +#include "../widget.h" +#include "widget_element.h" #include #include #include -#include "widget_element.h" #ifdef __cplusplus extern "C" { diff --git a/applications/services/loader/loader.c b/applications/services/loader/loader.c index 158b95de641..1520c1f3daa 100644 --- a/applications/services/loader/loader.c +++ b/applications/services/loader/loader.c @@ -1,5 +1,4 @@ #include "loader.h" -#include "core/core_defines.h" #include "loader_i.h" #include #include diff --git a/applications/services/rpc/rpc.c b/applications/services/rpc/rpc.c index d33ea178c84..53b139dd490 100644 --- a/applications/services/rpc/rpc.c +++ b/applications/services/rpc/rpc.c @@ -1,3 +1,4 @@ +#include "profiles/serial_profile.h" #include "rpc_i.h" #include @@ -331,7 +332,7 @@ static int32_t rpc_session_worker(void* context) { // Disconnect BLE session FURI_LOG_E("RPC", "BLE session closed due to a decode error"); Bt* bt = furi_record_open(RECORD_BT); - bt_set_profile(bt, BtProfileSerial); + bt_profile_restore_default(bt); furi_record_close(RECORD_BT); FURI_LOG_E("RPC", "Finished disconnecting the BLE session"); } diff --git a/applications/services/rpc/rpc.h b/applications/services/rpc/rpc.h index f7cda64f73b..d34efb4f8a4 100644 --- a/applications/services/rpc/rpc.h +++ b/applications/services/rpc/rpc.h @@ -94,6 +94,7 @@ void rpc_session_set_send_bytes_callback(RpcSession* session, RpcSendBytesCallba * * @param session pointer to RpcSession descriptor * @param callback callback to notify client that buffer is empty (can be NULL) + * @param context context to pass to callback */ void rpc_session_set_buffer_is_empty_callback( RpcSession* session, diff --git a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c index 5db98e9dec3..c148f094350 100644 --- a/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c +++ b/applications/settings/bt_settings_app/scenes/bt_settings_scene_start.c @@ -39,7 +39,7 @@ void bt_settings_scene_start_on_enter(void* context) { VariableItemList* var_item_list = app->var_item_list; VariableItem* item; - if(furi_hal_bt_is_ble_gatt_gap_supported()) { + if(furi_hal_bt_is_gatt_gap_supported()) { item = variable_item_list_add( var_item_list, "Bluetooth", diff --git a/applications/system/hid_app/application.fam b/applications/system/hid_app/application.fam index a1fb314b82c..cc218c31a35 100644 --- a/applications/system/hid_app/application.fam +++ b/applications/system/hid_app/application.fam @@ -4,6 +4,8 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_usb_app", stack_size=1 * 1024, + sources=["*.c", "!transport_ble.c"], + cdefines=["HID_TRANSPORT_USB"], fap_description="Use Flipper as a HID remote control over USB", fap_version="1.0", fap_category="USB", @@ -19,6 +21,9 @@ App( apptype=FlipperAppType.EXTERNAL, entry_point="hid_ble_app", stack_size=1 * 1024, + sources=["*.c", "!transport_usb.c"], + cdefines=["HID_TRANSPORT_BLE"], + fap_libs=["ble_profile"], fap_description="Use Flipper as a HID remote control over Bluetooth", fap_version="1.0", fap_category="Bluetooth", diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index 88a68f09d0f..c6d88124cf5 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -1,4 +1,6 @@ #include "hid.h" +#include +#include #include "views.h" #include #include @@ -68,13 +70,13 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con furi_assert(context); Hid* hid = context; bool connected = (status == BtStatusConnected); - if(hid->transport == HidTransportBle) { - if(connected) { - notification_internal_message(hid->notifications, &sequence_set_blue_255); - } else { - notification_internal_message(hid->notifications, &sequence_reset_blue); - } +#ifdef HID_TRANSPORT_BLE + if(connected) { + notification_internal_message(hid->notifications, &sequence_set_blue_255); + } else { + notification_internal_message(hid->notifications, &sequence_reset_blue); } +#endif hid_keynote_set_connected_status(hid->hid_keynote, connected); hid_keyboard_set_connected_status(hid->hid_keyboard, connected); hid_media_set_connected_status(hid->hid_media, connected); @@ -106,9 +108,8 @@ static uint32_t hid_exit(void* context) { return VIEW_NONE; } -Hid* hid_alloc(HidTransport transport) { +Hid* hid_alloc() { Hid* app = malloc(sizeof(Hid)); - app->transport = transport; // Gui app->gui = furi_record_open(RECORD_GUI); @@ -139,14 +140,14 @@ Hid* hid_alloc(HidTransport transport) { app->device_type_submenu, "Media", HidSubmenuIndexMedia, hid_submenu_callback, app); submenu_add_item( app->device_type_submenu, "Mouse", HidSubmenuIndexMouse, hid_submenu_callback, app); - if(app->transport == HidTransportBle) { - submenu_add_item( - app->device_type_submenu, - "TikTok Controller", - HidSubmenuIndexTikTok, - hid_submenu_callback, - app); - } +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->device_type_submenu, + "TikTok Controller", + HidSubmenuIndexTikTok, + hid_submenu_callback, + app); +#endif submenu_add_item( app->device_type_submenu, "Mouse Clicker", @@ -159,14 +160,14 @@ Hid* hid_alloc(HidTransport transport) { HidSubmenuIndexMouseJiggler, hid_submenu_callback, app); - if(transport == HidTransportBle) { - submenu_add_item( - app->device_type_submenu, - "Remove Pairing", - HidSubmenuIndexRemovePairing, - hid_submenu_callback, - app); - } +#ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->device_type_submenu, + "Remove Pairing", + HidSubmenuIndexRemovePairing, + hid_submenu_callback, + app); +#endif view_set_previous_callback(submenu_get_view(app->device_type_submenu), hid_exit); view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); @@ -244,10 +245,9 @@ void hid_free(Hid* app) { furi_assert(app); // Reset notification - if(app->transport == HidTransportBle) { - notification_internal_message(app->notifications, &sequence_reset_blue); - } - +#ifdef HID_TRANSPORT_BLE + notification_internal_message(app->notifications, &sequence_reset_blue); +#endif // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); submenu_free(app->device_type_submenu); @@ -281,131 +281,9 @@ void hid_free(Hid* app) { free(app); } -void hid_hal_keyboard_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_keyboard_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_keyboard_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_kb_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_consumer_key_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_consumer_key_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_consumer_key_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_kb_release_all(); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_move(dx, dy); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_move(dx, dy); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_scroll(delta); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_scroll(delta); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_press(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_press(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_press(event); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_release(Hid* instance, uint16_t event) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release(event); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(event); - } else { - furi_crash(); - } -} - -void hid_hal_mouse_release_all(Hid* instance) { - furi_assert(instance); - if(instance->transport == HidTransportBle) { - furi_hal_bt_hid_mouse_release_all(); - } else if(instance->transport == HidTransportUsb) { - furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); - furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); - } else { - furi_crash(); - } -} - int32_t hid_usb_app(void* p) { UNUSED(p); - Hid* app = hid_alloc(HidTransportUsb); + Hid* app = hid_alloc(); app = hid_app_alloc_view(app); FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); @@ -426,7 +304,7 @@ int32_t hid_usb_app(void* p) { int32_t hid_ble_app(void* p) { UNUSED(p); - Hid* app = hid_alloc(HidTransportBle); + Hid* app = hid_alloc(); app = hid_app_alloc_view(app); bt_disconnect(app->bt); @@ -446,7 +324,9 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - furi_check(bt_set_profile(app->bt, BtProfileHidKeyboard)); + app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid, NULL); + + furi_check(app->ble_hid_profile); furi_hal_bt_start_advertising(); bt_set_status_changed_callback(app->bt, bt_hid_connection_status_changed_callback, app); @@ -464,7 +344,7 @@ int32_t hid_ble_app(void* p) { bt_keys_storage_set_default_path(app->bt); - furi_check(bt_set_profile(app->bt, BtProfileSerial)); + furi_check(bt_profile_restore_default(app->bt)); hid_free(app); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index 49d8b4e045d..e6e974f308e 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -2,10 +2,11 @@ #include #include -#include #include #include +#include + #include #include #include @@ -34,6 +35,7 @@ typedef enum { typedef struct Hid Hid; struct Hid { + FuriHalBleProfileBase* ble_hid_profile; Bt* bt; Gui* gui; NotificationApp* notifications; diff --git a/applications/system/hid_app/transport_ble.c b/applications/system/hid_app/transport_ble.c new file mode 100644 index 00000000000..92a260adda2 --- /dev/null +++ b/applications/system/hid_app/transport_ble.c @@ -0,0 +1,60 @@ +#include "hid.h" + +#ifndef HID_TRANSPORT_BLE +#error "HID_TRANSPORT_BLE must be defined" +#endif + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_kb_press(instance->ble_hid_profile, event); +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_kb_release(instance->ble_hid_profile, event); +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_kb_release_all(instance->ble_hid_profile); +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_consumer_key_press(instance->ble_hid_profile, event); +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_consumer_key_release(instance->ble_hid_profile, event); +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_consumer_key_release_all(instance->ble_hid_profile); +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + ble_profile_hid_mouse_move(instance->ble_hid_profile, dx, dy); +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + ble_profile_hid_mouse_scroll(instance->ble_hid_profile, delta); +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_mouse_press(instance->ble_hid_profile, event); +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + ble_profile_hid_mouse_release(instance->ble_hid_profile, event); +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + ble_profile_hid_mouse_release_all(instance->ble_hid_profile); +} diff --git a/applications/system/hid_app/transport_usb.c b/applications/system/hid_app/transport_usb.c new file mode 100644 index 00000000000..882a715a5f1 --- /dev/null +++ b/applications/system/hid_app/transport_usb.c @@ -0,0 +1,61 @@ +#include "hid.h" + +#ifndef HID_TRANSPORT_USB +#error "HID_TRANSPORT_USB must be defined" +#endif + +void hid_hal_keyboard_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_kb_press(event); +} + +void hid_hal_keyboard_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_kb_release(event); +} + +void hid_hal_keyboard_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_kb_release_all(); +} + +void hid_hal_consumer_key_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_consumer_key_press(event); +} + +void hid_hal_consumer_key_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_consumer_key_release(event); +} + +void hid_hal_consumer_key_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_kb_release_all(); +} + +void hid_hal_mouse_move(Hid* instance, int8_t dx, int8_t dy) { + furi_assert(instance); + furi_hal_hid_mouse_move(dx, dy); +} + +void hid_hal_mouse_scroll(Hid* instance, int8_t delta) { + furi_assert(instance); + furi_hal_hid_mouse_scroll(delta); +} + +void hid_hal_mouse_press(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_mouse_press(event); +} + +void hid_hal_mouse_release(Hid* instance, uint16_t event) { + furi_assert(instance); + furi_hal_hid_mouse_release(event); +} + +void hid_hal_mouse_release_all(Hid* instance) { + furi_assert(instance); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_LEFT); + furi_hal_hid_mouse_release(HID_MOUSE_BTN_RIGHT); +} diff --git a/applications/system/hid_app/views/hid_media.c b/applications/system/hid_app/views/hid_media.c index 468529d56a8..6ef11729e95 100644 --- a/applications/system/hid_app/views/hid_media.c +++ b/applications/system/hid_app/views/hid_media.c @@ -1,7 +1,7 @@ #include "hid_media.h" #include -#include #include +#include #include #include "../hid.h" diff --git a/assets/icons/StatusBar/BLE_beacon_7x8.png b/assets/icons/StatusBar/BLE_beacon_7x8.png new file mode 100644 index 0000000000000000000000000000000000000000..e8480287ce43f472d31f0d15478179eb6772b847 GIT binary patch literal 117 zcmeAS@N?(olHy`uVBq!ia0vp^>_E)H$P6U;PnEj@DYgKg5ZC|z{{xw!hc4FvDb50q z$YKTtZXpn6ymYtj4^U9l)5S4_<9c#I;*293xH_CASQ<94F-Yq%Gxx4z3I{4+@O1Ta JS?83{1OUl%9BBXm literal 0 HcmV?d00001 diff --git a/firmware.scons b/firmware.scons index 901a762145b..bf3f46a9bf5 100644 --- a/firmware.scons +++ b/firmware.scons @@ -139,8 +139,12 @@ for app_dir, _ in fwenv["APPDIRS"]: fwenv.PrepareApplicationsBuild() + # Build external apps + configure SDK if env["IS_BASE_FIRMWARE"]: + # Ensure all libs are built - even if they are not used in firmware + fw_artifacts.append(fwenv["LIB_DIST_DIR"].glob("*.a")) + fwenv.SetDefault(FBT_FAP_DEBUG_ELF_ROOT=fwenv["BUILD_DIR"].Dir(".extapps")) fw_extapps = fwenv["FW_EXTAPPS"] = SConscript( "site_scons/extapps.scons", diff --git a/furi/core/check.c b/furi/core/check.c index 233b574b041..8025961699d 100644 --- a/furi/core/check.c +++ b/furi/core/check.c @@ -86,7 +86,7 @@ static void __furi_print_stack_info() { } static void __furi_print_bt_stack_info() { - const FuriHalBtHardfaultInfo* fault_info = furi_hal_bt_get_hardfault_info(); + const BleGlueHardfaultInfo* fault_info = ble_glue_get_hardfault_info(); if(fault_info == NULL) { furi_log_puts("\r\n\tcore2: not faulted"); } else { diff --git a/furi/core/thread.c b/furi/core/thread.c index abc85bb90d7..3c1a17258d4 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -11,6 +11,7 @@ #include #include +#include #include #define TAG "FuriThread" @@ -223,6 +224,12 @@ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority) { thread->priority = priority; } +FuriThreadPriority furi_thread_get_priority(FuriThread* thread) { + furi_assert(thread); + TaskHandle_t hTask = furi_thread_get_id(thread); + return (FuriThreadPriority)uxTaskPriorityGet(hTask); +} + void furi_thread_set_current_priority(FuriThreadPriority priority) { UBaseType_t new_priority = priority ? priority : FuriThreadPriorityNormal; vTaskPrioritySet(NULL, new_priority); @@ -497,22 +504,23 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo return (rflags); } -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items) { +uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count) { uint32_t i, count; TaskStatus_t* task; - if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_items == 0U)) { + if(FURI_IS_IRQ_MODE() || (thread_array == NULL) || (array_item_count == 0U)) { count = 0U; } else { vTaskSuspendAll(); count = uxTaskGetNumberOfTasks(); task = pvPortMalloc(count * sizeof(TaskStatus_t)); + configRUN_TIME_COUNTER_TYPE total_run_time; if(task != NULL) { - count = uxTaskGetSystemState(task, count, NULL); + count = uxTaskGetSystemState(task, count, &total_run_time); - for(i = 0U; (i < count) && (i < array_items); i++) { + for(i = 0U; (i < count) && (i < array_item_count); i++) { thread_array[i] = (FuriThreadId)task[i].xHandle; } count = i; diff --git a/furi/core/thread.h b/furi/core/thread.h index 44d66fb21a2..83c051cc224 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -138,6 +138,13 @@ void furi_thread_set_context(FuriThread* thread, void* context); */ void furi_thread_set_priority(FuriThread* thread, FuriThreadPriority priority); +/** Get FuriThread priority + * + * @param thread FuriThread instance + * @return FuriThreadPriority value + */ +FuriThreadPriority furi_thread_get_priority(FuriThread* thread); + /** Set current thread priority * * @param priority FuriThreadPriority value @@ -259,7 +266,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo * @param array_items array size * @return uint32_t threads count */ -uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_items); +uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); /** * @brief Get thread name diff --git a/lib/SConscript b/lib/SConscript index cf96a1b84b2..8125739325a 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -42,6 +42,7 @@ libs = env.BuildModules( "nanopb", "update_util", "heatshrink", + "ble_profile", "bit_lib", "datetime", ], diff --git a/lib/ble_profile/SConscript b/lib/ble_profile/SConscript new file mode 100644 index 00000000000..3b20d38f591 --- /dev/null +++ b/lib/ble_profile/SConscript @@ -0,0 +1,27 @@ +Import("env") + +env.Append( + CPPPATH=[ + "#/lib/ble_profile", + ], + SDK_HEADERS=[ + File("extra_profiles/hid_profile.h"), + File("extra_services/hid_service.h"), + ], +) + +libenv = env.Clone(FW_LIB_NAME="ble_profile") +libenv.AppendUnique( + CCFLAGS=[ + # Required for lib to be linkable with .faps + "-mword-relocations", + "-mlong-calls", + ], +) +libenv.ApplyLibFlags() + +sources = libenv.GlobRecursive("*.c") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/ble_profile/extra_profiles/hid_profile.c b/lib/ble_profile/extra_profiles/hid_profile.c new file mode 100644 index 00000000000..aaa66d96075 --- /dev/null +++ b/lib/ble_profile/extra_profiles/hid_profile.c @@ -0,0 +1,427 @@ +#include "hid_profile.h" + +#include +#include +#include +#include + +#include +#include +#include + +#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) +#define HID_INFO_COUNTRY_CODE (0x00) +#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) +#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) + +#define BLE_PROFILE_HID_KB_MAX_KEYS (6) +#define BLE_PROFILE_CONSUMER_MAX_KEYS (1) + +// Report ids cant be 0 +enum HidReportId { + ReportIdKeyboard = 1, + ReportIdMouse = 2, + ReportIdConsumer = 3, +}; +// Report numbers corresponded to the report id with an offset of 1 +enum HidInputNumber { + ReportNumberKeyboard = 0, + ReportNumberMouse = 1, + ReportNumberConsumer = 2, +}; + +typedef struct { + uint8_t mods; + uint8_t reserved; + uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidKbReport; + +typedef struct { + uint8_t btn; + int8_t x; + int8_t y; + int8_t wheel; +} FURI_PACKED FuriHalBtHidMouseReport; + +typedef struct { + uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS]; +} FURI_PACKED FuriHalBtHidConsumerReport; + +// keyboard+mouse+consumer hid report +static const uint8_t ble_profile_hid_report_map_data[] = { + // Keyboard Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_KEYBOARD), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdKeyboard), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), + HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(8), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(1), + HID_REPORT_SIZE(8), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_LED), + HID_REPORT_COUNT(8), + HID_REPORT_SIZE(1), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(8), + HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS), + HID_REPORT_SIZE(8), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(101), + HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), + HID_USAGE_MINIMUM(0), + HID_USAGE_MAXIMUM(101), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, + // Mouse Report + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_MOUSE), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_USAGE(HID_DESKTOP_POINTER), + HID_COLLECTION(HID_PHYSICAL_COLLECTION), + HID_REPORT_ID(ReportIdMouse), + HID_USAGE_PAGE(HID_PAGE_BUTTON), + HID_USAGE_MINIMUM(1), + HID_USAGE_MAXIMUM(3), + HID_LOGICAL_MINIMUM(0), + HID_LOGICAL_MAXIMUM(1), + HID_REPORT_COUNT(3), + HID_REPORT_SIZE(1), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_REPORT_SIZE(1), + HID_REPORT_COUNT(5), + HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), + HID_USAGE_PAGE(HID_PAGE_DESKTOP), + HID_USAGE(HID_DESKTOP_X), + HID_USAGE(HID_DESKTOP_Y), + HID_USAGE(HID_DESKTOP_WHEEL), + HID_LOGICAL_MINIMUM(-127), + HID_LOGICAL_MAXIMUM(127), + HID_REPORT_SIZE(8), + HID_REPORT_COUNT(3), + HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), + HID_END_COLLECTION, + HID_END_COLLECTION, + // Consumer Report + HID_USAGE_PAGE(HID_PAGE_CONSUMER), + HID_USAGE(HID_CONSUMER_CONTROL), + HID_COLLECTION(HID_APPLICATION_COLLECTION), + HID_REPORT_ID(ReportIdConsumer), + HID_LOGICAL_MINIMUM(0), + HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), + HID_USAGE_MINIMUM(0), + HID_RI_USAGE_MAXIMUM(16, 0x3FF), + HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS), + HID_REPORT_SIZE(16), + HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), + HID_END_COLLECTION, +}; + +typedef struct { + FuriHalBleProfileBase base; + + FuriHalBtHidKbReport* kb_report; + FuriHalBtHidMouseReport* mouse_report; + FuriHalBtHidConsumerReport* consumer_report; + + BleServiceBattery* battery_svc; + BleServiceDevInfo* dev_info_svc; + BleServiceHid* hid_svc; +} BleProfileHid; +_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout"); + +static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + BleProfileHid* profile = malloc(sizeof(BleProfileHid)); + + profile->base.config = ble_profile_hid; + + profile->battery_svc = ble_svc_battery_start(true); + profile->dev_info_svc = ble_svc_dev_info_start(); + profile->hid_svc = ble_svc_hid_start(); + + // Configure HID Keyboard + profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport)); + profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); + profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); + + // Configure Report Map characteristic + ble_svc_hid_update_report_map( + profile->hid_svc, + ble_profile_hid_report_map_data, + sizeof(ble_profile_hid_report_map_data)); + // Configure HID Information characteristic + uint8_t hid_info_val[4] = { + HID_INFO_BASE_USB_SPECIFICATION & 0x00ff, + (HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, + HID_INFO_COUNTRY_CODE, + BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK | + BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, + }; + ble_svc_hid_update_info(profile->hid_svc, hid_info_val); + + return &profile->base; +} + +static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + ble_svc_battery_stop(hid_profile->battery_svc); + ble_svc_dev_info_stop(hid_profile->dev_info_svc); + ble_svc_hid_stop(hid_profile->hid_svc); + + free(hid_profile->kb_report); + free(hid_profile->mouse_report); + free(hid_profile->consumer_report); +} + +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == 0) { + kb_report->key[i] = button & 0xFF; + break; + } + } + kb_report->mods |= (button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + if(kb_report->key[i] == (button & 0xFF)) { + kb_report->key[i] = 0; + break; + } + } + kb_report->mods &= ~(button >> 8); + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; + for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { + kb_report->key[i] = 0; + } + kb_report->mods = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberKeyboard, + (uint8_t*)kb_report, + sizeof(FuriHalBtHidKbReport)); +} + +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == 0) { + consumer_report->key[i] = button; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + if(consumer_report->key[i] == button) { + consumer_report->key[i] = 0; + break; + } + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; + for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 + consumer_report->key[i] = 0; + } + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberConsumer, + (uint8_t*)consumer_report, + sizeof(FuriHalBtHidConsumerReport)); +} + +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->x = dx; + mouse_report->y = dy; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->x = 0; + mouse_report->y = 0; + return state; +} + +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn |= button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn &= ~button; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->btn = 0; + return ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); +} + +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) { + furi_check(profile); + furi_check(profile->config == ble_profile_hid); + + BleProfileHid* hid_profile = (BleProfileHid*)profile; + FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; + mouse_report->wheel = delta; + bool state = ble_svc_hid_update_input_report( + hid_profile->hid_svc, + ReportNumberMouse, + (uint8_t*)mouse_report, + sizeof(FuriHalBtHidMouseReport)); + mouse_report->wheel = 0; + return state; +} + +static GapConfig template_config = { + .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, + .appearance_char = GAP_APPEARANCE_KEYBOARD, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeVerifyYesNo, + .conn_param = + { + .conn_int_min = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms + .slave_latency = 0, + .supervisor_timeout = 0, + }, +}; + +static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + BleProfileHidParams* hid_profile_params = profile_params; + + furi_check(config); + memcpy(config, &template_config, sizeof(GapConfig)); + // Set mac address + memcpy(config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + + // Change MAC address for HID profile + config->mac_address[2]++; + if(hid_profile_params) { + config->mac_address[0] ^= hid_profile_params->mac_xor; + config->mac_address[1] ^= hid_profile_params->mac_xor >> 8; + } + + // Set advertise name + memset(config->adv_name, 0, sizeof(config->adv_name)); + FuriString* name = furi_string_alloc_set(furi_hal_version_get_ble_local_device_name_ptr()); + + const char* clicker_str = "Control"; + if(hid_profile_params && hid_profile_params->device_name_prefix) { + clicker_str = hid_profile_params->device_name_prefix; + } + furi_string_replace_str(name, "Flipper", clicker_str); + if(furi_string_size(name) >= sizeof(config->adv_name)) { + furi_string_left(name, sizeof(config->adv_name) - 1); + } + memcpy(config->adv_name, furi_string_get_cstr(name), furi_string_size(name)); + furi_string_free(name); +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_start, + .stop = ble_profile_hid_stop, + .get_gap_config = ble_profile_hid_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks; diff --git a/lib/ble_profile/extra_profiles/hid_profile.h b/lib/ble_profile/extra_profiles/hid_profile.h new file mode 100644 index 00000000000..eb4884e452d --- /dev/null +++ b/lib/ble_profile/extra_profiles/hid_profile.h @@ -0,0 +1,105 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + const char* device_name_prefix; /**< Prefix for device name. Length must be less than 8 */ + uint16_t mac_xor; /**< XOR mask for device address, for uniqueness */ +} BleProfileHidParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid; + +/** Press keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release keyboard button + * + * @param profile profile instance + * @param button button code from HID specification + * + * @return true on success + */ +bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Release all keyboard buttons + * + * @param profile profile instance + * @return true on success + */ +bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile); + +/** Set the following consumer key to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set the following consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button); + +/** Set consumer key to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse movement and send HID report + * + * @param profile profile instance + * @param dx x coordinate delta + * @param dy y coordinate delta + */ +bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy); + +/** Set mouse button to pressed state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button); + +/** Set mouse button to released state and send HID report + * + * @param profile profile instance + * @param button key code + */ +bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile); + +/** Set mouse wheel position and send HID report + * + * @param profile profile instance + * @param delta number of scroll steps + */ +bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/hid_service.c b/lib/ble_profile/extra_services/hid_service.c similarity index 53% rename from targets/f7/ble_glue/services/hid_service.c rename to lib/ble_profile/extra_services/hid_service.c index cf2aca24e3f..d9ea09c1400 100644 --- a/targets/f7/ble_glue/services/hid_service.c +++ b/lib/ble_profile/extra_services/hid_service.c @@ -1,11 +1,26 @@ #include "hid_service.h" #include "app_common.h" #include -#include "gatt_char.h" +#include +#include #include +#include -#define TAG "BtHid" +#define TAG "BleHid" + +#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_MAX_LEN (255) +#define BLE_SVC_HID_REPORT_REF_LEN (2) +#define BLE_SVC_HID_INFO_LEN (4) +#define BLE_SVC_HID_CONTROL_POINT_LEN (1) + +#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) +#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) +#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) +#define BLE_SVC_HID_REPORT_COUNT \ + (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ + BLE_SVC_HID_FEATURE_REPORT_COUNT) typedef enum { HidSvcGattCharacteristicProtocolMode = 0, @@ -22,12 +37,14 @@ typedef struct { static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); -static const Service_UUID_t hid_svc_uuid = { +static const Service_UUID_t ble_svc_hid_uuid = { .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, }; -static bool - hid_svc_char_desc_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { +static bool ble_svc_hid_char_desc_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { const HidSvcReportId* report_id = context; *data_len = sizeof(HidSvcReportId); if(data) { @@ -41,19 +58,21 @@ typedef struct { uint16_t data_len; } HidSvcDataWrapper; -static bool - hid_svc_report_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { +static bool ble_svc_hid_report_data_callback( + const void* context, + const uint8_t** data, + uint16_t* data_len) { const HidSvcDataWrapper* report_data = context; if(data) { *data = report_data->data_ptr; *data_len = report_data->data_len; } else { - *data_len = HID_SVC_REPORT_MAP_MAX_LEN; + *data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN; } return false; } -static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteristicCount] = { +static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { [HidSvcGattCharacteristicProtocolMode] = {.name = "Protocol Mode", .data_prop_type = FlipperGattCharacteristicDataFixed, @@ -67,7 +86,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicReportMap] = {.name = "Report Map", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, + .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -78,7 +97,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicInfo] = {.name = "HID Information", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_INFO_LEN, + .data.fixed.length = BLE_SVC_HID_INFO_LEN, .data.fixed.ptr = NULL, .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -89,7 +108,7 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris [HidSvcGattCharacteristicCtrlPoint] = {.name = "HID Control Point", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = HID_SVC_CONTROL_POINT_LEN, + .data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN, .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, @@ -98,21 +117,21 @@ static const FlipperGattCharacteristicParams hid_svc_chars[HidSvcGattCharacteris .is_variable = CHAR_VALUE_LEN_CONSTANT}, }; -static const FlipperGattCharacteristicDescriptorParams hid_svc_char_descr_template = { +static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { .uuid_type = UUID_TYPE_16, .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, - .max_length = HID_SVC_REPORT_REF_LEN, - .data_callback.fn = hid_svc_char_desc_data_callback, + .max_length = BLE_SVC_HID_REPORT_REF_LEN, + .data_callback.fn = ble_svc_hid_char_desc_data_callback, .security_permissions = ATTR_PERMISSION_NONE, .access_permissions = ATTR_ACCESS_READ_WRITE, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT, }; -static const FlipperGattCharacteristicParams hid_svc_report_template = { +static const BleGattCharacteristicParams ble_svc_hid_report_template = { .name = "Report", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = hid_svc_report_data_callback, + .data.callback.fn = ble_svc_hid_report_data_callback, .data.callback.context = NULL, .uuid.Char_UUID_16 = REPORT_CHAR_UUID, .uuid_type = UUID_TYPE_16, @@ -122,88 +141,90 @@ static const FlipperGattCharacteristicParams hid_svc_report_template = { .is_variable = CHAR_VALUE_LEN_VARIABLE, }; -typedef struct { +struct BleServiceHid { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; - FlipperGattCharacteristicInstance input_report_chars[HID_SVC_INPUT_REPORT_COUNT]; - FlipperGattCharacteristicInstance output_report_chars[HID_SVC_OUTPUT_REPORT_COUNT]; - FlipperGattCharacteristicInstance feature_report_chars[HID_SVC_FEATURE_REPORT_COUNT]; -} HIDSvc; + BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; + BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT]; + BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT]; + BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT]; + GapSvcEventHandler* event_handler; +}; -static HIDSvc* hid_svc = NULL; +static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { + UNUSED(context); -static SVCCTL_EvtAckStatus_t hid_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; + BleEventAckStatus ret = BleEventNotAck; hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; // aci_gatt_attribute_modified_event_rp0* attribute_modified; if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { // Process modification events - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { // Process notification confirmation - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } } return ret; } -void hid_svc_start() { - tBleStatus status; - hid_svc = malloc(sizeof(HIDSvc)); +BleServiceHid* ble_svc_hid_start() { + BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); // Register event handler - SVCCTL_RegisterSvcHandler(hid_svc_event_handler); + hid_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); /** * Add Human Interface Device Service */ - status = aci_gatt_add_service( - UUID_TYPE_16, - &hid_svc_uuid, - PRIMARY_SERVICE, - 2 + /* protocol mode */ - (4 * HID_SVC_INPUT_REPORT_COUNT) + (3 * HID_SVC_OUTPUT_REPORT_COUNT) + - (3 * HID_SVC_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ - &hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add HID service: %d", status); + if(!ble_gatt_service_add( + UUID_TYPE_16, + &ble_svc_hid_uuid, + PRIMARY_SERVICE, + 2 + /* protocol mode */ + (4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) + + (3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + + 2, /* Service + Report Map + HID Information + HID Control Point */ + &hid_svc->svc_handle)) { + free(hid_svc); + return NULL; } // Maintain previously defined characteristic order - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( hid_svc->svc_handle, - &hid_svc_chars[HidSvcGattCharacteristicProtocolMode], + &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); uint8_t protocol_mode = 1; - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], &protocol_mode); // reports - FlipperGattCharacteristicDescriptorParams hid_svc_char_descr; - FlipperGattCharacteristicParams report_char; + BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; + BleGattCharacteristicParams report_char; HidSvcReportId report_id; - memcpy(&hid_svc_char_descr, &hid_svc_char_descr_template, sizeof(hid_svc_char_descr)); - memcpy(&report_char, &hid_svc_report_template, sizeof(report_char)); + memcpy( + &ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr)); + memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char)); - hid_svc_char_descr.data_callback.context = &report_id; - report_char.descriptor_params = &hid_svc_char_descr; + ble_svc_hid_char_descr.data_callback.context = &report_id; + report_char.descriptor_params = &ble_svc_hid_char_descr; typedef struct { uint8_t report_type; uint8_t report_count; - FlipperGattCharacteristicInstance* chars; + BleGattCharacteristicInstance* chars; } HidSvcReportCharProps; HidSvcReportCharProps hid_report_chars[] = { - {0x01, HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {0x02, HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {0x03, HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + {0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, }; for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); @@ -212,7 +233,7 @@ void hid_svc_start() { for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; report_idx++) { report_id.report_idx = report_idx + 1; - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( hid_svc->svc_handle, &report_char, &hid_report_chars[report_type_idx].chars[report_idx]); @@ -221,12 +242,14 @@ void hid_svc_start() { // Setup remaining characteristics for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - hid_svc->svc_handle, &hid_svc_chars[i], &hid_svc->chars[i]); + ble_gatt_characteristic_init( + hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); } + + return hid_svc; } -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { +bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { furi_assert(data); furi_assert(hid_svc); @@ -234,69 +257,64 @@ bool hid_svc_update_report_map(const uint8_t* data, uint16_t len) { .data_ptr = data, .data_len = len, }; - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); } -bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len) { +bool ble_svc_hid_update_input_report( + BleServiceHid* hid_svc, + uint8_t input_report_num, + uint8_t* data, + uint16_t len) { furi_assert(data); furi_assert(hid_svc); - furi_assert(input_report_num < HID_SVC_INPUT_REPORT_COUNT); + furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT); HidSvcDataWrapper report_data = { .data_ptr = data, .data_len = len, }; - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); } -bool hid_svc_update_info(uint8_t* data) { +bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { furi_assert(data); furi_assert(hid_svc); - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); } -bool hid_svc_is_started() { - return hid_svc != NULL; -} +void ble_svc_hid_stop(BleServiceHid* hid_svc) { + furi_assert(hid_svc); + ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler); + // Delete characteristics + for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); + } -void hid_svc_stop() { - tBleStatus status; - if(hid_svc) { - // Delete characteristics - for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); - } + typedef struct { + uint8_t report_count; + BleGattCharacteristicInstance* chars; + } HidSvcReportCharProps; - typedef struct { - uint8_t report_count; - FlipperGattCharacteristicInstance* chars; - } HidSvcReportCharProps; - - HidSvcReportCharProps hid_report_chars[] = { - {HID_SVC_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {HID_SVC_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {HID_SVC_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, - }; - - for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); - report_type_idx++) { - for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; - report_idx++) { - flipper_gatt_characteristic_delete( - hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); - } - } + HidSvcReportCharProps hid_report_chars[] = { + {BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, + {BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, + {BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, + }; - // Delete service - status = aci_gatt_del_service(hid_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete HID service: %d", status); + for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); + report_type_idx++) { + for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; + report_idx++) { + ble_gatt_characteristic_delete( + hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); } - free(hid_svc); - hid_svc = NULL; } + + // Delete service + ble_gatt_service_delete(hid_svc->svc_handle); + free(hid_svc); } diff --git a/lib/ble_profile/extra_services/hid_service.h b/lib/ble_profile/extra_services/hid_service.h new file mode 100644 index 00000000000..8e9cc297507 --- /dev/null +++ b/lib/ble_profile/extra_services/hid_service.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BleServiceHid BleServiceHid; + +BleServiceHid* ble_svc_hid_start(); + +void ble_svc_hid_stop(BleServiceHid* service); + +bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len); + +bool ble_svc_hid_update_input_report( + BleServiceHid* service, + uint8_t input_report_num, + uint8_t* data, + uint16_t len); + +// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes) +bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/st25tb/st25tb.c b/lib/nfc/protocols/st25tb/st25tb.c index 785cf831d93..2b3eebf9e14 100644 --- a/lib/nfc/protocols/st25tb/st25tb.c +++ b/lib/nfc/protocols/st25tb/st25tb.c @@ -1,6 +1,5 @@ #include "st25tb.h" -#include "core/string.h" #include "flipper_format.h" #include diff --git a/lib/stm32wb.scons b/lib/stm32wb.scons index 8a8ad964496..9f25744e97f 100644 --- a/lib/stm32wb.scons +++ b/lib/stm32wb.scons @@ -56,7 +56,6 @@ sources += Glob( ) sources += [ "stm32wb_copro/wpan/interface/patterns/ble_thread/tl/tl_mbox.c", - "stm32wb_copro/wpan/ble/svc/Src/svc_ctl.c", "stm32wb_copro/wpan/ble/core/auto/ble_gap_aci.c", "stm32wb_copro/wpan/ble/core/auto/ble_gatt_aci.c", "stm32wb_copro/wpan/ble/core/auto/ble_hal_aci.c", diff --git a/scripts/fbt_tools/fbt_hwtarget.py b/scripts/fbt_tools/fbt_hwtarget.py index 67975ed0f8f..a3b0d4a788c 100644 --- a/scripts/fbt_tools/fbt_hwtarget.py +++ b/scripts/fbt_tools/fbt_hwtarget.py @@ -30,8 +30,11 @@ def _loadDescription(self, target_id): if not target_json_file.exists(): raise Exception(f"Target file {target_json_file} does not exist") with open(target_json_file.get_abspath(), "r") as f: - vals = json.load(f) - return vals + try: + vals = json.load(f) + return vals + except json.JSONDecodeError as e: + raise Exception(f"Failed to parse target file {target_json_file}: {e}") def _processTargetDefinitions(self, target_id): target_dir = self._getTargetDir(target_id) diff --git a/scripts/sconsdist.py b/scripts/sconsdist.py index 2cf43dce02a..7ce0e884214 100755 --- a/scripts/sconsdist.py +++ b/scripts/sconsdist.py @@ -63,7 +63,13 @@ def get_project_file_name(self, project: ProjectDir, filetype: str) -> str: return dist_target_path def note_dist_component(self, component: str, extension: str, srcpath: str) -> None: - self._dist_components[f"{component}.{extension}"] = srcpath + component_key = f"{component}.{extension}" + if component_key in self._dist_components: + self.logger.debug( + f"Skipping duplicate component {component_key} in {srcpath}" + ) + return + self._dist_components[component_key] = srcpath def get_dist_file_name(self, dist_artifact_type: str, filetype: str) -> str: return f"{self.DIST_FILE_PREFIX}{self.target}-{dist_artifact_type}-{self.args.suffix}.{filetype}" @@ -162,8 +168,9 @@ def bundle_sdk(self): "scripts.dir", ) + sdk_bundle_path = self.get_dist_path(self.get_dist_file_name("sdk", "zip")) with zipfile.ZipFile( - self.get_dist_path(self.get_dist_file_name("sdk", "zip")), + sdk_bundle_path, "w", zipfile.ZIP_DEFLATED, ) as zf: @@ -205,6 +212,10 @@ def bundle_sdk(self): ), ) + self.logger.info( + fg.boldgreen(f"SDK bundle can be found at:\n\t{sdk_bundle_path}") + ) + def bundle_update_package(self): self.logger.debug( f"Generating update bundle with version {self.args.version} for {self.target}" diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 6d994653bdf..bdfa8c7a4ee 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,57.0,, +Version,+,58.0,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -38,6 +38,8 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, +Header,+,lib/ble_profile/extra_services/hid_service.h,, Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, @@ -169,6 +171,13 @@ Header,+,lib/toolbox/version.h,, Header,+,targets/f18/furi_hal/furi_hal_resources.h,, Header,+,targets/f18/furi_hal/furi_hal_spi_config.h,, Header,+,targets/f18/furi_hal/furi_hal_target_hw.h,, +Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, +Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, +Header,+,targets/f7/ble_glue/furi_ble/profile_interface.h,, +Header,+,targets/f7/ble_glue/profiles/serial_profile.h,, +Header,+,targets/f7/ble_glue/services/battery_service.h,, +Header,+,targets/f7/ble_glue/services/dev_info_service.h,, +Header,+,targets/f7/ble_glue/services/serial_service.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, @@ -191,8 +200,6 @@ Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_debug.h,, @@ -310,6 +317,9 @@ Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t +Function,-,Osal_MemCmp,int,"const void*, const void*, unsigned int" +Function,-,Osal_MemCpy,void*,"void*, const void*, unsigned int" +Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int @@ -602,9 +612,20 @@ Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" -Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" -Function,+,ble_app_init,_Bool, -Function,+,ble_app_thread_stop,void, +Function,-,ble_app_deinit,void, +Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,-,ble_app_init,_Bool, +Function,-,ble_event_app_notification,BleEventFlowStatus,void* +Function,-,ble_event_dispatcher_init,void, +Function,-,ble_event_dispatcher_process_event,BleEventFlowStatus,void* +Function,+,ble_event_dispatcher_register_svc_handler,GapSvcEventHandler*,"BleSvcEventHandlerCb, void*" +Function,-,ble_event_dispatcher_reset,void, +Function,+,ble_event_dispatcher_unregister_svc_handler,void,GapSvcEventHandler* +Function,+,ble_gatt_characteristic_delete,void,"uint16_t, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_init,void,"uint16_t, const BleGattCharacteristicParams*, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_update,_Bool,"uint16_t, BleGattCharacteristicInstance*, const void*" +Function,+,ble_gatt_service_add,_Bool,"uint8_t, const Service_UUID_t*, uint8_t, uint8_t, uint16_t*" +Function,+,ble_gatt_service_delete,_Bool,uint16_t Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode Function,-,ble_glue_fus_get_status,BleGlueCommandResult, Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, @@ -612,20 +633,55 @@ Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,-,ble_glue_get_hardfault_info,const BleGlueHardfaultInfo*, Function,+,ble_glue_init,void, Function,+,ble_glue_is_alive,_Bool, Function,+,ble_glue_is_radio_stack_ready,_Bool, Function,+,ble_glue_reinit_c2,_Bool, Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,ble_glue_start,_Bool, -Function,+,ble_glue_thread_stop,void, +Function,+,ble_glue_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,ble_profile_hid_consumer_key_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_kb_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_move,_Bool,"FuriHalBleProfileBase*, int8_t, int8_t" +Function,-,ble_profile_hid_mouse_press,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_scroll,_Bool,"FuriHalBleProfileBase*, int8_t" +Function,+,ble_profile_serial_notify_buffer_is_empty,void,FuriHalBleProfileBase* +Function,+,ble_profile_serial_set_event_callback,void,"FuriHalBleProfileBase*, uint16_t, FuriHalBtSerialCallback, void*" +Function,+,ble_profile_serial_set_rpc_active,void,"FuriHalBleProfileBase*, _Bool" +Function,+,ble_profile_serial_tx,_Bool,"FuriHalBleProfileBase*, uint8_t*, uint16_t" +Function,+,ble_svc_battery_start,BleServiceBattery*,_Bool +Function,+,ble_svc_battery_state_update,void,"uint8_t*, _Bool*" +Function,+,ble_svc_battery_stop,void,BleServiceBattery* +Function,+,ble_svc_battery_update_level,_Bool,"BleServiceBattery*, uint8_t" +Function,+,ble_svc_battery_update_power_state,_Bool,"BleServiceBattery*, _Bool" +Function,+,ble_svc_dev_info_start,BleServiceDevInfo*, +Function,+,ble_svc_dev_info_stop,void,BleServiceDevInfo* +Function,-,ble_svc_hid_start,BleServiceHid*, +Function,-,ble_svc_hid_stop,void,BleServiceHid* +Function,-,ble_svc_hid_update_info,_Bool,"BleServiceHid*, uint8_t*" +Function,-,ble_svc_hid_update_input_report,_Bool,"BleServiceHid*, uint8_t, uint8_t*, uint16_t" +Function,-,ble_svc_hid_update_report_map,_Bool,"BleServiceHid*, const uint8_t*, uint16_t" +Function,+,ble_svc_serial_notify_buffer_is_empty,void,BleServiceSerial* +Function,+,ble_svc_serial_set_callbacks,void,"BleServiceSerial*, uint16_t, SerialServiceEventCallback, void*" +Function,+,ble_svc_serial_set_rpc_active,void,"BleServiceSerial*, _Bool" +Function,+,ble_svc_serial_start,BleServiceSerial*, +Function,+,ble_svc_serial_stop,void,BleServiceSerial* +Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t" Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" -Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_profile_restore_default,_Bool,Bt* +Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1030,46 +1086,34 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, -Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode -Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, +Function,+,furi_hal_bt_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,+,furi_hal_bt_extra_beacon_get_data,uint8_t,uint8_t* +Function,+,furi_hal_bt_extra_beacon_is_active,_Bool, +Function,+,furi_hal_bt_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,+,furi_hal_bt_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,+,furi_hal_bt_extra_beacon_start,_Bool, +Function,+,furi_hal_bt_extra_beacon_stop,_Bool, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, -Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, -Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" -Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t -Function,+,furi_hal_bt_hid_start,void, -Function,+,furi_hal_bt_hid_stop,void, Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, -Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_gatt_gap_supported,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, -Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, -Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" -Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus -Function,+,furi_hal_bt_serial_start,void, -Function,+,furi_hal_bt_serial_stop,void, -Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1081,7 +1125,7 @@ Function,+,furi_hal_bt_stop_rx,void, Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t -Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bt_update_power_state,void,_Bool Function,+,furi_hal_bus_deinit_early,void, Function,+,furi_hal_bus_disable,void,FuriHalBus Function,+,furi_hal_bus_enable,void,FuriHalBus @@ -1530,6 +1574,7 @@ Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_priority,FuriThreadPriority,FuriThread* Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* @@ -1568,6 +1613,15 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,-,gap_emit_ble_beacon_status_event,void,_Bool +Function,-,gap_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,-,gap_extra_beacon_get_data,uint8_t,uint8_t* +Function,-,gap_extra_beacon_get_state,GapExtraBeaconState, +Function,-,gap_extra_beacon_init,void, +Function,-,gap_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,-,gap_extra_beacon_start,_Bool, +Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1591,6 +1645,7 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -2274,13 +2329,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,serial_svc_is_started,_Bool, -Function,-,serial_svc_notify_buffer_is_empty,void, -Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" -Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus -Function,-,serial_svc_start,void, -Function,-,serial_svc_stop,void, -Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" @@ -2693,6 +2741,8 @@ Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, +Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, +Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 12b8fe685f9..d856dc6948a 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,57.0,, +Version,+,58.0,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -39,6 +39,8 @@ Header,+,applications/services/power/power_service/power.h,, Header,+,applications/services/rpc/rpc_app.h,, Header,+,applications/services/storage/storage.h,, Header,+,lib/bit_lib/bit_lib.h,, +Header,+,lib/ble_profile/extra_profiles/hid_profile.h,, +Header,+,lib/ble_profile/extra_services/hid_service.h,, Header,+,lib/datetime/datetime.h,, Header,+,lib/digital_signal/digital_sequence.h,, Header,+,lib/digital_signal/digital_signal.h,, @@ -229,6 +231,13 @@ Header,+,lib/toolbox/stream/string_stream.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/version.h,, +Header,+,targets/f7/ble_glue/furi_ble/event_dispatcher.h,, +Header,+,targets/f7/ble_glue/furi_ble/gatt.h,, +Header,+,targets/f7/ble_glue/furi_ble/profile_interface.h,, +Header,+,targets/f7/ble_glue/profiles/serial_profile.h,, +Header,+,targets/f7/ble_glue/services/battery_service.h,, +Header,+,targets/f7/ble_glue/services/dev_info_service.h,, +Header,+,targets/f7/ble_glue/services/serial_service.h,, Header,+,targets/f7/furi_hal/furi_hal_bus.h,, Header,+,targets/f7/furi_hal/furi_hal_clock.h,, Header,+,targets/f7/furi_hal/furi_hal_dma.h,, @@ -257,8 +266,6 @@ Header,+,targets/f7/platform_specific/intrinsic_export.h,, Header,+,targets/f7/platform_specific/math_wrapper.h,, Header,+,targets/furi_hal_include/furi_hal.h,, Header,+,targets/furi_hal_include/furi_hal_bt.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_hid.h,, -Header,+,targets/furi_hal_include/furi_hal_bt_serial.h,, Header,+,targets/furi_hal_include/furi_hal_cortex.h,, Header,+,targets/furi_hal_include/furi_hal_crypto.h,, Header,+,targets/furi_hal_include/furi_hal_debug.h,, @@ -378,6 +385,9 @@ Function,-,LL_USART_DeInit,ErrorStatus,const USART_TypeDef* Function,+,LL_USART_Init,ErrorStatus,"USART_TypeDef*, const LL_USART_InitTypeDef*" Function,-,LL_USART_StructInit,void,LL_USART_InitTypeDef* Function,-,LL_mDelay,void,uint32_t +Function,-,Osal_MemCmp,int,"const void*, const void*, unsigned int" +Function,-,Osal_MemCpy,void*,"void*, const void*, unsigned int" +Function,-,Osal_MemSet,void*,"void*, int, unsigned int" Function,-,SystemCoreClockUpdate,void, Function,-,SystemInit,void, Function,-,_Exit,void,int @@ -670,9 +680,20 @@ Function,+,bit_lib_set_bit,void,"uint8_t*, size_t, _Bool" Function,+,bit_lib_set_bits,void,"uint8_t*, size_t, uint8_t, uint8_t" Function,+,bit_lib_test_parity,_Bool,"const uint8_t*, size_t, uint8_t, BitLibParity, uint8_t" Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity" -Function,+,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" -Function,+,ble_app_init,_Bool, -Function,+,ble_app_thread_stop,void, +Function,-,ble_app_deinit,void, +Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*" +Function,-,ble_app_init,_Bool, +Function,-,ble_event_app_notification,BleEventFlowStatus,void* +Function,-,ble_event_dispatcher_init,void, +Function,+,ble_event_dispatcher_process_event,BleEventFlowStatus,void* +Function,+,ble_event_dispatcher_register_svc_handler,GapSvcEventHandler*,"BleSvcEventHandlerCb, void*" +Function,-,ble_event_dispatcher_reset,void, +Function,+,ble_event_dispatcher_unregister_svc_handler,void,GapSvcEventHandler* +Function,+,ble_gatt_characteristic_delete,void,"uint16_t, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_init,void,"uint16_t, const BleGattCharacteristicParams*, BleGattCharacteristicInstance*" +Function,+,ble_gatt_characteristic_update,_Bool,"uint16_t, BleGattCharacteristicInstance*, const void*" +Function,+,ble_gatt_service_add,_Bool,"uint8_t, const Service_UUID_t*, uint8_t, uint8_t, uint16_t*" +Function,+,ble_gatt_service_delete,_Bool,uint16_t Function,+,ble_glue_force_c2_mode,BleGlueCommandResult,BleGlueC2Mode Function,-,ble_glue_fus_get_status,BleGlueCommandResult, Function,-,ble_glue_fus_stack_delete,BleGlueCommandResult, @@ -680,20 +701,55 @@ Function,-,ble_glue_fus_stack_install,BleGlueCommandResult,"uint32_t, uint32_t" Function,-,ble_glue_fus_wait_operation,BleGlueCommandResult, Function,+,ble_glue_get_c2_info,const BleGlueC2Info*, Function,-,ble_glue_get_c2_status,BleGlueStatus, +Function,-,ble_glue_get_hardfault_info,const BleGlueHardfaultInfo*, Function,+,ble_glue_init,void, Function,+,ble_glue_is_alive,_Bool, Function,+,ble_glue_is_radio_stack_ready,_Bool, Function,+,ble_glue_reinit_c2,_Bool, Function,+,ble_glue_set_key_storage_changed_callback,void,"BleGlueKeyStorageChangedCallback, void*" -Function,+,ble_glue_start,_Bool, -Function,+,ble_glue_thread_stop,void, +Function,-,ble_glue_start,_Bool, +Function,-,ble_glue_stop,void, Function,+,ble_glue_wait_for_c2_start,_Bool,int32_t +Function,-,ble_profile_hid_consumer_key_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_consumer_key_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_kb_press,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release,_Bool,"FuriHalBleProfileBase*, uint16_t" +Function,-,ble_profile_hid_kb_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_move,_Bool,"FuriHalBleProfileBase*, int8_t, int8_t" +Function,-,ble_profile_hid_mouse_press,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release,_Bool,"FuriHalBleProfileBase*, uint8_t" +Function,-,ble_profile_hid_mouse_release_all,_Bool,FuriHalBleProfileBase* +Function,-,ble_profile_hid_mouse_scroll,_Bool,"FuriHalBleProfileBase*, int8_t" +Function,+,ble_profile_serial_notify_buffer_is_empty,void,FuriHalBleProfileBase* +Function,+,ble_profile_serial_set_event_callback,void,"FuriHalBleProfileBase*, uint16_t, FuriHalBtSerialCallback, void*" +Function,+,ble_profile_serial_set_rpc_active,void,"FuriHalBleProfileBase*, _Bool" +Function,+,ble_profile_serial_tx,_Bool,"FuriHalBleProfileBase*, uint8_t*, uint16_t" +Function,+,ble_svc_battery_start,BleServiceBattery*,_Bool +Function,+,ble_svc_battery_state_update,void,"uint8_t*, _Bool*" +Function,+,ble_svc_battery_stop,void,BleServiceBattery* +Function,+,ble_svc_battery_update_level,_Bool,"BleServiceBattery*, uint8_t" +Function,+,ble_svc_battery_update_power_state,_Bool,"BleServiceBattery*, _Bool" +Function,+,ble_svc_dev_info_start,BleServiceDevInfo*, +Function,+,ble_svc_dev_info_stop,void,BleServiceDevInfo* +Function,-,ble_svc_hid_start,BleServiceHid*, +Function,-,ble_svc_hid_stop,void,BleServiceHid* +Function,-,ble_svc_hid_update_info,_Bool,"BleServiceHid*, uint8_t*" +Function,-,ble_svc_hid_update_input_report,_Bool,"BleServiceHid*, uint8_t, uint8_t*, uint16_t" +Function,-,ble_svc_hid_update_report_map,_Bool,"BleServiceHid*, const uint8_t*, uint16_t" +Function,+,ble_svc_serial_notify_buffer_is_empty,void,BleServiceSerial* +Function,+,ble_svc_serial_set_callbacks,void,"BleServiceSerial*, uint16_t, SerialServiceEventCallback, void*" +Function,+,ble_svc_serial_set_rpc_active,void,"BleServiceSerial*, _Bool" +Function,+,ble_svc_serial_start,BleServiceSerial*, +Function,+,ble_svc_serial_stop,void,BleServiceSerial* +Function,+,ble_svc_serial_update_tx,_Bool,"BleServiceSerial*, uint8_t*, uint16_t" Function,-,bsearch,void*,"const void*, const void*, size_t, size_t, __compar_fn_t" Function,+,bt_disconnect,void,Bt* Function,+,bt_forget_bonded_devices,void,Bt* Function,+,bt_keys_storage_set_default_path,void,Bt* Function,+,bt_keys_storage_set_storage_path,void,"Bt*, const char*" -Function,+,bt_set_profile,_Bool,"Bt*, BtProfile" +Function,+,bt_profile_restore_default,_Bool,Bt* +Function,+,bt_profile_start,FuriHalBleProfileBase*,"Bt*, const FuriHalBleProfileTemplate*, FuriHalBleProfileParams" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* @@ -1098,46 +1154,34 @@ Function,+,furi_event_flag_get,uint32_t,FuriEventFlag* Function,+,furi_event_flag_set,uint32_t,"FuriEventFlag*, uint32_t" Function,+,furi_event_flag_wait,uint32_t,"FuriEventFlag*, uint32_t, uint32_t, uint32_t" Function,+,furi_get_tick,uint32_t, -Function,+,furi_hal_bt_change_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_change_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" +Function,+,furi_hal_bt_check_profile_type,_Bool,"FuriHalBleProfileBase*, const FuriHalBleProfileTemplate*" Function,+,furi_hal_bt_clear_white_list,_Bool, Function,+,furi_hal_bt_dump_state,void,FuriString* Function,+,furi_hal_bt_ensure_c2_mode,_Bool,BleGlueC2Mode -Function,-,furi_hal_bt_get_hardfault_info,const FuriHalBtHardfaultInfo*, +Function,+,furi_hal_bt_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,+,furi_hal_bt_extra_beacon_get_data,uint8_t,uint8_t* +Function,+,furi_hal_bt_extra_beacon_is_active,_Bool, +Function,+,furi_hal_bt_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,+,furi_hal_bt_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,+,furi_hal_bt_extra_beacon_start,_Bool, +Function,+,furi_hal_bt_extra_beacon_stop,_Bool, Function,+,furi_hal_bt_get_key_storage_buff,void,"uint8_t**, uint16_t*" Function,+,furi_hal_bt_get_radio_stack,FuriHalBtStack, Function,+,furi_hal_bt_get_rssi,float, Function,+,furi_hal_bt_get_transmitted_packets,uint32_t, -Function,+,furi_hal_bt_hid_consumer_key_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_consumer_key_release_all,_Bool, -Function,+,furi_hal_bt_hid_kb_press,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release,_Bool,uint16_t -Function,+,furi_hal_bt_hid_kb_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_move,_Bool,"int8_t, int8_t" -Function,+,furi_hal_bt_hid_mouse_press,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release,_Bool,uint8_t -Function,+,furi_hal_bt_hid_mouse_release_all,_Bool, -Function,+,furi_hal_bt_hid_mouse_scroll,_Bool,int8_t -Function,+,furi_hal_bt_hid_start,void, -Function,+,furi_hal_bt_hid_stop,void, Function,-,furi_hal_bt_init,void, Function,+,furi_hal_bt_is_active,_Bool, Function,+,furi_hal_bt_is_alive,_Bool, -Function,+,furi_hal_bt_is_ble_gatt_gap_supported,_Bool, +Function,+,furi_hal_bt_is_gatt_gap_supported,_Bool, Function,+,furi_hal_bt_is_testing_supported,_Bool, Function,+,furi_hal_bt_lock_core2,void, Function,+,furi_hal_bt_nvm_sram_sem_acquire,void, Function,+,furi_hal_bt_nvm_sram_sem_release,void, Function,+,furi_hal_bt_reinit,void, -Function,+,furi_hal_bt_serial_notify_buffer_is_empty,void, -Function,+,furi_hal_bt_serial_set_event_callback,void,"uint16_t, FuriHalBtSerialCallback, void*" -Function,+,furi_hal_bt_serial_set_rpc_status,void,FuriHalBtSerialRpcStatus -Function,+,furi_hal_bt_serial_start,void, -Function,+,furi_hal_bt_serial_stop,void, -Function,+,furi_hal_bt_serial_tx,_Bool,"uint8_t*, uint16_t" Function,+,furi_hal_bt_set_key_storage_change_callback,void,"BleGlueKeyStorageChangedCallback, void*" Function,+,furi_hal_bt_start_advertising,void, -Function,+,furi_hal_bt_start_app,_Bool,"FuriHalBtProfile, GapEventCallback, void*" +Function,+,furi_hal_bt_start_app,FuriHalBleProfileBase*,"const FuriHalBleProfileTemplate*, FuriHalBleProfileParams, GapEventCallback, void*" Function,+,furi_hal_bt_start_packet_rx,void,"uint8_t, uint8_t" Function,+,furi_hal_bt_start_packet_tx,void,"uint8_t, uint8_t, uint8_t" Function,+,furi_hal_bt_start_radio_stack,_Bool, @@ -1149,7 +1193,7 @@ Function,+,furi_hal_bt_stop_rx,void, Function,+,furi_hal_bt_stop_tone_tx,void, Function,+,furi_hal_bt_unlock_core2,void, Function,+,furi_hal_bt_update_battery_level,void,uint8_t -Function,+,furi_hal_bt_update_power_state,void, +Function,+,furi_hal_bt_update_power_state,void,_Bool Function,+,furi_hal_bus_deinit_early,void, Function,+,furi_hal_bus_disable,void,FuriHalBus Function,+,furi_hal_bus_enable,void,FuriHalBus @@ -1705,6 +1749,7 @@ Function,+,furi_thread_get_current_priority,FuriThreadPriority, Function,+,furi_thread_get_heap_size,size_t,FuriThread* Function,+,furi_thread_get_id,FuriThreadId,FuriThread* Function,+,furi_thread_get_name,const char*,FuriThreadId +Function,+,furi_thread_get_priority,FuriThreadPriority,FuriThread* Function,+,furi_thread_get_return_code,int32_t,FuriThread* Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId Function,+,furi_thread_get_state,FuriThreadState,FuriThread* @@ -1743,6 +1788,15 @@ Function,-,gamma,double,double Function,-,gamma_r,double,"double, int*" Function,-,gammaf,float,float Function,-,gammaf_r,float,"float, int*" +Function,-,gap_emit_ble_beacon_status_event,void,_Bool +Function,-,gap_extra_beacon_get_config,const GapExtraBeaconConfig*, +Function,-,gap_extra_beacon_get_data,uint8_t,uint8_t* +Function,-,gap_extra_beacon_get_state,GapExtraBeaconState, +Function,-,gap_extra_beacon_init,void, +Function,-,gap_extra_beacon_set_config,_Bool,const GapExtraBeaconConfig* +Function,-,gap_extra_beacon_set_data,_Bool,"const uint8_t*, uint8_t" +Function,-,gap_extra_beacon_start,_Bool, +Function,-,gap_extra_beacon_stop,_Bool, Function,-,gap_get_state,GapState, Function,-,gap_init,_Bool,"GapConfig*, GapEventCallback, void*" Function,-,gap_start_advertising,void, @@ -1766,6 +1820,7 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" +Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*" Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*" @@ -2834,13 +2889,6 @@ Function,+,scene_manager_stop,void,SceneManager* Function,+,sd_api_get_fs_type_text,const char*,SDFsType Function,-,secure_getenv,char*,const char* Function,-,seed48,unsigned short*,unsigned short[3] -Function,-,serial_svc_is_started,_Bool, -Function,-,serial_svc_notify_buffer_is_empty,void, -Function,-,serial_svc_set_callbacks,void,"uint16_t, SerialServiceEventCallback, void*" -Function,-,serial_svc_set_rpc_status,void,SerialServiceRpcStatus -Function,-,serial_svc_start,void, -Function,-,serial_svc_stop,void, -Function,-,serial_svc_update_tx,_Bool,"uint8_t*, uint16_t" Function,-,setbuf,void,"FILE*, char*" Function,-,setbuffer,void,"FILE*, char*, int" Function,-,setenv,int,"const char*, const char*, int" @@ -3455,6 +3503,8 @@ Variable,+,_impure_data,_reent, Variable,+,_impure_ptr,_reent*, Variable,-,_sys_errlist,const char*[], Variable,-,_sys_nerr,int, +Variable,-,ble_profile_hid,const FuriHalBleProfileTemplate*, +Variable,-,ble_profile_serial,const FuriHalBleProfileTemplate*, Variable,+,cli_vcp,CliSession, Variable,+,firmware_api_interface,const ElfApiInterface*, Variable,+,furi_hal_i2c_bus_external,FuriHalI2cBus, diff --git a/targets/f7/ble_glue/app_common.h b/targets/f7/ble_glue/app_common.h index e969636d279..8097d23dbe2 100644 --- a/targets/f7/ble_glue/app_common.h +++ b/targets/f7/ble_glue/app_common.h @@ -7,6 +7,6 @@ #include #include -#include +#include #include "app_conf.h" diff --git a/targets/f7/ble_glue/app_conf.h b/targets/f7/ble_glue/app_conf.h index 25fa688c700..fbf6d0291de 100644 --- a/targets/f7/ble_glue/app_conf.h +++ b/targets/f7/ble_glue/app_conf.h @@ -46,7 +46,7 @@ * Maximum number of simultaneous connections that the device will support. * Valid values are from 1 to 8 */ -#define CFG_BLE_NUM_LINK 1 +#define CFG_BLE_NUM_LINK 2 /** * Maximum number of Services that can be stored in the GATT database. diff --git a/targets/f7/ble_glue/ble_app.c b/targets/f7/ble_glue/ble_app.c index 05dd46e943a..1f392529deb 100644 --- a/targets/f7/ble_glue/ble_app.c +++ b/targets/f7/ble_glue/ble_app.c @@ -1,19 +1,17 @@ #include "ble_app.h" +#include #include #include #include #include "gap.h" +#include "furi_ble/event_dispatcher.h" #include #include #define TAG "Bt" -#define BLE_APP_FLAG_HCI_EVENT (1UL << 0) -#define BLE_APP_FLAG_KILL_THREAD (1UL << 1) -#define BLE_APP_FLAG_ALL (BLE_APP_FLAG_HCI_EVENT | BLE_APP_FLAG_KILL_THREAD) - PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_CmdPacket_t ble_app_cmd_buffer; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint32_t ble_app_nvm[BLE_NVM_SRAM_SIZE]; @@ -24,12 +22,10 @@ _Static_assert( typedef struct { FuriMutex* hci_mtx; FuriSemaphore* hci_sem; - FuriThread* thread; } BleApp; static BleApp* ble_app = NULL; -static int32_t ble_app_hci_thread(void* context); static void ble_app_hci_event_handler(void* pPayload); static void ble_app_hci_status_not_handler(HCI_TL_CmdStatus_t status); @@ -81,30 +77,35 @@ static const SHCI_C2_Ble_Init_Cmd_Packet_t ble_init_cmd_packet = { SHCI_C2_BLE_INIT_OPTIONS_APPEARANCE_READONLY, }}; -bool ble_app_init() { +bool ble_app_init(void) { SHCI_CmdStatus_t status; ble_app = malloc(sizeof(BleApp)); // Allocate semafore and mutex for ble command buffer access ble_app->hci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); ble_app->hci_sem = furi_semaphore_alloc(1, 0); - // HCI transport layer thread to handle user asynch events - ble_app->thread = furi_thread_alloc_ex("BleHciDriver", 1024, ble_app_hci_thread, ble_app); - furi_thread_start(ble_app->thread); // Initialize Ble Transport Layer hci_init(ble_app_hci_event_handler, (void*)&hci_tl_config); - // Configure NVM store for pairing data - status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param); - if(status) { - FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); - } + do { + // Configure NVM store for pairing data + if((status = SHCI_C2_Config((SHCI_C2_CONFIG_Cmd_Param_t*)&config_param))) { + FURI_LOG_E(TAG, "Failed to configure 2nd core: %d", status); + break; + } + + // Start ble stack on 2nd core + if((status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet))) { + FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); + break; + } + + if((status = SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7))) { + FURI_LOG_E(TAG, "Failed to set flash activity control: %d", status); + break; + } + } while(false); - // Start ble stack on 2nd core - status = SHCI_C2_BLE_Init((SHCI_C2_Ble_Init_Cmd_Packet_t*)&ble_init_cmd_packet); - if(status) { - FURI_LOG_E(TAG, "Failed to start ble stack: %d", status); - } return status == SHCI_Success; } @@ -113,47 +114,18 @@ void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size) { *size = sizeof(ble_app_nvm); } -void ble_app_thread_stop() { - if(ble_app) { - FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_APP_FLAG_KILL_THREAD); - furi_thread_join(ble_app->thread); - furi_thread_free(ble_app->thread); - // Free resources - furi_mutex_free(ble_app->hci_mtx); - furi_semaphore_free(ble_app->hci_sem); - free(ble_app); - ble_app = NULL; - memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer)); - } -} - -static int32_t ble_app_hci_thread(void* arg) { - UNUSED(arg); - uint32_t flags = 0; - - while(1) { - flags = furi_thread_flags_wait(BLE_APP_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - if(flags & BLE_APP_FLAG_KILL_THREAD) { - break; - } - if(flags & BLE_APP_FLAG_HCI_EVENT) { - hci_user_evt_proc(); - } - } +void ble_app_deinit(void) { + furi_check(ble_app); - return 0; + furi_mutex_free(ble_app->hci_mtx); + furi_semaphore_free(ble_app->hci_sem); + free(ble_app); + ble_app = NULL; + memset(&ble_app_cmd_buffer, 0, sizeof(ble_app_cmd_buffer)); } -// Called by WPAN lib -void hci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); - furi_check(ble_app); - FuriThreadId thread_id = furi_thread_get_id(ble_app->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_APP_FLAG_HCI_EVENT); -} +/////////////////////////////////////////////////////////////////////////////// +// AN5289, 4.9 void hci_cmd_resp_release(uint32_t flag) { UNUSED(flag); @@ -166,13 +138,16 @@ void hci_cmd_resp_wait(uint32_t timeout) { furi_check(furi_semaphore_acquire(ble_app->hci_sem, timeout) == FuriStatusOk); } +/////////////////////////////////////////////////////////////////////////////// + static void ble_app_hci_event_handler(void* pPayload) { - SVCCTL_UserEvtFlowStatus_t svctl_return_status; + furi_check(ble_app); + tHCI_UserEvtRxParam* pParam = (tHCI_UserEvtRxParam*)pPayload; + BleEventFlowStatus event_flow_status = + ble_event_dispatcher_process_event((void*)&(pParam->pckt->evtserial)); - furi_check(ble_app); - svctl_return_status = SVCCTL_UserEvtRx((void*)&(pParam->pckt->evtserial)); - if(svctl_return_status != SVCCTL_UserEvtFlowDisable) { + if(event_flow_status != BleEventFlowDisable) { pParam->status = HCI_TL_UserEventFlow_Enable; } else { pParam->status = HCI_TL_UserEventFlow_Disable; diff --git a/targets/f7/ble_glue/ble_app.h b/targets/f7/ble_glue/ble_app.h index 2e6babab79d..22edccd1b10 100644 --- a/targets/f7/ble_glue/ble_app.h +++ b/targets/f7/ble_glue/ble_app.h @@ -3,13 +3,19 @@ #include #include +/* + * BLE stack init and cleanup + */ + #ifdef __cplusplus extern "C" { #endif -bool ble_app_init(); +bool ble_app_init(void); + void ble_app_get_key_storage_buff(uint8_t** addr, uint16_t* size); -void ble_app_thread_stop(); + +void ble_app_deinit(void); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/ble_conf.h b/targets/f7/ble_glue/ble_conf.h index 2b9c22dfe39..4c523a707e4 100644 --- a/targets/f7/ble_glue/ble_conf.h +++ b/targets/f7/ble_glue/ble_conf.h @@ -3,12 +3,9 @@ #include "app_conf.h" /** - * There is one handler per service enabled - * Note: There is no handler for the Device Information Service - * - * This shall take into account all registered handlers - * (from either the provided services or the custom services) + * We're not using WPAN's event dispatchers + * so both client & service max callback count is set to 0. */ -#define BLE_CFG_SVC_MAX_NBR_CB 7 +#define BLE_CFG_SVC_MAX_NBR_CB 0 #define BLE_CFG_CLT_MAX_NBR_CB 0 diff --git a/targets/f7/ble_glue/ble_event_thread.c b/targets/f7/ble_glue/ble_event_thread.c new file mode 100644 index 00000000000..6f9a1cdcd33 --- /dev/null +++ b/targets/f7/ble_glue/ble_event_thread.c @@ -0,0 +1,96 @@ +#include "app_common.h" + +#include +#include +#include + +#include +#include + +#define TAG "BleEvt" + +#define BLE_EVENT_THREAD_FLAG_SHCI_EVENT (1UL << 0) +#define BLE_EVENT_THREAD_FLAG_HCI_EVENT (1UL << 1) +#define BLE_EVENT_THREAD_FLAG_KILL_THREAD (1UL << 2) + +#define BLE_EVENT_THREAD_FLAG_ALL \ + (BLE_EVENT_THREAD_FLAG_SHCI_EVENT | BLE_EVENT_THREAD_FLAG_HCI_EVENT | \ + BLE_EVENT_THREAD_FLAG_KILL_THREAD) + +static FuriThread* event_thread = NULL; + +static int32_t ble_event_thread(void* context) { + UNUSED(context); + uint32_t flags = 0; + + while((flags & BLE_EVENT_THREAD_FLAG_KILL_THREAD) == 0) { + flags = + furi_thread_flags_wait(BLE_EVENT_THREAD_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); + if(flags & BLE_EVENT_THREAD_FLAG_SHCI_EVENT) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "shci_user_evt_proc"); +#endif + shci_user_evt_proc(); + } + if(flags & BLE_EVENT_THREAD_FLAG_HCI_EVENT) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "hci_user_evt_proc"); +#endif + hci_user_evt_proc(); + } + } + + return 0; +} + +void shci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "shci: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_SHCI_EVENT); +} + +void hci_notify_asynch_evt(void* pdata) { + UNUSED(pdata); + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "hci: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_HCI_EVENT); +} + +void ble_event_thread_stop(void) { + if(!event_thread) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_E(TAG, "thread_stop: event_thread is NULL"); +#endif + return; + } + + FuriThreadId thread_id = furi_thread_get_id(event_thread); + furi_assert(thread_id); + furi_thread_flags_set(thread_id, BLE_EVENT_THREAD_FLAG_KILL_THREAD); + furi_thread_join(event_thread); + furi_thread_free(event_thread); + event_thread = NULL; +} + +void ble_event_thread_start(void) { + furi_check(event_thread == NULL); + + event_thread = furi_thread_alloc_ex("BleEventWorker", 1024, ble_event_thread, NULL); + furi_thread_set_priority(event_thread, FuriThreadPriorityHigh); + furi_thread_start(event_thread); +} diff --git a/targets/f7/ble_glue/ble_event_thread.h b/targets/f7/ble_glue/ble_event_thread.h new file mode 100644 index 00000000000..bce858d6b71 --- /dev/null +++ b/targets/f7/ble_glue/ble_event_thread.h @@ -0,0 +1,15 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/* Controls for thread handling SHCI & HCI event queues. Used internally. */ + +void ble_event_thread_start(void); + +void ble_event_thread_stop(void); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/ble_glue.c b/targets/f7/ble_glue/ble_glue.c index 5f129ba8ce3..91cb020d72e 100644 --- a/targets/f7/ble_glue/ble_glue.c +++ b/targets/f7/ble_glue/ble_glue.c @@ -1,6 +1,11 @@ #include "ble_glue.h" #include "app_common.h" #include "ble_app.h" +#include "ble_event_thread.h" + +#include +#include +#include #include #include @@ -13,26 +18,26 @@ #define TAG "Core2" -#define BLE_GLUE_FLAG_SHCI_EVENT (1UL << 0) -#define BLE_GLUE_FLAG_KILL_THREAD (1UL << 1) -#define BLE_GLUE_FLAG_ALL (BLE_GLUE_FLAG_SHCI_EVENT | BLE_GLUE_FLAG_KILL_THREAD) +#define BLE_GLUE_HARDFAULT_CHECK_PERIOD_MS (5000) + +#define BLE_GLUE_HARDFAULT_INFO_MAGIC (0x1170FD0F) #define POOL_SIZE \ (CFG_TLBLE_EVT_QUEUE_LENGTH * 4U * \ DIVC((sizeof(TL_PacketHeader_t) + TL_BLE_EVENT_FRAME_SIZE), 4U)) -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_glue_event_pool[POOL_SIZE]; -PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_system_cmd_buff; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ble_event_pool[POOL_SIZE]; +PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ble_glue_cmd_buff; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) -static uint8_t ble_glue_system_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; +static uint8_t ble_glue_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U]; PLACE_IN_SECTION("MB_MEM2") ALIGN(4) -static uint8_t ble_glue_ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; +static uint8_t ble_spare_event_buff[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255]; typedef struct { FuriMutex* shci_mtx; - FuriThread* thread; + FuriTimer* hardfault_check_timer; BleGlueStatus status; BleGlueKeyStorageChangedCallback callback; BleGlueC2Info c2_info; @@ -41,9 +46,10 @@ typedef struct { static BleGlue* ble_glue = NULL; -static int32_t ble_glue_shci_thread(void* argument); -static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status); -static void ble_glue_sys_user_event_callback(void* pPayload); +// static int32_t ble_glue_shci_thread(void* argument); +static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status); +static void ble_sys_user_event_callback(void* pPayload); +static void ble_glue_clear_shared_memory(); void ble_glue_set_key_storage_changed_callback( BleGlueKeyStorageChangedCallback callback, @@ -54,43 +60,21 @@ void ble_glue_set_key_storage_changed_callback( ble_glue->context = context; } -/////////////////////////////////////////////////////////////////////////////// - -/* TL hook to catch hardfaults */ - -int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) { - if(furi_hal_bt_get_hardfault_info()) { - furi_crash("ST(R) Copro(R) HardFault"); - } - - return TL_SYS_SendCmd(buffer, size); -} - -void shci_register_io_bus(tSHciIO* fops) { - /* Register IO bus services */ - fops->Init = TL_SYS_Init; - fops->Send = ble_glue_TL_SYS_SendCmd; -} - -static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { - if(furi_hal_bt_get_hardfault_info()) { +static void furi_hal_bt_hardfault_check(void* context) { + UNUSED(context); + if(ble_glue_get_hardfault_info()) { furi_crash("ST(R) Copro(R) HardFault"); } - - return TL_BLE_SendCmd(buffer, size); -} - -void hci_register_io_bus(tHciIO* fops) { - /* Register IO bus services */ - fops->Init = TL_BLE_Init; - fops->Send = ble_glue_TL_BLE_SendCmd; } /////////////////////////////////////////////////////////////////////////////// -void ble_glue_init() { +void ble_glue_init(void) { ble_glue = malloc(sizeof(BleGlue)); ble_glue->status = BleGlueStatusStartup; + ble_glue->hardfault_check_timer = + furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL); + furi_timer_start(ble_glue->hardfault_check_timer, BLE_GLUE_HARDFAULT_CHECK_PERIOD_MS); #ifdef BLE_GLUE_DEBUG APPD_Init(); @@ -105,18 +89,17 @@ void ble_glue_init() { ble_glue->shci_mtx = furi_mutex_alloc(FuriMutexTypeNormal); // FreeRTOS system task creation - ble_glue->thread = furi_thread_alloc_ex("BleShciDriver", 1024, ble_glue_shci_thread, ble_glue); - furi_thread_start(ble_glue->thread); + ble_event_thread_start(); // System channel initialization - SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_system_cmd_buff; - SHci_Tl_Init_Conf.StatusNotCallBack = ble_glue_sys_status_not_callback; - shci_init(ble_glue_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf); + SHci_Tl_Init_Conf.p_cmdbuffer = (uint8_t*)&ble_glue_cmd_buff; + SHci_Tl_Init_Conf.StatusNotCallBack = ble_sys_status_not_callback; + shci_init(ble_sys_user_event_callback, (void*)&SHci_Tl_Init_Conf); /**< Memory Manager channel initialization */ - tl_mm_config.p_BleSpareEvtBuffer = ble_glue_ble_spare_event_buff; - tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_system_spare_event_buff; - tl_mm_config.p_AsynchEvtPool = ble_glue_event_pool; + tl_mm_config.p_BleSpareEvtBuffer = ble_spare_event_buff; + tl_mm_config.p_SystemSpareEvtBuffer = ble_glue_spare_event_buff; + tl_mm_config.p_AsynchEvtPool = ble_event_pool; tl_mm_config.AsynchEvtPoolSize = POOL_SIZE; TL_MM_Init(&tl_mm_config); TL_Enable(); @@ -124,15 +107,15 @@ void ble_glue_init() { /* * From now, the application is waiting for the ready event ( VS_HCI_C2_Ready ) * received on the system channel before starting the Stack - * This system event is received with ble_glue_sys_user_event_callback() + * This system event is received with ble_sys_user_event_callback() */ } -const BleGlueC2Info* ble_glue_get_c2_info() { +const BleGlueC2Info* ble_glue_get_c2_info(void) { return &ble_glue->c2_info; } -BleGlueStatus ble_glue_get_c2_status() { +BleGlueStatus ble_glue_get_c2_status(void) { return ble_glue->status; } @@ -159,7 +142,7 @@ static const char* ble_glue_get_reltype_str(const uint8_t reltype) { } } -static void ble_glue_update_c2_fw_info() { +static void ble_glue_update_c2_fw_info(void) { WirelessFwInfo_t wireless_info; SHCI_GetWirelessFwInfo(&wireless_info); BleGlueC2Info* local_info = &ble_glue->c2_info; @@ -178,7 +161,7 @@ static void ble_glue_update_c2_fw_info() { local_info->StackType = wireless_info.StackType; snprintf( local_info->StackTypeString, - BLE_GLUE_MAX_VERSION_STRING_LEN, + BLE_MAX_VERSION_STRING_LEN, "%d.%d.%d:%s", local_info->VersionMajor, local_info->VersionMinor, @@ -193,7 +176,7 @@ static void ble_glue_update_c2_fw_info() { local_info->FusMemorySizeFlash = wireless_info.FusMemorySizeFlash; } -static void ble_glue_dump_stack_info() { +static void ble_glue_dump_stack_info(void) { const BleGlueC2Info* c2_info = &ble_glue->c2_info; FURI_LOG_I( TAG, @@ -216,59 +199,63 @@ static void ble_glue_dump_stack_info() { c2_info->MemorySizeFlash); } -bool ble_glue_wait_for_c2_start(int32_t timeout) { +bool ble_glue_wait_for_c2_start(int32_t timeout_ms) { bool started = false; + FuriHalCortexTimer timer = furi_hal_cortex_timer_get(timeout_ms * 1000); do { + furi_delay_tick(1); started = ble_glue->status == BleGlueStatusC2Started; - if(!started) { - timeout--; - furi_delay_tick(1); - } - } while(!started && (timeout > 0)); - - if(started) { - FURI_LOG_I( - TAG, - "C2 boot completed, mode: %s", - ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); - ble_glue_update_c2_fw_info(); - ble_glue_dump_stack_info(); - } else { + } while(!started && !furi_hal_cortex_timer_is_expired(timer)); + + if(!started) { FURI_LOG_E(TAG, "C2 startup failed"); ble_glue->status = BleGlueStatusBroken; + return false; } - return started; + FURI_LOG_I( + TAG, + "C2 boot completed, mode: %s", + ble_glue->c2_info.mode == BleGlueC2ModeFUS ? "FUS" : "Stack"); + ble_glue_update_c2_fw_info(); + ble_glue_dump_stack_info(); + return true; } -bool ble_glue_start() { +bool ble_glue_start(void) { furi_assert(ble_glue); if(ble_glue->status != BleGlueStatusC2Started) { return false; } - bool ret = false; - if(ble_app_init()) { - FURI_LOG_I(TAG, "Radio stack started"); - ble_glue->status = BleGlueStatusRadioStackRunning; - ret = true; - if(SHCI_C2_SetFlashActivityControl(FLASH_ACTIVITY_CONTROL_SEM7) == SHCI_Success) { - FURI_LOG_I(TAG, "Flash activity control switched to SEM7"); - } else { - FURI_LOG_E(TAG, "Failed to switch flash activity control to SEM7"); - } - } else { + if(!ble_app_init()) { FURI_LOG_E(TAG, "Radio stack startup failed"); ble_glue->status = BleGlueStatusRadioStackMissing; - ble_app_thread_stop(); + ble_app_deinit(); + return false; } - return ret; + FURI_LOG_I(TAG, "Radio stack started"); + ble_glue->status = BleGlueStatusRadioStackRunning; + return true; } -bool ble_glue_is_alive() { +void ble_glue_stop(void) { + furi_assert(ble_glue); + + ble_event_thread_stop(); + // Free resources + furi_mutex_free(ble_glue->shci_mtx); + furi_timer_free(ble_glue->hardfault_check_timer); + + ble_glue_clear_shared_memory(); + free(ble_glue); + ble_glue = NULL; +} + +bool ble_glue_is_alive(void) { if(!ble_glue) { return false; } @@ -276,7 +263,7 @@ bool ble_glue_is_alive() { return ble_glue->status >= BleGlueStatusC2Started; } -bool ble_glue_is_radio_stack_ready() { +bool ble_glue_is_radio_stack_ready(void) { if(!ble_glue) { return false; } @@ -319,7 +306,7 @@ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode desired_mode) { return BleGlueCommandResultError; } -static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { +static void ble_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { switch(status) { case SHCI_TL_CmdBusy: furi_mutex_acquire(ble_glue->shci_mtx, FuriWaitForever); @@ -341,7 +328,7 @@ static void ble_glue_sys_status_not_callback(SHCI_TL_CmdStatus_t status) { * ( eg ((tSHCI_UserEvtRxParam*)pPayload)->status shall be set to SHCI_TL_UserEventFlow_Disable ) * When the status is not filled, the buffer is released by default */ -static void ble_glue_sys_user_event_callback(void* pPayload) { +static void ble_sys_user_event_callback(void* pPayload) { UNUSED(pPayload); #ifdef BLE_GLUE_DEBUG @@ -375,60 +362,18 @@ static void ble_glue_sys_user_event_callback(void* pPayload) { } } -static void ble_glue_clear_shared_memory() { - memset(ble_glue_event_pool, 0, sizeof(ble_glue_event_pool)); - memset(&ble_glue_system_cmd_buff, 0, sizeof(ble_glue_system_cmd_buff)); - memset(ble_glue_system_spare_event_buff, 0, sizeof(ble_glue_system_spare_event_buff)); - memset(ble_glue_ble_spare_event_buff, 0, sizeof(ble_glue_ble_spare_event_buff)); -} - -void ble_glue_thread_stop() { - if(ble_glue) { - FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_KILL_THREAD); - furi_thread_join(ble_glue->thread); - furi_thread_free(ble_glue->thread); - // Free resources - furi_mutex_free(ble_glue->shci_mtx); - ble_glue_clear_shared_memory(); - free(ble_glue); - ble_glue = NULL; - } -} - -// Wrap functions -static int32_t ble_glue_shci_thread(void* context) { - UNUSED(context); - uint32_t flags = 0; - - while(true) { - flags = furi_thread_flags_wait(BLE_GLUE_FLAG_ALL, FuriFlagWaitAny, FuriWaitForever); - if(flags & BLE_GLUE_FLAG_SHCI_EVENT) { - shci_user_evt_proc(); - } - if(flags & BLE_GLUE_FLAG_KILL_THREAD) { - break; - } - } - - return 0; -} - -void shci_notify_asynch_evt(void* pdata) { - UNUSED(pdata); - if(ble_glue) { - FuriThreadId thread_id = furi_thread_get_id(ble_glue->thread); - furi_assert(thread_id); - furi_thread_flags_set(thread_id, BLE_GLUE_FLAG_SHCI_EVENT); - } +static void ble_glue_clear_shared_memory(void) { + memset(ble_event_pool, 0, sizeof(ble_event_pool)); + memset(&ble_glue_cmd_buff, 0, sizeof(ble_glue_cmd_buff)); + memset(ble_glue_spare_event_buff, 0, sizeof(ble_glue_spare_event_buff)); + memset(ble_spare_event_buff, 0, sizeof(ble_spare_event_buff)); } -bool ble_glue_reinit_c2() { - return SHCI_C2_Reinit() == SHCI_Success; +bool ble_glue_reinit_c2(void) { + return (SHCI_C2_Reinit() == SHCI_Success); } -BleGlueCommandResult ble_glue_fus_stack_delete() { +BleGlueCommandResult ble_glue_fus_stack_delete(void) { FURI_LOG_I(TAG, "Erasing stack"); SHCI_CmdStatus_t erase_stat = SHCI_C2_FUS_FwDelete(); FURI_LOG_I(TAG, "Cmd res = %x", erase_stat); @@ -450,8 +395,9 @@ BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_ return BleGlueCommandResultError; } -BleGlueCommandResult ble_glue_fus_get_status() { +BleGlueCommandResult ble_glue_fus_get_status(void) { furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); + SHCI_FUS_GetState_ErrorCode_t error_code = 0; uint8_t fus_state = SHCI_C2_FUS_GetState(&error_code); FURI_LOG_I(TAG, "FUS state: %x, error: %x", fus_state, error_code); @@ -465,7 +411,7 @@ BleGlueCommandResult ble_glue_fus_get_status() { return BleGlueCommandResultOK; } -BleGlueCommandResult ble_glue_fus_wait_operation() { +BleGlueCommandResult ble_glue_fus_wait_operation(void) { furi_check(ble_glue->c2_info.mode == BleGlueC2ModeFUS); while(true) { @@ -479,3 +425,12 @@ BleGlueCommandResult ble_glue_fus_wait_operation() { } } } + +const BleGlueHardfaultInfo* ble_glue_get_hardfault_info(void) { + /* AN5289, 4.8.2 */ + const BleGlueHardfaultInfo* info = (BleGlueHardfaultInfo*)(SRAM2A_BASE); + if(info->magic != BLE_GLUE_HARDFAULT_INFO_MAGIC) { + return NULL; + } + return info; +} diff --git a/targets/f7/ble_glue/ble_glue.h b/targets/f7/ble_glue/ble_glue.h index bd2588a0263..05c34148cf9 100644 --- a/targets/f7/ble_glue/ble_glue.h +++ b/targets/f7/ble_glue/ble_glue.h @@ -7,13 +7,17 @@ extern "C" { #endif +/* + * Low-level interface to Core2 - startup, shutdown, mode switching, FUS commands. + */ + typedef enum { BleGlueC2ModeUnknown = 0, BleGlueC2ModeFUS, BleGlueC2ModeStack, } BleGlueC2Mode; -#define BLE_GLUE_MAX_VERSION_STRING_LEN 20 +#define BLE_MAX_VERSION_STRING_LEN (20) typedef struct { BleGlueC2Mode mode; /** @@ -29,7 +33,7 @@ typedef struct { uint8_t MemorySizeSram1; /*< Multiple of 1K */ uint8_t MemorySizeFlash; /*< Multiple of 4K */ uint8_t StackType; - char StackTypeString[BLE_GLUE_MAX_VERSION_STRING_LEN]; + char StackTypeString[BLE_MAX_VERSION_STRING_LEN]; /** * Fus Info */ @@ -55,35 +59,37 @@ typedef void ( *BleGlueKeyStorageChangedCallback)(uint8_t* change_addr_start, uint16_t size, void* context); /** Initialize start core2 and initialize transport */ -void ble_glue_init(); +void ble_glue_init(void); /** Start Core2 Radio stack * * @return true on success */ -bool ble_glue_start(); +bool ble_glue_start(void); + +void ble_glue_stop(void); /** Is core2 alive and at least FUS is running * * @return true if core2 is alive */ -bool ble_glue_is_alive(); +bool ble_glue_is_alive(void); /** Waits for C2 to reports its mode to callback * * @return true if it reported before reaching timeout */ -bool ble_glue_wait_for_c2_start(int32_t timeout); +bool ble_glue_wait_for_c2_start(int32_t timeout_ms); -BleGlueStatus ble_glue_get_c2_status(); +BleGlueStatus ble_glue_get_c2_status(void); -const BleGlueC2Info* ble_glue_get_c2_info(); +const BleGlueC2Info* ble_glue_get_c2_info(void); /** Is core2 radio stack present and ready * * @return true if present and ready */ -bool ble_glue_is_radio_stack_ready(); +bool ble_glue_is_radio_stack_ready(void); /** Set callback for NVM in RAM changes * @@ -94,9 +100,6 @@ void ble_glue_set_key_storage_changed_callback( BleGlueKeyStorageChangedCallback callback, void* context); -/** Stop SHCI thread */ -void ble_glue_thread_stop(); - bool ble_glue_reinit_c2(); typedef enum { @@ -113,13 +116,26 @@ typedef enum { */ BleGlueCommandResult ble_glue_force_c2_mode(BleGlueC2Mode mode); -BleGlueCommandResult ble_glue_fus_stack_delete(); +BleGlueCommandResult ble_glue_fus_stack_delete(void); BleGlueCommandResult ble_glue_fus_stack_install(uint32_t src_addr, uint32_t dst_addr); -BleGlueCommandResult ble_glue_fus_get_status(); +BleGlueCommandResult ble_glue_fus_get_status(void); + +BleGlueCommandResult ble_glue_fus_wait_operation(void); -BleGlueCommandResult ble_glue_fus_wait_operation(); +typedef struct { + uint32_t magic; + uint32_t source_pc; + uint32_t source_lr; + uint32_t source_sp; +} BleGlueHardfaultInfo; + +/** Get hardfault info + * + * @return hardfault info. NULL if no hardfault + */ +const BleGlueHardfaultInfo* ble_glue_get_hardfault_info(void); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/ble_tl_hooks.c b/targets/f7/ble_glue/ble_tl_hooks.c new file mode 100644 index 00000000000..092afd7424a --- /dev/null +++ b/targets/f7/ble_glue/ble_tl_hooks.c @@ -0,0 +1,40 @@ +#include "ble_glue.h" + +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// + +/* + * TL hooks to catch hardfaults + */ + +int32_t ble_glue_TL_SYS_SendCmd(uint8_t* buffer, uint16_t size) { + if(ble_glue_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_SYS_SendCmd(buffer, size); +} + +void shci_register_io_bus(tSHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_SYS_Init; + fops->Send = ble_glue_TL_SYS_SendCmd; +} + +static int32_t ble_glue_TL_BLE_SendCmd(uint8_t* buffer, uint16_t size) { + if(ble_glue_get_hardfault_info()) { + furi_crash("ST(R) Copro(R) HardFault"); + } + + return TL_BLE_SendCmd(buffer, size); +} + +void hci_register_io_bus(tHciIO* fops) { + /* Register IO bus services */ + fops->Init = TL_BLE_Init; + fops->Send = ble_glue_TL_BLE_SendCmd; +} diff --git a/targets/f7/ble_glue/extra_beacon.c b/targets/f7/ble_glue/extra_beacon.c new file mode 100644 index 00000000000..446b31380fb --- /dev/null +++ b/targets/f7/ble_glue/extra_beacon.c @@ -0,0 +1,161 @@ +#include "extra_beacon.h" +#include "gap.h" + +#include +#include + +#define TAG "BleExtraBeacon" + +#define GAP_MS_TO_SCAN_INTERVAL(x) ((uint16_t)((x) / 0.625)) + +// Also used as an indicator of whether the beacon had ever been configured +#define GAP_MIN_ADV_INTERVAL_MS (20) + +typedef struct { + GapExtraBeaconConfig last_config; + GapExtraBeaconState extra_beacon_state; + uint8_t extra_beacon_data[EXTRA_BEACON_MAX_DATA_SIZE]; + uint8_t extra_beacon_data_len; + FuriMutex* state_mutex; +} ExtraBeacon; + +static ExtraBeacon extra_beacon = {0}; + +void gap_extra_beacon_init() { + if(extra_beacon.state_mutex) { + // Already initialized - restore state if needed + FURI_LOG_I(TAG, "Restoring state"); + gap_extra_beacon_set_data( + extra_beacon.extra_beacon_data, extra_beacon.extra_beacon_data_len); + if(extra_beacon.extra_beacon_state == GapExtraBeaconStateStarted) { + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + gap_extra_beacon_set_config(&extra_beacon.last_config); + } + + } else { + // First time init + FURI_LOG_I(TAG, "Init"); + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + extra_beacon.extra_beacon_data_len = 0; + memset(extra_beacon.extra_beacon_data, 0, EXTRA_BEACON_MAX_DATA_SIZE); + extra_beacon.state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); + } +} + +bool gap_extra_beacon_set_config(const GapExtraBeaconConfig* config) { + furi_check(extra_beacon.state_mutex); + furi_check(config); + + furi_check(config->min_adv_interval_ms <= config->max_adv_interval_ms); + furi_check(config->min_adv_interval_ms >= GAP_MIN_ADV_INTERVAL_MS); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStopped) { + return false; + } + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + if(config != &extra_beacon.last_config) { + memcpy(&extra_beacon.last_config, config, sizeof(GapExtraBeaconConfig)); + } + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_start() { + furi_check(extra_beacon.state_mutex); + furi_check(extra_beacon.last_config.min_adv_interval_ms >= GAP_MIN_ADV_INTERVAL_MS); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStopped) { + return false; + } + + FURI_LOG_I(TAG, "Starting"); + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + const GapExtraBeaconConfig* config = &extra_beacon.last_config; + tBleStatus status = aci_gap_additional_beacon_start( + GAP_MS_TO_SCAN_INTERVAL(config->min_adv_interval_ms), + GAP_MS_TO_SCAN_INTERVAL(config->max_adv_interval_ms), + (uint8_t)config->adv_channel_map, + config->address_type, + config->address, + (uint8_t)config->adv_power_level); + if(status) { + FURI_LOG_E(TAG, "Failed to start: 0x%x", status); + return false; + } + extra_beacon.extra_beacon_state = GapExtraBeaconStateStarted; + gap_emit_ble_beacon_status_event(true); + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_stop() { + furi_check(extra_beacon.state_mutex); + + if(extra_beacon.extra_beacon_state != GapExtraBeaconStateStarted) { + return false; + } + + FURI_LOG_I(TAG, "Stopping"); + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + tBleStatus status = aci_gap_additional_beacon_stop(); + if(status) { + FURI_LOG_E(TAG, "Failed to stop: 0x%x", status); + return false; + } + extra_beacon.extra_beacon_state = GapExtraBeaconStateStopped; + gap_emit_ble_beacon_status_event(false); + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +bool gap_extra_beacon_set_data(const uint8_t* data, uint8_t length) { + furi_check(extra_beacon.state_mutex); + furi_check(data); + furi_check(length <= EXTRA_BEACON_MAX_DATA_SIZE); + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + if(data != extra_beacon.extra_beacon_data) { + memcpy(extra_beacon.extra_beacon_data, data, length); + } + extra_beacon.extra_beacon_data_len = length; + + tBleStatus status = aci_gap_additional_beacon_set_data(length, data); + if(status) { + FURI_LOG_E(TAG, "Failed updating adv data: %d", status); + return false; + } + furi_mutex_release(extra_beacon.state_mutex); + + return true; +} + +uint8_t gap_extra_beacon_get_data(uint8_t* data) { + furi_check(extra_beacon.state_mutex); + furi_check(data); + + furi_mutex_acquire(extra_beacon.state_mutex, FuriWaitForever); + memcpy(data, extra_beacon.extra_beacon_data, extra_beacon.extra_beacon_data_len); + furi_mutex_release(extra_beacon.state_mutex); + + return extra_beacon.extra_beacon_data_len; +} + +GapExtraBeaconState gap_extra_beacon_get_state() { + furi_check(extra_beacon.state_mutex); + + return extra_beacon.extra_beacon_state; +} + +const GapExtraBeaconConfig* gap_extra_beacon_get_config() { + furi_check(extra_beacon.state_mutex); + + if(extra_beacon.last_config.min_adv_interval_ms < GAP_MIN_ADV_INTERVAL_MS) { + return NULL; + } + + return &extra_beacon.last_config; +} \ No newline at end of file diff --git a/targets/f7/ble_glue/extra_beacon.h b/targets/f7/ble_glue/extra_beacon.h new file mode 100644 index 00000000000..675ea538c93 --- /dev/null +++ b/targets/f7/ble_glue/extra_beacon.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Additinal non-connetable beacon API. + * Not to be used directly, but through furi_hal_bt_extra_beacon_* APIs. + */ + +#define EXTRA_BEACON_MAX_DATA_SIZE (31) +#define EXTRA_BEACON_MAC_ADDR_SIZE (6) + +typedef enum { + GapAdvChannelMap37 = 0b001, + GapAdvChannelMap38 = 0b010, + GapAdvChannelMap39 = 0b100, + GapAdvChannelMapAll = 0b111, +} GapAdvChannelMap; + +typedef enum { + GapAdvPowerLevel_Neg40dBm = 0x00, + GapAdvPowerLevel_Neg20_85dBm = 0x01, + GapAdvPowerLevel_Neg19_75dBm = 0x02, + GapAdvPowerLevel_Neg18_85dBm = 0x03, + GapAdvPowerLevel_Neg17_6dBm = 0x04, + GapAdvPowerLevel_Neg16_5dBm = 0x05, + GapAdvPowerLevel_Neg15_25dBm = 0x06, + GapAdvPowerLevel_Neg14_1dBm = 0x07, + GapAdvPowerLevel_Neg13_15dBm = 0x08, + GapAdvPowerLevel_Neg12_05dBm = 0x09, + GapAdvPowerLevel_Neg10_9dBm = 0x0A, + GapAdvPowerLevel_Neg9_9dBm = 0x0B, + GapAdvPowerLevel_Neg8_85dBm = 0x0C, + GapAdvPowerLevel_Neg7_8dBm = 0x0D, + GapAdvPowerLevel_Neg6_9dBm = 0x0E, + GapAdvPowerLevel_Neg5_9dBm = 0x0F, + GapAdvPowerLevel_Neg4_95dBm = 0x10, + GapAdvPowerLevel_Neg4dBm = 0x11, + GapAdvPowerLevel_Neg3_15dBm = 0x12, + GapAdvPowerLevel_Neg2_45dBm = 0x13, + GapAdvPowerLevel_Neg1_8dBm = 0x14, + GapAdvPowerLevel_Neg1_3dBm = 0x15, + GapAdvPowerLevel_Neg0_85dBm = 0x16, + GapAdvPowerLevel_Neg0_5dBm = 0x17, + GapAdvPowerLevel_Neg0_15dBm = 0x18, + GapAdvPowerLevel_0dBm = 0x19, + GapAdvPowerLevel_1dBm = 0x1A, + GapAdvPowerLevel_2dBm = 0x1B, + GapAdvPowerLevel_3dBm = 0x1C, + GapAdvPowerLevel_4dBm = 0x1D, + GapAdvPowerLevel_5dBm = 0x1E, + GapAdvPowerLevel_6dBm = 0x1F, +} GapAdvPowerLevelInd; + +typedef enum { + GapAddressTypePublic = 0, + GapAddressTypeRandom = 1, +} GapAddressType; + +typedef struct { + uint16_t min_adv_interval_ms, max_adv_interval_ms; + GapAdvChannelMap adv_channel_map; + GapAdvPowerLevelInd adv_power_level; + GapAddressType address_type; + uint8_t address[EXTRA_BEACON_MAC_ADDR_SIZE]; +} GapExtraBeaconConfig; + +typedef enum { + GapExtraBeaconStateUndefined = 0, + GapExtraBeaconStateStopped, + GapExtraBeaconStateStarted, +} GapExtraBeaconState; + +void gap_extra_beacon_init(); + +GapExtraBeaconState gap_extra_beacon_get_state(); + +bool gap_extra_beacon_start(); + +bool gap_extra_beacon_stop(); + +bool gap_extra_beacon_set_config(const GapExtraBeaconConfig* config); + +const GapExtraBeaconConfig* gap_extra_beacon_get_config(); + +bool gap_extra_beacon_set_data(const uint8_t* data, uint8_t length); + +// Fill "data" with last configured extra beacon data and return its length +uint8_t gap_extra_beacon_get_data(uint8_t* data); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/furi_ble/event_dispatcher.c b/targets/f7/ble_glue/furi_ble/event_dispatcher.c new file mode 100644 index 00000000000..ce3661d6d9a --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/event_dispatcher.c @@ -0,0 +1,97 @@ +#include "event_dispatcher.h" +#include +#include +#include + +#include + +struct GapEventHandler { + void* context; + BleSvcEventHandlerCb callback; +}; + +LIST_DEF(GapSvcEventHandlerList, GapSvcEventHandler, M_POD_OPLIST); + +static GapSvcEventHandlerList_t handlers; +static bool initialized = false; + +BleEventFlowStatus ble_event_dispatcher_process_event(void* payload) { + furi_check(initialized); + + GapSvcEventHandlerList_it_t it; + BleEventAckStatus ack_status = BleEventNotAck; + + for(GapSvcEventHandlerList_it(it, handlers); !GapSvcEventHandlerList_end_p(it); + GapSvcEventHandlerList_next(it)) { + const GapSvcEventHandler* item = GapSvcEventHandlerList_cref(it); + ack_status = item->callback(payload, item->context); + if(ack_status == BleEventNotAck) { + /* Keep going */ + continue; + } else if((ack_status == BleEventAckFlowEnable) || (ack_status == BleEventAckFlowDisable)) { + break; + } + } + + /* Handlers for client-mode events are also to be implemented here. But not today. */ + + /* Now, decide on a flow control action based on results of all handlers */ + switch(ack_status) { + case BleEventNotAck: + /* The event has NOT been managed yet. Pass to app for processing */ + return ble_event_app_notification(payload); + case BleEventAckFlowEnable: + return BleEventFlowEnable; + case BleEventAckFlowDisable: + return BleEventFlowDisable; + default: + return BleEventFlowEnable; + } +} + +void ble_event_dispatcher_init(void) { + furi_assert(!initialized); + + GapSvcEventHandlerList_init(handlers); + initialized = true; +} + +void ble_event_dispatcher_reset(void) { + furi_assert(initialized); + furi_check(GapSvcEventHandlerList_size(handlers) == 0); + + GapSvcEventHandlerList_clear(handlers); +} + +GapSvcEventHandler* + ble_event_dispatcher_register_svc_handler(BleSvcEventHandlerCb handler, void* context) { + furi_check(handler); + furi_check(context); + furi_check(initialized); + + GapSvcEventHandler* item = GapSvcEventHandlerList_push_raw(handlers); + item->context = context; + item->callback = handler; + + return item; +} + +void ble_event_dispatcher_unregister_svc_handler(GapSvcEventHandler* handler) { + furi_check(handler); + + bool found = false; + GapSvcEventHandlerList_it_t it; + + for(GapSvcEventHandlerList_it(it, handlers); !GapSvcEventHandlerList_end_p(it); + GapSvcEventHandlerList_next(it)) { + const GapSvcEventHandler* item = GapSvcEventHandlerList_cref(it); + + if(item == handler) { + GapSvcEventHandlerList_remove(handlers, it); + found = true; + break; + } + } + + furi_check(found); +} diff --git a/targets/f7/ble_glue/furi_ble/event_dispatcher.h b/targets/f7/ble_glue/furi_ble/event_dispatcher.h new file mode 100644 index 00000000000..90fc0762f83 --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/event_dispatcher.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + BleEventNotAck, + BleEventAckFlowEnable, + BleEventAckFlowDisable, +} BleEventAckStatus; + +typedef enum { + BleEventFlowDisable, + BleEventFlowEnable, +} BleEventFlowStatus; + +/* Using other types so not to leak all the BLE stack headers + (we don't have a wrapper for them yet) + * Event data is hci_uart_pckt* + * Context is user-defined + */ +typedef BleEventAckStatus (*BleSvcEventHandlerCb)(void* event, void* context); + +typedef struct GapEventHandler GapSvcEventHandler; + +/* To be called once at BLE system startup */ +void ble_event_dispatcher_init(void); + +/* To be called at stack reset - ensures that all handlers are unregistered */ +void ble_event_dispatcher_reset(void); + +BleEventFlowStatus ble_event_dispatcher_process_event(void* payload); + +/* Final handler for event not ack'd by services - to be implemented by app */ +BleEventFlowStatus ble_event_app_notification(void* pckt); + +/* Add a handler to the list of handlers */ +FURI_WARN_UNUSED GapSvcEventHandler* + ble_event_dispatcher_register_svc_handler(BleSvcEventHandlerCb handler, void* context); + +/* Remove a handler from the list of handlers */ +void ble_event_dispatcher_unregister_svc_handler(GapSvcEventHandler* handler); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/gatt_char.c b/targets/f7/ble_glue/furi_ble/gatt.c similarity index 73% rename from targets/f7/ble_glue/services/gatt_char.c rename to targets/f7/ble_glue/furi_ble/gatt.c index f6e27f53e68..dcea5f98730 100644 --- a/targets/f7/ble_glue/services/gatt_char.c +++ b/targets/f7/ble_glue/furi_ble/gatt.c @@ -1,4 +1,5 @@ -#include "gatt_char.h" +#include "gatt.h" +#include #include @@ -6,19 +7,19 @@ #define GATT_MIN_READ_KEY_SIZE (10) -void flipper_gatt_characteristic_init( +void ble_gatt_characteristic_init( uint16_t svc_handle, - const FlipperGattCharacteristicParams* char_descriptor, - FlipperGattCharacteristicInstance* char_instance) { + const BleGattCharacteristicParams* char_descriptor, + BleGattCharacteristicInstance* char_instance) { furi_assert(char_descriptor); furi_assert(char_instance); // Copy the descriptor to the instance, since it may point to stack memory - char_instance->characteristic = malloc(sizeof(FlipperGattCharacteristicParams)); + char_instance->characteristic = malloc(sizeof(BleGattCharacteristicParams)); memcpy( (void*)char_instance->characteristic, char_descriptor, - sizeof(FlipperGattCharacteristicParams)); + sizeof(BleGattCharacteristicParams)); uint16_t char_data_size = 0; if(char_descriptor->data_prop_type == FlipperGattCharacteristicDataFixed) { @@ -46,7 +47,7 @@ void flipper_gatt_characteristic_init( char_instance->descriptor_handle = 0; if((status == 0) && char_descriptor->descriptor_params) { uint8_t const* char_data = NULL; - const FlipperGattCharacteristicDescriptorParams* char_data_descriptor = + const BleGattCharacteristicDescriptorParams* char_data_descriptor = char_descriptor->descriptor_params; bool release_data = char_data_descriptor->data_callback.fn( char_data_descriptor->data_callback.context, &char_data, &char_data_size); @@ -74,9 +75,9 @@ void flipper_gatt_characteristic_init( } } -void flipper_gatt_characteristic_delete( +void ble_gatt_characteristic_delete( uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance) { + BleGattCharacteristicInstance* char_instance) { tBleStatus status = aci_gatt_del_char(svc_handle, char_instance->handle); if(status) { FURI_LOG_E( @@ -85,12 +86,12 @@ void flipper_gatt_characteristic_delete( free((void*)char_instance->characteristic); } -bool flipper_gatt_characteristic_update( +bool ble_gatt_characteristic_update( uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance, + BleGattCharacteristicInstance* char_instance, const void* source) { furi_assert(char_instance); - const FlipperGattCharacteristicParams* char_descriptor = char_instance->characteristic; + const BleGattCharacteristicParams* char_descriptor = char_instance->characteristic; FURI_LOG_D(TAG, "Updating %s char", char_descriptor->name); const uint8_t* char_data = NULL; @@ -119,4 +120,28 @@ bool flipper_gatt_characteristic_update( free((void*)char_data); } return result != BLE_STATUS_SUCCESS; -} \ No newline at end of file +} + +bool ble_gatt_service_add( + uint8_t Service_UUID_Type, + const Service_UUID_t* Service_UUID, + uint8_t Service_Type, + uint8_t Max_Attribute_Records, + uint16_t* Service_Handle) { + tBleStatus result = aci_gatt_add_service( + Service_UUID_Type, Service_UUID, Service_Type, Max_Attribute_Records, Service_Handle); + if(result) { + FURI_LOG_E(TAG, "Failed to add service: %x", result); + } + + return result == BLE_STATUS_SUCCESS; +} + +bool ble_gatt_service_delete(uint16_t svc_handle) { + tBleStatus result = aci_gatt_del_service(svc_handle); + if(result) { + FURI_LOG_E(TAG, "Failed to delete service: %x", result); + } + + return result == BLE_STATUS_SUCCESS; +} diff --git a/targets/f7/ble_glue/furi_ble/gatt.h b/targets/f7/ble_glue/furi_ble/gatt.h new file mode 100644 index 00000000000..5a33e9e542e --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/gatt.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Callback signature for getting characteristic data + * Is called when characteristic is created to get max data length. Data ptr is NULL in this case + * The result is passed to aci_gatt_add_char as "Char_Value_Length" + * For updates, called with a context - see flipper_gatt_characteristic_update + * Returns true if *data ownership is transferred to the caller and will be freed */ +typedef bool ( + *cbBleGattCharacteristicData)(const void* context, const uint8_t** data, uint16_t* data_len); + +/* Used to specify the type of data for a characteristic - constant or callback-based */ +typedef enum { + FlipperGattCharacteristicDataFixed, + FlipperGattCharacteristicDataCallback, +} BleGattCharacteristicDataType; + +typedef struct { + Char_Desc_Uuid_t uuid; + struct { + cbBleGattCharacteristicData fn; + const void* context; + } data_callback; + uint8_t uuid_type; + uint8_t max_length; + uint8_t security_permissions; + uint8_t access_permissions; + uint8_t gatt_evt_mask; + uint8_t is_variable; +} BleGattCharacteristicDescriptorParams; + +/* Describes a single characteristic, providing data or callbacks to get data */ +typedef struct { + const char* name; + BleGattCharacteristicDescriptorParams* descriptor_params; + union { + struct { + const uint8_t* ptr; + uint16_t length; + } fixed; + struct { + cbBleGattCharacteristicData fn; + const void* context; + } callback; + } data; + Char_UUID_t uuid; + // Some packed bitfields to save space + BleGattCharacteristicDataType data_prop_type : 2; + uint8_t is_variable : 2; + uint8_t uuid_type : 2; + uint8_t char_properties; + uint8_t security_permissions; + uint8_t gatt_evt_mask; +} BleGattCharacteristicParams; + +_Static_assert( + sizeof(BleGattCharacteristicParams) == 36, + "BleGattCharacteristicParams size must be 36 bytes"); + +typedef struct { + const BleGattCharacteristicParams* characteristic; + uint16_t handle; + uint16_t descriptor_handle; +} BleGattCharacteristicInstance; + +/* Initialize a characteristic instance; copies the characteristic descriptor + * into the instance */ +void ble_gatt_characteristic_init( + uint16_t svc_handle, + const BleGattCharacteristicParams* char_descriptor, + BleGattCharacteristicInstance* char_instance); + +/* Delete a characteristic instance; frees the copied characteristic + * descriptor from the instance */ +void ble_gatt_characteristic_delete( + uint16_t svc_handle, + BleGattCharacteristicInstance* char_instance); + +/* Update a characteristic instance; if source==NULL, uses the data from + * the characteristic: + * - For fixed data, fixed.ptr is used as the source if source==NULL + * - For callback-based data, collback.context is passed as the context + * if source==NULL + */ +bool ble_gatt_characteristic_update( + uint16_t svc_handle, + BleGattCharacteristicInstance* char_instance, + const void* source); + +bool ble_gatt_service_add( + uint8_t Service_UUID_Type, + const Service_UUID_t* Service_UUID, + uint8_t Service_Type, + uint8_t Max_Attribute_Records, + uint16_t* Service_Handle); + +bool ble_gatt_service_delete(uint16_t svc_handle); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/furi_ble/profile_interface.h b/targets/f7/ble_glue/furi_ble/profile_interface.h new file mode 100644 index 00000000000..f1b42837bd6 --- /dev/null +++ b/targets/f7/ble_glue/furi_ble/profile_interface.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct FuriHalBleProfileTemplate FuriHalBleProfileTemplate; + +/* Actual profiles must inherit (include this structure) as their first field */ +typedef struct { + /* Pointer to the config for this profile. Must be used to check if the + * instance belongs to the profile */ + const FuriHalBleProfileTemplate* config; +} FuriHalBleProfileBase; + +typedef void* FuriHalBleProfileParams; + +typedef FuriHalBleProfileBase* (*FuriHalBleProfileStart)(FuriHalBleProfileParams profile_params); +typedef void (*FuriHalBleProfileStop)(FuriHalBleProfileBase* profile); +typedef void (*FuriHalBleProfileGetGapConfig)( + GapConfig* target_config, + FuriHalBleProfileParams profile_params); + +struct FuriHalBleProfileTemplate { + /* Returns an instance of the profile */ + FuriHalBleProfileStart start; + /* Destroys the instance of the profile. Must check if instance belongs to the profile */ + FuriHalBleProfileStop stop; + /* Called before starting the profile to get the GAP configuration */ + FuriHalBleProfileGetGapConfig get_gap_config; +}; + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/gap.c b/targets/f7/ble_glue/gap.c index 8e3ec58b737..86623b02fb2 100644 --- a/targets/f7/ble_glue/gap.c +++ b/targets/f7/ble_glue/gap.c @@ -1,12 +1,15 @@ #include "gap.h" #include "app_common.h" +#include +#include "furi_ble/event_dispatcher.h" #include #include #include +#include -#define TAG "BtGap" +#define TAG "BleGap" #define FAST_ADV_TIMEOUT 30000 #define INITIAL_ADV_TIMEOUT 60000 @@ -83,7 +86,7 @@ static void gap_verify_connection_parameters(Gap* gap) { } } -SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { +BleEventFlowStatus ble_event_app_notification(void* pckt) { hci_event_pckt* event_pckt; evt_le_meta_event* meta_evt; evt_blecore_aci* blue_evt; @@ -269,7 +272,7 @@ SVCCTL_UserEvtFlowStatus_t SVCCTL_App_Notification(void* pckt) { if(gap) { furi_mutex_release(gap->state_mutex); } - return SVCCTL_UserEvtFlowEnable; + return BleEventFlowEnable; } static void set_advertisment_service_uid(uint8_t* uid, uint8_t uid_len) { @@ -372,6 +375,8 @@ static void gap_advertise_start(GapState new_state) { uint16_t min_interval; uint16_t max_interval; + FURI_LOG_I(TAG, "Start: %d", new_state); + if(new_state == GapStateAdvFast) { min_interval = 0x80; // 80 ms max_interval = 0xa0; // 100 ms @@ -414,7 +419,8 @@ static void gap_advertise_start(GapState new_state) { furi_timer_start(gap->advertise_timer, INITIAL_ADV_TIMEOUT); } -static void gap_advertise_stop() { +static void gap_advertise_stop(void) { + FURI_LOG_I(TAG, "Stop"); tBleStatus ret; if(gap->state > GapStateIdle) { if(gap->state == GapStateConnected) { @@ -440,7 +446,7 @@ static void gap_advertise_stop() { gap->on_event_cb(event, gap->context); } -void gap_start_advertising() { +void gap_start_advertising(void) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); if(gap->state == GapStateIdle) { gap->state = GapStateStartingAdv; @@ -452,7 +458,7 @@ void gap_start_advertising() { furi_mutex_release(gap->state_mutex); } -void gap_stop_advertising() { +void gap_stop_advertising(void) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); if(gap->state > GapStateIdle) { FURI_LOG_I(TAG, "Stop advertising"); @@ -481,8 +487,7 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { // Initialization of GATT & GAP layer gap->service.adv_name = config->adv_name; gap_init_svc(gap); - // Initialization of the BLE Services - SVCCTL_Init(); + ble_event_dispatcher_init(); // Initialization of the GAP state gap->state_mutex = furi_mutex_alloc(FuriMutexTypeNormal); gap->state = GapStateIdle; @@ -505,10 +510,11 @@ bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context) { // Set callback gap->on_event_cb = on_event_cb; gap->context = context; + return true; } -GapState gap_get_state() { +GapState gap_get_state(void) { GapState state; if(gap) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); @@ -520,7 +526,7 @@ GapState gap_get_state() { return state; } -void gap_thread_stop() { +void gap_thread_stop(void) { if(gap) { furi_mutex_acquire(gap->state_mutex, FuriWaitForever); gap->enable_adv = false; @@ -533,6 +539,8 @@ void gap_thread_stop() { furi_mutex_free(gap->state_mutex); furi_message_queue_free(gap->command_queue); furi_timer_free(gap->advertise_timer); + + ble_event_dispatcher_reset(); free(gap); gap = NULL; } @@ -563,3 +571,9 @@ static int32_t gap_app(void* context) { return 0; } + +void gap_emit_ble_beacon_status_event(bool active) { + GapEvent event = {.type = active ? GapEventTypeBeaconStart : GapEventTypeBeaconStop}; + gap->on_event_cb(event, gap->context); + FURI_LOG_I(TAG, "Beacon status event: %d", active); +} diff --git a/targets/f7/ble_glue/gap.h b/targets/f7/ble_glue/gap.h index 1e207299f23..a90d0730471 100644 --- a/targets/f7/ble_glue/gap.h +++ b/targets/f7/ble_glue/gap.h @@ -7,6 +7,10 @@ #define GAP_MAC_ADDR_SIZE (6) +/* + * GAP helpers - background thread that handles BLE GAP events and advertising. + */ + #ifdef __cplusplus extern "C" { #endif @@ -19,6 +23,8 @@ typedef enum { GapEventTypePinCodeShow, GapEventTypePinCodeVerify, GapEventTypeUpdateMTU, + GapEventTypeBeaconStart, + GapEventTypeBeaconStop, } GapEventType; typedef union { @@ -73,13 +79,15 @@ typedef struct { bool gap_init(GapConfig* config, GapEventCallback on_event_cb, void* context); -void gap_start_advertising(); +void gap_start_advertising(void); + +void gap_stop_advertising(void); -void gap_stop_advertising(); +GapState gap_get_state(void); -GapState gap_get_state(); +void gap_thread_stop(void); -void gap_thread_stop(); +void gap_emit_ble_beacon_status_event(bool active); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/profiles/serial_profile.c b/targets/f7/ble_glue/profiles/serial_profile.c new file mode 100644 index 00000000000..a3949abfc88 --- /dev/null +++ b/targets/f7/ble_glue/profiles/serial_profile.c @@ -0,0 +1,114 @@ +#include "serial_profile.h" + +#include +#include +#include +#include +#include +#include + +typedef struct { + FuriHalBleProfileBase base; + + BleServiceDevInfo* dev_info_svc; + BleServiceBattery* battery_svc; + BleServiceSerial* serial_svc; +} BleProfileSerial; +_Static_assert(offsetof(BleProfileSerial, base) == 0, "Wrong layout"); + +static FuriHalBleProfileBase* ble_profile_serial_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + BleProfileSerial* profile = malloc(sizeof(BleProfileSerial)); + + profile->base.config = ble_profile_serial; + + profile->dev_info_svc = ble_svc_dev_info_start(); + profile->battery_svc = ble_svc_battery_start(true); + profile->serial_svc = ble_svc_serial_start(); + + return &profile->base; +} + +static void ble_profile_serial_stop(FuriHalBleProfileBase* profile) { + furi_check(profile); + furi_check(profile->config == ble_profile_serial); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_battery_stop(serial_profile->battery_svc); + ble_svc_dev_info_stop(serial_profile->dev_info_svc); + ble_svc_serial_stop(serial_profile->serial_svc); +} + +static GapConfig serial_template_config = { + .adv_service_uuid = 0x3080, + .appearance_char = 0x8600, + .bonding_mode = true, + .pairing_method = GapPairingPinCodeShow, + .conn_param = { + .conn_int_min = 0x18, // 30 ms + .conn_int_max = 0x24, // 45 ms + .slave_latency = 0, + .supervisor_timeout = 0, + }}; + +static void + ble_profile_serial_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + furi_check(config); + memcpy(config, &serial_template_config, sizeof(GapConfig)); + // Set mac address + memcpy(config->mac_address, furi_hal_version_get_ble_mac(), sizeof(config->mac_address)); + // Set advertise name + strlcpy( + config->adv_name, + furi_hal_version_get_ble_local_device_name_ptr(), + FURI_HAL_VERSION_DEVICE_NAME_LENGTH); + config->adv_service_uuid |= furi_hal_version_get_hw_color(); +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_serial_start, + .stop = ble_profile_serial_stop, + .get_gap_config = ble_profile_serial_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_serial = &profile_callbacks; + +void ble_profile_serial_set_event_callback( + FuriHalBleProfileBase* profile, + uint16_t buff_size, + FuriHalBtSerialCallback callback, + void* context) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_set_callbacks(serial_profile->serial_svc, buff_size, callback, context); +} + +void ble_profile_serial_notify_buffer_is_empty(FuriHalBleProfileBase* profile) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_notify_buffer_is_empty(serial_profile->serial_svc); +} + +void ble_profile_serial_set_rpc_active(FuriHalBleProfileBase* profile, bool active) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + ble_svc_serial_set_rpc_active(serial_profile->serial_svc, active); +} + +bool ble_profile_serial_tx(FuriHalBleProfileBase* profile, uint8_t* data, uint16_t size) { + furi_check(profile && (profile->config == ble_profile_serial)); + + BleProfileSerial* serial_profile = (BleProfileSerial*)profile; + + if(size > BLE_PROFILE_SERIAL_PACKET_SIZE_MAX) { + return false; + } + + return ble_svc_serial_update_tx(serial_profile->serial_svc, data, size); +} diff --git a/targets/f7/ble_glue/profiles/serial_profile.h b/targets/f7/ble_glue/profiles/serial_profile.h new file mode 100644 index 00000000000..e07eaef0311 --- /dev/null +++ b/targets/f7/ble_glue/profiles/serial_profile.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define BLE_PROFILE_SERIAL_PACKET_SIZE_MAX BLE_SVC_SERIAL_DATA_LEN_MAX + +typedef enum { + FuriHalBtSerialRpcStatusNotActive, + FuriHalBtSerialRpcStatusActive, +} FuriHalBtSerialRpcStatus; + +/** Serial service callback type */ +typedef SerialServiceEventCallback FuriHalBtSerialCallback; + +/** Serial profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_serial; + +/** Send data through BLE + * + * @param profile Profile instance + * @param data data buffer + * @param size data buffer size + * + * @return true on success + */ +bool ble_profile_serial_tx(FuriHalBleProfileBase* profile, uint8_t* data, uint16_t size); + +/** Set BLE RPC status + * + * @param profile Profile instance + * @param active true if RPC is active + */ +void ble_profile_serial_set_rpc_active(FuriHalBleProfileBase* profile, bool active); + +/** Notify that application buffer is empty + * @param profile Profile instance + */ +void ble_profile_serial_notify_buffer_is_empty(FuriHalBleProfileBase* profile); + +/** Set Serial service events callback + * + * @param profile Profile instance + * @param buffer_size Applicaition buffer size + * @param calback FuriHalBtSerialCallback instance + * @param context pointer to context + */ +void ble_profile_serial_set_event_callback( + FuriHalBleProfileBase* profile, + uint16_t buff_size, + FuriHalBtSerialCallback callback, + void* context); + +#ifdef __cplusplus +} +#endif diff --git a/targets/f7/ble_glue/services/battery_service.c b/targets/f7/ble_glue/services/battery_service.c index 63f736b3b7a..f4bc1ff7bba 100644 --- a/targets/f7/ble_glue/services/battery_service.c +++ b/targets/f7/ble_glue/services/battery_service.c @@ -1,28 +1,30 @@ #include "battery_service.h" #include "app_common.h" -#include "gatt_char.h" +#include +#include #include #include -#include + +#include #define TAG "BtBatterySvc" enum { - // Common states + /* Common states */ BatterySvcPowerStateUnknown = 0b00, BatterySvcPowerStateUnsupported = 0b01, - // Level states + /* Level states */ BatterySvcPowerStateGoodLevel = 0b10, BatterySvcPowerStateCriticallyLowLevel = 0b11, - // Charging states + /* Charging states */ BatterySvcPowerStateNotCharging = 0b10, BatterySvcPowerStateCharging = 0b11, - // Discharging states + /* Discharging states */ BatterySvcPowerStateNotDischarging = 0b10, BatterySvcPowerStateDischarging = 0b11, - // Battery states + /* Battery states */ BatterySvcPowerStateBatteryNotPresent = 0b10, BatterySvcPowerStateBatteryPresent = 0b11, }; @@ -46,96 +48,110 @@ typedef enum { BatterySvcGattCharacteristicCount, } BatterySvcGattCharacteristicId; -static const FlipperGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] = - {[BatterySvcGattCharacteristicBatteryLevel] = - {.name = "Battery Level", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = 1, - .uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, - .security_permissions = ATTR_PERMISSION_AUTHEN_READ, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [BatterySvcGattCharacteristicPowerState] = { - .name = "Power State", +static const BleGattCharacteristicParams battery_svc_chars[BatterySvcGattCharacteristicCount] = { + [BatterySvcGattCharacteristicBatteryLevel] = + {.name = "Battery Level", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = 1, - .uuid.Char_UUID_16 = BATTERY_POWER_STATE, + .uuid.Char_UUID_16 = BATTERY_LEVEL_CHAR_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}}; - -typedef struct { + .is_variable = CHAR_VALUE_LEN_CONSTANT}, + [BatterySvcGattCharacteristicPowerState] = { + .name = "Power State", + .data_prop_type = FlipperGattCharacteristicDataFixed, + .data.fixed.length = 1, + .uuid.Char_UUID_16 = BATTERY_POWER_STATE, + .uuid_type = UUID_TYPE_16, + .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, + .security_permissions = ATTR_PERMISSION_AUTHEN_READ, + .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, + .is_variable = CHAR_VALUE_LEN_CONSTANT}}; + +struct BleServiceBattery { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount]; -} BatterySvc; + BleGattCharacteristicInstance chars[BatterySvcGattCharacteristicCount]; + bool auto_update; +}; -static BatterySvc* battery_svc = NULL; +LIST_DEF(BatterySvcInstanceList, BleServiceBattery*, M_POD_OPLIST); -void battery_svc_start() { - battery_svc = malloc(sizeof(BatterySvc)); - tBleStatus status; +/* We need to keep track of all battery service instances so that we can update + * them when the battery state changes. */ +static BatterySvcInstanceList_t instances; +static bool instances_initialized = false; - // Add Battery service - status = aci_gatt_add_service( - UUID_TYPE_16, (Service_UUID_t*)&service_uuid, PRIMARY_SERVICE, 8, &battery_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Battery service: %d", status); +BleServiceBattery* ble_svc_battery_start(bool auto_update) { + BleServiceBattery* battery_svc = malloc(sizeof(BleServiceBattery)); + + if(!ble_gatt_service_add( + UUID_TYPE_16, + (Service_UUID_t*)&service_uuid, + PRIMARY_SERVICE, + 8, + &battery_svc->svc_handle)) { + free(battery_svc); + return NULL; } for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( battery_svc->svc_handle, &battery_svc_chars[i], &battery_svc->chars[i]); } - battery_svc_update_power_state(); + battery_svc->auto_update = auto_update; + if(auto_update) { + if(!instances_initialized) { + BatterySvcInstanceList_init(instances); + instances_initialized = true; + } + + BatterySvcInstanceList_push_back(instances, battery_svc); + } + + return battery_svc; } -void battery_svc_stop() { - tBleStatus status; - if(battery_svc) { - for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]); +void ble_svc_battery_stop(BleServiceBattery* battery_svc) { + furi_assert(battery_svc); + if(battery_svc->auto_update) { + BatterySvcInstanceList_it_t it; + for(BatterySvcInstanceList_it(it, instances); !BatterySvcInstanceList_end_p(it); + BatterySvcInstanceList_next(it)) { + if(*BatterySvcInstanceList_ref(it) == battery_svc) { + BatterySvcInstanceList_remove(instances, it); + break; + } } - // Delete Battery service - status = aci_gatt_del_service(battery_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Battery service: %d", status); - } - free(battery_svc); - battery_svc = NULL; } -} -bool battery_svc_is_started() { - return battery_svc != NULL; + for(size_t i = 0; i < BatterySvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(battery_svc->svc_handle, &battery_svc->chars[i]); + } + /* Delete Battery service */ + ble_gatt_service_delete(battery_svc->svc_handle); + free(battery_svc); } -bool battery_svc_update_level(uint8_t battery_charge) { - // Check if service was started - if(battery_svc == NULL) { - return false; - } - // Update battery level characteristic - return flipper_gatt_characteristic_update( +bool ble_svc_battery_update_level(BleServiceBattery* battery_svc, uint8_t battery_charge) { + furi_check(battery_svc); + /* Update battery level characteristic */ + return ble_gatt_characteristic_update( battery_svc->svc_handle, &battery_svc->chars[BatterySvcGattCharacteristicBatteryLevel], &battery_charge); } -bool battery_svc_update_power_state() { - // Check if service was started - if(battery_svc == NULL) { - return false; - } - // Update power state characteristic +bool ble_svc_battery_update_power_state(BleServiceBattery* battery_svc, bool charging) { + furi_check(battery_svc); + + /* Update power state characteristic */ BattrySvcPowerState power_state = { .level = BatterySvcPowerStateUnsupported, .present = BatterySvcPowerStateBatteryPresent, }; - if(furi_hal_power_is_charging()) { + if(charging) { power_state.charging = BatterySvcPowerStateCharging; power_state.discharging = BatterySvcPowerStateNotDischarging; } else { @@ -143,8 +159,29 @@ bool battery_svc_update_power_state() { power_state.discharging = BatterySvcPowerStateDischarging; } - return flipper_gatt_characteristic_update( + return ble_gatt_characteristic_update( battery_svc->svc_handle, &battery_svc->chars[BatterySvcGattCharacteristicPowerState], &power_state); } + +void ble_svc_battery_state_update(uint8_t* battery_level, bool* charging) { + if(!instances_initialized) { +#ifdef FURI_BLE_EXTRA_LOG + FURI_LOG_W(TAG, "Battery service not initialized"); +#endif + return; + } + + BatterySvcInstanceList_it_t it; + for(BatterySvcInstanceList_it(it, instances); !BatterySvcInstanceList_end_p(it); + BatterySvcInstanceList_next(it)) { + BleServiceBattery* battery_svc = *BatterySvcInstanceList_ref(it); + if(battery_level) { + ble_svc_battery_update_level(battery_svc, *battery_level); + } + if(charging) { + ble_svc_battery_update_power_state(battery_svc, *charging); + } + } +} diff --git a/targets/f7/ble_glue/services/battery_service.h b/targets/f7/ble_glue/services/battery_service.h index f38bfc00d26..dccc440476e 100644 --- a/targets/f7/ble_glue/services/battery_service.h +++ b/targets/f7/ble_glue/services/battery_service.h @@ -7,15 +7,27 @@ extern "C" { #endif -void battery_svc_start(); +/* + * Battery service. Can be used in most profiles. + * If auto_update is true, the service will automatically update the battery + * level and charging state from power state updates. + */ -void battery_svc_stop(); +typedef struct BleServiceBattery BleServiceBattery; -bool battery_svc_is_started(); +BleServiceBattery* ble_svc_battery_start(bool auto_update); -bool battery_svc_update_level(uint8_t battery_level); +void ble_svc_battery_stop(BleServiceBattery* service); -bool battery_svc_update_power_state(); +bool ble_svc_battery_update_level(BleServiceBattery* service, uint8_t battery_level); + +bool ble_svc_battery_update_power_state(BleServiceBattery* service, bool charging); + +/* Global function, callable without a service instance + * Will update all service instances created with auto_update==true + * Both parameters are optional, pass NULL if no value is available + */ +void ble_svc_battery_state_update(uint8_t* battery_level, bool* charging); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/dev_info_service.c b/targets/f7/ble_glue/services/dev_info_service.c index 59af23e5c24..37caa8c90b4 100644 --- a/targets/f7/ble_glue/services/dev_info_service.c +++ b/targets/f7/ble_glue/services/dev_info_service.c @@ -1,6 +1,6 @@ #include "dev_info_service.h" #include "app_common.h" -#include "gatt_char.h" +#include #include #include @@ -20,45 +20,30 @@ typedef enum { DevInfoSvcGattCharacteristicCount, } DevInfoSvcGattCharacteristicId; -#define DEVICE_INFO_HARDWARE_REV_SIZE 4 -typedef struct { - uint16_t service_handle; - FlipperGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount]; - FuriString* version_string; - char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE]; -} DevInfoSvc; +#define DEVICE_INFO_HARDWARE_REV_SIZE (4) +#define DEVICE_INFO_SOFTWARE_REV_SIZE (40) -static DevInfoSvc* dev_info_svc = NULL; +struct BleServiceDevInfo { + uint16_t service_handle; + BleGattCharacteristicInstance characteristics[DevInfoSvcGattCharacteristicCount]; +}; static const char dev_info_man_name[] = "Flipper Devices Inc."; static const char dev_info_serial_num[] = "1.0"; static const char dev_info_rpc_version[] = TOSTRING(PROTOBUF_MAJOR_VERSION.PROTOBUF_MINOR_VERSION); +static char hardware_revision[DEVICE_INFO_HARDWARE_REV_SIZE] = {0}; +static char software_revision[DEVICE_INFO_SOFTWARE_REV_SIZE] = {0}; -static bool dev_info_char_firmware_rev_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; - *data_len = strlen(dev_info_svc->hardware_revision); - if(data) { - *data = (const uint8_t*)&dev_info_svc->hardware_revision; - } - return false; -} - -static bool dev_info_char_software_rev_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const DevInfoSvc* dev_info_svc = *(DevInfoSvc**)context; - *data_len = furi_string_size(dev_info_svc->version_string); +static bool + dev_info_char_data_callback(const void* context, const uint8_t** data, uint16_t* data_len) { + *data_len = (uint16_t)strlen(context); //-V1029 if(data) { - *data = (const uint8_t*)furi_string_get_cstr(dev_info_svc->version_string); + *data = (const uint8_t*)context; } return false; } -static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCharacteristicCount] = +static const BleGattCharacteristicParams ble_svc_dev_info_chars[DevInfoSvcGattCharacteristicCount] = {[DevInfoSvcGattCharacteristicMfgName] = {.name = "Manufacturer Name", .data_prop_type = FlipperGattCharacteristicDataFixed, @@ -84,8 +69,8 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh [DevInfoSvcGattCharacteristicFirmwareRev] = {.name = "Firmware Revision", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.context = &dev_info_svc, - .data.callback.fn = dev_info_char_firmware_rev_callback, + .data.callback.context = hardware_revision, + .data.callback.fn = dev_info_char_data_callback, .uuid.Char_UUID_16 = FIRMWARE_REVISION_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, @@ -95,8 +80,8 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh [DevInfoSvcGattCharacteristicSoftwareRev] = {.name = "Software Revision", .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.context = &dev_info_svc, - .data.callback.fn = dev_info_char_software_rev_callback, + .data.callback.context = software_revision, + .data.callback.fn = dev_info_char_data_callback, .uuid.Char_UUID_16 = SOFTWARE_REVISION_UUID, .uuid_type = UUID_TYPE_16, .char_properties = CHAR_PROP_READ, @@ -115,64 +100,52 @@ static const FlipperGattCharacteristicParams dev_info_svc_chars[DevInfoSvcGattCh .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, .is_variable = CHAR_VALUE_LEN_CONSTANT}}; -void dev_info_svc_start() { - dev_info_svc = malloc(sizeof(DevInfoSvc)); - dev_info_svc->version_string = furi_string_alloc_printf( +BleServiceDevInfo* ble_svc_dev_info_start(void) { + BleServiceDevInfo* dev_info_svc = malloc(sizeof(BleServiceDevInfo)); + snprintf( + software_revision, + sizeof(software_revision), "%s %s %s %s", version_get_githash(NULL), version_get_gitbranch(NULL), version_get_gitbranchnum(NULL), version_get_builddate(NULL)); - snprintf( - dev_info_svc->hardware_revision, - sizeof(dev_info_svc->hardware_revision), - "%d", - version_get_target(NULL)); - tBleStatus status; + snprintf(hardware_revision, sizeof(hardware_revision), "%d", version_get_target(NULL)); // Add Device Information Service uint16_t uuid = DEVICE_INFORMATION_SERVICE_UUID; - status = aci_gatt_add_service( - UUID_TYPE_16, - (Service_UUID_t*)&uuid, - PRIMARY_SERVICE, - 1 + 2 * DevInfoSvcGattCharacteristicCount, - &dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Device Information Service: %d", status); + if(!ble_gatt_service_add( + UUID_TYPE_16, + (Service_UUID_t*)&uuid, + PRIMARY_SERVICE, + 1 + 2 * DevInfoSvcGattCharacteristicCount, + &dev_info_svc->service_handle)) { + free(dev_info_svc); + return NULL; } for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( + ble_gatt_characteristic_init( dev_info_svc->service_handle, - &dev_info_svc_chars[i], + &ble_svc_dev_info_chars[i], &dev_info_svc->characteristics[i]); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( dev_info_svc->service_handle, &dev_info_svc->characteristics[i], NULL); } + + return dev_info_svc; } -void dev_info_svc_stop() { - tBleStatus status; - if(dev_info_svc) { - // Delete service characteristics - for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete( - dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); - } - - // Delete service - status = aci_gatt_del_service(dev_info_svc->service_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete device info service: %d", status); - } - - furi_string_free(dev_info_svc->version_string); - free(dev_info_svc); - dev_info_svc = NULL; +void ble_svc_dev_info_stop(BleServiceDevInfo* dev_info_svc) { + furi_assert(dev_info_svc); + /* Delete service characteristics */ + for(size_t i = 0; i < DevInfoSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete( + dev_info_svc->service_handle, &dev_info_svc->characteristics[i]); } -} -bool dev_info_svc_is_started() { - return dev_info_svc != NULL; + /* Delete service */ + ble_gatt_service_delete(dev_info_svc->service_handle); + + free(dev_info_svc); } diff --git a/targets/f7/ble_glue/services/dev_info_service.h b/targets/f7/ble_glue/services/dev_info_service.h index 8cce20a6cca..42471e56d40 100644 --- a/targets/f7/ble_glue/services/dev_info_service.h +++ b/targets/f7/ble_glue/services/dev_info_service.h @@ -7,11 +7,16 @@ extern "C" { #endif -void dev_info_svc_start(); +/* + * Device information service. + * Holds Flipper name, version and other information. + */ -void dev_info_svc_stop(); +typedef struct BleServiceDevInfo BleServiceDevInfo; -bool dev_info_svc_is_started(); +BleServiceDevInfo* ble_svc_dev_info_start(void); + +void ble_svc_dev_info_stop(BleServiceDevInfo* service); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/gatt_char.h b/targets/f7/ble_glue/services/gatt_char.h deleted file mode 100644 index 959ab67a49f..00000000000 --- a/targets/f7/ble_glue/services/gatt_char.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -// Callback signature for getting characteristic data -// Is called when characteristic is created to get max data length. Data ptr is NULL in this case -// The result is passed to aci_gatt_add_char as "Char_Value_Length" -// For updates, called with a context - see flipper_gatt_characteristic_update -// Returns true if *data ownership is transferred to the caller and will be freed -typedef bool (*cbFlipperGattCharacteristicData)( - const void* context, - const uint8_t** data, - uint16_t* data_len); - -typedef enum { - FlipperGattCharacteristicDataFixed, - FlipperGattCharacteristicDataCallback, -} FlipperGattCharacteristicDataType; - -typedef struct { - Char_Desc_Uuid_t uuid; - struct { - cbFlipperGattCharacteristicData fn; - const void* context; - } data_callback; - uint8_t uuid_type; - uint8_t max_length; - uint8_t security_permissions; - uint8_t access_permissions; - uint8_t gatt_evt_mask; - uint8_t is_variable; -} FlipperGattCharacteristicDescriptorParams; - -typedef struct { - const char* name; - FlipperGattCharacteristicDescriptorParams* descriptor_params; - union { - struct { - const uint8_t* ptr; - uint16_t length; - } fixed; - struct { - cbFlipperGattCharacteristicData fn; - const void* context; - } callback; - } data; - Char_UUID_t uuid; - // Some packed bitfields to save space - FlipperGattCharacteristicDataType data_prop_type : 2; - uint8_t is_variable : 2; - uint8_t uuid_type : 2; - uint8_t char_properties; - uint8_t security_permissions; - uint8_t gatt_evt_mask; -} FlipperGattCharacteristicParams; - -_Static_assert( - sizeof(FlipperGattCharacteristicParams) == 36, - "FlipperGattCharacteristicParams size must be 36 bytes"); - -typedef struct { - const FlipperGattCharacteristicParams* characteristic; - uint16_t handle; - uint16_t descriptor_handle; -} FlipperGattCharacteristicInstance; - -// Initialize a characteristic instance; copies the characteristic descriptor into the instance -void flipper_gatt_characteristic_init( - uint16_t svc_handle, - const FlipperGattCharacteristicParams* char_descriptor, - FlipperGattCharacteristicInstance* char_instance); - -// Delete a characteristic instance; frees the copied characteristic descriptor from the instance -void flipper_gatt_characteristic_delete( - uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance); - -// Update a characteristic instance; if source==NULL, uses the data from the characteristic -// - For fixed data, fixed.ptr is used as the source if source==NULL -// - For callback-based data, collback.context is passed as the context if source==NULL -bool flipper_gatt_characteristic_update( - uint16_t svc_handle, - FlipperGattCharacteristicInstance* char_instance, - const void* source); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/targets/f7/ble_glue/services/hid_service.h b/targets/f7/ble_glue/services/hid_service.h deleted file mode 100644 index 211adcd6c44..00000000000 --- a/targets/f7/ble_glue/services/hid_service.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#include - -#define HID_SVC_REPORT_MAP_MAX_LEN (255) -#define HID_SVC_REPORT_MAX_LEN (255) -#define HID_SVC_REPORT_REF_LEN (2) -#define HID_SVC_INFO_LEN (4) -#define HID_SVC_CONTROL_POINT_LEN (1) - -#define HID_SVC_INPUT_REPORT_COUNT (3) -#define HID_SVC_OUTPUT_REPORT_COUNT (0) -#define HID_SVC_FEATURE_REPORT_COUNT (0) -#define HID_SVC_REPORT_COUNT \ - (HID_SVC_INPUT_REPORT_COUNT + HID_SVC_OUTPUT_REPORT_COUNT + HID_SVC_FEATURE_REPORT_COUNT) - -void hid_svc_start(); - -void hid_svc_stop(); - -bool hid_svc_is_started(); - -bool hid_svc_update_report_map(const uint8_t* data, uint16_t len); - -bool hid_svc_update_input_report(uint8_t input_report_num, uint8_t* data, uint16_t len); - -// Expects data to be of length HID_SVC_INFO_LEN (4 bytes) -bool hid_svc_update_info(uint8_t* data); diff --git a/targets/f7/ble_glue/services/serial_service.c b/targets/f7/ble_glue/services/serial_service.c index 0db25b3d3af..a8f10e6d7b7 100644 --- a/targets/f7/ble_glue/services/serial_service.c +++ b/targets/f7/ble_glue/services/serial_service.c @@ -1,11 +1,13 @@ #include "serial_service.h" #include "app_common.h" #include -#include "gatt_char.h" +#include +#include #include #include "serial_service_uuid.inc" +#include #define TAG "BtSerialSvc" @@ -17,12 +19,12 @@ typedef enum { SerialSvcGattCharacteristicCount, } SerialSvcGattCharacteristicId; -static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattCharacteristicCount] = { +static const BleGattCharacteristicParams ble_svc_serial_chars[SerialSvcGattCharacteristicCount] = { [SerialSvcGattCharacteristicRx] = {.name = "RX", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, - .uuid.Char_UUID_128 = SERIAL_SVC_RX_CHAR_UUID, + .data.fixed.length = BLE_SVC_SERIAL_DATA_LEN_MAX, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_RX_CHAR_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP | CHAR_PROP_WRITE | CHAR_PROP_READ, .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, @@ -31,8 +33,8 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara [SerialSvcGattCharacteristicTx] = {.name = "TX", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = SERIAL_SVC_DATA_LEN_MAX, - .uuid.Char_UUID_128 = SERIAL_SVC_TX_CHAR_UUID, + .data.fixed.length = BLE_SVC_SERIAL_DATA_LEN_MAX, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_TX_CHAR_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_INDICATE, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, @@ -42,7 +44,7 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara {.name = "Flow control", .data_prop_type = FlipperGattCharacteristicDataFixed, .data.fixed.length = sizeof(uint32_t), - .uuid.Char_UUID_128 = SERIAL_SVC_FLOW_CONTROL_UUID, + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_FLOW_CONTROL_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ, @@ -51,28 +53,28 @@ static const FlipperGattCharacteristicParams serial_svc_chars[SerialSvcGattChara [SerialSvcGattCharacteristicStatus] = { .name = "RPC status", .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = sizeof(SerialServiceRpcStatus), - .uuid.Char_UUID_128 = SERIAL_SVC_RPC_STATUS_UUID, + .data.fixed.length = sizeof(uint32_t), + .uuid.Char_UUID_128 = BLE_SVC_SERIAL_RPC_STATUS_UUID, .uuid_type = UUID_TYPE_128, .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE | CHAR_PROP_NOTIFY, .security_permissions = ATTR_PERMISSION_AUTHEN_READ | ATTR_PERMISSION_AUTHEN_WRITE, .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, .is_variable = CHAR_VALUE_LEN_CONSTANT}}; -typedef struct { +struct BleServiceSerial { uint16_t svc_handle; - FlipperGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount]; + BleGattCharacteristicInstance chars[SerialSvcGattCharacteristicCount]; FuriMutex* buff_size_mtx; uint32_t buff_size; uint16_t bytes_ready_to_receive; SerialServiceEventCallback callback; void* context; -} SerialSvc; + GapSvcEventHandler* event_handler; +}; -static SerialSvc* serial_svc = NULL; - -static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { - SVCCTL_EvtAckStatus_t ret = SVCCTL_EvtNotAck; +static BleEventAckStatus ble_svc_serial_event_handler(void* event, void* context) { + BleServiceSerial* serial_svc = (BleServiceSerial*)context; + BleEventAckStatus ret = BleEventNotAck; hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; aci_gatt_attribute_modified_event_rp0* attribute_modified; @@ -82,7 +84,7 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { if(attribute_modified->Attr_Handle == serial_svc->chars[SerialSvcGattCharacteristicRx].handle + 2) { // Descriptor handle - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; FURI_LOG_D(TAG, "RX descriptor event"); } else if( attribute_modified->Attr_Handle == @@ -111,13 +113,12 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { FURI_LOG_D(TAG, "Available buff size: %ld", buff_free_size); furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } else if( attribute_modified->Attr_Handle == serial_svc->chars[SerialSvcGattCharacteristicStatus].handle + 1) { - SerialServiceRpcStatus* rpc_status = - (SerialServiceRpcStatus*)attribute_modified->Attr_Data; - if(*rpc_status == SerialServiceRpcStatusNotActive) { + bool* rpc_status = (bool*)attribute_modified->Attr_Data; + if(!*rpc_status) { if(serial_svc->callback) { SerialServiceEvent event = { .event = SerialServiceEventTypesBleResetRequest, @@ -134,43 +135,47 @@ static SVCCTL_EvtAckStatus_t serial_svc_event_handler(void* event) { }; serial_svc->callback(event, serial_svc->context); } - ret = SVCCTL_EvtAckFlowEnable; + ret = BleEventAckFlowEnable; } } return ret; } -static void serial_svc_update_rpc_char(SerialServiceRpcStatus status) { - flipper_gatt_characteristic_update( +typedef enum { + SerialServiceRpcStatusNotActive = 0UL, + SerialServiceRpcStatusActive = 1UL, +} SerialServiceRpcStatus; + +static void + ble_svc_serial_update_rpc_char(BleServiceSerial* serial_svc, SerialServiceRpcStatus status) { + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicStatus], &status); } -void serial_svc_start() { - UNUSED(serial_svc_chars); - tBleStatus status; - serial_svc = malloc(sizeof(SerialSvc)); - // Register event handler - SVCCTL_RegisterSvcHandler(serial_svc_event_handler); +BleServiceSerial* ble_svc_serial_start(void) { + BleServiceSerial* serial_svc = malloc(sizeof(BleServiceSerial)); - // Add service - status = aci_gatt_add_service( - UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to add Serial service: %d", status); - } + serial_svc->event_handler = + ble_event_dispatcher_register_svc_handler(ble_svc_serial_event_handler, serial_svc); - // Add characteristics + if(!ble_gatt_service_add( + UUID_TYPE_128, &service_uuid, PRIMARY_SERVICE, 12, &serial_svc->svc_handle)) { + free(serial_svc); + return NULL; + } for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_init( - serial_svc->svc_handle, &serial_svc_chars[i], &serial_svc->chars[i]); + ble_gatt_characteristic_init( + serial_svc->svc_handle, &ble_svc_serial_chars[i], &serial_svc->chars[i]); } - serial_svc_update_rpc_char(SerialServiceRpcStatusNotActive); - // Allocate buffer size mutex + ble_svc_serial_update_rpc_char(serial_svc, SerialServiceRpcStatusNotActive); serial_svc->buff_size_mtx = furi_mutex_alloc(FuriMutexTypeNormal); + + return serial_svc; } -void serial_svc_set_callbacks( +void ble_svc_serial_set_callbacks( + BleServiceSerial* serial_svc, uint16_t buff_size, SerialServiceEventCallback callback, void* context) { @@ -181,13 +186,13 @@ void serial_svc_set_callbacks( serial_svc->bytes_ready_to_receive = buff_size; uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], &buff_size_reversed); } -void serial_svc_notify_buffer_is_empty() { +void ble_svc_serial_notify_buffer_is_empty(BleServiceSerial* serial_svc) { furi_assert(serial_svc); furi_assert(serial_svc->buff_size_mtx); @@ -197,7 +202,7 @@ void serial_svc_notify_buffer_is_empty() { serial_svc->bytes_ready_to_receive = serial_svc->buff_size; uint32_t buff_size_reversed = REVERSE_BYTES_U32(serial_svc->buff_size); - flipper_gatt_characteristic_update( + ble_gatt_characteristic_update( serial_svc->svc_handle, &serial_svc->chars[SerialSvcGattCharacteristicFlowCtrl], &buff_size_reversed); @@ -205,35 +210,26 @@ void serial_svc_notify_buffer_is_empty() { furi_check(furi_mutex_release(serial_svc->buff_size_mtx) == FuriStatusOk); } -void serial_svc_stop() { - tBleStatus status; - if(serial_svc) { - for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { - flipper_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]); - } - // Delete service - status = aci_gatt_del_service(serial_svc->svc_handle); - if(status) { - FURI_LOG_E(TAG, "Failed to delete Serial service: %d", status); - } - // Delete buffer size mutex - furi_mutex_free(serial_svc->buff_size_mtx); - free(serial_svc); - serial_svc = NULL; - } -} +void ble_svc_serial_stop(BleServiceSerial* serial_svc) { + furi_check(serial_svc); -bool serial_svc_is_started() { - return serial_svc != NULL; + ble_event_dispatcher_unregister_svc_handler(serial_svc->event_handler); + + for(uint8_t i = 0; i < SerialSvcGattCharacteristicCount; i++) { + ble_gatt_characteristic_delete(serial_svc->svc_handle, &serial_svc->chars[i]); + } + ble_gatt_service_delete(serial_svc->svc_handle); + furi_mutex_free(serial_svc->buff_size_mtx); + free(serial_svc); } -bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { - if(data_len > SERIAL_SVC_DATA_LEN_MAX) { +bool ble_svc_serial_update_tx(BleServiceSerial* serial_svc, uint8_t* data, uint16_t data_len) { + if(data_len > BLE_SVC_SERIAL_DATA_LEN_MAX) { return false; } for(uint16_t remained = data_len; remained > 0;) { - uint8_t value_len = MIN(SERIAL_SVC_CHAR_VALUE_LEN_MAX, remained); + uint8_t value_len = MIN(BLE_SVC_SERIAL_CHAR_VALUE_LEN_MAX, remained); uint16_t value_offset = data_len - remained; remained -= value_len; @@ -256,7 +252,8 @@ bool serial_svc_update_tx(uint8_t* data, uint16_t data_len) { return true; } -void serial_svc_set_rpc_status(SerialServiceRpcStatus status) { +void ble_svc_serial_set_rpc_active(BleServiceSerial* serial_svc, bool active) { furi_assert(serial_svc); - serial_svc_update_rpc_char(status); + ble_svc_serial_update_rpc_char( + serial_svc, active ? SerialServiceRpcStatusActive : SerialServiceRpcStatusNotActive); } diff --git a/targets/f7/ble_glue/services/serial_service.h b/targets/f7/ble_glue/services/serial_service.h index 7d38066f47f..91ad886e061 100644 --- a/targets/f7/ble_glue/services/serial_service.h +++ b/targets/f7/ble_glue/services/serial_service.h @@ -3,17 +3,16 @@ #include #include -#define SERIAL_SVC_DATA_LEN_MAX (486) -#define SERIAL_SVC_CHAR_VALUE_LEN_MAX (243) - #ifdef __cplusplus extern "C" { #endif -typedef enum { - SerialServiceRpcStatusNotActive = 0UL, - SerialServiceRpcStatusActive = 1UL, -} SerialServiceRpcStatus; +/* + * Serial service. Implements RPC over BLE, with flow control. + */ + +#define BLE_SVC_SERIAL_DATA_LEN_MAX (486) +#define BLE_SVC_SERIAL_CHAR_VALUE_LEN_MAX (243) typedef enum { SerialServiceEventTypeDataReceived, @@ -33,22 +32,23 @@ typedef struct { typedef uint16_t (*SerialServiceEventCallback)(SerialServiceEvent event, void* context); -void serial_svc_start(); +typedef struct BleServiceSerial BleServiceSerial; + +BleServiceSerial* ble_svc_serial_start(void); -void serial_svc_set_callbacks( +void ble_svc_serial_stop(BleServiceSerial* service); + +void ble_svc_serial_set_callbacks( + BleServiceSerial* service, uint16_t buff_size, SerialServiceEventCallback callback, void* context); -void serial_svc_set_rpc_status(SerialServiceRpcStatus status); - -void serial_svc_notify_buffer_is_empty(); - -void serial_svc_stop(); +void ble_svc_serial_set_rpc_active(BleServiceSerial* service, bool active); -bool serial_svc_is_started(); +void ble_svc_serial_notify_buffer_is_empty(BleServiceSerial* service); -bool serial_svc_update_tx(uint8_t* data, uint16_t data_len); +bool ble_svc_serial_update_tx(BleServiceSerial* service, uint8_t* data, uint16_t data_len); #ifdef __cplusplus } diff --git a/targets/f7/ble_glue/services/serial_service_uuid.inc b/targets/f7/ble_glue/services/serial_service_uuid.inc index a297d9ad604..577e8f2eda4 100644 --- a/targets/f7/ble_glue/services/serial_service_uuid.inc +++ b/targets/f7/ble_glue/services/serial_service_uuid.inc @@ -2,11 +2,11 @@ static const Service_UUID_t service_uuid = { .Service_UUID_128 = \ { 0x00, 0x00, 0xfe, 0x60, 0xcc, 0x7a, 0x48, 0x2a, 0x98, 0x4a, 0x7f, 0x2e, 0xd5, 0xb3, 0xe5, 0x8f }}; -#define SERIAL_SVC_TX_CHAR_UUID \ +#define BLE_SVC_SERIAL_TX_CHAR_UUID \ { 0x00, 0x00, 0xfe, 0x61, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_RX_CHAR_UUID \ +#define BLE_SVC_SERIAL_RX_CHAR_UUID \ { 0x00, 0x00, 0xfe, 0x62, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_FLOW_CONTROL_UUID \ +#define BLE_SVC_SERIAL_FLOW_CONTROL_UUID \ { 0x00, 0x00, 0xfe, 0x63, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } -#define SERIAL_SVC_RPC_STATUS_UUID \ +#define BLE_SVC_SERIAL_RPC_STATUS_UUID \ { 0x00, 0x00, 0xfe, 0x64, 0x8e, 0x22, 0x45, 0x41, 0x9d, 0x4c, 0x21, 0xed, 0xae, 0x82, 0xed, 0x19 } diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index 48bce998eea..c276b5cf4e7 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -1,8 +1,13 @@ +#include "ble_glue.h" +#include +#include #include +#include #include #include +#include #include #include @@ -10,97 +15,30 @@ #include #include -#include -#include #include #include #include #define TAG "FuriHalBt" -#define FURI_HAL_BT_DEFAULT_MAC_ADDR \ +#define furi_hal_bt_DEFAULT_MAC_ADDR \ { 0x6c, 0x7a, 0xd8, 0xac, 0x57, 0x72 } /* Time, in ms, to wait for mode transition before crashing */ #define C2_MODE_SWITCH_TIMEOUT 10000 -#define FURI_HAL_BT_HARDFAULT_INFO_MAGIC 0x1170FD0F - typedef struct { FuriMutex* core2_mtx; - FuriTimer* hardfault_check_timer; FuriHalBtStack stack; } FuriHalBt; static FuriHalBt furi_hal_bt = { .core2_mtx = NULL, - .hardfault_check_timer = NULL, .stack = FuriHalBtStackUnknown, }; -typedef void (*FuriHalBtProfileStart)(void); -typedef void (*FuriHalBtProfileStop)(void); - -typedef struct { - FuriHalBtProfileStart start; - FuriHalBtProfileStart stop; - GapConfig config; - uint16_t appearance_char; - uint16_t advertise_service_uuid; -} FuriHalBtProfileConfig; - -FuriHalBtProfileConfig profile_config[FuriHalBtProfileNumber] = { - [FuriHalBtProfileSerial] = - { - .start = furi_hal_bt_serial_start, - .stop = furi_hal_bt_serial_stop, - .config = - { - .adv_service_uuid = 0x3080, - .appearance_char = 0x8600, - .bonding_mode = true, - .pairing_method = GapPairingPinCodeShow, - .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, - .conn_param = - { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms - .slave_latency = 0, - .supervisor_timeout = 0, - }, - }, - }, - [FuriHalBtProfileHidKeyboard] = - { - .start = furi_hal_bt_hid_start, - .stop = furi_hal_bt_hid_stop, - .config = - { - .adv_service_uuid = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, - .appearance_char = GAP_APPEARANCE_KEYBOARD, - .bonding_mode = true, - .pairing_method = GapPairingPinCodeVerifyYesNo, - .mac_address = FURI_HAL_BT_DEFAULT_MAC_ADDR, - .conn_param = - { - .conn_int_min = 0x18, // 30 ms - .conn_int_max = 0x24, // 45 ms - .slave_latency = 0, - .supervisor_timeout = 0, - }, - }, - }, -}; -FuriHalBtProfileConfig* current_profile = NULL; - -static void furi_hal_bt_hardfault_check(void* context) { - UNUSED(context); - if(furi_hal_bt_get_hardfault_info()) { - furi_crash("ST(R) Copro(R) HardFault"); - } -} - void furi_hal_bt_init() { + FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bus_enable(FuriHalBusHSEM); furi_hal_bus_enable(FuriHalBusIPCC); furi_hal_bus_enable(FuriHalBusAES2); @@ -112,12 +50,6 @@ void furi_hal_bt_init() { furi_assert(furi_hal_bt.core2_mtx); } - if(!furi_hal_bt.hardfault_check_timer) { - furi_hal_bt.hardfault_check_timer = - furi_timer_alloc(furi_hal_bt_hardfault_check, FuriTimerTypePeriodic, NULL); - furi_timer_start(furi_hal_bt.hardfault_check_timer, 5000); - } - // Explicitly tell that we are in charge of CLK48 domain furi_check(LL_HSEM_1StepLock(HSEM, CFG_HW_CLK48_CONFIG_SEMID) == 0); @@ -168,7 +100,6 @@ bool furi_hal_bt_start_radio_stack() { // Wait until C2 is started or timeout if(!ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT)) { FURI_LOG_E(TAG, "Core2 start failed"); - ble_glue_thread_stop(); break; } @@ -187,14 +118,15 @@ bool furi_hal_bt_start_radio_stack() { // Starting radio stack if(!ble_glue_start()) { FURI_LOG_E(TAG, "Failed to start radio stack"); - ble_glue_thread_stop(); - ble_app_thread_stop(); + ble_app_deinit(); + ble_glue_stop(); break; } res = true; } while(false); furi_mutex_release(furi_hal_bt.core2_mtx); + gap_extra_beacon_init(); return res; } @@ -202,7 +134,7 @@ FuriHalBtStack furi_hal_bt_get_radio_stack() { return furi_hal_bt.stack; } -bool furi_hal_bt_is_ble_gatt_gap_supported() { +bool furi_hal_bt_is_gatt_gap_supported() { if(furi_hal_bt.stack == FuriHalBtStackLight || furi_hal_bt.stack == FuriHalBtStackFull) { return true; } else { @@ -218,55 +150,52 @@ bool furi_hal_bt_is_testing_supported() { } } -bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { +static FuriHalBleProfileBase* current_profile = NULL; +static GapConfig current_config = {0}; + +bool furi_hal_bt_check_profile_type( + FuriHalBleProfileBase* profile, + const FuriHalBleProfileTemplate* profile_template) { + if(!profile || !profile_template) { + return false; + } + + return profile->config == profile_template; +} + +FuriHalBleProfileBase* furi_hal_bt_start_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params, + GapEventCallback event_cb, + void* context) { furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = false; + furi_check(profile_template); + furi_check(current_profile == NULL); do { if(!ble_glue_is_radio_stack_ready()) { FURI_LOG_E(TAG, "Can't start BLE App - radio stack did not start"); break; } - if(!furi_hal_bt_is_ble_gatt_gap_supported()) { + if(!furi_hal_bt_is_gatt_gap_supported()) { FURI_LOG_E(TAG, "Can't start Ble App - unsupported radio stack"); break; } - // Set mac address - memcpy( - profile_config[profile].config.mac_address, - furi_hal_version_get_ble_mac(), - sizeof(profile_config[profile].config.mac_address)); - // Set advertise name - strlcpy( - profile_config[profile].config.adv_name, - furi_hal_version_get_ble_local_device_name_ptr(), - FURI_HAL_VERSION_DEVICE_NAME_LENGTH); - // Configure GAP - GapConfig* config = &profile_config[profile].config; - if(profile == FuriHalBtProfileSerial) { - config->adv_service_uuid |= furi_hal_version_get_hw_color(); - } else if(profile == FuriHalBtProfileHidKeyboard) { - // Change MAC address for HID profile - config->mac_address[2]++; - // Change name Flipper -> Control - const char* clicker_str = "Control"; - memcpy(&config->adv_name[1], clicker_str, strlen(clicker_str)); - } - if(!gap_init(config, event_cb, context)) { + + profile_template->get_gap_config(¤t_config, params); + + if(!gap_init(¤t_config, event_cb, context)) { gap_thread_stop(); FURI_LOG_E(TAG, "Failed to init GAP"); break; } // Start selected profile services - if(furi_hal_bt_is_ble_gatt_gap_supported()) { - profile_config[profile].start(); + if(furi_hal_bt_is_gatt_gap_supported()) { + current_profile = profile_template->start(params); } - ret = true; } while(false); - current_profile = &profile_config[profile]; - return ret; + return current_profile; } void furi_hal_bt_reinit() { @@ -274,21 +203,25 @@ void furi_hal_bt_reinit() { FURI_LOG_I(TAG, "Disconnect and stop advertising"); furi_hal_bt_stop_advertising(); - FURI_LOG_I(TAG, "Stop current profile services"); - current_profile->stop(); + if(current_profile) { + FURI_LOG_I(TAG, "Stop current profile services"); + current_profile->config->stop(current_profile); + current_profile = NULL; + } // Magic happens here hci_reset(); FURI_LOG_I(TAG, "Stop BLE related RTOS threads"); - ble_app_thread_stop(); gap_thread_stop(); + ble_app_deinit(); FURI_LOG_I(TAG, "Reset SHCI"); furi_check(ble_glue_reinit_c2()); + ble_glue_stop(); + // enterprise delay furi_delay_ms(100); - ble_glue_thread_stop(); furi_hal_bus_disable(FuriHalBusHSEM); furi_hal_bus_disable(FuriHalBusIPCC); @@ -296,25 +229,20 @@ void furi_hal_bt_reinit() { furi_hal_bus_disable(FuriHalBusPKA); furi_hal_bus_disable(FuriHalBusCRC); - FURI_LOG_I(TAG, "Start BT initialization"); furi_hal_bt_init(); - furi_hal_bt_start_radio_stack(); furi_hal_power_insomnia_exit(); } -bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context) { +FuriHalBleProfileBase* furi_hal_bt_change_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams profile_params, + GapEventCallback event_cb, + void* context) { furi_assert(event_cb); - furi_assert(profile < FuriHalBtProfileNumber); - bool ret = true; furi_hal_bt_reinit(); - - ret = furi_hal_bt_start_app(profile, event_cb, context); - if(ret) { - current_profile = &profile_config[profile]; - } - return ret; + return furi_hal_bt_start_app(profile_template, profile_params, event_cb, context); } bool furi_hal_bt_is_active() { @@ -337,15 +265,11 @@ void furi_hal_bt_stop_advertising() { } void furi_hal_bt_update_battery_level(uint8_t battery_level) { - if(battery_svc_is_started()) { - battery_svc_update_level(battery_level); - } + ble_svc_battery_state_update(&battery_level, NULL); } -void furi_hal_bt_update_power_state() { - if(battery_svc_is_started()) { - battery_svc_update_power_state(); - } +void furi_hal_bt_update_power_state(bool charging) { + ble_svc_battery_state_update(NULL, &charging); } void furi_hal_bt_get_key_storage_buff(uint8_t** key_buff_addr, uint16_t* key_buff_size) { @@ -484,11 +408,30 @@ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode) { return false; } -const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info() { - /* AN5289, 4.8.2 */ - const FuriHalBtHardfaultInfo* info = (FuriHalBtHardfaultInfo*)(SRAM2A_BASE); - if(info->magic != FURI_HAL_BT_HARDFAULT_INFO_MAGIC) { - return NULL; - } - return info; +bool furi_hal_bt_extra_beacon_set_data(const uint8_t* data, uint8_t len) { + return gap_extra_beacon_set_data(data, len); +} + +uint8_t furi_hal_bt_extra_beacon_get_data(uint8_t* data) { + return gap_extra_beacon_get_data(data); +} + +bool furi_hal_bt_extra_beacon_set_config(const GapExtraBeaconConfig* config) { + return gap_extra_beacon_set_config(config); +} + +const GapExtraBeaconConfig* furi_hal_bt_extra_beacon_get_config() { + return gap_extra_beacon_get_config(); +} + +bool furi_hal_bt_extra_beacon_start() { + return gap_extra_beacon_start(); +} + +bool furi_hal_bt_extra_beacon_stop() { + return gap_extra_beacon_stop(); +} + +bool furi_hal_bt_extra_beacon_is_active() { + return gap_extra_beacon_get_state() == GapExtraBeaconStateStarted; } diff --git a/targets/f7/furi_hal/furi_hal_bt_hid.c b/targets/f7/furi_hal/furi_hal_bt_hid.c deleted file mode 100644 index 7ec712af4db..00000000000 --- a/targets/f7/furi_hal/furi_hal_bt_hid.c +++ /dev/null @@ -1,289 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include - -#define FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION (0x0101) -#define FURI_HAL_BT_INFO_COUNTRY_CODE (0x00) -#define FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) -#define FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) - -#define FURI_HAL_BT_HID_KB_MAX_KEYS 6 -#define FURI_HAL_BT_HID_CONSUMER_MAX_KEYS 1 - -// Report ids cant be 0 -enum HidReportId { - ReportIdKeyboard = 1, - ReportIdMouse = 2, - ReportIdConsumer = 3, -}; -// Report numbers corresponded to the report id with an offset of 1 -enum HidInputNumber { - ReportNumberKeyboard = 0, - ReportNumberMouse = 1, - ReportNumberConsumer = 2, -}; - -typedef struct { - uint8_t mods; - uint8_t reserved; - uint8_t key[FURI_HAL_BT_HID_KB_MAX_KEYS]; -} __attribute__((__packed__)) FuriHalBtHidKbReport; - -typedef struct { - uint8_t btn; - int8_t x; - int8_t y; - int8_t wheel; -} __attribute__((__packed__)) FuriHalBtHidMouseReport; - -typedef struct { - uint16_t key[FURI_HAL_BT_HID_CONSUMER_MAX_KEYS]; -} __attribute__((__packed__)) FuriHalBtHidConsumerReport; - -// keyboard+mouse+consumer hid report -static const uint8_t furi_hal_bt_hid_report_map_data[] = { - // Keyboard Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_KEYBOARD), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdKeyboard), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), - HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(8), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(1), - HID_REPORT_SIZE(8), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_LED), - HID_REPORT_COUNT(8), - HID_REPORT_SIZE(1), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(8), - HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(FURI_HAL_BT_HID_KB_MAX_KEYS), - HID_REPORT_SIZE(8), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(101), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(0), - HID_USAGE_MAXIMUM(101), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, - // Mouse Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_MOUSE), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_USAGE(HID_DESKTOP_POINTER), - HID_COLLECTION(HID_PHYSICAL_COLLECTION), - HID_REPORT_ID(ReportIdMouse), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(3), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_COUNT(3), - HID_REPORT_SIZE(1), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_X), - HID_USAGE(HID_DESKTOP_Y), - HID_USAGE(HID_DESKTOP_WHEEL), - HID_LOGICAL_MINIMUM(-127), - HID_LOGICAL_MAXIMUM(127), - HID_REPORT_SIZE(8), - HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), - HID_END_COLLECTION, - HID_END_COLLECTION, - // Consumer Report - HID_USAGE_PAGE(HID_PAGE_CONSUMER), - HID_USAGE(HID_CONSUMER_CONTROL), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdConsumer), - HID_LOGICAL_MINIMUM(0), - HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), - HID_USAGE_MINIMUM(0), - HID_RI_USAGE_MAXIMUM(16, 0x3FF), - HID_REPORT_COUNT(FURI_HAL_BT_HID_CONSUMER_MAX_KEYS), - HID_REPORT_SIZE(16), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, -}; -FuriHalBtHidKbReport* kb_report = NULL; -FuriHalBtHidMouseReport* mouse_report = NULL; -FuriHalBtHidConsumerReport* consumer_report = NULL; - -void furi_hal_bt_hid_start() { - // Start device info - if(!dev_info_svc_is_started()) { - dev_info_svc_start(); - } - // Start battery service - if(!battery_svc_is_started()) { - battery_svc_start(); - } - // Start HID service - if(!hid_svc_is_started()) { - hid_svc_start(); - } - // Configure HID Keyboard - kb_report = malloc(sizeof(FuriHalBtHidKbReport)); - mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); - consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); - // Configure Report Map characteristic - hid_svc_update_report_map( - furi_hal_bt_hid_report_map_data, sizeof(furi_hal_bt_hid_report_map_data)); - // Configure HID Information characteristic - uint8_t hid_info_val[4] = { - FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0x00ff, - (FURI_HAL_BT_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, - FURI_HAL_BT_INFO_COUNTRY_CODE, - FURI_HAL_BT_HID_INFO_FLAG_REMOTE_WAKE_MSK | - FURI_HAL_BT_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, - }; - hid_svc_update_info(hid_info_val); -} - -void furi_hal_bt_hid_stop() { - furi_assert(kb_report); - furi_assert(mouse_report); - furi_assert(consumer_report); - // Stop all services - if(dev_info_svc_is_started()) { - dev_info_svc_stop(); - } - if(battery_svc_is_started()) { - battery_svc_stop(); - } - if(hid_svc_is_started()) { - hid_svc_stop(); - } - free(kb_report); - free(mouse_report); - free(consumer_report); - kb_report = NULL; - mouse_report = NULL; - consumer_report = NULL; -} - -bool furi_hal_bt_hid_kb_press(uint16_t button) { - furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { - if(kb_report->key[i] == 0) { - kb_report->key[i] = button & 0xFF; - break; - } - } - kb_report->mods |= (button >> 8); - return hid_svc_update_input_report( - ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); -} - -bool furi_hal_bt_hid_kb_release(uint16_t button) { - furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { - if(kb_report->key[i] == (button & 0xFF)) { - kb_report->key[i] = 0; - break; - } - } - kb_report->mods &= ~(button >> 8); - return hid_svc_update_input_report( - ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); -} - -bool furi_hal_bt_hid_kb_release_all() { - furi_assert(kb_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_KB_MAX_KEYS; i++) { - kb_report->key[i] = 0; - } - kb_report->mods = 0; - return hid_svc_update_input_report( - ReportNumberKeyboard, (uint8_t*)kb_report, sizeof(FuriHalBtHidKbReport)); -} - -bool furi_hal_bt_hid_consumer_key_press(uint16_t button) { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == 0) { - consumer_report->key[i] = button; - break; - } - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_consumer_key_release(uint16_t button) { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == button) { - consumer_report->key[i] = 0; - break; - } - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_consumer_key_release_all() { - furi_assert(consumer_report); - for(uint8_t i = 0; i < FURI_HAL_BT_HID_CONSUMER_MAX_KEYS; i++) { //-V1008 - consumer_report->key[i] = 0; - } - return hid_svc_update_input_report( - ReportNumberConsumer, (uint8_t*)consumer_report, sizeof(FuriHalBtHidConsumerReport)); -} - -bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy) { - furi_assert(mouse_report); - mouse_report->x = dx; - mouse_report->y = dy; - bool state = hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); - mouse_report->x = 0; - mouse_report->y = 0; - return state; -} - -bool furi_hal_bt_hid_mouse_press(uint8_t button) { - furi_assert(mouse_report); - mouse_report->btn |= button; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_release(uint8_t button) { - furi_assert(mouse_report); - mouse_report->btn &= ~button; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_release_all() { - furi_assert(mouse_report); - mouse_report->btn = 0; - return hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); -} - -bool furi_hal_bt_hid_mouse_scroll(int8_t delta) { - furi_assert(mouse_report); - mouse_report->wheel = delta; - bool state = hid_svc_update_input_report( - ReportNumberMouse, (uint8_t*)mouse_report, sizeof(FuriHalBtHidMouseReport)); - mouse_report->wheel = 0; - return state; -} diff --git a/targets/f7/furi_hal/furi_hal_bt_serial.c b/targets/f7/furi_hal/furi_hal_bt_serial.c deleted file mode 100644 index 2927d946f98..00000000000 --- a/targets/f7/furi_hal/furi_hal_bt_serial.c +++ /dev/null @@ -1,64 +0,0 @@ -#include -#include -#include -#include - -#include - -void furi_hal_bt_serial_start() { - // Start device info - if(!dev_info_svc_is_started()) { - dev_info_svc_start(); - } - // Start battery service - if(!battery_svc_is_started()) { - battery_svc_start(); - } - // Start Serial service - if(!serial_svc_is_started()) { - serial_svc_start(); - } -} - -void furi_hal_bt_serial_set_event_callback( - uint16_t buff_size, - FuriHalBtSerialCallback callback, - void* context) { - serial_svc_set_callbacks(buff_size, callback, context); -} - -void furi_hal_bt_serial_notify_buffer_is_empty() { - serial_svc_notify_buffer_is_empty(); -} - -void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status) { - SerialServiceRpcStatus st; - if(status == FuriHalBtSerialRpcStatusActive) { - st = SerialServiceRpcStatusActive; - } else { - st = SerialServiceRpcStatusNotActive; - } - serial_svc_set_rpc_status(st); -} - -bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size) { - if(size > FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX) { - return false; - } - return serial_svc_update_tx(data, size); -} - -void furi_hal_bt_serial_stop() { - // Stop all services - if(dev_info_svc_is_started()) { - dev_info_svc_stop(); - } - // Start battery service - if(battery_svc_is_started()) { - battery_svc_stop(); - } - // Start Serial service - if(serial_svc_is_started()) { - serial_svc_stop(); - } -} diff --git a/targets/f7/furi_hal/furi_hal_cortex.c b/targets/f7/furi_hal/furi_hal_cortex.c index 6b5efc376c0..9865e6ef893 100644 --- a/targets/f7/furi_hal/furi_hal_cortex.c +++ b/targets/f7/furi_hal/furi_hal_cortex.c @@ -28,7 +28,7 @@ uint32_t furi_hal_cortex_instructions_per_microsecond() { return FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND; } -FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { +FURI_WARN_UNUSED FuriHalCortexTimer furi_hal_cortex_timer_get(uint32_t timeout_us) { furi_check(timeout_us < (UINT32_MAX / FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND)); FuriHalCortexTimer cortex_timer = {0}; diff --git a/targets/f7/inc/FreeRTOSConfig.h b/targets/f7/inc/FreeRTOSConfig.h index 3bc57f8f3be..6a68bbae736 100644 --- a/targets/f7/inc/FreeRTOSConfig.h +++ b/targets/f7/inc/FreeRTOSConfig.h @@ -28,7 +28,15 @@ /* Heap size determined automatically by linker */ // #define configTOTAL_HEAP_SIZE ((size_t)0) #define configMAX_TASK_NAME_LEN (32) -#define configGENERATE_RUN_TIME_STATS 0 + +/* Run-time stats - broken ATM, to be fixed */ +/* +#define configGENERATE_RUN_TIME_STATS 1 +#define configRUN_TIME_COUNTER_TYPE uint64_t +#define portGET_RUN_TIME_COUNTER_VALUE() (DWT->CYCCNT) +#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() +*/ + #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 #define configUSE_MUTEXES 1 diff --git a/targets/f7/target.json b/targets/f7/target.json index 0f0161fe4e3..25872198bf4 100644 --- a/targets/f7/target.json +++ b/targets/f7/target.json @@ -8,7 +8,10 @@ "sdk_header_paths": [ "../furi_hal_include", "furi_hal", - "platform_specific" + "platform_specific", + "ble_glue/furi_ble", + "ble_glue/services", + "ble_glue/profiles" ], "startup_script": "startup_stm32wb55xx_cm4.s", "linker_script_flash": "stm32wb55xx_flash.ld", diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 4d538265dbb..939c9a30ddc 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -8,15 +8,15 @@ #include #include #include -#include +#include +#include #include #include - -#include +#include #define FURI_HAL_BT_STACK_VERSION_MAJOR (1) #define FURI_HAL_BT_STACK_VERSION_MINOR (12) -#define FURI_HAL_BT_C2_START_TIMEOUT 1000 +#define FURI_HAL_BT_C2_START_TIMEOUT (1000) #ifdef __cplusplus extern "C" { @@ -28,14 +28,6 @@ typedef enum { FuriHalBtStackFull, } FuriHalBtStack; -typedef enum { - FuriHalBtProfileSerial, - FuriHalBtProfileHidKeyboard, - - // Keep last for Profiles number calculation - FuriHalBtProfileNumber, -} FuriHalBtProfile; - /** Initialize */ void furi_hal_bt_init(); @@ -62,7 +54,7 @@ FuriHalBtStack furi_hal_bt_get_radio_stack(); * * @return true if supported */ -bool furi_hal_bt_is_ble_gatt_gap_supported(); +bool furi_hal_bt_is_gatt_gap_supported(); /** Check if radio stack supports testing * @@ -70,15 +62,31 @@ bool furi_hal_bt_is_ble_gatt_gap_supported(); */ bool furi_hal_bt_is_testing_supported(); -/** Start BLE app +/** Check if particular instance of profile belongs to given type * - * @param profile FuriHalBtProfile instance - * @param event_cb GapEventCallback instance - * @param context pointer to context + * @param profile FuriHalBtProfile instance. If NULL, uses current profile + * @param profile_template basic profile template to check against * * @return true on success */ -bool furi_hal_bt_start_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); +bool furi_hal_bt_check_profile_type( + FuriHalBleProfileBase* profile, + const FuriHalBleProfileTemplate* profile_template); + +/** Start BLE app + * + * @param profile_template FuriHalBleProfileTemplate instance + * @param params Parameters to pass to the profile. Can be NULL + * @param event_cb GapEventCallback instance + * @param context pointer to context + * + * @return instance of profile, NULL on failure +*/ +FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_start_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams params, + GapEventCallback event_cb, + void* context); /** Reinitialize core2 * @@ -89,13 +97,17 @@ void furi_hal_bt_reinit(); /** Change BLE app * Restarts 2nd core * - * @param profile FuriHalBtProfile instance + * @param profile FuriHalBleProfileTemplate instance * @param event_cb GapEventCallback instance * @param context pointer to context * - * @return true on success + * @return instance of profile, NULL on failure */ -bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void* context); +FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app( + const FuriHalBleProfileTemplate* profile_template, + FuriHalBleProfileParams profile_params, + GapEventCallback event_cb, + void* context); /** Update battery level * @@ -104,7 +116,7 @@ bool furi_hal_bt_change_app(FuriHalBtProfile profile, GapEventCallback event_cb, void furi_hal_bt_update_battery_level(uint8_t battery_level); /** Update battery power state */ -void furi_hal_bt_update_power_state(); +void furi_hal_bt_update_power_state(bool charging); /** Checks if BLE state is active * @@ -224,18 +236,60 @@ uint32_t furi_hal_bt_get_transmitted_packets(); */ bool furi_hal_bt_ensure_c2_mode(BleGlueC2Mode mode); -typedef struct { - uint32_t magic; - uint32_t source_pc; - uint32_t source_lr; - uint32_t source_sp; -} FuriHalBtHardfaultInfo; +/** + * Extra BLE beacon API + */ + +/** Set extra beacon data. Can be called in any state + * + * @param[in] data data to set + * @param[in] len data length. Must be <= EXTRA_BEACON_MAX_DATA_SIZE + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_set_data(const uint8_t* data, uint8_t len); + +/** Get last configured extra beacon data + * + * @param data data buffer to write to. Must be at least EXTRA_BEACON_MAX_DATA_SIZE bytes long + * + * @return valid data length + */ +uint8_t furi_hal_bt_extra_beacon_get_data(uint8_t* data); + +/** Configure extra beacon. + * + * @param[in] config extra beacon config: interval, power, address, etc. + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_set_config(const GapExtraBeaconConfig* config); + +/** Start extra beacon. + * Beacon must configured with furi_hal_bt_extra_beacon_set_config() + * and in stopped state before calling this function. + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_start(); + +/** Stop extra beacon + * + * @return true on success + */ +bool furi_hal_bt_extra_beacon_stop(); + +/** Check if extra beacon is active. + * + * @return extra beacon state + */ +bool furi_hal_bt_extra_beacon_is_active(); -/** Get hardfault info +/** Get last configured extra beacon config * - * @return hardfault info. NULL if no hardfault + * @return extra beacon config. NULL if beacon had never been configured. */ -const FuriHalBtHardfaultInfo* furi_hal_bt_get_hardfault_info(); +const GapExtraBeaconConfig* furi_hal_bt_extra_beacon_get_config(); #ifdef __cplusplus } diff --git a/targets/furi_hal_include/furi_hal_bt_hid.h b/targets/furi_hal_include/furi_hal_bt_hid.h deleted file mode 100644 index 4e74bbda751..00000000000 --- a/targets/furi_hal_include/furi_hal_bt_hid.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Start Hid Keyboard Profile - */ -void furi_hal_bt_hid_start(); - -/** Stop Hid Keyboard Profile - */ -void furi_hal_bt_hid_stop(); - -/** Press keyboard button - * - * @param button button code from HID specification - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_press(uint16_t button); - -/** Release keyboard button - * - * @param button button code from HID specification - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_release(uint16_t button); - -/** Release all keyboard buttons - * - * @return true on success - */ -bool furi_hal_bt_hid_kb_release_all(); - -/** Set mouse movement and send HID report - * - * @param dx x coordinate delta - * @param dy y coordinate delta - */ -bool furi_hal_bt_hid_mouse_move(int8_t dx, int8_t dy); - -/** Set mouse button to pressed state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_press(uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_release(uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_mouse_release_all(); - -/** Set mouse wheel position and send HID report - * - * @param delta number of scroll steps - */ -bool furi_hal_bt_hid_mouse_scroll(int8_t delta); - -/** Set the following consumer key to pressed state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_press(uint16_t button); - -/** Set the following consumer key to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_release(uint16_t button); - -/** Set consumer key to released state and send HID report - * - * @param button key code - */ -bool furi_hal_bt_hid_consumer_key_release_all(); - -#ifdef __cplusplus -} -#endif diff --git a/targets/furi_hal_include/furi_hal_bt_serial.h b/targets/furi_hal_include/furi_hal_bt_serial.h deleted file mode 100644 index 0472d31d181..00000000000 --- a/targets/furi_hal_include/furi_hal_bt_serial.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define FURI_HAL_BT_SERIAL_PACKET_SIZE_MAX SERIAL_SVC_DATA_LEN_MAX - -typedef enum { - FuriHalBtSerialRpcStatusNotActive, - FuriHalBtSerialRpcStatusActive, -} FuriHalBtSerialRpcStatus; - -/** Serial service callback type */ -typedef SerialServiceEventCallback FuriHalBtSerialCallback; - -/** Start Serial Profile - */ -void furi_hal_bt_serial_start(); - -/** Stop Serial Profile - */ -void furi_hal_bt_serial_stop(); - -/** Set Serial service events callback - * - * @param buffer_size Applicaition buffer size - * @param calback FuriHalBtSerialCallback instance - * @param context pointer to context - */ -void furi_hal_bt_serial_set_event_callback( - uint16_t buff_size, - FuriHalBtSerialCallback callback, - void* context); - -/** Set BLE RPC status - * - * @param status FuriHalBtSerialRpcStatus instance - */ -void furi_hal_bt_serial_set_rpc_status(FuriHalBtSerialRpcStatus status); - -/** Notify that application buffer is empty - */ -void furi_hal_bt_serial_notify_buffer_is_empty(); - -/** Send data through BLE - * - * @param data data buffer - * @param size data buffer size - * - * @return true on success - */ -bool furi_hal_bt_serial_tx(uint8_t* data, uint16_t size); - -#ifdef __cplusplus -} -#endif From 88a3b4593d5eb6d79ccc6058f2efeee2534a90e5 Mon Sep 17 00:00:00 2001 From: Maxwell Pray Date: Fri, 16 Feb 2024 23:26:58 -0800 Subject: [PATCH 43/58] Add IR codes for PLUS U5/V3-200R projector remote (controls PLUS U5-632h and PLUS U5-732h) (#3453) * Add IR codes for PLUS U5/V3-200R projector to projector.ir. * Add U5/V3-200R "freeze" and "cancel" IR codes. * Remove pause and play IR codes for U5/V3-200R remote. --- .../resources/infrared/assets/projector.ir | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/applications/main/infrared/resources/infrared/assets/projector.ir b/applications/main/infrared/resources/infrared/assets/projector.ir index e9861de21f6..dcfcb06ffad 100644 --- a/applications/main/infrared/resources/infrared/assets/projector.ir +++ b/applications/main/infrared/resources/infrared/assets/projector.ir @@ -826,4 +826,28 @@ name: Mute type: raw frequency: 38000 duty_cycle: 0.33 -data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 \ No newline at end of file +data: 9011 4388 557 1617 532 1617 532 489 533 489 558 464 558 440 582 440 582 1593 556 466 556 466 556 1594 556 467 555 1595 555 1595 529 1620 554 1596 554 467 554 468 555 1595 579 443 581 1569 581 441 581 441 580 442 581 1569 581 1569 581 441 581 1569 580 441 581 1569 581 1569 581 1570 579 42152 8957 2159 556 +# PLUS U5/V3-200R Standby +name: Power +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9033 4255 562 543 564 1701 562 1674 563 567 565 541 564 567 565 567 537 1700 564 1700 537 568 564 1699 564 541 565 1699 565 540 565 567 565 570 535 567 565 567 565 1673 564 567 565 1673 564 567 565 540 618 514 565 1700 563 1674 564 567 564 1674 563 569 563 1672 565 1700 589 1648 564 +# PLUS U5/V3-200R Mute +name: Mute +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9034 4255 564 567 564 1671 566 1699 564 540 565 567 565 540 564 566 566 1699 563 1672 567 565 566 1673 565 567 565 1672 565 566 566 539 565 567 565 1698 539 566 566 1698 564 541 565 565 567 1672 565 566 566 1672 565 567 565 1699 538 566 566 1698 564 1673 566 566 565 1672 566 566 566 +# PLUS U5/V3-200R 1/Volume up +name: Vol_up +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9035 4254 565 566 565 1672 566 1699 564 539 567 566 566 540 564 567 565 1698 539 1698 566 566 564 1673 565 566 565 1672 565 566 566 539 566 566 566 567 563 541 565 566 566 538 566 566 566 1671 566 566 566 1697 565 1675 563 1699 563 1674 565 1699 538 1700 564 565 565 1674 564 567 565 +# PLUS U5/V3-200R 3/Volume down +name: Vol_dn +type: raw +frequency: 38000 +duty_cycle: 0.330000 +data: 9010 4253 564 566 566 1671 566 1699 565 565 539 568 564 566 565 539 566 1699 565 1672 565 566 566 1672 564 567 565 1672 565 567 565 567 564 541 564 1698 566 539 565 567 565 567 562 542 565 1699 564 539 567 1699 565 540 564 1698 566 1672 565 1698 566 1672 565 567 565 1671 565 566 566 From fcf3b50f690f1f32a5a0b7f93c14837aca03ad7c Mon Sep 17 00:00:00 2001 From: Max Andreev Date: Sat, 17 Feb 2024 10:32:32 +0300 Subject: [PATCH 44/58] Add notification sending (#3449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- .github/workflows/reindex.yml | 27 +++++++++++++--- scripts/send_firebase_notification.py | 44 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 scripts/send_firebase_notification.py diff --git a/.github/workflows/reindex.yml b/.github/workflows/reindex.yml index 82cb0468077..4489c7093f3 100644 --- a/.github/workflows/reindex.yml +++ b/.github/workflows/reindex.yml @@ -1,15 +1,32 @@ -name: 'Reindex' +name: 'Post-release hooks' on: release: - types: [prereleased,released] + types: [prereleased, released] jobs: reindex: - name: 'Reindex updates' + name: 'Post-release hooks' runs-on: [self-hosted, FlipperZeroShell] steps: - - name: Trigger reindex + - name: 'Checkout code' + uses: actions/checkout@v4 + + - name: 'Trigger reindex' run: | curl --fail -L -H "Token: ${{ secrets.INDEXER_TOKEN }}" \ - "${{ secrets.INDEXER_URL }}"/firmware/reindex + "${{ secrets.INDEXER_URL }}"/firmware/reindex; + + - name: 'Send release notification' + if: ${{ github.event.action == 'released' }} + run: | + echo '${{ secrets.FIREBASE_TOKEN }}' > firebase-token.json; + python3 -m pip install firebase-admin==6.4.0; + python3 scripts/send_firebase_notification.py \ + "--version=${{ github.event.release.name }}" \ + "--token=firebase-token.json"; + + - name: 'Remove firebase token' + if: always() + run: | + rm -rf firebase-token.json; diff --git a/scripts/send_firebase_notification.py b/scripts/send_firebase_notification.py new file mode 100644 index 00000000000..102cf006676 --- /dev/null +++ b/scripts/send_firebase_notification.py @@ -0,0 +1,44 @@ +import argparse +import logging +from firebase_admin import messaging, credentials, initialize_app + + +class FirebaseNotifications: + def __init__(self, service_account_file): + try: + cred = credentials.Certificate(service_account_file) + self.firebase_app = initialize_app(cred) + except Exception as e: + logging.exception(e) + raise e + + def send(self, title, body, condition): + try: + message = messaging.Message( + notification=messaging.Notification(title=title, body=body), + condition=condition, + ) + messaging.send(message, app=self.firebase_app) + except Exception as e: + logging.exception(e) + raise e + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("--token_file", help="Firebase token file", required=True) + parser.add_argument( + "--version", help="Firmware version to notify with", required=True + ) + args = parser.parse_args() + return args + + +if __name__ == "__main__": + args = parse_args() + notification = FirebaseNotifications(args.token_file) + notification.send( + title="Firmware Update Available", + body=f"New firmware version is ready to install: {args.version}", + condition="'flipper_update_firmware_release' in topics", + ) From 2c650b5bc7e2586248e32f0df726831fbea52502 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Sat, 17 Feb 2024 12:15:44 +0400 Subject: [PATCH 45/58] [FL-3699] HID: Add confirmation dialogue to the remove pairing option (#3263) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HID: Add confirmation dialogue to the un-pair option * Initial refactor to use SceneManager * Make PVS happy * Fix the exit dialog Co-authored-by: あく --- .../hid_app/assets/DolphinNice_96x59.png | Bin 0 -> 2459 bytes applications/system/hid_app/hid.c | 80 +++++++++--------- applications/system/hid_app/hid.h | 6 ++ .../system/hid_app/scenes/hid_scene.c | 30 +++++++ .../system/hid_app/scenes/hid_scene.h | 29 +++++++ .../system/hid_app/scenes/hid_scene_config.h | 3 + .../hid_app/scenes/hid_scene_exit_confirm.c | 45 ++++++++++ .../system/hid_app/scenes/hid_scene_main.c | 22 +++++ .../system/hid_app/scenes/hid_scene_unpair.c | 63 ++++++++++++++ applications/system/hid_app/views.h | 3 +- 10 files changed, 242 insertions(+), 39 deletions(-) create mode 100644 applications/system/hid_app/assets/DolphinNice_96x59.png create mode 100644 applications/system/hid_app/scenes/hid_scene.c create mode 100644 applications/system/hid_app/scenes/hid_scene.h create mode 100644 applications/system/hid_app/scenes/hid_scene_config.h create mode 100644 applications/system/hid_app/scenes/hid_scene_exit_confirm.c create mode 100644 applications/system/hid_app/scenes/hid_scene_main.c create mode 100644 applications/system/hid_app/scenes/hid_scene_unpair.c diff --git a/applications/system/hid_app/assets/DolphinNice_96x59.png b/applications/system/hid_app/assets/DolphinNice_96x59.png new file mode 100644 index 0000000000000000000000000000000000000000..a299d3630239b4486e249cc501872bed5996df3b GIT binary patch literal 2459 zcmbVO3s4i+8V(M(gEFORwSrA`4O0uPn|M|5y* zB*aMDxC&7(gP9JN;POOi-9khrC>Z9YJs2U!LnVcQEEC0fDtKo&ILlzb30%M}3J^;~ zv7RzcsilOs4Mq@tD*&R;!LMSk2A~{(`HK9|hQBqEX)3sQr9Je6SZU*F-^fD-p+~Hs; zHLkO%v?>ZoxEv+F#whudr%615FkA0DYR0tMEo}3OOY#xecLWe>xV?u5KtSmC^ z7)Fmj6gjfKstiEV-*Cxbbb+&rRWuI_rBJ)ybs_f1Rn&f2>q3pYwI^|J(hdn{j{0EZIm_F zpIyIWLsRUgOItR-dUbVd|6Zo=_BU_Tj4|{{jxO#=JH4o8er(5{!nZD_j4}MH&zh~9 zVLC~y(0-D6GO0ghZD8BYzP?o{>22~lT6^d@X{SwQ8vrNY-PPIMajIwC)`s14Ep72@ zeq7YOzM`?U{+W)ocXBr`eSOcpk?Rxc=ou5&)fWW|pD};-Z0mvk9}=&`Rb&y<77W~a z(>6YM;6Y5aIU~JKZ}mQZynKHiSTQ#Bczn@&jTiN^?vPJ(jhm7cXLx0oum5P$`TceG zU+wR;OO^)8CVlnM)5p$CO&e94KJt>HccCaHGusmW_b`T6m| z-R6V6Db1pErTot?^d22ojm+2>_)FbD`_+WbDGMx9f@hO27maS2`csiV(D&Fs`PS2& zvrq18du_&zXID(!KIxsU$)iuTYuZ?zmYiP&n&i@Be{IdbS-jA2c0QAlu5NXQv_0K< z3Hvs4eeu6B7yD&CNT~gIkMV&UkRU=V!iQ(+_(O&u^ah$+s{_yn(yBYeD40HeU{xGsIT6W Zfq!wOp!Q #include +#include "hid_icons.h" #define TAG "HidApp" @@ -19,7 +20,22 @@ enum HidDebugSubmenuIndex { HidSubmenuIndexRemovePairing, }; -static void bt_hid_remove_pairing(Bt* bt) { +bool hid_custom_event_callback(void* context, uint32_t event) { + furi_assert(context); + Hid* app = context; + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +bool hid_back_event_callback(void* context) { + furi_assert(context); + Hid* app = context; + FURI_LOG_D("HID", "Back event"); + scene_manager_next_scene(app->scene_manager, HidSceneExitConfirm); + return true; +} + +void bt_hid_remove_pairing(Hid* app) { + Bt* bt = app->bt; bt_disconnect(bt); // Wait 2nd core to update nvm storage @@ -62,7 +78,7 @@ static void hid_submenu_callback(void* context, uint32_t index) { app->view_id = HidViewMouseJiggler; view_dispatcher_switch_to_view(app->view_dispatcher, HidViewMouseJiggler); } else if(index == HidSubmenuIndexRemovePairing) { - bt_hid_remove_pairing(app->bt); + scene_manager_next_scene(app->scene_manager, HidSceneUnpair); } } @@ -86,23 +102,6 @@ static void bt_hid_connection_status_changed_callback(BtStatus status, void* con hid_tiktok_set_connected_status(hid->hid_tiktok, connected); } -static void hid_dialog_callback(DialogExResult result, void* context) { - furi_assert(context); - Hid* app = context; - if(result == DialogExResultLeft) { - view_dispatcher_stop(app->view_dispatcher); - } else if(result == DialogExResultRight) { - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view - } else if(result == DialogExResultCenter) { - view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); - } -} - -static uint32_t hid_exit_confirm_view(void* context) { - UNUSED(context); - return HidViewExitConfirm; -} - static uint32_t hid_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -124,6 +123,12 @@ Hid* hid_alloc() { app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, hid_back_event_callback); + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + + // Scene Manager + app->scene_manager = scene_manager_alloc(&hid_scene_handlers, app); + // Device Type Submenu view app->device_type_submenu = submenu_alloc(); submenu_add_item( @@ -172,58 +177,48 @@ Hid* hid_alloc() { view_dispatcher_add_view( app->view_dispatcher, HidViewSubmenu, submenu_get_view(app->device_type_submenu)); app->view_id = HidViewSubmenu; - view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); return app; } Hid* hid_app_alloc_view(void* context) { furi_assert(context); Hid* app = context; + // Dialog view app->dialog = dialog_ex_alloc(); - dialog_ex_set_result_callback(app->dialog, hid_dialog_callback); - dialog_ex_set_context(app->dialog, app); - dialog_ex_set_left_button_text(app->dialog, "Exit"); - dialog_ex_set_right_button_text(app->dialog, "Stay"); - dialog_ex_set_center_button_text(app->dialog, "Menu"); - dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); - view_dispatcher_add_view( - app->view_dispatcher, HidViewExitConfirm, dialog_ex_get_view(app->dialog)); + view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog)); + + // Popup view + app->popup = popup_alloc(); + view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup)); // Keynote view app->hid_keynote = hid_keynote_alloc(app); - view_set_previous_callback(hid_keynote_get_view(app->hid_keynote), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeynote, hid_keynote_get_view(app->hid_keynote)); // Keyboard view app->hid_keyboard = hid_keyboard_alloc(app); - view_set_previous_callback(hid_keyboard_get_view(app->hid_keyboard), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewKeyboard, hid_keyboard_get_view(app->hid_keyboard)); // Media view app->hid_media = hid_media_alloc(app); - view_set_previous_callback(hid_media_get_view(app->hid_media), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMedia, hid_media_get_view(app->hid_media)); // TikTok view app->hid_tiktok = hid_tiktok_alloc(app); - view_set_previous_callback(hid_tiktok_get_view(app->hid_tiktok), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, BtHidViewTikTok, hid_tiktok_get_view(app->hid_tiktok)); // Mouse view app->hid_mouse = hid_mouse_alloc(app); - view_set_previous_callback(hid_mouse_get_view(app->hid_mouse), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouse, hid_mouse_get_view(app->hid_mouse)); // Mouse clicker view app->hid_mouse_clicker = hid_mouse_clicker_alloc(app); - view_set_previous_callback( - hid_mouse_clicker_get_view(app->hid_mouse_clicker), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseClicker, @@ -231,8 +226,6 @@ Hid* hid_app_alloc_view(void* context) { // Mouse jiggler view app->hid_mouse_jiggler = hid_mouse_jiggler_alloc(app); - view_set_previous_callback( - hid_mouse_jiggler_get_view(app->hid_mouse_jiggler), hid_exit_confirm_view); view_dispatcher_add_view( app->view_dispatcher, HidViewMouseJiggler, @@ -251,8 +244,10 @@ void hid_free(Hid* app) { // Free views view_dispatcher_remove_view(app->view_dispatcher, HidViewSubmenu); submenu_free(app->device_type_submenu); - view_dispatcher_remove_view(app->view_dispatcher, HidViewExitConfirm); + view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); + popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); hid_keynote_free(app->hid_keynote); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeyboard); @@ -267,6 +262,7 @@ void hid_free(Hid* app) { hid_mouse_jiggler_free(app->hid_mouse_jiggler); view_dispatcher_remove_view(app->view_dispatcher, BtHidViewTikTok); hid_tiktok_free(app->hid_tiktok); + scene_manager_free(app->scene_manager); view_dispatcher_free(app->view_dispatcher); // Close records @@ -285,6 +281,8 @@ int32_t hid_usb_app(void* p) { UNUSED(p); Hid* app = hid_alloc(); app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as USB app"); + FuriHalUsbInterface* usb_mode_prev = furi_hal_usb_get_config(); furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_hid, NULL) == true); @@ -293,6 +291,8 @@ int32_t hid_usb_app(void* p) { dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); furi_hal_usb_set_config(usb_mode_prev, NULL); @@ -307,6 +307,8 @@ int32_t hid_ble_app(void* p) { Hid* app = hid_alloc(); app = hid_app_alloc_view(app); + FURI_LOG_D("HID", "Starting as BLE app"); + bt_disconnect(app->bt); // Wait 2nd core to update nvm storage @@ -333,6 +335,8 @@ int32_t hid_ble_app(void* p) { dolphin_deed(DolphinDeedPluginStart); + scene_manager_next_scene(app->scene_manager, HidSceneMain); + view_dispatcher_run(app->view_dispatcher); bt_set_status_changed_callback(app->bt, NULL, NULL); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index e6e974f308e..a79b2bcd3e9 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,8 @@ #include "views/hid_mouse_jiggler.h" #include "views/hid_tiktok.h" +#include "scenes/hid_scene.h" + #define HID_BT_KEYS_STORAGE_NAME ".bt_hid.keys" typedef enum { @@ -40,8 +43,10 @@ struct Hid { Gui* gui; NotificationApp* notifications; ViewDispatcher* view_dispatcher; + SceneManager* scene_manager; Submenu* device_type_submenu; DialogEx* dialog; + Popup* popup; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; HidMedia* hid_media; @@ -53,6 +58,7 @@ struct Hid { HidTransport transport; uint32_t view_id; }; +void bt_hid_remove_pairing(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); void hid_hal_keyboard_release(Hid* instance, uint16_t event); diff --git a/applications/system/hid_app/scenes/hid_scene.c b/applications/system/hid_app/scenes/hid_scene.c new file mode 100644 index 00000000000..89399a809e5 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.c @@ -0,0 +1,30 @@ +#include "hid_scene.h" + +// Generate scene on_enter handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter, +void (*const hid_on_enter_handlers[])(void*) = { +#include "hid_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_event handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event, +bool (*const hid_on_event_handlers[])(void* context, SceneManagerEvent event) = { +#include "hid_scene_config.h" +}; +#undef ADD_SCENE + +// Generate scene on_exit handlers array +#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit, +void (*const hid_on_exit_handlers[])(void* context) = { +#include "hid_scene_config.h" +}; +#undef ADD_SCENE + +// Initialize scene handlers configuration structure +const SceneManagerHandlers hid_scene_handlers = { + .on_enter_handlers = hid_on_enter_handlers, + .on_event_handlers = hid_on_event_handlers, + .on_exit_handlers = hid_on_exit_handlers, + .scene_num = HidSceneNum, +}; diff --git a/applications/system/hid_app/scenes/hid_scene.h b/applications/system/hid_app/scenes/hid_scene.h new file mode 100644 index 00000000000..9a2e6bb32f5 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +// Generate scene id and total number +#define ADD_SCENE(prefix, name, id) HidScene##id, +typedef enum { +#include "hid_scene_config.h" + HidSceneNum, +} HidScene; +#undef ADD_SCENE + +extern const SceneManagerHandlers hid_scene_handlers; + +// Generate scene on_enter handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*); +#include "hid_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_event handlers declaration +#define ADD_SCENE(prefix, name, id) \ + bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); +#include "hid_scene_config.h" +#undef ADD_SCENE + +// Generate scene on_exit handlers declaration +#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context); +#include "hid_scene_config.h" +#undef ADD_SCENE diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h new file mode 100644 index 00000000000..8f3a788d1fa --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -0,0 +1,3 @@ +ADD_SCENE(hid, main, Main) +ADD_SCENE(hid, unpair, Unpair) +ADD_SCENE(hid, exit_confirm, ExitConfirm) \ No newline at end of file diff --git a/applications/system/hid_app/scenes/hid_scene_exit_confirm.c b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c new file mode 100644 index 00000000000..94e783e939c --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_exit_confirm.c @@ -0,0 +1,45 @@ +#include "../hid.h" +#include "../views.h" + +static void hid_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) { + furi_assert(context); + Hid* app = context; + if(result == DialogExResultLeft) { + view_dispatcher_stop(app->view_dispatcher); + } else if(result == DialogExResultRight) { + scene_manager_previous_scene(app->scene_manager); + } else if(result == DialogExResultCenter) { + scene_manager_search_and_switch_to_previous_scene(app->scene_manager, HidSceneMain); + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewSubmenu); + } +} + +void hid_scene_exit_confirm_on_enter(void* context) { + Hid* app = context; + + // Exit dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_exit_confirm_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_left_button_text(app->dialog, "Exit"); + dialog_ex_set_right_button_text(app->dialog, "Stay"); + dialog_ex_set_center_button_text(app->dialog, "Menu"); + dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_exit_confirm_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); +} diff --git a/applications/system/hid_app/scenes/hid_scene_main.c b/applications/system/hid_app/scenes/hid_scene_main.c new file mode 100644 index 00000000000..6c4a1168208 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_main.c @@ -0,0 +1,22 @@ +#include "../hid.h" +#include "../views.h" + +void hid_scene_main_on_enter(void* context) { + Hid* app = context; + + view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); +} + +bool hid_scene_main_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_main_on_exit(void* context) { + Hid* app = context; + UNUSED(app); +} diff --git a/applications/system/hid_app/scenes/hid_scene_unpair.c b/applications/system/hid_app/scenes/hid_scene_unpair.c new file mode 100644 index 00000000000..7b0bbd9e62e --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_unpair.c @@ -0,0 +1,63 @@ +#include "../hid.h" +#include "../views.h" +#include "hid_icons.h" + +static void hid_scene_unpair_dialog_callback(DialogExResult result, void* context) { + Hid* app = context; + + if(result == DialogExResultRight) { + // Unpair all devices + bt_hid_remove_pairing(app); + + // Show popup + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup); + } else if(result == DialogExResultLeft) { + scene_manager_previous_scene(app->scene_manager); + } +} + +void hid_scene_unpair_popup_callback(void* context) { + Hid* app = context; + + scene_manager_previous_scene(app->scene_manager); +} + +void hid_scene_unpair_on_enter(void* context) { + Hid* app = context; + + // Un-pair dialog view + dialog_ex_reset(app->dialog); + dialog_ex_set_result_callback(app->dialog, hid_scene_unpair_dialog_callback); + dialog_ex_set_context(app->dialog, app); + dialog_ex_set_header(app->dialog, "Unpair All Devices?", 64, 3, AlignCenter, AlignTop); + dialog_ex_set_text( + app->dialog, "All previous pairings\nwill be lost!", 64, 22, AlignCenter, AlignTop); + dialog_ex_set_left_button_text(app->dialog, "Back"); + dialog_ex_set_right_button_text(app->dialog, "Unpair"); + + // Un-pair success popup view + popup_set_icon(app->popup, 32, 5, &I_DolphinNice_96x59); + popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); + popup_set_timeout(app->popup, 1500); + popup_set_context(app->popup, app); + popup_set_callback(app->popup, hid_scene_unpair_popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewDialog); +} + +bool hid_scene_unpair_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + UNUSED(app); + UNUSED(event); + + return consumed; +} + +void hid_scene_unpair_on_exit(void* context) { + Hid* app = context; + + dialog_ex_reset(app->dialog); + popup_reset(app->popup); +} diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 1bea3355e0d..f94a55cc65b 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -7,5 +7,6 @@ typedef enum { HidViewMouseClicker, HidViewMouseJiggler, BtHidViewTikTok, - HidViewExitConfirm, + HidViewDialog, + HidViewPopup, } HidView; \ No newline at end of file From 4e1089ec49e2a300ab36ea68d824b4cb2fee025d Mon Sep 17 00:00:00 2001 From: Violet Shreve Date: Sun, 18 Feb 2024 04:20:27 -0500 Subject: [PATCH 46/58] Quote $FBT_TOOLCHAIN_PATH to avoid splitting (#3459) --- scripts/toolchain/fbtenv.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index 27fb8f975b5..ef7768e826a 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -203,7 +203,7 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; - rm $FBT_TOOLCHAIN_PATH/toolchain/current || true; + rm "$FBT_TOOLCHAIN_PATH/toolchain/current" || true; tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; From bc309cebe610c68704d9ca3eab66c6e258da6738 Mon Sep 17 00:00:00 2001 From: hedger Date: Mon, 26 Feb 2024 16:16:19 +0400 Subject: [PATCH 47/58] Toolchain fixes (#3451) toolchain: updated to v33 with debugging & other fixes toolchain: better error handling during update/env configuration process debugging: improved udev rules file, added readme on installation firmware: bumped compiler C/C++ standards (stricter code checks) firmware: fixed warnings emerging from newer standards ufbt: FBT_NOENV is now also supported by ufbt fbt: added ccache-related variables to env forward list on Windows --- .../debug/accessor/accessor_view_manager.cpp | 2 +- .../debug/accessor/callback_connector.h | 15 +++++++++++---- .../debug/accessor/helpers/wiegand.cpp | 13 +++++-------- applications/debug/accessor/helpers/wiegand.h | 14 ++++++-------- .../accessor/scene/accessor_scene_start.cpp | 2 +- .../debug/battery_test_app/battery_test_app.c | 3 ++- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 6 ++++-- .../app_api_table.cpp | 4 ++-- .../main/nfc/api/nfc_app_api_table.cpp | 4 ++-- .../loader/firmware_api/firmware_api.cpp | 8 ++++---- .../js_app/plugin_api/app_api_table.cpp | 4 ++-- fbt | 5 +---- fbt.cmd | 2 +- .../debug/{41-udev.rules => 41-flipper.rules} | 17 ++++++++++++----- scripts/debug/README.md | 19 +++++++++++++++++++ scripts/fbt/util.py | 2 ++ scripts/toolchain/fbtenv.cmd | 4 +++- scripts/toolchain/fbtenv.sh | 17 +++++++++++++++-- .../toolchain/windows-toolchain-download.ps1 | 14 +++++++++++--- site_scons/cc.scons | 4 ++-- targets/f7/furi_hal/furi_hal_ibutton.c | 3 ++- targets/f7/furi_hal/furi_hal_infrared.c | 10 +++++++--- targets/f7/furi_hal/furi_hal_nfc_irq.c | 3 ++- targets/f7/furi_hal/furi_hal_rfid.c | 3 ++- targets/f7/furi_hal/furi_hal_spi.c | 3 ++- targets/f7/furi_hal/furi_hal_subghz.c | 6 ++++-- targets/f7/src/update.c | 2 +- 27 files changed, 126 insertions(+), 63 deletions(-) rename lib/callback-connector/callback-connector.h => applications/debug/accessor/callback_connector.h (90%) rename scripts/debug/{41-udev.rules => 41-flipper.rules} (72%) create mode 100644 scripts/debug/README.md diff --git a/applications/debug/accessor/accessor_view_manager.cpp b/applications/debug/accessor/accessor_view_manager.cpp index db723d68c23..955c0b28673 100644 --- a/applications/debug/accessor/accessor_view_manager.cpp +++ b/applications/debug/accessor/accessor_view_manager.cpp @@ -1,6 +1,6 @@ #include "accessor_view_manager.h" #include "accessor_event.h" -#include +#include "callback_connector.h" AccessorAppViewManager::AccessorAppViewManager() { event_queue = furi_message_queue_alloc(10, sizeof(AccessorEvent)); diff --git a/lib/callback-connector/callback-connector.h b/applications/debug/accessor/callback_connector.h similarity index 90% rename from lib/callback-connector/callback-connector.h rename to applications/debug/accessor/callback_connector.h index 10fec3e2d0e..dd99c5b261a 100644 --- a/lib/callback-connector/callback-connector.h +++ b/applications/debug/accessor/callback_connector.h @@ -1,10 +1,13 @@ #ifndef CALLBACKCONNECTOR_H #define CALLBACKCONNECTOR_H + +#ifdef __cplusplus #include namespace cbc { namespace Details { -template class FuncMemberWrapper { +template +class FuncMemberWrapper { public: FuncMemberWrapper() = delete; using member_fun_t = Ret (T::*)(Args...); @@ -43,7 +46,8 @@ template typename FuncMemberWrapper::const_member_fun_t FuncMemberWrapper::const_member{}; -template struct FunctorWrapper { +template +struct FunctorWrapper { public: static std::function functor; static auto instatiate(Functor fn) { @@ -75,7 +79,8 @@ auto const_instantiate(T* t, Ret (T::*ptr)(Args...) const) { return FuncMemberWrapper::instantiate(t, ptr); } -template auto const_instantiate(T* t, Func ptr) { +template +auto const_instantiate(T* t, Func ptr) { return const_instantiate(t, ptr); } @@ -91,9 +96,11 @@ auto obtain_connector(T* t, Ret (T::*ptr)(Args...) const) { return Details::FuncMemberWrapper::instantiate(t, ptr); } -template auto obtain_connector(Functor functor) { +template +auto obtain_connector(Functor functor) { return Details::deducer(std::move(functor), &Functor::operator()); } } //end of cbc scope +#endif // __cplusplus #endif // CALLBACKCONNECTOR_H diff --git a/applications/debug/accessor/helpers/wiegand.cpp b/applications/debug/accessor/helpers/wiegand.cpp index 10b284eaacd..f20b09120ef 100644 --- a/applications/debug/accessor/helpers/wiegand.cpp +++ b/applications/debug/accessor/helpers/wiegand.cpp @@ -2,12 +2,12 @@ #include #include -volatile unsigned long WIEGAND::_cardTempHigh = 0; -volatile unsigned long WIEGAND::_cardTemp = 0; -volatile unsigned long WIEGAND::_lastWiegand = 0; +unsigned long WIEGAND::_cardTempHigh = 0; +unsigned long WIEGAND::_cardTemp = 0; +unsigned long WIEGAND::_lastWiegand = 0; unsigned long WIEGAND::_code = 0; unsigned long WIEGAND::_codeHigh = 0; -volatile int WIEGAND::_bitCount = 0; +int WIEGAND::_bitCount = 0; int WIEGAND::_wiegandType = 0; constexpr uint32_t clocks_in_ms = 64 * 1000; @@ -98,10 +98,7 @@ void WIEGAND::ReadD1() { _lastWiegand = DWT->CYCCNT; // Keep track of last wiegand bit received } -unsigned long WIEGAND::GetCardId( - volatile unsigned long* codehigh, - volatile unsigned long* codelow, - char bitlength) { +unsigned long WIEGAND::GetCardId(unsigned long* codehigh, unsigned long* codelow, char bitlength) { if(bitlength == 26) // EM tag return (*codelow & 0x1FFFFFE) >> 1; diff --git a/applications/debug/accessor/helpers/wiegand.h b/applications/debug/accessor/helpers/wiegand.h index 8127f42865c..be80f94cd51 100644 --- a/applications/debug/accessor/helpers/wiegand.h +++ b/applications/debug/accessor/helpers/wiegand.h @@ -15,15 +15,13 @@ class WIEGAND { private: static bool DoWiegandConversion(); - static unsigned long GetCardId( - volatile unsigned long* codehigh, - volatile unsigned long* codelow, - char bitlength); + static unsigned long + GetCardId(unsigned long* codehigh, unsigned long* codelow, char bitlength); - static volatile unsigned long _cardTempHigh; - static volatile unsigned long _cardTemp; - static volatile unsigned long _lastWiegand; - static volatile int _bitCount; + static unsigned long _cardTempHigh; + static unsigned long _cardTemp; + static unsigned long _lastWiegand; + static int _bitCount; static int _wiegandType; static unsigned long _code; static unsigned long _codeHigh; diff --git a/applications/debug/accessor/scene/accessor_scene_start.cpp b/applications/debug/accessor/scene/accessor_scene_start.cpp index 6f5a4d112b9..d31001d2d7a 100644 --- a/applications/debug/accessor/scene/accessor_scene_start.cpp +++ b/applications/debug/accessor/scene/accessor_scene_start.cpp @@ -1,7 +1,7 @@ #include "../accessor_app.h" #include "../accessor_view_manager.h" #include "../accessor_event.h" -#include +#include "callback_connector.h" #include "accessor_scene_start.h" void AccessorSceneStart::on_enter(AccessorApp* app) { diff --git a/applications/debug/battery_test_app/battery_test_app.c b/applications/debug/battery_test_app/battery_test_app.c index eabf3c04b44..82c814ef4a0 100644 --- a/applications/debug/battery_test_app/battery_test_app.c +++ b/applications/debug/battery_test_app/battery_test_app.c @@ -12,7 +12,8 @@ void battery_test_dialog_callback(DialogExResult result, void* context) { } } -uint32_t battery_test_exit_confirm_view() { +uint32_t battery_test_exit_confirm_view(void* context) { + UNUSED(context); return BatteryTestAppViewExitDialog; } diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index f8ce82bf311..1242b8857a5 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -495,7 +495,8 @@ static bool subghz_device_cc1101_ext_stop_debug() { return ret; } -static void subghz_device_cc1101_ext_capture_ISR() { +static void subghz_device_cc1101_ext_capture_ISR(void* context) { + UNUSED(context); if(!furi_hal_gpio_read(subghz_device_cc1101_ext->g0_pin)) { if(subghz_device_cc1101_ext->async_rx.capture_callback) { if(subghz_device_cc1101_ext->async_mirror_pin != NULL) @@ -674,7 +675,8 @@ static void subghz_device_cc1101_ext_async_tx_refill(uint32_t* buffer, size_t sa } } -static void subghz_device_cc1101_ext_async_tx_dma_isr() { +static void subghz_device_cc1101_ext_async_tx_dma_isr(void* context) { + UNUSED(context); furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateAsyncTx); #if SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_CHANNEL == LL_DMA_CHANNEL_3 diff --git a/applications/examples/example_plugins_advanced/app_api_table.cpp b/applications/examples/example_plugins_advanced/app_api_table.cpp index aacfb8c181d..db15c84d188 100644 --- a/applications/examples/example_plugins_advanced/app_api_table.cpp +++ b/applications/examples/example_plugins_advanced/app_api_table.cpp @@ -18,8 +18,8 @@ constexpr HashtableApiInterface applicaton_hashtable_api_interface{ .resolver_callback = &elf_resolve_from_hashtable, }, /* pointers to application's API table boundaries */ - .table_cbegin = app_api_table.cbegin(), - .table_cend = app_api_table.cend(), + app_api_table.cbegin(), + app_api_table.cend(), }; /* Casting to generic resolver to use in Composite API resolver */ diff --git a/applications/main/nfc/api/nfc_app_api_table.cpp b/applications/main/nfc/api/nfc_app_api_table.cpp index ca190665bba..6a769c9eba5 100644 --- a/applications/main/nfc/api/nfc_app_api_table.cpp +++ b/applications/main/nfc/api/nfc_app_api_table.cpp @@ -18,8 +18,8 @@ constexpr HashtableApiInterface nfc_application_hashtable_api_interface{ .resolver_callback = &elf_resolve_from_hashtable, }, /* pointers to application's API table boundaries */ - .table_cbegin = nfc_app_api_table.cbegin(), - .table_cend = nfc_app_api_table.cend(), + nfc_app_api_table.cbegin(), + nfc_app_api_table.cend(), }; /* Casting to generic resolver to use in Composite API resolver */ diff --git a/applications/services/loader/firmware_api/firmware_api.cpp b/applications/services/loader/firmware_api/firmware_api.cpp index 47554f6280c..833f99abe40 100644 --- a/applications/services/loader/firmware_api/firmware_api.cpp +++ b/applications/services/loader/firmware_api/firmware_api.cpp @@ -17,8 +17,8 @@ constexpr HashtableApiInterface mock_elf_api_interface{ .api_version_minor = 0, .resolver_callback = &elf_resolve_from_hashtable, }, - .table_cbegin = nullptr, - .table_cend = nullptr, + nullptr, + nullptr, }; const ElfApiInterface* const firmware_api_interface = &mock_elf_api_interface; @@ -29,8 +29,8 @@ constexpr HashtableApiInterface elf_api_interface{ .api_version_minor = (elf_api_version & 0xFFFF), .resolver_callback = &elf_resolve_from_hashtable, }, - .table_cbegin = elf_api_table.cbegin(), - .table_cend = elf_api_table.cend(), + elf_api_table.cbegin(), + elf_api_table.cend(), }; const ElfApiInterface* const firmware_api_interface = &elf_api_interface; #endif diff --git a/applications/system/js_app/plugin_api/app_api_table.cpp b/applications/system/js_app/plugin_api/app_api_table.cpp index aacfb8c181d..db15c84d188 100644 --- a/applications/system/js_app/plugin_api/app_api_table.cpp +++ b/applications/system/js_app/plugin_api/app_api_table.cpp @@ -18,8 +18,8 @@ constexpr HashtableApiInterface applicaton_hashtable_api_interface{ .resolver_callback = &elf_resolve_from_hashtable, }, /* pointers to application's API table boundaries */ - .table_cbegin = app_api_table.cbegin(), - .table_cend = app_api_table.cend(), + app_api_table.cbegin(), + app_api_table.cend(), }; /* Casting to generic resolver to use in Composite API resolver */ diff --git a/fbt b/fbt index e6133d07b17..efb031eb26f 100755 --- a/fbt +++ b/fbt @@ -12,15 +12,12 @@ SCONS_DEFAULT_FLAGS="--warn=target-not-built"; SCONS_EP="python3 -m SCons"; # public variables -FBT_NOENV="${FBT_NOENV:-""}"; FBT_NO_SYNC="${FBT_NO_SYNC:-""}"; FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}"; FBT_VERBOSE="${FBT_VERBOSE:-""}"; FBT_GIT_SUBMODULE_SHALLOW="${FBT_GIT_SUBMODULE_SHALLOW:-""}"; -if [ -z "$FBT_NOENV" ]; then - FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; -fi +FBT_VERBOSE="$FBT_VERBOSE" . "$SCRIPT_PATH/scripts/toolchain/fbtenv.sh"; if [ -z "$FBT_VERBOSE" ]; then SCONS_DEFAULT_FLAGS="$SCONS_DEFAULT_FLAGS -Q"; diff --git a/fbt.cmd b/fbt.cmd index 20432be1c53..d8e42f80c59 100644 --- a/fbt.cmd +++ b/fbt.cmd @@ -1,5 +1,5 @@ @echo off -call "%~dp0scripts\toolchain\fbtenv.cmd" env +call "%~dp0scripts\toolchain\fbtenv.cmd" env || exit /b set SCONS_EP=python -m SCons diff --git a/scripts/debug/41-udev.rules b/scripts/debug/41-flipper.rules similarity index 72% rename from scripts/debug/41-udev.rules rename to scripts/debug/41-flipper.rules index 6b4b37b9204..8663e99ab5c 100644 --- a/scripts/debug/41-udev.rules +++ b/scripts/debug/41-flipper.rules @@ -1,10 +1,17 @@ -#Flipper Zero serial port +# Flipper Zero serial port SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess", GROUP="dialout" -#Flipper Zero DFU + +# Flipper Zero DFU SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", ATTRS{manufacturer}=="STMicroelectronics", TAG+="uaccess", GROUP="dialout" -#Flipper ESP32s2 BlackMagic + +# Flipper ESP32s2 BlackMagic SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="40??", ATTRS{manufacturer}=="Flipper Devices Inc.", TAG+="uaccess", GROUP="dialout" -#Flipper U2F + +# Flipper ESP32s2 in DAP mode +SUBSYSTEMS=="usb", ATTRS{idVendor}=="303a", ATTRS{idProduct}=="40??", ATTRS{manufacturer}=="CMSIS-DAP", TAG+="uaccess", GROUP="dialout" + +# Flipper U2F SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5741", ATTRS{manufacturer}=="Flipper Devices Inc.", ENV{ID_SECURITY_TOKEN}="1" -#ST-Link-V3 + +# ST-Link-V3 SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="37??", ATTRS{manufacturer}=="STMicroelectronics", TAG+="uaccess", GROUP="dialout" diff --git a/scripts/debug/README.md b/scripts/debug/README.md new file mode 100644 index 00000000000..59f2bf0c180 --- /dev/null +++ b/scripts/debug/README.md @@ -0,0 +1,19 @@ +## Installing udev rules + +On Linux, unprivileged users need to be in the `dialout` group to access serial ports and other USB devices. + +To add your user to the `dialout` group, run the following command: + +```bash +sudo usermod -a -G dialout $USER +``` + +To install the udev rules needed for debugging & CLI access to Flipper, run the following command: + +```bash +sudo cp 41-flipper.rules /etc/udev/rules.d/ +sudo udevadm control --reload-rules +sudo udevadm trigger +``` + +Note that not all possible debug interfaces are listed the `41-flipper.rules` file. If your interface is not supported out of the box, you may need to add a a rule for it. You can do so by adding a new line to the file according to udev rules syntax. Use `lsusb -v` to find the vendor and product IDs of your device. \ No newline at end of file diff --git a/scripts/fbt/util.py b/scripts/fbt/util.py index 62946756823..a6a6313030c 100644 --- a/scripts/fbt/util.py +++ b/scripts/fbt/util.py @@ -23,6 +23,8 @@ "PYTHONNOUSERSITE", "TMP", "TEMP", + "USERPROFILE", + "LOCALAPPDATA", # ccache "CCACHE_DISABLE", # Colors for tools diff --git a/scripts/toolchain/fbtenv.cmd b/scripts/toolchain/fbtenv.cmd index 446f04aa7d8..29b48369a1a 100644 --- a/scripts/toolchain/fbtenv.cmd +++ b/scripts/toolchain/fbtenv.cmd @@ -13,7 +13,7 @@ if not ["%FBT_NOENV%"] == [""] ( exit /b 0 ) -set "FLIPPER_TOOLCHAIN_VERSION=28" +set "FLIPPER_TOOLCHAIN_VERSION=33" if ["%FBT_TOOLCHAIN_PATH%"] == [""] ( set "FBT_TOOLCHAIN_PATH=%FBT_ROOT%" @@ -58,3 +58,5 @@ if not "%1" == "env" ( cd "%FBT_ROOT%" cmd /k ) + +exit /b 0 diff --git a/scripts/toolchain/fbtenv.sh b/scripts/toolchain/fbtenv.sh index ef7768e826a..495cdba6f91 100755 --- a/scripts/toolchain/fbtenv.sh +++ b/scripts/toolchain/fbtenv.sh @@ -4,7 +4,7 @@ # public variables DEFAULT_SCRIPT_PATH="$(pwd -P)"; -FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"28"}"; +FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"33"}"; if [ -z ${FBT_TOOLCHAIN_PATH+x} ] ; then FBT_TOOLCHAIN_PATH_WAS_SET=0; @@ -76,6 +76,14 @@ fbtenv_restore_env() unset FBT_TOOLCHAIN_PATH; } +fbtenv_check_if_noenv_set() +{ + if [ -n "${FBT_NOENV:-""}" ]; then + return 1; + fi + return 0; +} + fbtenv_check_sourced() { if [ -n "${FBT_SKIP_CHECK_SOURCED:-""}" ]; then @@ -203,7 +211,9 @@ fbtenv_show_unpack_percentage() fbtenv_unpack_toolchain() { echo "Unpacking toolchain to '$FBT_TOOLCHAIN_PATH/toolchain':"; - rm "$FBT_TOOLCHAIN_PATH/toolchain/current" || true; + if [ -L "$FBT_TOOLCHAIN_PATH/toolchain/current" ]; then + rm "$FBT_TOOLCHAIN_PATH/toolchain/current"; + fi tar -xvf "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_TAR" -C "$FBT_TOOLCHAIN_PATH/toolchain" 2>&1 | fbtenv_show_unpack_percentage; mkdir -p "$FBT_TOOLCHAIN_PATH/toolchain" || return 1; mv "$FBT_TOOLCHAIN_PATH/toolchain/$TOOLCHAIN_DIR" "$TOOLCHAIN_ARCH_DIR" || return 1; @@ -296,6 +306,9 @@ fbtenv_print_config() fbtenv_main() { + if ! fbtenv_check_if_noenv_set; then + return 0; + fi fbtenv_check_sourced || return 1; fbtenv_get_kernel_type || return 1; if [ "$1" = "--restore" ]; then diff --git a/scripts/toolchain/windows-toolchain-download.ps1 b/scripts/toolchain/windows-toolchain-download.ps1 index 11d8a564d30..025f8341f79 100644 --- a/scripts/toolchain/windows-toolchain-download.ps1 +++ b/scripts/toolchain/windows-toolchain-download.ps1 @@ -13,19 +13,21 @@ $toolchain_zip = "$toolchain_dist_folder-$toolchain_version.zip" $toolchain_zip_temp_path = "$download_dir\$toolchain_zip" $toolchain_dist_temp_path = "$download_dir\$toolchain_dist_folder" +try { + if (Test-Path -LiteralPath "$toolchain_target_path") { Write-Host -NoNewline "Removing old Windows toolchain.." Remove-Item -LiteralPath "$toolchain_target_path" -Force -Recurse Write-Host "done!" } -if (Test-path -Path "$toolchain_target_path\..\current") { +if (Test-path -LiteralPath "$toolchain_target_path\..\current") { Write-Host -NoNewline "Unlinking 'current'.." Remove-Item -LiteralPath "$toolchain_target_path\..\current" -Force Write-Host "done!" } -if (!(Test-Path -Path "$toolchain_zip_temp_path" -PathType Leaf)) { +if (!(Test-Path -LiteralPath "$toolchain_zip_temp_path" -PathType Leaf)) { Write-Host -NoNewline "Downloading Windows toolchain.." $wc = New-Object net.webclient $wc.Downloadfile("$toolchain_url", "$toolchain_zip_temp_path") @@ -57,4 +59,10 @@ Write-Host -NoNewline "Cleaning up temporary files.." Remove-Item -LiteralPath "$toolchain_zip_temp_path" -Force Write-Host "done!" -# dasdasd +} catch { + Write-Host "An error occurred" + Write-Host $_ + Write-Host "Please close VSCode and any other programs that may be using the toolchain and try again." + $host.SetShouldExit(1) + Exit 1 +} diff --git a/site_scons/cc.scons b/site_scons/cc.scons index 029f9d415e9..507cd2d12c1 100644 --- a/site_scons/cc.scons +++ b/site_scons/cc.scons @@ -3,10 +3,10 @@ Import("ENV") ENV.AppendUnique( CFLAGS=[ - "-std=gnu17", + "-std=gnu2x", ], CXXFLAGS=[ - "-std=c++17", + "-std=c++20", "-fno-rtti", "-fno-use-cxa-atexit", "-fno-exceptions", diff --git a/targets/f7/furi_hal/furi_hal_ibutton.c b/targets/f7/furi_hal/furi_hal_ibutton.c index f8f7e4966dc..bf1127a857c 100644 --- a/targets/f7/furi_hal/furi_hal_ibutton.c +++ b/targets/f7/furi_hal/furi_hal_ibutton.c @@ -25,7 +25,8 @@ typedef struct { FuriHalIbutton* furi_hal_ibutton = NULL; -static void furi_hal_ibutton_emulate_isr() { +static void furi_hal_ibutton_emulate_isr(void* context) { + UNUSED(context); if(LL_TIM_IsActiveFlag_UPDATE(FURI_HAL_IBUTTON_TIMER)) { LL_TIM_ClearFlag_UPDATE(FURI_HAL_IBUTTON_TIMER); furi_hal_ibutton->callback(furi_hal_ibutton->context); diff --git a/targets/f7/furi_hal/furi_hal_infrared.c b/targets/f7/furi_hal/furi_hal_infrared.c index cc41568728b..af3c8fc6b3d 100644 --- a/targets/f7/furi_hal/furi_hal_infrared.c +++ b/targets/f7/furi_hal/furi_hal_infrared.c @@ -94,7 +94,9 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void); static void furi_hal_infrared_tx_dma_polarity_isr(); static void furi_hal_infrared_tx_dma_isr(); -static void furi_hal_infrared_tim_rx_isr() { +static void furi_hal_infrared_tim_rx_isr(void* context) { + UNUSED(context); + static uint32_t previous_captured_ch2 = 0; /* Timeout */ @@ -259,7 +261,8 @@ static uint8_t furi_hal_infrared_get_current_dma_tx_buffer(void) { return buf_num; } -static void furi_hal_infrared_tx_dma_polarity_isr() { +static void furi_hal_infrared_tx_dma_polarity_isr(void* context) { + UNUSED(context); #if INFRARED_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 if(LL_DMA_IsActiveFlag_TE1(INFRARED_DMA)) { LL_DMA_ClearFlag_TE1(INFRARED_DMA); @@ -281,7 +284,8 @@ static void furi_hal_infrared_tx_dma_polarity_isr() { #endif } -static void furi_hal_infrared_tx_dma_isr() { +static void furi_hal_infrared_tx_dma_isr(void* context) { + UNUSED(context); #if INFRARED_DMA_CH2_CHANNEL == LL_DMA_CHANNEL_2 if(LL_DMA_IsActiveFlag_TE2(INFRARED_DMA)) { LL_DMA_ClearFlag_TE2(INFRARED_DMA); diff --git a/targets/f7/furi_hal/furi_hal_nfc_irq.c b/targets/f7/furi_hal/furi_hal_nfc_irq.c index 170d8dee619..edacc52bfca 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_irq.c +++ b/targets/f7/furi_hal/furi_hal_nfc_irq.c @@ -3,7 +3,8 @@ #include #include -static void furi_hal_nfc_int_callback() { +static void furi_hal_nfc_int_callback(void* context) { + UNUSED(context); furi_hal_nfc_event_set(FuriHalNfcEventInternalTypeIrq); } diff --git a/targets/f7/furi_hal/furi_hal_rfid.c b/targets/f7/furi_hal/furi_hal_rfid.c index 67f11d6ff7b..c409329227a 100644 --- a/targets/f7/furi_hal/furi_hal_rfid.c +++ b/targets/f7/furi_hal/furi_hal_rfid.c @@ -315,7 +315,8 @@ void furi_hal_rfid_tim_read_capture_stop() { furi_hal_bus_disable(RFID_CAPTURE_TIM_BUS); } -static void furi_hal_rfid_dma_isr() { +static void furi_hal_rfid_dma_isr(void* context) { + UNUSED(context); #if RFID_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 if(LL_DMA_IsActiveFlag_HT1(RFID_DMA)) { LL_DMA_ClearFlag_HT1(RFID_DMA); diff --git a/targets/f7/furi_hal/furi_hal_spi.c b/targets/f7/furi_hal/furi_hal_spi.c index 98ca71af357..d295ad19686 100644 --- a/targets/f7/furi_hal/furi_hal_spi.c +++ b/targets/f7/furi_hal/furi_hal_spi.c @@ -169,7 +169,8 @@ bool furi_hal_spi_bus_trx( return ret; } -static void spi_dma_isr() { +static void spi_dma_isr(void* context) { + UNUSED(context); #if SPI_DMA_RX_CHANNEL == LL_DMA_CHANNEL_6 if(LL_DMA_IsActiveFlag_TC6(SPI_DMA) && LL_DMA_IsEnabledIT_TC(SPI_DMA_RX_DEF)) { LL_DMA_ClearFlag_TC6(SPI_DMA); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index e73a325aabc..392345b0ca9 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -413,7 +413,8 @@ volatile uint32_t furi_hal_subghz_capture_delta_duration = 0; volatile FuriHalSubGhzCaptureCallback furi_hal_subghz_capture_callback = NULL; volatile void* furi_hal_subghz_capture_callback_context = NULL; -static void furi_hal_subghz_capture_ISR() { +static void furi_hal_subghz_capture_ISR(void* context) { + UNUSED(context); // Channel 1 if(LL_TIM_IsActiveFlag_CC1(TIM2)) { LL_TIM_ClearFlag_CC1(TIM2); @@ -647,7 +648,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { } } -static void furi_hal_subghz_async_tx_dma_isr() { +static void furi_hal_subghz_async_tx_dma_isr(void* context) { + UNUSED(context); furi_assert(furi_hal_subghz.state == SubGhzStateAsyncTx); #if SUBGHZ_DMA_CH1_CHANNEL == LL_DMA_CHANNEL_1 diff --git a/targets/f7/src/update.c b/targets/f7/src/update.c index e9228a6e956..42ac90e7b91 100644 --- a/targets/f7/src/update.c +++ b/targets/f7/src/update.c @@ -18,7 +18,7 @@ static FATFS* pfs = NULL; #define CHECK_FRESULT(result) \ { \ if((result) != FR_OK) { \ - return false; \ + return 0; \ } \ } From 7fc4ba7c1c89b6be92cc0da69e1549dcebb26621 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 27 Feb 2024 14:56:11 +0100 Subject: [PATCH 48/58] FBT/uFBT: Enable C++20/GNU23 in VSCode IntelliSense (#3394) * FBT/uFBT: ms-vscode.cpptools can't be a configuration provider to itself * FBT/uFBT: Enable C++20/C23 in IntelliSense as the compilation options do --- .vscode/example/cpptools/c_cpp_properties.json | 15 ++++++--------- .vscode/example/settings.json | 4 ++-- .../.vscode/c_cpp_properties.json | 5 ++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.vscode/example/cpptools/c_cpp_properties.json b/.vscode/example/cpptools/c_cpp_properties.json index d1cac63e966..3f8d15a5d49 100644 --- a/.vscode/example/cpptools/c_cpp_properties.json +++ b/.vscode/example/cpptools/c_cpp_properties.json @@ -5,27 +5,24 @@ "compilerPath": "${workspaceFolder}/toolchain/x86_64-windows/bin/arm-none-eabi-gcc.exe", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", - "configurationProvider": "ms-vscode.cpptools", - "cStandard": "gnu17", - "cppStandard": "c++17" + "cStandard": "gnu23", + "cppStandard": "c++20" }, { "name": "Linux", "compilerPath": "${workspaceFolder}/toolchain/x86_64-linux/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", - "configurationProvider": "ms-vscode.cpptools", - "cStandard": "gnu17", - "cppStandard": "c++17" + "cStandard": "gnu23", + "cppStandard": "c++20" }, { "name": "Mac", "compilerPath": "${workspaceFolder}/toolchain/x86_64-darwin/bin/arm-none-eabi-gcc", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/build/latest/compile_commands.json", - "configurationProvider": "ms-vscode.cpptools", - "cStandard": "gnu17", - "cppStandard": "c++17" + "cStandard": "gnu23", + "cppStandard": "c++20" } ], "version": 4 diff --git a/.vscode/example/settings.json b/.vscode/example/settings.json index 8199171464b..00da3af2f81 100644 --- a/.vscode/example/settings.json +++ b/.vscode/example/settings.json @@ -1,6 +1,6 @@ { - "C_Cpp.default.cStandard": "gnu17", - "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.default.cStandard": "gnu23", + "C_Cpp.default.cppStandard": "c++20", "python.formatting.provider": "black", "workbench.tree.indent": 12, "cortex-debug.enableTelemetry": false, diff --git a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json index f957ee98bb9..7ab146375e7 100644 --- a/scripts/ufbt/project_template/.vscode/c_cpp_properties.json +++ b/scripts/ufbt/project_template/.vscode/c_cpp_properties.json @@ -5,9 +5,8 @@ "compilerPath": "@UFBT_TOOLCHAIN_GCC@", "intelliSenseMode": "gcc-arm", "compileCommands": "${workspaceFolder}/.vscode/compile_commands.json", - "configurationProvider": "ms-vscode.cpptools", - "cStandard": "gnu17", - "cppStandard": "c++17" + "cStandard": "gnu23", + "cppStandard": "c++20" } ], "version": 4 From 18ea2371a4964e881f60c9691f91616dcb2389b4 Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Thu, 29 Feb 2024 08:09:55 +1000 Subject: [PATCH 49/58] Gui: reset canvas orientation and frame when entering direct draw mode (#3488) --- applications/services/gui/gui.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/applications/services/gui/gui.c b/applications/services/gui/gui.c index 9deaf23fe4b..0e29a5a01fd 100644 --- a/applications/services/gui/gui.c +++ b/applications/services/gui/gui.c @@ -499,6 +499,8 @@ Canvas* gui_direct_draw_acquire(Gui* gui) { gui->direct_draw = true; gui_unlock(gui); + canvas_set_orientation(gui->canvas, CanvasOrientationHorizontal); + canvas_frame_set(gui->canvas, 0, 0, GUI_DISPLAY_WIDTH, GUI_DISPLAY_HEIGHT); canvas_reset(gui->canvas); canvas_commit(gui->canvas); From 4b7ca736d6ed4c3905dbc22433872cfb19787eaa Mon Sep 17 00:00:00 2001 From: Sergei Gavrilov Date: Fri, 1 Mar 2024 21:58:15 +1000 Subject: [PATCH 50/58] WiFi board: fixed update script on Windows (unfortunately also Mac and Linux) (#3485) * WiFi board update: windows fix * Scripts: add python\Scripts to the path * Windows: another way to call esptool * Aaaaaand once more! * logz * wifi board scripts: udev rule, dev channel * fbt: added ARGS variable for passing extra options to certain scripts; removed `devboard_flash_dev` in favor of `devboard_flash ARGS="-c dev"` * fbt: fully removed `devboard_flash_dev * scripts: wifi_board.py: cleanup * ufbt: ported ARGS for supported targets to ufbt * docs: updated for ARGS=... --------- Co-authored-by: hedger Co-authored-by: hedger --- SConstruct | 61 ++++++++++++-- documentation/fbt.md | 8 +- scripts/fbt_tools/fbt_dist.py | 3 + scripts/ufbt/SConstruct | 41 +++++++-- scripts/ufbt/site_tools/ufbt_help.py | 3 +- scripts/wifi_board.py | 120 +++++++++++++++------------ site_scons/commandline.scons | 5 ++ 7 files changed, 170 insertions(+), 71 deletions(-) diff --git a/SConstruct b/SConstruct index 6d24da920a6..2bc0128ccc5 100644 --- a/SConstruct +++ b/SConstruct @@ -66,6 +66,7 @@ if GetOption("fullenv") or any( # Target for self-update package dist_basic_arguments = [ + "${ARGS}", "--bundlever", "${UPDATE_VERSION_STRING}", ] @@ -182,6 +183,7 @@ fap_deploy = distenv.PhonyTarget( "send", "${SOURCE}", "/ext/apps", + "${ARGS}", ] ] ), @@ -208,7 +210,7 @@ distenv.Alias("jflash", firmware_jflash) distenv.PhonyTarget( "gdb_trace_all", - "$GDB $GDBOPTS $SOURCES $GDBFLASH", + [["${GDB}", "${GDBOPTS}", "${SOURCES}", "${GDBFLASH}"]], source=firmware_env["FW_ELF"], GDBOPTS="${GDBOPTS_BASE}", GDBREMOTE="${OPENOCD_GDB_PIPE}", @@ -272,19 +274,35 @@ distenv.PhonyTarget( # Just start OpenOCD distenv.PhonyTarget( "openocd", - "${OPENOCDCOM}", + [["${OPENOCDCOM}", "${ARGS}"]], ) # Linter distenv.PhonyTarget( "lint", - [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]], + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/lint.py", + "check", + "${LINT_SOURCES}", + "${ARGS}", + ] + ], LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) distenv.PhonyTarget( "format", - [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]], + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/lint.py", + "format", + "${LINT_SOURCES}", + "${ARGS}", + ] + ], LINT_SOURCES=[n.srcnode() for n in firmware_env["LINT_SOURCES"]], ) @@ -307,7 +325,16 @@ firmware_env.Append( ) -black_commandline = "@${PYTHON3} -m black ${PY_BLACK_ARGS} ${PY_LINT_SOURCES}" +black_commandline = [ + [ + "@${PYTHON3}", + "-m", + "black", + "${PY_BLACK_ARGS}", + "${PY_LINT_SOURCES}", + "${ARGS}", + ] +] black_base_args = [ "--include", '"(\\.scons|\\.py|SConscript|SConstruct|\\.fam)$"', @@ -333,12 +360,28 @@ distenv.PhonyTarget( # Start Flipper CLI via PySerial's miniterm distenv.PhonyTarget( - "cli", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]] + "cli", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/serial_cli.py", + "-p", + "${FLIP_PORT}", + "${ARGS}", + ] + ], ) -# Update WiFi devboard firmware +# Update WiFi devboard firmware with release channel distenv.PhonyTarget( - "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]] + "devboard_flash", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/wifi_board.py", + "${ARGS}", + ] + ], ) @@ -353,7 +396,7 @@ distenv.PhonyTarget( distenv.PhonyTarget( "get_stlink", distenv.Action( - lambda **kw: distenv.GetDevices(), + lambda **_: distenv.GetDevices(), None, ), ) diff --git a/documentation/fbt.md b/documentation/fbt.md index 02de2949fa8..0220178a2da 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -71,13 +71,13 @@ To use language servers other than the default VS Code C/C++ language server, us - `debug` - build and flash firmware, then attach with gdb with firmware's .elf loaded. - `debug_other`, `debug_other_blackmagic` - attach GDB without loading any `.elf`. It will allow you to manually add external `.elf` files with `add-symbol-file` in GDB. - `updater_debug` - attach GDB with the updater's `.elf` loaded. -- `devboard_flash` - update WiFi dev board with the latest firmware. +- `devboard_flash` - Update WiFi dev board. Supports `ARGS="..."` to pass extra arguments to the update script, e.g. `ARGS="-c dev"`. - `blackmagic` - debug firmware with Blackmagic probe (WiFi dev board). -- `openocd` - just start OpenOCD. +- `openocd` - just start OpenOCD. You can pass extra arguments with `ARGS="..."`. - `get_blackmagic` - output the blackmagic address in the GDB remote format. Useful for IDE integration. - `get_stlink` - output serial numbers for attached STLink probes. Used for specifying an adapter with `SWD_TRANSPORT_SERIAL=...`. -- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. -- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. +- `lint`, `format` - run clang-format on the C source code to check and reformat it according to the `.clang-format` specs. Supports `ARGS="..."` to pass extra arguments to clang-format. +- `lint_py`, `format_py` - run [black](https://black.readthedocs.io/en/stable/index.html) on the Python source code, build system files & application manifests. Supports `ARGS="..."` to pass extra arguments to black. - `firmware_pvs` - generate a PVS Studio report for the firmware. Requires PVS Studio to be available on your system's `PATH`. - `cli` - start a Flipper CLI session over USB. diff --git a/scripts/fbt_tools/fbt_dist.py b/scripts/fbt_tools/fbt_dist.py index bf586b8fbba..324a2281f55 100644 --- a/scripts/fbt_tools/fbt_dist.py +++ b/scripts/fbt_tools/fbt_dist.py @@ -150,6 +150,7 @@ def generate(env): "--interface=${SWD_TRANSPORT}", "--serial=${SWD_TRANSPORT_SERIAL}", "${SOURCE}", + "${ARGS}", ], Touch("${TARGET}"), ] @@ -162,6 +163,7 @@ def generate(env): "-p", "${FLIP_PORT}", "${UPDATE_BUNDLE_DIR}/update.fuf", + "${ARGS}", ], Touch("${TARGET}"), ] @@ -180,6 +182,7 @@ def generate(env): "--stack_type=${COPRO_STACK_TYPE}", "--stack_file=${COPRO_STACK_BIN}", "--stack_addr=${COPRO_STACK_ADDR}", + "${ARGS}", ] ], "${COPROCOMSTR}", diff --git a/scripts/ufbt/SConstruct b/scripts/ufbt/SConstruct index a26c2e404a5..8178a83da4e 100644 --- a/scripts/ufbt/SConstruct +++ b/scripts/ufbt/SConstruct @@ -315,26 +315,56 @@ else: appenv.PhonyTarget( "cli", - [["${PYTHON3}", "${FBT_SCRIPT_DIR}/serial_cli.py", "-p", "${FLIP_PORT}"]], + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/serial_cli.py", + "-p", + "${FLIP_PORT}", + "${ARGS}", + ] + ], ) # Update WiFi devboard firmware dist_env.PhonyTarget( - "devboard_flash", [["${PYTHON3}", "${FBT_SCRIPT_DIR}/wifi_board.py"]] + "devboard_flash", + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/wifi_board.py", + "${ARGS}", + ] + ], ) # Linter - dist_env.PhonyTarget( "lint", - [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "check", "${LINT_SOURCES}"]], + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/lint.py", + "check", + "${LINT_SOURCES}", + "${ARGS}", + ] + ], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) dist_env.PhonyTarget( "format", - [["${PYTHON3}", "${FBT_SCRIPT_DIR}/lint.py", "format", "${LINT_SOURCES}"]], + [ + [ + "${PYTHON3}", + "${FBT_SCRIPT_DIR}/lint.py", + "format", + "${LINT_SOURCES}", + "${ARGS}", + ] + ], source=original_app_dir.File(".clang-format"), LINT_SOURCES=[original_app_dir], ) @@ -456,6 +486,7 @@ if dolphin_src_dir.exists(): "send", "${SOURCE}", "/ext/dolphin", + "${ARGS}", ] ], source=ufbt_build_dir.Dir("dolphin"), diff --git a/scripts/ufbt/site_tools/ufbt_help.py b/scripts/ufbt/site_tools/ufbt_help.py index 4873b385c65..632fd98b2df 100644 --- a/scripts/ufbt/site_tools/ufbt_help.py +++ b/scripts/ufbt/site_tools/ufbt_help.py @@ -29,7 +29,8 @@ debug, debug_other, blackmagic: Start GDB devboard_flash: - Update WiFi dev board with the latest firmware + Update WiFi dev board. + Supports ARGS="..." to pass extra arguments to the update script, e.g. ARGS="-c dev" Other: cli: diff --git a/scripts/wifi_board.py b/scripts/wifi_board.py index 3f89ebdc656..b01b6225ddc 100755 --- a/scripts/wifi_board.py +++ b/scripts/wifi_board.py @@ -1,16 +1,16 @@ #!/usr/bin/env python3 -from flipper.app import App -from serial.tools.list_ports_common import ListPortInfo - +import json import logging import os -import tempfile import subprocess -import serial.tools.list_ports as list_ports -import json -import requests import tarfile +import tempfile + +import requests +import serial.tools.list_ports as list_ports +from flipper.app import App +from serial.tools.list_ports_common import ListPortInfo class UpdateDownloader: @@ -29,15 +29,15 @@ class UpdateDownloader: def __init__(self): self.logger = logging.getLogger() - def download(self, channel_id: str, dir: str) -> bool: + def download(self, channel_id: str, target_dir: str) -> bool: # Aliases if channel_id in self.CHANNEL_ID_ALIAS: channel_id = self.CHANNEL_ID_ALIAS[channel_id] # Make directory - if not os.path.exists(dir): - self.logger.info(f"Creating directory {dir}") - os.makedirs(dir) + if not os.path.exists(target_dir): + self.logger.info(f"Creating directory {target_dir}") + os.makedirs(target_dir) # Download json index self.logger.info(f"Downloading {self.UPDATE_INDEX}") @@ -79,19 +79,14 @@ def download(self, channel_id: str, dir: str) -> bool: self.logger.info(f"Using version '{version['version']}'") # Get changelog - changelog = None - try: - changelog = version["changelog"] - except Exception as e: - self.logger.error(f"Failed to get changelog: {e}") - - # print changelog - if changelog is not None: + if changelog := version.get("changelog"): self.logger.info(f"Changelog:") for line in changelog.split("\n"): if line.strip() == "": continue self.logger.info(f" {line}") + else: + self.logger.warning(f"Changelog not found") # Find file file_url = None @@ -106,7 +101,7 @@ def download(self, channel_id: str, dir: str) -> bool: # Make file path file_name = file_url.split("/")[-1] - file_path = os.path.join(dir, file_name) + file_path = os.path.join(target_dir, file_name) # Download file self.logger.info(f"Downloading {file_url} to {file_path}") @@ -117,7 +112,7 @@ def download(self, channel_id: str, dir: str) -> bool: # Unzip tgz self.logger.info(f"Unzipping {file_path}") with tarfile.open(file_path, "r") as tar: - tar.extractall(dir) + tar.extractall(target_dir) return True @@ -133,16 +128,24 @@ def init(self): # logging self.logger = logging.getLogger() - def find_wifi_board(self) -> bool: + @staticmethod + def _grep_ports(regexp: str) -> list[ListPortInfo]: # idk why, but python thinks that list_ports.grep returns tuple[str, str, str] - blackmagics: list[ListPortInfo] = list(list_ports.grep("blackmagic")) # type: ignore - daps: list[ListPortInfo] = list(list_ports.grep("CMSIS-DAP")) # type: ignore + return list(list_ports.grep(regexp)) # type: ignore + + def is_wifi_board_connected(self) -> bool: + return ( + len(self._grep_ports("ESP32-S2")) > 0 + or len(self._grep_ports("CMSIS-DAP")) > 0 + ) - return len(blackmagics) > 0 or len(daps) > 0 + @staticmethod + def is_windows() -> bool: + return os.name == "nt" - def find_wifi_board_bootloader(self): - # idk why, but python thinks that list_ports.grep returns tuple[str, str, str] - ports: list[ListPortInfo] = list(list_ports.grep("ESP32-S2")) # type: ignore + @classmethod + def find_port(cls, regexp: str) -> str: + ports: list[ListPortInfo] = cls._grep_ports(regexp) if len(ports) == 0: # Blackmagic probe serial port not found, will be handled later @@ -151,27 +154,28 @@ def find_wifi_board_bootloader(self): raise Exception("More than one WiFi board found") else: port = ports[0] - if os.name == "nt": - port.device = f"\\\\.\\{port.device}" - return port.device + return f"\\\\.\\{port.device}" if cls.is_windows() else port.device + + def find_wifi_board_bootloader_port(self): + return self.find_port("ESP32-S2") + + def find_wifi_board_bootloader_port_damn_windows(self): + self.logger.info("Trying to find WiFi board using VID:PID") + return self.find_port("VID:PID=303A:0002") def update(self): try: - port = self.find_wifi_board_bootloader() + port = self.find_wifi_board_bootloader_port() + + # Damn windows fix + if port is None and self.is_windows(): + port = self.find_wifi_board_bootloader_port_damn_windows() except Exception as e: self.logger.error(f"{e}") return 1 - if self.args.port != "auto": - port = self.args.port - - available_ports = [p[0] for p in list(list_ports.comports())] - if port not in available_ports: - self.logger.error(f"Port {port} not found") - return 1 - if port is None: - if self.find_wifi_board(): + if self.is_wifi_board_connected(): self.logger.error("WiFi board found, but not in bootloader mode.") self.logger.info("Please hold down BOOT button and press RESET button") else: @@ -179,6 +183,13 @@ def update(self): self.logger.info( "Please connect WiFi board to your computer, hold down BOOT button and press RESET button" ) + if not self.is_windows(): + self.logger.info( + "If you are using Linux, you may need to add udev rules to access the device" + ) + self.logger.info( + "Check out 41-flipper.rules & README in scripts/debug folder" + ) return 1 # get temporary dir @@ -197,24 +208,29 @@ def update(self): with open(os.path.join(temp_dir, "flash.command"), "r") as f: flash_command = f.read() - flash_command = flash_command.replace("\n", "").replace("\r", "") - flash_command = flash_command.replace("(PORT)", port) - - # We can't reset the board after flashing via usb - flash_command = flash_command.replace( - "--after hard_reset", "--after no_reset_stub" + replacements = ( + ("\n", ""), + ("\r", ""), + ("(PORT)", port), + # We can't reset the board after flashing via usb + ("--after hard_reset", "--after no_reset_stub"), ) - args = flash_command.split(" ")[0:] - args = list(filter(None, args)) + # hellish toolchain fix + if self.is_windows(): + replacements += (("esptool.py", "python -m esptool"),) + else: + replacements += (("esptool.py", "python3 -m esptool"),) + + for old, new in replacements: + flash_command = flash_command.replace(old, new) - esptool_params = [] - esptool_params.extend(args) + args = list(filter(None, flash_command.split())) self.logger.info(f'Running command: "{" ".join(args)}" in "{temp_dir}"') process = subprocess.Popen( - esptool_params, + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=temp_dir, diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index 2d2abd5c676..3ea5dcd6b70 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -274,6 +274,11 @@ vars.AddVariables( help="Enable strict import check for .faps", default=True, ), + ( + "ARGS", + "Extra arguments to pass to certain scripts supporting it", + "", + ), ) Return("vars") From 1070064f8954b17047ff64c5f892f787d5b2e731 Mon Sep 17 00:00:00 2001 From: Rodrigo Basoalto Date: Mon, 4 Mar 2024 06:50:28 -0300 Subject: [PATCH 51/58] Parser for Santiago, Chile BIP transit card (#3456) * Parser for Santiago, Chile BIP transit card It's used for all public transportation in the city of Santiago, Chile (bus, subway). We've reverse-engineered most of the non-zero bits of data found, and checked them across dozens of cards. * Update to use bit_lib instead of nfc_util * PR feedback Co-authored-by: Rodrigo Basoalto Co-authored-by: gornekich --- applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/bip.c | 368 ++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/bip.c diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 4bcc798237c..b6a0f09d370 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -128,6 +128,15 @@ App( sources=["plugins/supported_cards/aime.c"], ) +App( + appid="bip_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="bip_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/bip.c"], +) + App( appid="umarsh_parser", apptype=FlipperAppType.PLUGIN, diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c new file mode 100644 index 00000000000..211c27cb56c --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -0,0 +1,368 @@ +#include "nfc_supported_card_plugin.h" + +#include +#include +#include +#include + +#define TAG "Bip" + +#define SECTOR_BLOCK_OFFSET(sector, block) (((sector) * 4) + (block)) + +static const uint64_t bip_keys_a[] = { + 0x3a42f33af429, + 0x6338a371c0ed, + 0xf124c2578ad0, + 0x32ac3b90ac13, + 0x4ad1e273eaf1, + 0xe2c42591368a, + 0x2a3c347a1200, + 0x16f3d5ab1139, + 0x937a4fff3011, + 0x35c3d2caee88, + 0x693143f10368, + 0xa3f97428dd01, + 0x63f17a449af0, + 0xc4652c54261c, + 0xd49e2826664f, + 0x3df14c8000a1, +}; + +static const uint64_t bip_keys_b[] = { + 0x1fc235ac1309, + 0x243f160918d1, + 0x9afc42372af1, + 0x682d401abb09, + 0x067db45454a9, + 0x15fc4c7613fe, + 0x68d30288910a, + 0xf59a36a2546d, + 0x64e3c10394c2, + 0xb736412614af, + 0x324f5df65310, + 0x643fb6de2217, + 0x82f435dedf01, + 0x0263de1278f3, + 0x51284c3686a6, + 0x6a470d54127c, +}; + +bool bip_verify(Nfc* nfc) { + bool verified = true; + + const uint8_t verify_sector = 0; + uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector); + + MfClassicKey key_a_0 = {}; + bit_lib_num_to_bytes_be(bip_keys_a[0], COUNT_OF(key_a_0.data), key_a_0.data); + + MfClassicAuthContext auth_ctx = {}; + MfClassicError error = + mf_classic_poller_sync_auth(nfc, block_num, &key_a_0, MfClassicKeyTypeA, &auth_ctx); + + if(error == MfClassicErrorNotPresent) { + FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); + verified = false; + } + + return verified; +} + +static bool bip_read(Nfc* nfc, NfcDevice* device) { + furi_assert(nfc); + furi_assert(device); + + bool is_read = false; + + MfClassicData* data = mf_classic_alloc(); + nfc_device_copy_data(device, NfcProtocolMfClassic, data); + + do { + MfClassicType type = MfClassicType1k; + MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Card not MIFARE Classic 1k"); + break; + } + + data->type = type; + MfClassicDeviceKeys keys = {}; + for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) { + bit_lib_num_to_bytes_be(bip_keys_a[i], sizeof(MfClassicKey), keys.key_a[i].data); + FURI_BIT_SET(keys.key_a_mask, i); + bit_lib_num_to_bytes_be(bip_keys_b[i], sizeof(MfClassicKey), keys.key_b[i].data); + FURI_BIT_SET(keys.key_b_mask, i); + } + + error = mf_classic_poller_sync_read(nfc, &keys, data); + if(error == MfClassicErrorNotPresent) { + FURI_LOG_W(TAG, "Failed to read data. Bad keys?"); + break; + } + + nfc_device_set_data(device, NfcProtocolMfClassic, data); + + is_read = true; + } while(false); + + mf_classic_free(data); + + return is_read; +} + +typedef struct { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; +} BipTimestamp; + +static void parse_bip_timestamp(const MfClassicBlock* block, BipTimestamp* timestamp) { + furi_assert(block); + furi_assert(timestamp); + + timestamp->day = (((block->data[1] << 8) + block->data[0]) >> 6) & 0x1f; + timestamp->month = (((block->data[1] << 8) + block->data[0]) >> 11) & 0xf; + timestamp->year = 2000 + ((((block->data[2] << 8) + block->data[1]) >> 7) & 0x1f); + timestamp->hour = (((block->data[3] << 8) + block->data[2]) >> 4) & 0x1f; + timestamp->minute = (((block->data[3] << 8) + block->data[2]) >> 9) & 0x3f; + timestamp->second = (((block->data[4] << 8) + block->data[3]) >> 7) & 0x3f; +} + +static int compare_bip_timestamp(const BipTimestamp* t1, const BipTimestamp* t2) { + furi_assert(t1); + furi_assert(t2); + if(t1->year != t2->year) { + return t1->year - t2->year; + } + if(t1->month != t2->month) { + return t1->month - t2->month; + } + if(t1->day != t2->day) { + return t1->day - t2->day; + } + if(t1->hour != t2->hour) { + return t1->hour - t2->hour; + } + if(t1->minute != t2->minute) { + return t1->minute - t2->minute; + } + if(t1->second != t2->second) { + return t1->second - t2->second; + } + return 0; +} + +static void print_bip_timestamp(const BipTimestamp* timestamp, FuriString* str) { + furi_assert(timestamp); + furi_assert(str); + furi_string_cat_printf( + str, + "%04u-%02u-%02u %02u:%02u:%02u", + timestamp->year, + timestamp->month, + timestamp->day, + timestamp->hour, + timestamp->minute, + timestamp->second); +} + +static bool is_bip_block_empty(const MfClassicBlock* block) { + furi_assert(block); + // check if all but last byte are zero (last is checksum) + for(size_t i = 0; i < sizeof(block->data) - 1; i++) { + if(block->data[i] != 0) { + return false; + } + } + return true; +} + +static void parse_uint16_le(const uint8_t* data, uint16_t* value) { + furi_assert(data); + furi_assert(value); + + *value = (data[0]) | (data[1] << 8); +} + +static void parse_uint32_le(const uint8_t* data, uint32_t* value) { + furi_assert(data); + furi_assert(value); + + *value = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static void parse_uint16_txn_amount(const uint8_t* data, uint16_t* value) { + furi_assert(data); + furi_assert(value); + + parse_uint16_le(data, value); + *value = *value >> 2; +} + +typedef struct { + BipTimestamp timestamp; + uint16_t amount; +} BipTransaction; + +static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + bool parsed = true; + + struct { + uint32_t card_id; + uint16_t balance; + uint16_t flags; + BipTimestamp trip_time_window; + BipTransaction top_ups[3]; + BipTransaction charges[3]; + } bip_data = { + .card_id = 0, + .balance = 0, + .flags = 0, + .trip_time_window = {0, 0, 0, 0, 0, 0}, + .top_ups = + { + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + }, + .charges = + { + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + {{0, 0, 0, 0, 0, 0}, 0}, + }, + }; + + const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic); + + do { + // verify first sector keys + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 0); + uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6); + if(key != bip_keys_a[0]) { + parsed = false; + break; + } + key = bit_lib_bytes_to_num_be(sec_tr->key_b.data, 6); + if(key != bip_keys_b[0]) { + parsed = false; + break; + } + + // Get Card ID, little-endian 4 bytes at sector 0 block 1, bytes 4-7 + parse_uint32_le(&data->block[SECTOR_BLOCK_OFFSET(0, 1)].data[4], &bip_data.card_id); + + // Get balance, little-endian 2 bytes at sector 8 block 1, bytes 0-1 + parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[0], &bip_data.balance); + + // Get balance flags (negative balance, etc.), little-endian 2 bytes at sector 8 block 1, bytes 2-3 + parse_uint16_le(&data->block[SECTOR_BLOCK_OFFSET(8, 1)].data[2], &bip_data.flags); + + // Get trip time window, proprietary format, at sector 5 block 1, bytes 0-7 + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(5, 1)], &bip_data.trip_time_window); + + // Last 3 top-ups: sector 10, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 9-10 + for(size_t i = 0; i < 3; i++) { + if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(10, i)])) { + continue; + } + BipTransaction* top_up = &bip_data.top_ups[i]; + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(10, i)], &top_up->timestamp); + parse_uint16_txn_amount( + &data->block[SECTOR_BLOCK_OFFSET(10, i)].data[9], &top_up->amount); + } + + // Last 3 charges (i.e. trips), sector 11, ring-buffer of 3 blocks, timestamp in bytes 0-7, amount in bytes 10-11 + for(size_t i = 0; i < 3; i++) { + if(is_bip_block_empty(&data->block[SECTOR_BLOCK_OFFSET(11, i)])) { + continue; + } + BipTransaction* charge = &bip_data.charges[i]; + parse_bip_timestamp(&data->block[SECTOR_BLOCK_OFFSET(11, i)], &charge->timestamp); + parse_uint16_txn_amount( + &data->block[SECTOR_BLOCK_OFFSET(11, i)].data[10], &charge->amount); + } + + // All data is now parsed and stored in bip_data, now print it + + // Print basic info + furi_string_printf( + parsed_data, + "\e#Tarjeta Bip!\n" + "Card Number: %ld\n" + "Balance: $%d (flags %x)\n" + "Current Trip Window Ends:\n @", + bip_data.card_id, + bip_data.balance, + bip_data.flags); + + print_bip_timestamp(&bip_data.trip_time_window, parsed_data); + + // Find newest top-up + size_t newest_top_up = 0; + for(size_t i = 1; i < 3; i++) { + const BipTimestamp* newest = &bip_data.top_ups[newest_top_up].timestamp; + const BipTimestamp* current = &bip_data.top_ups[i].timestamp; + if(compare_bip_timestamp(current, newest) > 0) { + newest_top_up = i; + } + } + + // Print top-ups, newest first + furi_string_cat_printf(parsed_data, "\n\e#Last Top-ups"); + for(size_t i = 0; i < 3; i++) { + const BipTransaction* top_up = &bip_data.top_ups[(3u + newest_top_up - i) % 3]; + furi_string_cat_printf(parsed_data, "\n+$%d\n @", top_up->amount); + print_bip_timestamp(&top_up->timestamp, parsed_data); + } + + // Find newest charge + size_t newest_charge = 0; + for(size_t i = 1; i < 3; i++) { + const BipTimestamp* newest = &bip_data.charges[newest_charge].timestamp; + const BipTimestamp* current = &bip_data.charges[i].timestamp; + if(compare_bip_timestamp(current, newest) > 0) { + newest_charge = i; + } + } + + // Print charges + furi_string_cat_printf(parsed_data, "\n\e#Last Charges (Trips)"); + for(size_t i = 0; i < 3; i++) { + const BipTransaction* charge = &bip_data.charges[(3u + newest_charge - i) % 3]; + furi_string_cat_printf(parsed_data, "\n-$%d\n @", charge->amount); + print_bip_timestamp(&charge->timestamp, parsed_data); + } + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin bip_plugin = { + .protocol = NfcProtocolMfClassic, + .verify = bip_verify, + .read = bip_read, + .parse = bip_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor bip_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &bip_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* bip_plugin_ep() { + return &bip_plugin_descriptor; +} From 7b26dc5ebfaa9d137e9d1764f95ee3738ee75a01 Mon Sep 17 00:00:00 2001 From: philicious Date: Tue, 5 Mar 2024 03:42:39 +0100 Subject: [PATCH 52/58] NFC: Fix washcity plugin verify function being to greedy (#3467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * NFC: Fix washcity plugin verify function being to greedy It verifies only a single sector and that one using one of the commonly used key 0xA0A1A2A3A4A5 This leads to false-positives and the dicts not being used at all * nfc app: washcity plugin: fix verify function Co-authored-by: gornekich Co-authored-by: あく --- .../main/nfc/plugins/supported_cards/washcity.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/washcity.c b/applications/main/nfc/plugins/supported_cards/washcity.c index 2c0ec094c16..179b569d7e2 100644 --- a/applications/main/nfc/plugins/supported_cards/washcity.c +++ b/applications/main/nfc/plugins/supported_cards/washcity.c @@ -57,20 +57,20 @@ static bool washcity_verify(Nfc* nfc) { bool verified = false; do { - const uint8_t ticket_sector_number = 0; - const uint8_t ticket_block_number = - mf_classic_get_first_block_num_of_sector(ticket_sector_number) + 1; - FURI_LOG_D(TAG, "Verifying sector %u", ticket_sector_number); + const uint8_t verify_sector_number = 1; + const uint8_t verify_block_number = + mf_classic_get_first_block_num_of_sector(verify_sector_number); + FURI_LOG_D(TAG, "Verifying sector %u", verify_sector_number); MfClassicKey key = {0}; bit_lib_num_to_bytes_be( - washcity_1k_keys[ticket_sector_number].a, COUNT_OF(key.data), key.data); + washcity_1k_keys[verify_sector_number].a, COUNT_OF(key.data), key.data); MfClassicAuthContext auth_context; MfClassicError error = mf_classic_poller_sync_auth( - nfc, ticket_block_number, &key, MfClassicKeyTypeA, &auth_context); + nfc, verify_block_number, &key, MfClassicKeyTypeA, &auth_context); if(error != MfClassicErrorNone) { - FURI_LOG_D(TAG, "Failed to read block %u: %d", ticket_block_number, error); + FURI_LOG_D(TAG, "Failed to read block %u: %d", verify_block_number, error); break; } From a6c5a1ba6972131d08adef3a3d81a5d3eb776610 Mon Sep 17 00:00:00 2001 From: assasinfil Date: Tue, 5 Mar 2024 05:56:35 +0300 Subject: [PATCH 53/58] Troyka parser improvements (#3390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated troyka layout (full version) * Changed to furi func * Small refactor * Bitlib refactor * Moved to API * Rollback troyka parser * Fix functions * Parser func refactor start * Layout E3 refactored * Layout E4 refactored * Layout 6 refactored * Layout E5 refactored * Layout 2 refactored * Layout E5 fix * Layout E6 refactored, valid_date need fix * Layout E6 fix * Layout FCB refactored * Layout F0B refactored * Layout 8 refactored * Layout A refactored * Layout C refactored * Layout D refactored * Layout E1 refactored * Layout E2 refactored * Fixes * Old code cleanup * Memory cleanup * Unused imports cleanup * Keys struct refactor * Layout E1 fix * Added debug info for layout and department * Fix PVS warnings * Fix more PVS warnings Co-authored-by: gornekich Co-authored-by: あく --- .../nfc/api/mosgortrans/mosgortrans_util.c | 1308 +++++++++++++++++ .../nfc/api/mosgortrans/mosgortrans_util.h | 17 + .../main/nfc/api/nfc_app_api_table_i.h | 7 +- .../main/nfc/plugins/supported_cards/bip.c | 4 +- .../main/nfc/plugins/supported_cards/troika.c | 247 +--- 5 files changed, 1398 insertions(+), 185 deletions(-) create mode 100644 applications/main/nfc/api/mosgortrans/mosgortrans_util.c create mode 100644 applications/main/nfc/api/mosgortrans/mosgortrans_util.h diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c new file mode 100644 index 00000000000..f2484a2af56 --- /dev/null +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -0,0 +1,1308 @@ +#include "mosgortrans_util.h" + +#define TAG "Mosgortrans" + +void from_days_to_datetime(uint32_t days, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = days * 24 * 60 * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = minutes * 60; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +void from_seconds_to_datetime(uint32_t seconds, DateTime* datetime, uint16_t start_year) { + uint32_t timestamp = seconds; + DateTime start_datetime = {0}; + start_datetime.year = start_year - 1; + start_datetime.month = 12; + start_datetime.day = 31; + timestamp += datetime_datetime_to_timestamp(&start_datetime); + datetime_timestamp_to_datetime(timestamp, datetime); +} + +typedef struct { + uint16_t view; //101 + uint16_t type; //102 + uint8_t layout; //111 + uint8_t layout2; //112 + uint16_t blank_type; //121 + uint16_t type_of_extended; //122 + uint8_t extended; //123 + uint8_t benefit_code; //124 + uint32_t number; //201 + uint16_t use_before_date; //202 + uint16_t use_before_date2; //202.2 + uint16_t use_with_date; //205 + uint8_t requires_activation; //301 + uint16_t activate_during; //302 + uint16_t extension_counter; //304 + uint8_t blocked; //303 + uint32_t valid_from_date; //311 + uint16_t valid_to_date; //312 + uint8_t valid_for_days; //313 + uint32_t valid_for_minutes; //314 + uint16_t valid_for_time; //316 + uint16_t valid_for_time2; //316.2 + uint32_t valid_to_time; //317 + uint16_t remaining_trips; //321 + uint8_t remaining_trips1; //321.1 + uint32_t remaining_funds; //322 + uint16_t total_trips; //331 + uint16_t start_trip_date; //402 + uint16_t start_trip_time; //403 + uint32_t start_trip_neg_minutes; //404 + uint32_t start_trip_minutes; //405 + uint8_t start_trip_seconds; //406 + uint8_t minutes_pass; //412 + uint8_t passage_5_minutes; //413 + uint8_t metro_ride_with; //414 + uint8_t transport_type; //421 + uint8_t transport_type_flag; //421.0 + uint8_t transport_type1; //421.1 + uint8_t transport_type2; //421.2 + uint8_t transport_type3; //421.3 + uint8_t transport_type4; //421.4 + uint16_t validator; //422 + uint8_t validator1; //422.1 + uint16_t validator2; //422.2 + uint16_t route; //424 + uint8_t passage_in_metro; //431 + uint8_t transfer_in_metro; //432 + uint8_t passages_ground_transport; //433 + uint8_t fare_trip; //441 + uint16_t crc16; //501.1 + uint16_t crc16_2; //501.2 + uint32_t hash; //502 + uint16_t hash1; //502.1 + uint32_t hash2; //502.2 + uint8_t geozone_a; //GeoZoneA + uint8_t geozone_b; //GeoZoneB + uint8_t company; //Company + uint8_t units; //Units + uint64_t rfu1; //rfu1 + uint16_t rfu2; //rfu2 + uint32_t rfu3; //rfu3 + uint8_t rfu4; //rfu4 + uint8_t rfu5; //rfu5 + uint8_t write_enabled; //write_enabled + uint32_t tech_code; //TechCode + uint8_t interval; //Interval + uint16_t app_code1; //AppCode1 + uint16_t app_code2; //AppCode2 + uint16_t app_code3; //AppCode3 + uint16_t app_code4; //AppCode4 + uint16_t type1; //Type1 + uint16_t type2; //Type2 + uint16_t type3; //Type3 + uint16_t type4; //Type4 + uint8_t zoo; //zoo +} BlockData; + +void parse_layout_2(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->benefit_code = bit_lib_get_bits(block->data, 0x48, 8); //124 + data_block->rfu1 = bit_lib_get_bits_32(block->data, 0x50, 32); //rfu1 + data_block->crc16 = bit_lib_get_bits_16(block->data, 0x70, 16); //501.1 + data_block->blocked = bit_lib_get_bits(block->data, 0x80, 1); //303 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0x81, 12); //403 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x8D, 16); //402 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x9D, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0xAD, 16); //312 + data_block->start_trip_seconds = bit_lib_get_bits(block->data, 0xDB, 6); //406 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xC3, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xC5, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xC7, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xC9, 2); //421.4 + data_block->use_with_date = bit_lib_get_bits_16(block->data, 0xBD, 16); //205 + data_block->route = bit_lib_get_bits(block->data, 0xCD, 1); //424 + data_block->validator1 = bit_lib_get_bits_16(block->data, 0xCE, 15); //422.1 + data_block->validator = bit_lib_get_bits_16(block->data, 0xCD, 16); + data_block->total_trips = bit_lib_get_bits_16(block->data, 0xDD, 16); //331 + data_block->write_enabled = bit_lib_get_bits(block->data, 0xED, 1); //write_enabled + data_block->rfu2 = bit_lib_get_bits(block->data, 0xEE, 2); //rfu2 + data_block->crc16_2 = bit_lib_get_bits_16(block->data, 0xF0, 16); //501.2 +} + +void parse_layout_6(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->geozone_a = bit_lib_get_bits(block->data, 0x48, 4); //GeoZoneA + data_block->geozone_b = bit_lib_get_bits(block->data, 0x4C, 4); //GeoZoneB + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x50, 10); //121 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x5A, 10); //122 + data_block->rfu1 = bit_lib_get_bits_16(block->data, 0x64, 12); //rfu1 + data_block->crc16 = bit_lib_get_bits_16(block->data, 0x70, 16); //501.1 + data_block->blocked = bit_lib_get_bits(block->data, 0x80, 1); //303 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0x81, 12); //403 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x8D, 16); //402 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x9D, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0xAD, 16); //312 + data_block->company = bit_lib_get_bits(block->data, 0xBD, 4); //Company + data_block->validator1 = bit_lib_get_bits(block->data, 0xC1, 4); //422.1 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xC5, 10); //321 + data_block->units = bit_lib_get_bits(block->data, 0xCF, 6); //Units + data_block->validator2 = bit_lib_get_bits_16(block->data, 0xD5, 10); //422.2 + data_block->total_trips = bit_lib_get_bits_16(block->data, 0xDF, 16); //331 + data_block->extended = bit_lib_get_bits(block->data, 0xEF, 1); //123 + data_block->crc16_2 = bit_lib_get_bits_16(block->data, 0xF0, 16); //501.2 +} + +void parse_layout_8(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->rfu1 = bit_lib_get_bits_64(block->data, 0x48, 56); //rfu1 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu2 = bit_lib_get_bits(block->data, 0x99, 7); //rfu2 + data_block->remaining_trips1 = bit_lib_get_bits(block->data, 0xA0, 8); //321.1 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0xA8, 8); //321 + data_block->validator1 = bit_lib_get_bits(block->data, 0xB0, 2); //422.1 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB1, 15); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->rfu3 = bit_lib_get_bits_32(block->data, 0xE0, 32); //rfu3 +} + +void parse_layout_A(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x40, 12); //311 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x4C, 19); //314 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x5F, 1); //301 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x60, 19); //405 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x77, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0x7E, 2); //421.0 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0x80, 8); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0x88, 16); //422 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0x98, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0x9A, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0x9C, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0x9E, 2); //421.4 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 +} + +void parse_layout_C(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x38, 16); //202 + data_block->rfu1 = bit_lib_get_bits_64(block->data, 0x48, 56); //rfu1 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu2 = bit_lib_get_bits_16(block->data, 0x99, 13); //rfu2 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA6, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB0, 16); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0xE0, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xF0, 11); //403 + data_block->transport_type = bit_lib_get_bits(block->data, 0xFB, 2); //421 + data_block->rfu3 = bit_lib_get_bits(block->data, 0xFD, 2); //rfu3 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xFF, 1); //432 +} + +void parse_layout_D(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->rfu1 = bit_lib_get_bits(block->data, 0x38, 8); //rfu1 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x40, 16); //202 + data_block->valid_for_time = bit_lib_get_bits_16(block->data, 0x50, 11); //316 + data_block->rfu2 = bit_lib_get_bits(block->data, 0x5B, 5); //rfu2 + data_block->use_before_date2 = bit_lib_get_bits_16(block->data, 0x60, 16); //202.2 + data_block->valid_for_time2 = bit_lib_get_bits_16(block->data, 0x70, 11); //316.2 + data_block->rfu3 = bit_lib_get_bits(block->data, 0x7B, 5); //rfu3 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x80, 16); //311 + data_block->valid_for_days = bit_lib_get_bits(block->data, 0x90, 8); //313 + data_block->requires_activation = bit_lib_get_bits(block->data, 0x98, 1); //301 + data_block->rfu4 = bit_lib_get_bits(block->data, 0x99, 2); //rfu4 + data_block->passage_5_minutes = bit_lib_get_bits(block->data, 0x9B, 5); //413 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xA0, 2); //421.1 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xA2, 1); //431 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xA3, 3); //433 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA6, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB0, 16); //422 + data_block->hash = bit_lib_get_bits_32(block->data, 0xC0, 32); //502 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0xE0, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xF0, 11); //403 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xFB, 2); //421.2 + data_block->rfu5 = bit_lib_get_bits(block->data, 0xFD, 2); //rfu5 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xFF, 1); //432 +} + +void parse_layout_E1(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x3D, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4D, 10); //121 + data_block->validator = bit_lib_get_bits_16(block->data, 0x80, 16); //422 + data_block->start_trip_date = bit_lib_get_bits_16(block->data, 0x90, 16); //402 + data_block->start_trip_time = bit_lib_get_bits_16(block->data, 0xA0, 11); //403 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xAB, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xAD, 2); //421.2 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xB1, 1); //432 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xB2, 1); //431 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xB3, 3); //433 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xB9, 8); //412 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xC4, 19); //322 + data_block->fare_trip = bit_lib_get_bits(block->data, 0xD7, 2); //441 + data_block->blocked = bit_lib_get_bits(block->data, 0x9D, 1); //303 + data_block->zoo = bit_lib_get_bits(block->data, 0xDA, 1); //zoo + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E2(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x57, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x61, 16); //311 + data_block->activate_during = bit_lib_get_bits_16(block->data, 0x71, 9); //302 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x83, 20); //314 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x9A, 8); //412 + data_block->transport_type = bit_lib_get_bits(block->data, 0xA3, 2); //421 + data_block->passage_in_metro = bit_lib_get_bits(block->data, 0xA5, 1); //431 + data_block->transfer_in_metro = bit_lib_get_bits(block->data, 0xA6, 1); //432 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA7, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB1, 16); //422 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0xC4, 20); //404 + data_block->requires_activation = bit_lib_get_bits(block->data, 0xD8, 1); //301 + data_block->blocked = bit_lib_get_bits(block->data, 0xD9, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xDA, 1); //123 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E3(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 61, 16); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4D, 10); //121 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xBC, 22); //322 + data_block->hash = bit_lib_get_bits_32(block->data, 224, 32); //502 + data_block->validator = bit_lib_get_bits_16(block->data, 0x80, 16); //422 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x90, 23); //405 + data_block->fare_trip = bit_lib_get_bits(block->data, 0xD2, 2); //441 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xAB, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0xB2, 2); //421.0 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xB4, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xB6, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xB8, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xBA, 2); //421.4 + data_block->blocked = bit_lib_get_bits(block->data, 0xD4, 1); //303 +} + +void parse_layout_E4(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x54, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x5E, 13); //311 + data_block->activate_during = bit_lib_get_bits_16(block->data, 0x6B, 9); //302 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x74, 10); //304 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x80, 20); //314 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x98, 7); //412 + data_block->transport_type_flag = bit_lib_get_bits(block->data, 0x9F, 2); //421.0 + data_block->transport_type1 = bit_lib_get_bits(block->data, 0xA1, 2); //421.1 + data_block->transport_type2 = bit_lib_get_bits(block->data, 0xA3, 2); //421.2 + data_block->transport_type3 = bit_lib_get_bits(block->data, 0xA5, 2); //421.3 + data_block->transport_type4 = bit_lib_get_bits(block->data, 0xA7, 2); //421.4 + data_block->remaining_trips = bit_lib_get_bits_16(block->data, 0xA9, 10); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xB3, 16); //422 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0xC3, 20); //404 + data_block->requires_activation = bit_lib_get_bits(block->data, 0xD7, 1); //301 + data_block->blocked = bit_lib_get_bits(block->data, 0xD8, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xD9, 1); //123 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E5(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x3D, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x4A, 10); //121 + data_block->valid_to_time = bit_lib_get_bits_32(block->data, 0x54, 23); //317 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x6B, 10); //304 + data_block->start_trip_minutes = bit_lib_get_bits_32(block->data, 0x80, 23); //405 + data_block->metro_ride_with = bit_lib_get_bits(block->data, 0x97, 7); //414 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0x9E, 7); //412 + data_block->remaining_funds = bit_lib_get_bits_32(block->data, 0xA7, 19); //322 + data_block->validator = bit_lib_get_bits_16(block->data, 0xBA, 16); //422 + data_block->blocked = bit_lib_get_bits(block->data, 0xCA, 1); //303 + data_block->route = bit_lib_get_bits_16(block->data, 0xCC, 12); //424 + data_block->passages_ground_transport = bit_lib_get_bits(block->data, 0xD8, 7); //433 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_E6(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->layout2 = bit_lib_get_bits(block->data, 0x38, 5); //112 + data_block->type_of_extended = bit_lib_get_bits_16(block->data, 0x3D, 10); //122 + data_block->use_before_date = bit_lib_get_bits_16(block->data, 0x47, 13); //202 + data_block->blank_type = bit_lib_get_bits_16(block->data, 0x54, 10); //121 + data_block->valid_from_date = bit_lib_get_bits_32(block->data, 0x5E, 23); //311 + data_block->extension_counter = bit_lib_get_bits_16(block->data, 0x75, 10); //304 + data_block->valid_for_minutes = bit_lib_get_bits_32(block->data, 0x80, 20); //314 + data_block->start_trip_neg_minutes = bit_lib_get_bits_32(block->data, 0x94, 20); //404 + data_block->metro_ride_with = bit_lib_get_bits(block->data, 0xA8, 7); //414 + data_block->minutes_pass = bit_lib_get_bits(block->data, 0xAF, 7); //412 + data_block->remaining_trips = bit_lib_get_bits(block->data, 0xB6, 7); //321 + data_block->validator = bit_lib_get_bits_16(block->data, 0xBD, 16); //422 + data_block->blocked = bit_lib_get_bits(block->data, 0xCD, 1); //303 + data_block->extended = bit_lib_get_bits(block->data, 0xCE, 1); //123 + data_block->route = bit_lib_get_bits_16(block->data, 0xD4, 12); //424 + data_block->hash = bit_lib_get_bits_32(block->data, 0xE0, 32); //502 +} + +void parse_layout_FCB(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->tech_code = bit_lib_get_bits_32(block->data, 0x38, 10); //tech_code + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x42, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0x52, 16); //312 + data_block->interval = bit_lib_get_bits(block->data, 0x62, 4); //interval + data_block->app_code1 = bit_lib_get_bits_16(block->data, 0x66, 10); //app_code1 + data_block->hash1 = bit_lib_get_bits_16(block->data, 0x70, 16); //502.1 + data_block->type1 = bit_lib_get_bits_16(block->data, 0x80, 10); //type1 + data_block->app_code2 = bit_lib_get_bits_16(block->data, 0x8A, 10); //app_code2 + data_block->type2 = bit_lib_get_bits_16(block->data, 0x94, 10); //type2 + data_block->app_code3 = bit_lib_get_bits_16(block->data, 0x9E, 10); //app_code3 + data_block->type3 = bit_lib_get_bits_16(block->data, 0xA8, 10); //type3 + data_block->app_code4 = bit_lib_get_bits_16(block->data, 0xB2, 10); //app_code4 + data_block->type4 = bit_lib_get_bits_16(block->data, 0xBC, 10); //type4 + data_block->hash2 = bit_lib_get_bits_32(block->data, 0xE0, 32); //502.2 +} + +void parse_layout_F0B(BlockData* data_block, const MfClassicBlock* block) { + data_block->view = bit_lib_get_bits_16(block->data, 0x00, 10); //101 + data_block->type = bit_lib_get_bits_16(block->data, 0x0A, 10); //102 + data_block->number = bit_lib_get_bits_32(block->data, 0x14, 32); //201 + data_block->layout = bit_lib_get_bits(block->data, 0x34, 4); //111 + data_block->tech_code = bit_lib_get_bits_32(block->data, 0x38, 10); //tech_code + data_block->valid_from_date = bit_lib_get_bits_16(block->data, 0x42, 16); //311 + data_block->valid_to_date = bit_lib_get_bits_16(block->data, 0x52, 16); //312 + data_block->hash1 = bit_lib_get_bits_32(block->data, 0x70, 16); //502.1 +} + +void parse_transport_type(BlockData* data_block, FuriString* transport) { + switch(data_block->transport_type_flag) { + case 1: + uint8_t transport_type = + (data_block->transport_type1 || data_block->transport_type2 || + data_block->transport_type3 || data_block->transport_type4); + switch(transport_type) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + case 3: + furi_string_cat(transport, "MCC"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + break; + case 2: + furi_string_cat(transport, "Ground"); + break; + default: + furi_string_cat(transport, ""); + break; + } +} + +bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result) { + BlockData data_block = {}; + const uint16_t valid_departments[] = {0x106, 0x108, 0x10A, 0x10E, 0x110, 0x117}; + uint16_t transport_departament = bit_lib_get_bits_16(block->data, 0, 10); + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf(result, "Transport departament: %x\n", transport_departament); + } + bool departament_valid = false; + for(uint8_t i = 0; i < 6; i++) { + if(transport_departament == valid_departments[i]) { + departament_valid = true; + break; + } + } + if(!departament_valid) { + return false; + } + FURI_LOG_D(TAG, "Transport departament: %x", transport_departament); + uint16_t layout_type = bit_lib_get_bits_16(block->data, 52, 4); + if(layout_type == 0xE) { + layout_type = bit_lib_get_bits_16(block->data, 52, 9); + } else if(layout_type == 0xF) { + layout_type = bit_lib_get_bits_16(block->data, 52, 14); + } + if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { + furi_string_cat_printf(result, "Layout: %x\n", layout_type); + } + FURI_LOG_D(TAG, "Layout type %x", layout_type); + switch(layout_type) { + case 0x02: { + parse_layout_2(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + + if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) { + furi_string_cat(result, "\e#No ticket\n"); + return true; + } + //remaining_trips + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_number + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //trip_from + DateTime card_start_trip_minutes_s = {0}; + from_seconds_to_datetime( + data_block.start_trip_date * 24 * 60 * 60 + data_block.start_trip_time * 60 + + data_block.start_trip_seconds, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + break; + } + case 0x06: { + parse_layout_6(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_number + furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); + //trip_from + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + (data_block.start_trip_date) * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + //validator + furi_string_cat_printf( + result, "Validator: %05d", data_block.validator1 * 1024 + data_block.validator2); + break; + } + case 0x08: { + parse_layout_8(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + break; + } + case 0x0A: { + parse_layout_A(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2016); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 2016); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_valid_to_date_s, + 2016); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_from + if(data_block.start_trip_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.start_trip_minutes, + &card_start_trip_minutes_s, + 2016); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //trip_switch + if(data_block.minutes_pass) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.start_trip_minutes + + data_block.minutes_pass, + &card_start_switch_trip_minutes_s, + 2016); + furi_string_cat_printf( + result, + "Trip switch: %02d.%02d.%04d %02d:%02d\n", + card_start_switch_trip_minutes_s.day, + card_start_switch_trip_minutes_s.month, + card_start_switch_trip_minutes_s.year, + card_start_switch_trip_minutes_s.hour, + card_start_switch_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0x0C: { + parse_layout_C(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0x0D: { + parse_layout_D(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.valid_for_days, &card_valid_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + } + //trip_switch + if(data_block.passage_5_minutes) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time + + data_block.passage_5_minutes, + &card_start_switch_trip_minutes_s, + 1992); + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0xE1: + case 0x1C1: { + parse_layout_E1(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_funds + furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100); + //trip_from + if(data_block.start_trip_date) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_date * 24 * 60 + data_block.start_trip_time, + &card_start_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + switch(data_block.transport_type1) { + case 1: + switch(data_block.transport_type2) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + break; + case 2: + furi_string_cat(transport, "Ground"); + break; + case 3: + furi_string_cat(transport, "MCC"); + break; + default: + furi_string_cat(transport, ""); + break; + } + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE2: + case 0x1C2: { + parse_layout_E2(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_valid_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_valid_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_valid_from_date_s.day, + card_valid_from_date_s.month, + card_valid_from_date_s.year); + //valid_to_date + if(data_block.activate_during) { + DateTime card_valid_to_date_s = {0}; + from_days_to_datetime( + data_block.valid_from_date + data_block.activate_during, + &card_valid_to_date_s, + 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + } else { + DateTime card_valid_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_valid_to_date_s, + 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_valid_to_date_s.day, + card_valid_to_date_s.month, + card_valid_to_date_s.year); + } + //trip_from + if(data_block.start_trip_neg_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_to_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 1992); //-time + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //trip_switch + if(data_block.minutes_pass) { + DateTime card_start_switch_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes + data_block.minutes_pass, + &card_start_switch_trip_minutes_s, + 1992); + furi_string_cat_printf( + result, + "Trip switch: %02d.%02d.%04d %02d:%02d\n", + card_start_switch_trip_minutes_s.day, + card_start_switch_trip_minutes_s.month, + card_start_switch_trip_minutes_s.year, + card_start_switch_trip_minutes_s.hour, + card_start_switch_trip_minutes_s.minute); + } + //transport + FuriString* transport = furi_string_alloc(); + switch(data_block.transport_type) { + case 1: + furi_string_cat(transport, "Metro"); + break; + case 2: + furi_string_cat(transport, "Monorail"); + break; + case 3: + furi_string_cat(transport, "Ground"); + break; + default: + furi_string_cat(transport, "Unknown"); + break; + } + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE3: + case 0x1C3: { + parse_layout_E3(&data_block, block); + // number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + // use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 1992); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + // remaining_funds + furi_string_cat_printf(result, "Balance: %lu rub\n", data_block.remaining_funds); + // start_trip_minutes + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime(data_block.start_trip_minutes, &card_start_trip_minutes_s, 2016); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + // transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + // validator + furi_string_cat_printf(result, "Validator: %05d\n", data_block.validator); + // fare + FuriString* fare = furi_string_alloc(); + switch(data_block.fare_trip) { + case 0: + furi_string_cat(fare, ""); + break; + case 1: + furi_string_cat(fare, "Single"); + break; + case 2: + furi_string_cat(fare, "90 minutes"); + break; + default: + furi_string_cat(fare, "Unknown"); + break; + } + furi_string_cat_printf(result, "Fare: %s", furi_string_get_cstr(fare)); + furi_string_free(fare); + furi_string_free(transport); + break; + } + case 0xE4: + case 0x1C4: { + parse_layout_E4(&data_block, block); + + // number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + // use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2016); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + // remaining_funds + furi_string_cat_printf(result, "Balance: %lu rub\n", data_block.remaining_funds); + // valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 2016); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + // valid_to_date + DateTime card_use_to_date_s = {0}; + if(data_block.requires_activation) { + from_days_to_datetime( + data_block.valid_from_date + data_block.activate_during, + &card_use_to_date_s, + 2016); + } else { + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - 1, + &card_use_to_date_s, + 2016); + } + + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + // trip_number + // furi_string_cat_printf(result, "Trips left: %d", data_block.remaining_trips); + // trip_from + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date * 24 * 60 + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2016); + //transport + FuriString* transport = furi_string_alloc(); + parse_transport_type(&data_block, transport); + furi_string_cat_printf(result, "Transport: %s\n", furi_string_get_cstr(transport)); + // validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + furi_string_free(transport); + break; + } + case 0xE5: + case 0x1C5: { + parse_layout_E5(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2019); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_funds + furi_string_cat_printf(result, "Balance: %ld rub\n", data_block.remaining_funds / 100); + //start_trip_minutes + if(data_block.start_trip_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes, &card_start_trip_minutes_s, 2019); + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //start_m_trip_minutes + if(data_block.metro_ride_with) { + DateTime card_start_m_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes + data_block.metro_ride_with, + &card_start_m_trip_minutes_s, + 2019); + furi_string_cat_printf( + result, + "(M) from: %02d.%02d.%04d %02d:%02d\n", + card_start_m_trip_minutes_s.day, + card_start_m_trip_minutes_s.month, + card_start_m_trip_minutes_s.year, + card_start_m_trip_minutes_s.hour, + card_start_m_trip_minutes_s.minute); + } + if(data_block.minutes_pass) { + DateTime card_start_change_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.start_trip_minutes + data_block.minutes_pass, + &card_start_change_trip_minutes_s, + 2019); + furi_string_cat_printf( + result, + "Trip edit: %02d.%02d.%04d %02d:%02d\n", + card_start_change_trip_minutes_s.day, + card_start_change_trip_minutes_s.month, + card_start_change_trip_minutes_s.year, + card_start_change_trip_minutes_s.hour, + card_start_change_trip_minutes_s.minute); + } + //transport + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0xE6: + case 0x1C6: { + parse_layout_E6(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //use_before_date + DateTime card_use_before_date_s = {0}; + from_days_to_datetime(data_block.use_before_date, &card_use_before_date_s, 2019); + furi_string_cat_printf( + result, + "Use before: %02d.%02d.%04d\n", + card_use_before_date_s.day, + card_use_before_date_s.month, + card_use_before_date_s.year); + //remaining_trips + furi_string_cat_printf(result, "Trips left: %d\n", data_block.remaining_trips); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_minutes_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 2019); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - 1, + &card_use_to_date_s, + 2019); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d\n", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + //start_trip_minutes + if(data_block.start_trip_neg_minutes) { + DateTime card_start_trip_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes, + &card_start_trip_minutes_s, + 2019); //-time + furi_string_cat_printf( + result, + "Trip from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_minutes_s.day, + card_start_trip_minutes_s.month, + card_start_trip_minutes_s.year, + card_start_trip_minutes_s.hour, + card_start_trip_minutes_s.minute); + } + //start_trip_m_minutes + if(data_block.metro_ride_with) { + DateTime card_start_trip_m_minutes_s = {0}; + from_minutes_to_datetime( + data_block.valid_from_date + data_block.valid_for_minutes - + data_block.start_trip_neg_minutes + data_block.metro_ride_with, + &card_start_trip_m_minutes_s, + 2019); + furi_string_cat_printf( + result, + "(M) from: %02d.%02d.%04d %02d:%02d\n", + card_start_trip_m_minutes_s.day, + card_start_trip_m_minutes_s.month, + card_start_trip_m_minutes_s.year, + card_start_trip_m_minutes_s.hour, + card_start_trip_m_minutes_s.minute); + } + //transport + //validator + if(data_block.validator) { + furi_string_cat_printf(result, "Validator: %05d", data_block.validator); + } + break; + } + case 0x3CCB: { + parse_layout_FCB(&data_block, block); + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_use_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + break; + } + case 0x3C0B: { + //number + furi_string_cat_printf(result, "Number: %010lu\n", data_block.number); + //valid_from_date + DateTime card_use_from_date_s = {0}; + from_days_to_datetime(data_block.valid_from_date, &card_use_from_date_s, 1992); + furi_string_cat_printf( + result, + "Valid from: %02d.%02d.%04d\n", + card_use_from_date_s.day, + card_use_from_date_s.month, + card_use_from_date_s.year); + //valid_to_date + DateTime card_use_to_date_s = {0}; + from_days_to_datetime(data_block.valid_to_date, &card_use_to_date_s, 1992); + furi_string_cat_printf( + result, + "Valid to: %02d.%02d.%04d", + card_use_to_date_s.day, + card_use_to_date_s.month, + card_use_to_date_s.year); + break; + } + default: + result = NULL; + return false; + } + + return true; +} diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.h b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h new file mode 100644 index 00000000000..e5da8ddeb44 --- /dev/null +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* result); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/main/nfc/api/nfc_app_api_table_i.h b/applications/main/nfc/api/nfc_app_api_table_i.h index 08bff072eae..bf0e926ee67 100644 --- a/applications/main/nfc/api/nfc_app_api_table_i.h +++ b/applications/main/nfc/api/nfc_app_api_table_i.h @@ -1,4 +1,5 @@ #include "gallagher/gallagher_util.h" +#include "mosgortrans/mosgortrans_util.h" /* * A list of app's private functions and objects to expose for plugins. @@ -10,4 +11,8 @@ static constexpr auto nfc_app_api_table = sort(create_array_t( gallagher_deobfuscate_and_parse_credential, void, (GallagherCredential * credential, const uint8_t* cardholder_data_obfuscated)), - API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*))); + API_VARIABLE(GALLAGHER_CARDAX_ASCII, const uint8_t*), + API_METHOD( + mosgortrans_parse_transport_block, + bool, + (const MfClassicBlock* block, FuriString* result)))); diff --git a/applications/main/nfc/plugins/supported_cards/bip.c b/applications/main/nfc/plugins/supported_cards/bip.c index 211c27cb56c..f6fed6774b1 100644 --- a/applications/main/nfc/plugins/supported_cards/bip.c +++ b/applications/main/nfc/plugins/supported_cards/bip.c @@ -296,8 +296,8 @@ static bool bip_parse(const NfcDevice* device, FuriString* parsed_data) { furi_string_printf( parsed_data, "\e#Tarjeta Bip!\n" - "Card Number: %ld\n" - "Balance: $%d (flags %x)\n" + "Card Number: %lu\n" + "Balance: $%hu (flags %hu)\n" "Current Trip Window Ends:\n @", bip_data.card_id, bip_data.balance, diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index ba7c3bf5e3b..50806e137d3 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -1,10 +1,12 @@ #include "nfc_supported_card_plugin.h" +#include #include #include #include #include +#include "../../api/mosgortrans/mosgortrans_util.h" #include "furi_hal_rtc.h" #define TAG "Troika" @@ -19,19 +21,6 @@ typedef struct { uint32_t data_sector; } TroikaCardConfig; -typedef enum { - TroikaLayoutUnknown = 0x0, - TroikaLayout2 = 0x2, - TroikaLayoutE = 0xE, -} TroikaLayout; - -typedef enum { - TroikaSublayoutUnknown = 0x0, - TroikaSublayout3 = 0x3, - TroikaSublayout5 = 0x5, - TroikaSublayout6 = 0x6, -} TroikaSubLayout; - static const MfClassicKeyPair troika_1k_keys[] = { {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, @@ -52,33 +41,53 @@ static const MfClassicKeyPair troika_1k_keys[] = { }; static const MfClassicKeyPair troika_4k_keys[] = { - {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, - {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, - {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698}, - {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dbb}, - {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763}, - {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, {.a = 0x08b386463229, .b = 0x5efbaecef46b}, - {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880}, - {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, - {.a = 0x6b02733bb6ec, .b = 0x7038cd25c408}, {.a = 0x403d706ba880, .b = 0xb39d19a280df}, - {.a = 0xc11f4597efb5, .b = 0x70d901648cb9}, {.a = 0x0db520c78c1c, .b = 0x73e5b9d9d3a4}, - {.a = 0x3ebce0925b2f, .b = 0x372cc880f216}, {.a = 0x16a27af45407, .b = 0x9868925175ba}, - {.a = 0xaba208516740, .b = 0xce26ecb95252}, {.a = 0xcd64e567abcd, .b = 0x8f79c4fd8a01}, - {.a = 0x764cd061f1e6, .b = 0xa74332f74994}, {.a = 0x1cc219e9fec1, .b = 0xb90de525ceb6}, - {.a = 0x2fe3cb83ea43, .b = 0xfba88f109b32}, {.a = 0x07894ffec1d6, .b = 0xefcb0e689db3}, - {.a = 0x04c297b91308, .b = 0xc8454c154cb5}, {.a = 0x7a38e3511a38, .b = 0xab16584c972a}, - {.a = 0x7545df809202, .b = 0xecf751084a80}, {.a = 0x5125974cd391, .b = 0xd3eafb5df46d}, - {.a = 0x7a86aa203788, .b = 0xe41242278ca2}, {.a = 0xafcef64c9913, .b = 0x9db96dca4324}, - {.a = 0x04eaa462f70b, .b = 0xac17b93e2fae}, {.a = 0xe734c210f27e, .b = 0x29ba8c3e9fda}, - {.a = 0xd5524f591eed, .b = 0x5daf42861b4d}, {.a = 0xe4821a377b75, .b = 0xe8709e486465}, - {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70}, + {.a = 0xEC29806D9738, .b = 0xFBF225DC5D58}, //1 + {.a = 0xA0A1A2A3A4A5, .b = 0x7DE02A7F6025}, //2 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //3 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //4 + {.a = 0x73068F118C13, .b = 0x2B7F3253FAC5}, //5 + {.a = 0xFBC2793D540B, .b = 0xD3A297DC2698}, //6 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //7 + {.a = 0xAE3D65A3DAD4, .b = 0x0F1C63013DBA}, //8 + {.a = 0xA73F5DC1D333, .b = 0xE35173494A81}, //9 + {.a = 0x69A32F1C2F19, .b = 0x6B8BD9860763}, //10 + {.a = 0x9BECDF3D9273, .b = 0xF8493407799D}, //11 + {.a = 0x08B386463229, .b = 0x5EFBAECEF46B}, //12 + {.a = 0xCD4C61C26E3D, .b = 0x31C7610DE3B0}, //13 + {.a = 0xA82607B01C0D, .b = 0x2910989B6880}, //14 + {.a = 0x0E8F64340BA4, .b = 0x4ACEC1205D75}, //15 + {.a = 0x2AA05ED1856F, .b = 0xEAAC88E5DC99}, //16 + {.a = 0x6B02733BB6EC, .b = 0x7038CD25C408}, //17 + {.a = 0x403D706BA880, .b = 0xB39D19A280DF}, //18 + {.a = 0xC11F4597EFB5, .b = 0x70D901648CB9}, //19 + {.a = 0x0DB520C78C1C, .b = 0x73E5B9D9D3A4}, //20 + {.a = 0x3EBCE0925B2F, .b = 0x372CC880F216}, //21 + {.a = 0x16A27AF45407, .b = 0x9868925175BA}, //22 + {.a = 0xABA208516740, .b = 0xCE26ECB95252}, //23 + {.a = 0xCD64E567ABCD, .b = 0x8F79C4FD8A01}, //24 + {.a = 0x764CD061F1E6, .b = 0xA74332F74994}, //25 + {.a = 0x1CC219E9FEC1, .b = 0xB90DE525CEB6}, //26 + {.a = 0x2FE3CB83EA43, .b = 0xFBA88F109B32}, //27 + {.a = 0x07894FFEC1D6, .b = 0xEFCB0E689DB3}, //28 + {.a = 0x04C297B91308, .b = 0xC8454C154CB5}, //29 + {.a = 0x7A38E3511A38, .b = 0xAB16584C972A}, //30 + {.a = 0x7545DF809202, .b = 0xECF751084A80}, //31 + {.a = 0x5125974CD391, .b = 0xD3EAFB5DF46D}, //32 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //33 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //34 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //35 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //36 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //37 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //38 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //39 + {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //40 }; static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; if(type == MfClassicType1k) { - config->data_sector = 8; + config->data_sector = 11; config->keys = troika_1k_keys; } else if(type == MfClassicType4k) { config->data_sector = 8; // Further testing needed @@ -90,126 +99,6 @@ static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) return success; } -static TroikaLayout troika_get_layout(const MfClassicData* data, uint8_t start_block_num) { - furi_assert(data); - - // Layout is stored in byte 6 of block, length 4 bits (bits 52 - 55), second nibble. - const uint8_t* layout_ptr = &data->block[start_block_num].data[6]; - const uint8_t layout = (*layout_ptr & 0x0F); - - TroikaLayout result = TroikaLayoutUnknown; - switch(layout) { - case TroikaLayout2: - case TroikaLayoutE: - result = layout; - break; - default: - // If debug is enabled - pass the actual layout value for the debug text - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - return layout; - } else { - return TroikaLayoutUnknown; - } - } - - return result; -} - -static TroikaSubLayout troika_get_sub_layout(const MfClassicData* data, uint8_t start_block_num) { - furi_assert(data); - - // Sublayout is stored in byte 7 (bits 56 - 60) of block, length 5 bits (first nibble and one bit from second nibble) - const uint8_t* sub_layout_ptr = &data->block[start_block_num].data[7]; - const uint8_t sub_layout = (*sub_layout_ptr & 0x3F) >> 3; - - TroikaSubLayout result = TroikaSublayoutUnknown; - switch(sub_layout) { - case TroikaSublayout3: - case TroikaSublayout5: - case TroikaSublayout6: - result = sub_layout; - break; - default: - // If debug is enabled - pass the actual sublayout value for the debug text - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - return sub_layout; - } else { - return TroikaSublayoutUnknown; - } - } - - return result; -} - -static bool troika_has_balance(TroikaLayout layout, TroikaSubLayout sub_layout) { - UNUSED(sub_layout); - // Layout 0x2 has no balance - - if(layout == TroikaLayout2) { - return false; - } - - return true; -} - -static uint16_t troika_get_balance( - const MfClassicData* data, - uint8_t start_block_num, - TroikaLayout layout, - TroikaSubLayout sub_layout) { - furi_assert(data); - - // In layout 0x3 balance in bits 188:209 ( from sector start, length 22). - // In layout 0x5 balance in bits 165:185 ( from sector start, length 20). - - uint32_t balance = 0; - uint8_t balance_data_offset = 0; - bool supported_layout = false; - - if(layout == TroikaLayoutE && sub_layout == TroikaSublayout3) { - balance_data_offset = 7; - supported_layout = true; - } else if(layout == TroikaLayoutE && sub_layout == TroikaSublayout5) { - balance_data_offset = 4; - supported_layout = true; - } - - if(supported_layout) { - const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[balance_data_offset]; - balance |= (temp_ptr[0] & 0x3) << 18; - balance |= temp_ptr[1] << 10; - balance |= temp_ptr[2] << 2; - balance |= (temp_ptr[3] & 0xC0) >> 6; - } - - return balance / 100; -} - -static uint32_t troika_get_number( - const MfClassicData* data, - uint8_t start_block_num, - TroikaLayout layout, - TroikaSubLayout sub_layout) { - furi_assert(data); - UNUSED(sub_layout); - - if(layout == TroikaLayoutE || layout == TroikaLayout2) { - const uint8_t* temp_ptr = &data->block[start_block_num].data[2]; - - uint32_t number = 0; - for(size_t i = 1; i < 5; i++) { - number <<= 8; - number |= temp_ptr[i]; - } - number >>= 4; - number |= (temp_ptr[0] & 0xf) << 28; - - return number; - } else { - return 0; - } -} - static bool troika_verify_type(Nfc* nfc, MfClassicType type) { bool verified = false; @@ -230,7 +119,7 @@ static bool troika_verify_type(Nfc* nfc, MfClassicType type) { FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error); break; } - + FURI_LOG_D(TAG, "Verify success!"); verified = true; } while(false); @@ -306,40 +195,34 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data)); if(key != cfg.keys[cfg.data_sector].a) break; - // Get the block number of the block that contains the data - const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector); + FuriString* metro_result = furi_string_alloc(); + FuriString* ground_result = furi_string_alloc(); + FuriString* tat_result = furi_string_alloc(); - // Get layout, sublayout, balance and number - TroikaLayout layout = troika_get_layout(data, start_block_num); - TroikaSubLayout sub_layout = troika_get_sub_layout(data, start_block_num); + bool result1 = mosgortrans_parse_transport_block(&data->block[32], metro_result); + bool result2 = mosgortrans_parse_transport_block(&data->block[28], ground_result); + bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - // If debug is enabled - proceed even if layout or sublayout is unknown, that will make collecting data easier - if(layout == TroikaLayoutUnknown || sub_layout == TroikaSublayoutUnknown) break; + furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); + if(result1) { + furi_string_cat_printf( + parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); } - uint32_t number = troika_get_number(data, start_block_num, layout, sub_layout); - - furi_string_printf(parsed_data, "\e#Troika\nNum: %lu", number); - - if(troika_has_balance(layout, sub_layout) || - furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - uint16_t balance = troika_get_balance(data, start_block_num, layout, sub_layout); - furi_string_cat_printf(parsed_data, "\nBalance: %u RUR", balance); - } else { - furi_string_cat_printf(parsed_data, "\nBalance: Not available"); + if(result2) { + furi_string_cat_printf( + parsed_data, "\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); } - if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) { - furi_string_cat_printf( - parsed_data, - "\nLayout: %02x\nSublayout: %02x\nData Block: %u", - layout, - sub_layout, - start_block_num); + if(result3) { + furi_string_cat_printf(parsed_data, "\e#TAT\n%s\n", furi_string_get_cstr(tat_result)); } - parsed = true; + furi_string_free(tat_result); + furi_string_free(ground_result); + furi_string_free(metro_result); + + parsed = result1 || result2 || result3; } while(false); return parsed; @@ -363,4 +246,4 @@ static const FlipperAppPluginDescriptor troika_plugin_descriptor = { /* Plugin entry point - must return a pointer to const descriptor */ const FlipperAppPluginDescriptor* troika_plugin_ep() { return &troika_plugin_descriptor; -} +} \ No newline at end of file From 5e47048369477e6569f62a05d196eca520492402 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 5 Mar 2024 04:03:46 +0100 Subject: [PATCH 54/58] Archive: Fix item focus after aborting the Delete operation (#3475) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: hedger Co-authored-by: あく --- applications/main/archive/scenes/archive_scene_browser.c | 1 + applications/main/archive/scenes/archive_scene_delete.c | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index d156d171d5f..284e534abaa 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -157,6 +157,7 @@ bool archive_scene_browser_on_event(void* context, SceneManagerEvent event) { break; case ArchiveBrowserEventFileMenuDelete: if(archive_get_tab(browser) != ArchiveTabFavorites) { + archive_show_file_menu(browser, false); scene_manager_set_scene_state( archive->scene_manager, ArchiveAppSceneBrowser, SCENE_STATE_NEED_REFRESH); scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneDelete); diff --git a/applications/main/archive/scenes/archive_scene_delete.c b/applications/main/archive/scenes/archive_scene_delete.c index d3964d0febf..45d5b47cc90 100644 --- a/applications/main/archive/scenes/archive_scene_delete.c +++ b/applications/main/archive/scenes/archive_scene_delete.c @@ -29,6 +29,12 @@ void archive_scene_delete_on_enter(void* context) { filename = furi_string_alloc(); ArchiveFile_t* current = archive_get_current_file(app->browser); + + FuriString* filename_no_ext = furi_string_alloc(); + path_extract_filename(current->path, filename_no_ext, true); + strlcpy(app->text_store, furi_string_get_cstr(filename_no_ext), MAX_NAME_LEN); + furi_string_free(filename_no_ext); + path_extract_filename(current->path, filename, false); char delete_str[64]; From ecab4d53d2bd0b9e58503184c4985e94c90cdf20 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 6 Mar 2024 08:25:21 +0200 Subject: [PATCH 55/58] [FL-870] Auto-generated firmware documentation take two (#2944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add doxygen and doxygen-awesome css, cleanup docs files * Ignore more libraries and remove leftover local variables * Create an actual intro page * .md files linting * Add doxygen action * Fix Doxygen path * Fix doxyfile path * Try to upload * Change docs branch * Add submudules checkout * Disable doxygen on PR * Mention the firmware docs in the readme * More dev docs mentions in the readme * Fix runner group, add tags * Test dev in PR * Disable running on PR * Fix a typo in the doxyfile * Try upload to S3 * Fix local path * Fix S3 ACL * Add delete flag, unifying dev and tags * Update ignored directories * More ignored directories * Even more ignored directories * Fix submodule * Change S3 uploader * Change S3 uploader version * Fix aws sync flags * Fix ACL * Disable ACL * Improve ignores, add WiFi devboard docs * TEMP: generate dev docs * TEMP: generate 0.89.0 docs * Disabling PR trigger * Enable submodules and test build * Enable test build * Disable test build * Change docs directory structure * Fix accidentally committed submodule * Fix submodules * Update links to the developer documentation * Markdown linting * Update workflow, enable test build * Fix doxygen dir path * Update Doxyfile-awesome.cfg * Change paths * Fix upload docs path * Disable pull_request debug trigger * Disable tags building * Remove autolinks and namespaces * Establish basic documentation structure * Add missing changes * Improve stylesheet, move some files * Improve examples * Improve the main page * Improve application dev docs * Improve system programming docs * Improve development tools docs * Improve other docs * Improve application examples * Fix formatting * Fix PVS-studio warnings * Improve visuals * Fix doxygen syntax warnings * Fix broken links * Update doxygen action Co-authored-by: DrunkBatya Co-authored-by: あく Co-authored-by: Georgii Surkov Co-authored-by: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> --- .github/workflows/docs.yml | 56 ++++ .gitmodules | 5 +- .vscode/ReadMe.md | 2 +- ReadMe.md | 6 +- .../drivers/subghz/cc1101_ext/cc1101_ext.h | 4 +- .../examples/example_apps_assets/README.md | 6 +- .../example_apps_assets/example_apps_assets.c | 4 + .../examples/example_apps_data/README.md | 6 +- .../example_apps_data/example_apps_data.c | 4 + .../example_ble_beacon/ble_beacon_app.h | 4 + .../example_custom_font/example_custom_font.c | 4 + .../examples/example_images/ReadMe.md | 19 +- .../examples/example_images/example_images.c | 4 + .../example_plugins/example_plugins.c | 6 +- .../example_plugins/example_plugins_multi.c | 6 +- .../examples/example_plugins/plugin1.c | 7 +- .../examples/example_plugins/plugin2.c | 7 +- .../example_plugins/plugin_interface.h | 8 +- .../example_plugins_advanced/app_api.h | 9 +- .../example_plugins_advanced/plugin1.c | 5 +- .../example_plugins_advanced/plugin2.c | 5 +- .../plugin_interface.h | 8 +- .../examples/example_thermo/README.md | 10 +- .../examples/example_thermo/example_thermo.c | 5 +- applications/services/gui/canvas.h | 1 - applications/services/gui/canvas_i.h | 2 +- applications/services/gui/elements.h | 3 +- applications/services/gui/modules/dialog_ex.h | 1 - applications/services/gui/scene_manager.h | 1 - applications/services/gui/view.h | 20 +- applications/services/gui/view_stack.h | 4 +- applications/services/storage/storage.h | 6 +- .../services/storage/storage_internal_api.c | 9 +- assets/ReadMe.md | 16 +- assets/dolphin/ReadMe.md | 2 +- documentation/.gitignore | 3 +- documentation/AppManifests.md | 32 +-- documentation/AppsOnSDCard.md | 28 +- documentation/ExpansionModules.md | 2 +- documentation/FuriCheck.md | 2 +- documentation/FuriHalBus.md | 2 +- documentation/FuriHalDebuging.md | 2 +- documentation/HardwareTargets.md | 4 +- documentation/KeyCombo.md | 2 +- documentation/LFRFIDRaw.md | 2 +- documentation/OTA.md | 30 ++- documentation/UnitTests.md | 14 +- documentation/UniversalRemotes.md | 8 +- .../Firmware update on Developer Board.md | 246 ++++++++++++++++++ .../Get started with the Dev Board.md | 175 +++++++++++++ .../Reading logs via the Dev Board.md | 152 +++++++++++ documentation/doxygen/Doxyfile-awesome.cfg | 11 + .../{Doxyfile => doxygen/Doxyfile.cfg} | 90 +++++-- documentation/doxygen/applications.dox | 12 + documentation/doxygen/dev_board.dox | 10 + documentation/doxygen/dev_tools.dox | 9 + documentation/doxygen/doxygen-awesome-css | 1 + documentation/doxygen/examples.dox | 10 + documentation/doxygen/expansion_modules.dox | 8 + documentation/doxygen/favicon.ico | Bin 0 -> 2734 bytes documentation/doxygen/file_formats.dox | 13 + documentation/doxygen/header.html | 84 ++++++ documentation/doxygen/index.dox | 25 ++ documentation/doxygen/logo.png | Bin 0 -> 873 bytes documentation/doxygen/misc.dox | 9 + documentation/doxygen/system.dox | 13 + documentation/fbt.md | 10 +- .../file_formats/BadUsbScriptFormat.md | 26 +- .../file_formats/InfraredFileFormats.md | 27 +- .../file_formats/LfRfidFileFormat.md | 2 +- documentation/file_formats/NfcFileFormats.md | 2 +- .../file_formats/SubGhzFileFormats.md | 30 +-- .../file_formats/iButtonFileFormat.md | 2 +- furi/core/check.h | 10 +- furi/core/kernel.h | 2 +- furi/core/log.h | 9 +- furi/core/memmgr_heap.h | 6 +- furi/core/message_queue.h | 2 - furi/core/string.h | 2 +- furi/core/thread.h | 4 +- lib/flipper_application/elf/elf_file.h | 5 +- lib/nfc/nfc.h | 2 +- lib/nfc/nfc_scanner.h | 6 +- targets/f7/furi_hal/furi_hal_rtc.h | 2 +- targets/furi_hal_include/furi_hal_bt.h | 9 +- targets/furi_hal_include/furi_hal_i2c.h | 4 +- targets/furi_hal_include/furi_hal_infrared.h | 8 +- targets/furi_hal_include/furi_hal_nfc.h | 18 +- targets/furi_hal_include/furi_hal_power.h | 10 +- 89 files changed, 1208 insertions(+), 254 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 documentation/devboard/Firmware update on Developer Board.md create mode 100644 documentation/devboard/Get started with the Dev Board.md create mode 100644 documentation/devboard/Reading logs via the Dev Board.md create mode 100644 documentation/doxygen/Doxyfile-awesome.cfg rename documentation/{Doxyfile => doxygen/Doxyfile.cfg} (98%) create mode 100644 documentation/doxygen/applications.dox create mode 100644 documentation/doxygen/dev_board.dox create mode 100644 documentation/doxygen/dev_tools.dox create mode 160000 documentation/doxygen/doxygen-awesome-css create mode 100644 documentation/doxygen/examples.dox create mode 100644 documentation/doxygen/expansion_modules.dox create mode 100644 documentation/doxygen/favicon.ico create mode 100644 documentation/doxygen/file_formats.dox create mode 100644 documentation/doxygen/header.html create mode 100644 documentation/doxygen/index.dox create mode 100644 documentation/doxygen/logo.png create mode 100644 documentation/doxygen/misc.dox create mode 100644 documentation/doxygen/system.dox diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..e6e53ee0160 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,56 @@ +name: 'Generate documentation with Doxygen' + +on: + push: + branches: + - dev + +env: + TARGETS: f7 + DEFAULT_TARGET: f7 + +jobs: + doxygen: + if: ${{ !github.event.pull_request.head.repo.fork }} + runs-on: ubuntu-latest + steps: + - name: 'Wipe workspace' + run: find ./ -mount -maxdepth 1 -exec rm -rf {} \; + + - name: 'Checkout code' + uses: actions/checkout@v4 + with: + submodules: true + fetch-depth: 1 + ref: ${{ github.event.pull_request.head.sha }} + + - name: 'Get commit details' + id: names + run: | + if [[ ${{ github.event_name }} == 'pull_request' ]]; then + TYPE="pull" + elif [[ "${{ github.ref }}" == "refs/tags/"* ]]; then + TYPE="tag" + else + TYPE="other" + fi + python3 scripts/get_env.py "--event_file=${{ github.event_path }}" "--type=$TYPE" + + - name: 'Generate documentation' + uses: mattnotmitt/doxygen-action@v1.9.8 + with: + working-directory: 'documentation/' + doxyfile-path: './doxygen/Doxyfile-awesome.cfg' + + - name: 'Upload documentation' + uses: jakejarvis/s3-sync-action@v0.5.1 + env: + AWS_S3_BUCKET: "${{ secrets.FW_DOCS_AWS_BUCKET }}" + AWS_ACCESS_KEY_ID: "${{ secrets.FW_DOCS_AWS_ACCESS_KEY }}" + AWS_SECRET_ACCESS_KEY: "${{ secrets.FW_DOCS_AWS_SECRET_KEY }}" + AWS_REGION: "${{ secrets.FW_DOCS_AWS_REGION }}" + SOURCE_DIR: "./documentation/doxygen/build/html" + DEST_DIR: "${{steps.names.outputs.branch_name}}" + with: + args: "--delete" + diff --git a/.gitmodules b/.gitmodules index 52cf4a207b7..c4c68a6a77d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,4 +37,7 @@ url = https://github.com/STMicroelectronics/stm32wbxx_hal_driver [submodule "lib/stm32wb_copro"] path = lib/stm32wb_copro - url = https://github.com/flipperdevices/stm32wb_copro.git + url = https://github.com/flipperdevices/stm32wb_copro.git +[submodule "documentation/doxygen/doxygen-awesome-css"] + path = documentation/doxygen/doxygen-awesome-css + url = https://github.com/jothepro/doxygen-awesome-css.git diff --git a/.vscode/ReadMe.md b/.vscode/ReadMe.md index 5aed0435cb9..c7fc69f78b6 100644 --- a/.vscode/ReadMe.md +++ b/.vscode/ReadMe.md @@ -1,4 +1,4 @@ -# Visual Studio Code workspace for Flipper Zero +# Visual Studio Code workspace for Flipper Zero {#vscode} ## Setup diff --git a/ReadMe.md b/ReadMe.md index 387ac2de78b..34776ebd157 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -11,6 +11,7 @@ - [Flipper Zero Official Website](https://flipperzero.one). A simple way to explain to your friends what Flipper Zero can do. - [Flipper Zero Firmware Update](https://update.flipperzero.one). Improvements for your dolphin: latest firmware releases, upgrade tools for PC and mobile devices. - [User Documentation](https://docs.flipperzero.one). Learn more about your dolphin: specs, usage guides, and anything you want to ask. +- [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). Dive into the Flipper Zero Firmware source code: build system, firmware structure, and more. # Contributing @@ -18,7 +19,7 @@ Our main goal is to build a healthy and sustainable community around Flipper, so ## I need help -The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). +The best place to search for answers is our [User Documentation](https://docs.flipperzero.one). If you can't find the answer there, check our [Discord Server](https://flipp.dev/discord) or our [Forum](https://forum.flipperzero.one/). If you want to contribute to the firmware development, or modify it for your own needs, you can also check our [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen). ## I want to report an issue @@ -95,7 +96,8 @@ Make sure your Flipper is on, and your firmware is functioning. Connect your Fli - [Hardware combos and Un-bricking](/documentation/KeyCombo.md) - recovering your Flipper from the most nasty situations - [Flipper File Formats](/documentation/file_formats) - everything about how Flipper stores your data and how you can work with it - [Universal Remotes](/documentation/UniversalRemotes.md) - contributing your infrared remote to the universal remote database -- And much more in the [documentation](/documentation) folder +- [Firmware Roadmap](/documentation/RoadMap.md) +- And much more in the [Developer Documentation](https://developer.flipper.net/flipperzero/doxygen) # Project structure diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h index d972fcb6618..dffb8a46d93 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.h +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.h @@ -1,6 +1,6 @@ /** - * @file furi_hal_subghz.h - * SubGhz HAL API + * @file cc1101_ext.h + * @brief External CC1101 transceiver access API. */ #pragma once diff --git a/applications/examples/example_apps_assets/README.md b/applications/examples/example_apps_assets/README.md index 024c0877be7..bf7e63e4284 100644 --- a/applications/examples/example_apps_assets/README.md +++ b/applications/examples/example_apps_assets/README.md @@ -1,7 +1,11 @@ -# Apps Assets folder Example +# Apps Assets folder Example {#example_app_assets} This example shows how to use the Apps Assets folder to store data that is not part of the application itself, but is required for its operation, and that data is provided with the application. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_assets). + ## What is the Apps Assets Folder? The **Apps Assets** folder is a folder where external applications unpack their assets. diff --git a/applications/examples/example_apps_assets/example_apps_assets.c b/applications/examples/example_apps_assets/example_apps_assets.c index 2c2cc8a8747..dae81a8dacf 100644 --- a/applications/examples/example_apps_assets/example_apps_assets.c +++ b/applications/examples/example_apps_assets/example_apps_assets.c @@ -1,3 +1,7 @@ +/** + * @file example_apps_assets.c + * @brief Application assets example. + */ #include #include #include diff --git a/applications/examples/example_apps_data/README.md b/applications/examples/example_apps_data/README.md index 0e51daf18cd..fb761754706 100644 --- a/applications/examples/example_apps_data/README.md +++ b/applications/examples/example_apps_data/README.md @@ -1,7 +1,11 @@ -# Apps Data folder Example +# Apps Data folder Example {#example_app_data} This example demonstrates how to utilize the Apps Data folder to store data that is not part of the app itself, such as user data, configuration files, and so forth. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_apps_data). + ## What is the Apps Data Folder? The **Apps Data** folder is a folder used to store data for external apps that are not part of the main firmware. diff --git a/applications/examples/example_apps_data/example_apps_data.c b/applications/examples/example_apps_data/example_apps_data.c index 7a297b01cfd..f40d526c9a8 100644 --- a/applications/examples/example_apps_data/example_apps_data.c +++ b/applications/examples/example_apps_data/example_apps_data.c @@ -1,3 +1,7 @@ +/** + * @file example_apps_data.c + * @brief Application data example. + */ #include #include diff --git a/applications/examples/example_ble_beacon/ble_beacon_app.h b/applications/examples/example_ble_beacon/ble_beacon_app.h index 563bd5bedae..61c8c56d1e7 100644 --- a/applications/examples/example_ble_beacon/ble_beacon_app.h +++ b/applications/examples/example_ble_beacon/ble_beacon_app.h @@ -1,3 +1,7 @@ +/** + * @file ble_beacon_app.h + * @brief BLE beacon example. + */ #pragma once #include "extra_beacon.h" diff --git a/applications/examples/example_custom_font/example_custom_font.c b/applications/examples/example_custom_font/example_custom_font.c index 15eeb5f02a4..2fec419041b 100644 --- a/applications/examples/example_custom_font/example_custom_font.c +++ b/applications/examples/example_custom_font/example_custom_font.c @@ -1,3 +1,7 @@ +/** + * @file example_custom_font.c + * @brief Custom font example. + */ #include #include diff --git a/applications/examples/example_images/ReadMe.md b/applications/examples/example_images/ReadMe.md index d884a0a975a..bf57950086b 100644 --- a/applications/examples/example_images/ReadMe.md +++ b/applications/examples/example_images/ReadMe.md @@ -1,11 +1,21 @@ -# Application icons +# Application icons {#example_app_images} + +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_images). + +## General principle + To use icons, do the following: -* add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located -* add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest -* every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension + +* Add a line to the application manifest: `fap_icon_assets="folder"`, where `folder` points to the folder where your icons are located +* Add `#include "application_id_icons.h"` to the application code, where `application_id` is the appid from the manifest +* Every icon in the folder will be available as a `I_icon_name` variable, where `icon_name` is the name of the icon file without the extension ## Example + We have an application with the following manifest: + ``` App( appid="example_images", @@ -17,6 +27,7 @@ App( So the icons are in the `images` folder and will be available in the generated `example_images_icons.h` file. The example code is located in `example_images_main.c` and contains the following line: + ``` #include "example_images_icons.h" ``` diff --git a/applications/examples/example_images/example_images.c b/applications/examples/example_images/example_images.c index b00818cd668..c43a30b698a 100644 --- a/applications/examples/example_images/example_images.c +++ b/applications/examples/example_images/example_images.c @@ -1,3 +1,7 @@ +/** + * @file example_images.c + * @brief Custom images example. + */ #include #include diff --git a/applications/examples/example_plugins/example_plugins.c b/applications/examples/example_plugins/example_plugins.c index 7e71e0d2eb5..4f2150884c7 100644 --- a/applications/examples/example_plugins/example_plugins.c +++ b/applications/examples/example_plugins/example_plugins.c @@ -1,5 +1,7 @@ -/* - * An example of a plugin host application. +/** + * @file example_plugins.c + * @brief Plugin host application example. + * * Loads a single plugin and calls its methods. */ diff --git a/applications/examples/example_plugins/example_plugins_multi.c b/applications/examples/example_plugins/example_plugins_multi.c index 3525b39ea4a..40abff56123 100644 --- a/applications/examples/example_plugins/example_plugins_multi.c +++ b/applications/examples/example_plugins/example_plugins_multi.c @@ -1,5 +1,7 @@ -/* - * An example of an advanced plugin host application. +/** + * @file example_plugins_multi.c + * @brief Advanced plugin host application example. + * * It uses PluginManager to load all plugins from a directory */ diff --git a/applications/examples/example_plugins/plugin1.c b/applications/examples/example_plugins/plugin1.c index 15621935339..de8041f3435 100644 --- a/applications/examples/example_plugins/plugin1.c +++ b/applications/examples/example_plugins/plugin1.c @@ -1,4 +1,9 @@ -/* A simple plugin implementing example_plugins application's plugin interface */ +/** + * @file plugin1.c + * @brief Plugin example 1. + * + * A simple plugin implementing example_plugins application's plugin interface + */ #include "plugin_interface.h" diff --git a/applications/examples/example_plugins/plugin2.c b/applications/examples/example_plugins/plugin2.c index 0b774dad21a..a196437f4b7 100644 --- a/applications/examples/example_plugins/plugin2.c +++ b/applications/examples/example_plugins/plugin2.c @@ -1,4 +1,9 @@ -/* Second plugin implementing example_plugins application's plugin interface */ +/** + * @file plugin2.c + * @brief Plugin example 2. + * + * Second plugin implementing example_plugins application's plugin interface + */ #include "plugin_interface.h" diff --git a/applications/examples/example_plugins/plugin_interface.h b/applications/examples/example_plugins/plugin_interface.h index 25d95d29436..85428429eed 100644 --- a/applications/examples/example_plugins/plugin_interface.h +++ b/applications/examples/example_plugins/plugin_interface.h @@ -1,7 +1,11 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ #pragma once -/* Common interface between a plugin and host application */ - #define PLUGIN_APP_ID "example_plugins" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_plugins_advanced/app_api.h b/applications/examples/example_plugins_advanced/app_api.h index 7035b79f52b..60a52e6f762 100644 --- a/applications/examples/example_plugins_advanced/app_api.h +++ b/applications/examples/example_plugins_advanced/app_api.h @@ -1,9 +1,12 @@ -#pragma once - -/* +/** + * @file app_api.h + * @brief Application API example. + * * This file contains an API that is internally implemented by the application * It is also exposed to plugins to allow them to use the application's API. */ +#pragma once + #include #ifdef __cplusplus diff --git a/applications/examples/example_plugins_advanced/plugin1.c b/applications/examples/example_plugins_advanced/plugin1.c index bf0ab50b42e..91308100799 100644 --- a/applications/examples/example_plugins_advanced/plugin1.c +++ b/applications/examples/example_plugins_advanced/plugin1.c @@ -1,4 +1,7 @@ -/* +/** + * @file plugin1.c + * @brief Plugin example 1. + * * This plugin uses both firmware's API interface and private application headers. * It can be loaded by a plugin manager that uses CompoundApiInterface, * which combines both interfaces. diff --git a/applications/examples/example_plugins_advanced/plugin2.c b/applications/examples/example_plugins_advanced/plugin2.c index f0b2f726db3..1ea5590b22e 100644 --- a/applications/examples/example_plugins_advanced/plugin2.c +++ b/applications/examples/example_plugins_advanced/plugin2.c @@ -1,4 +1,7 @@ -/* +/** + * @file plugin2.c + * @brief Plugin example 2. + * * This plugin uses both firmware's API interface and private application headers. * It can be loaded by a plugin manager that uses CompoundApiInterface, * which combines both interfaces. diff --git a/applications/examples/example_plugins_advanced/plugin_interface.h b/applications/examples/example_plugins_advanced/plugin_interface.h index d99b335ff05..d78dc9ecc1c 100644 --- a/applications/examples/example_plugins_advanced/plugin_interface.h +++ b/applications/examples/example_plugins_advanced/plugin_interface.h @@ -1,7 +1,11 @@ +/** + * @file plugin_interface.h + * @brief Example plugin interface. + * + * Common interface between a plugin and host application + */ #pragma once -/* Common interface between a plugin and host application */ - #define PLUGIN_APP_ID "example_plugins_advanced" #define PLUGIN_API_VERSION 1 diff --git a/applications/examples/example_thermo/README.md b/applications/examples/example_thermo/README.md index d298de64309..4af2e60432e 100644 --- a/applications/examples/example_thermo/README.md +++ b/applications/examples/example_thermo/README.md @@ -1,8 +1,14 @@ -# 1-Wire Thermometer +# 1-Wire Thermometer {#example_thermo} + This example application demonstrates the use of the 1-Wire library with a DS18B20 thermometer. It also covers basic GUI, input handling, threads and localisation. +## Source code + +Source code for this example can be found [here](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/examples/example_thermo). + ## Electrical connections + Before launching the application, connect the sensor to Flipper's external GPIO according to the table below: | DS18B20 | Flipper | | :-----: | :-----: | @@ -15,12 +21,14 @@ Before launching the application, connect the sensor to Flipper's external GPIO *NOTE 2*: For any other pin than 17, connect an external 4.7k pull-up resistor to pin 9. ## Launching the application + In order to launch this demo, follow the steps below: 1. Make sure your Flipper has an SD card installed. 2. Connect your Flipper to the computer via a USB cable. 3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice. ## Changing the data pin + It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below: ```c diff --git a/applications/examples/example_thermo/example_thermo.c b/applications/examples/example_thermo/example_thermo.c index 5abd963a190..576ece38264 100644 --- a/applications/examples/example_thermo/example_thermo.c +++ b/applications/examples/example_thermo/example_thermo.c @@ -1,4 +1,7 @@ -/* +/** + * @file example_thermo.c + * @brief 1-Wire thermometer example. + * * This file contains an example application that reads and displays * the temperature from a DS18B20 1-wire thermometer. * diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index a369e213bd9..b1a1b04be25 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -239,7 +239,6 @@ void canvas_draw_bitmap( * @param x x coordinate * @param y y coordinate * @param icon Icon instance - * @param flip IconFlip * @param rotation IconRotation */ void canvas_draw_icon_ex( diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index 0982830c98f..5d2a38ddf79 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -107,7 +107,7 @@ CanvasOrientation canvas_get_orientation(const Canvas* canvas); /** Draw a u8g2 bitmap * - * @param canvas Canvas instance + * @param u8g2 u8g2 instance * @param x x coordinate * @param y y coordinate * @param width width diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 48533513128..dbaf6d68108 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -188,8 +188,7 @@ void elements_bubble(Canvas* canvas, uint8_t x, uint8_t y, uint8_t width, uint8_ * @param canvas Canvas instance * @param x left x coordinates * @param y top y coordinate - * @param width bubble width - * @param height bubble height + * @param text text to display * @param horizontal horizontal aligning * @param vertical aligning */ diff --git a/applications/services/gui/modules/dialog_ex.h b/applications/services/gui/modules/dialog_ex.h index 26a46535450..91424f78c0a 100644 --- a/applications/services/gui/modules/dialog_ex.h +++ b/applications/services/gui/modules/dialog_ex.h @@ -114,7 +114,6 @@ void dialog_ex_set_text( * @param x x position * @param y y position * @param icon The icon - * @param name icon to be shown */ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* icon); diff --git a/applications/services/gui/scene_manager.h b/applications/services/gui/scene_manager.h index c349a12ceac..54dfa9cd478 100644 --- a/applications/services/gui/scene_manager.h +++ b/applications/services/gui/scene_manager.h @@ -108,7 +108,6 @@ bool scene_manager_handle_back_event(SceneManager* scene_manager); * Calls Scene event handler with Tick event parameter * * @param scene_manager SceneManager instance - * @return true if event was consumed, false otherwise */ void scene_manager_handle_tick_event(SceneManager* scene_manager); diff --git a/applications/services/gui/view.h b/applications/services/gui/view.h index 7a2003a63bc..eafe445fa18 100644 --- a/applications/services/gui/view.h +++ b/applications/services/gui/view.h @@ -34,44 +34,44 @@ typedef enum { typedef struct View View; /** View Draw callback - * @param canvas, pointer to canvas - * @param view_model, pointer to context + * @param canvas pointer to canvas + * @param model pointer to model * @warning called from GUI thread */ typedef void (*ViewDrawCallback)(Canvas* canvas, void* model); /** View Input callback - * @param event, pointer to input event data - * @param context, pointer to context + * @param event pointer to input event data + * @param context pointer to context * @return true if event handled, false if event ignored * @warning called from GUI thread */ typedef bool (*ViewInputCallback)(InputEvent* event, void* context); /** View Custom callback - * @param event, number of custom event - * @param context, pointer to context + * @param event number of custom event + * @param context pointer to context * @return true if event handled, false if event ignored */ typedef bool (*ViewCustomCallback)(uint32_t event, void* context); /** View navigation callback - * @param context, pointer to context + * @param context pointer to context * @return next view id * @warning called from GUI thread */ typedef uint32_t (*ViewNavigationCallback)(void* context); /** View callback - * @param context, pointer to context + * @param context pointer to context * @warning called from GUI thread */ typedef void (*ViewCallback)(void* context); /** View Update Callback Called upon model change, need to be propagated to GUI * throw ViewPort update - * @param view, pointer to view - * @param context, pointer to context + * @param view pointer to view + * @param context pointer to context * @warning called from GUI thread */ typedef void (*ViewUpdateCallback)(View* view, void* context); diff --git a/applications/services/gui/view_stack.h b/applications/services/gui/view_stack.h index cd62b4f6a03..ed17f682f15 100644 --- a/applications/services/gui/view_stack.h +++ b/applications/services/gui/view_stack.h @@ -44,7 +44,7 @@ View* view_stack_get_view(ViewStack* view_stack); * Adds View on top of ViewStack. * * @param view_stack instance - * @view view view to add + * @param view view to add */ void view_stack_add_view(ViewStack* view_stack, View* view); @@ -52,7 +52,7 @@ void view_stack_add_view(ViewStack* view_stack, View* view); * If no View to remove found - ignore. * * @param view_stack instance - * @view view view to remove + * @param view view to remove */ void view_stack_remove_view(ViewStack* view_stack, View* view); diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 20a371fc081..eaef59cd6dd 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -339,7 +339,7 @@ FS_Error storage_common_merge(Storage* storage, const char* old_path, const char * @brief Create a directory. * * @param storage pointer to a storage API instance. - * @param fs_path pointer to a zero-terminated string containing the directory path. + * @param path pointer to a zero-terminated string containing the directory path. * @return FSE_OK if the directory has been successfully created, any other error code on failure. */ FS_Error storage_common_mkdir(Storage* storage, const char* path); @@ -366,7 +366,6 @@ FS_Error storage_common_fs_info( * * @param storage pointer to a storage API instance. * @param path pointer to a zero-terminated string containing the path in question. - * @return true if the path was successfully resolved, false otherwise. */ void storage_common_resolve_path_and_ensure_app_directory(Storage* storage, FuriString* path); @@ -526,7 +525,8 @@ FS_Error storage_int_backup(Storage* storage, const char* dstname); * @param converter pointer to a filename conversion function (may be NULL). * @return FSE_OK if the storage was successfully restored, any other error code on failure. */ -FS_Error storage_int_restore(Storage* api, const char* dstname, Storage_name_converter converter); +FS_Error + storage_int_restore(Storage* storage, const char* dstname, Storage_name_converter converter); /***************** Simplified Functions ******************/ diff --git a/applications/services/storage/storage_internal_api.c b/applications/services/storage/storage_internal_api.c index 6d620b9c07b..d91c71c0ec9 100644 --- a/applications/services/storage/storage_internal_api.c +++ b/applications/services/storage/storage_internal_api.c @@ -2,8 +2,8 @@ #include "storage.h" #include -FS_Error storage_int_backup(Storage* api, const char* dstname) { - TarArchive* archive = tar_archive_alloc(api); +FS_Error storage_int_backup(Storage* storage, const char* dstname) { + TarArchive* archive = tar_archive_alloc(storage); bool success = tar_archive_open(archive, dstname, TAR_OPEN_MODE_WRITE) && tar_archive_add_dir(archive, STORAGE_INT_PATH_PREFIX, "") && tar_archive_finalize(archive); @@ -11,8 +11,9 @@ FS_Error storage_int_backup(Storage* api, const char* dstname) { return success ? FSE_OK : FSE_INTERNAL; } -FS_Error storage_int_restore(Storage* api, const char* srcname, Storage_name_converter converter) { - TarArchive* archive = tar_archive_alloc(api); +FS_Error + storage_int_restore(Storage* storage, const char* srcname, Storage_name_converter converter) { + TarArchive* archive = tar_archive_alloc(storage); bool success = tar_archive_open(archive, srcname, TAR_OPEN_MODE_READ) && tar_archive_unpack_to(archive, STORAGE_INT_PATH_PREFIX, converter); tar_archive_free(archive); diff --git a/assets/ReadMe.md b/assets/ReadMe.md index 84310e731f6..50e25a8c908 100644 --- a/assets/ReadMe.md +++ b/assets/ReadMe.md @@ -1,17 +1,19 @@ -# Requirements +# Firmware Assets {#firmware_assets} + +## Requirements - Python3 - Python3 packages: Pillow & heatshrink2 -# Compiling +## Compiling ```bash ./fbt icons proto dolphin_internal dolphin_blocking dolphin_ext resources ``` -# Asset naming rules +## Asset naming rules -## Images and Animations +### Images and Animations `NAME_VARIANT_SIZE` @@ -22,16 +24,16 @@ Image names will be automatically prefixed with `I_`, animation names with `A_`. Icons and Animations will be gathered into `icon.h` and `icon.c`. -## Dolphin and Games assets +### Dolphin and Games assets Rules are same as for Images and Animations plus assets are grouped by level and level prepends `NAME`. Good starting point: https://docs.unrealengine.com/4.27/en-US/ProductionPipelines/AssetNaming/ -# Important notes +## Important notes Don't include assets that you are not using, compiler is not going to strip unused assets. -# Structure +## Structure - `dolphin` - Dolphin game assets sources. Goes to `compiled` and `resources` folders in `build` directory. - `icons` - Icons sources. Goes to `compiled` folder in `build` directory. - `protobuf` - Protobuf sources. Goes to `compiled` folder in `build` directory. diff --git a/assets/dolphin/ReadMe.md b/assets/dolphin/ReadMe.md index e7572571ca0..d47dca0f7cc 100644 --- a/assets/dolphin/ReadMe.md +++ b/assets/dolphin/ReadMe.md @@ -1,4 +1,4 @@ -# Dolphin assets +# Dolphin assets {#dolphin_assets} Dolphin assets are split into 3 parts: diff --git a/documentation/.gitignore b/documentation/.gitignore index c18ff03bbb8..9c7aebc7494 100644 --- a/documentation/.gitignore +++ b/documentation/.gitignore @@ -1,2 +1 @@ -/html -/latex \ No newline at end of file +/doxygen/build \ No newline at end of file diff --git a/documentation/AppManifests.md b/documentation/AppManifests.md index 9afdccb0e41..b612df1b79f 100644 --- a/documentation/AppManifests.md +++ b/documentation/AppManifests.md @@ -1,14 +1,14 @@ -# Flipper Application Manifests (.fam) +# Flipper Application Manifests (.fam) {#app_manifests} All components of Flipper Zero firmware — services, user applications, and system settings — are developed independently. Each component has a build system manifest file named `application.fam`, which defines the basic properties of that component and its relations to other parts of the system. -When building firmware, **`fbt`** collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](./fbt.md#firmware-application-set) for details on build configurations. +When building firmware, `fbt` collects all application manifests and processes their dependencies. Then it builds only those components referenced in the current build configuration. See [FBT docs](fbt.md) for details on build configurations. ## Application definition A firmware component's properties are declared in a Python code snippet, forming a call to the `App()` function with various parameters. -Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are optional and may only be meaningful for certain application types. +Only two parameters are mandatory: **appid** and **apptype**. Others are optional and may only be meaningful for certain application types. ### Parameters @@ -34,7 +34,7 @@ Only two parameters are mandatory: **_appid_** and **_apptype_**. Others are opt - **flags**: internal flags for system apps. Do not use. - **cdefines**: C preprocessor definitions to declare globally for other apps when the current application is included in the active build configuration. **For external applications**: specified definitions are used when building the application itself. - **requires**: list of application IDs to include in the build configuration when the current application is referenced in the list of applications to build. -- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, **`fbt`** will abort the firmware build process. +- **conflicts**: list of application IDs with which the current application conflicts. If any of them is found in the constructed application list, `fbt` will abort the firmware build process. - **provides**: functionally identical to **_requires_** field. - **stack_size**: stack size in bytes to allocate for an application on its startup. Note that allocating a stack too small for an app to run will cause a system crash due to stack overflow, and allocating too much stack space will reduce usable heap memory size for apps to process data. _Note: you can use `ps` and `free` CLI commands to profile your app's memory usage._ - **icon**: animated icon name from built-in assets to be used when building the app as a part of the firmware. @@ -55,11 +55,11 @@ The following parameters are used only for [FAPs](./AppsOnSDCard.md): - **fap_description**: string, may be empty. Short application description. - **fap_author**: string, may be empty. Application's author. - **fap_weburl**: string, may be empty. Application's homepage. -- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](./AppsOnSDCard.md#fap-assets) for details. -- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. **`fbt`** will run the specified command for each file in the list. +- **fap_icon_assets**: string. If present, it defines a folder name to be used for gathering image assets for this application. These images will be preprocessed and built alongside the application. See [FAP assets](AppsOnSDCard.md) for details. +- **fap_extbuild**: provides support for parts of application sources to be built by external tools. Contains a list of `ExtFile(path="file name", command="shell command")` definitions. `fbt` will run the specified command for each file in the list. - **fal_embedded**: boolean, default `False`. Applies only to PLUGIN type. If `True`, the plugin will be embedded into host application's .fap file as a resource and extracted to `apps_assets/APPID` folder on its start. This allows plugins to be distributed as a part of the host application. -Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by **`fbt`**: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by **`fbt`**. +Note that commands are executed at the firmware root folder, and all intermediate files must be placed in an application's temporary build folder. For that, you can use pattern expansion by `fbt`: `${FAP_WORK_DIR}` will be replaced with the path to the application's temporary build folder, and `${FAP_SRC_DIR}` will be replaced with the path to the application's source folder. You can also use other variables defined internally by `fbt`. Example for building an app from Rust sources: @@ -77,12 +77,12 @@ Example for building an app from Rust sources: Library sources must be placed in a subfolder of the `lib` folder within the application's source folder. Each library is defined as a call to the `Lib()` function, accepting the following parameters: - - **name**: name of the library's folder. Required. - - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. - - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. - - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. - - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. - - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. + - **name**: name of the library's folder. Required. + - **fap_include_paths**: list of the library's relative paths to add to the parent fap's include path list. The default value is `["."]`, meaning the library's source root. + - **sources**: list of filename masks to be used for gathering include files for this library. Paths are relative to the library's source root. The default value is `["*.c*"]`. + - **cflags**: list of additional compiler flags to be used for building this library. The default value is `[]`. + - **cdefines**: list of additional preprocessor definitions to be used for building this library. The default value is `[]`. + - **cincludes**: list of additional include paths to be used for building this library. Paths are relative to the application's root. This can be used for providing external search paths for this library's code — for configuration headers. The default value is `[]`. Example for building an app with a private library: @@ -105,12 +105,12 @@ Example for building an app with a private library: ], ``` -For that snippet, **`fbt`** will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, **`fbt`** will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, **`fbt`** will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. -For the `loclass` library, **`fbt`** will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, **`fbt`** will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. +For that snippet, `fbt` will build 2 libraries: one from sources in `lib/mbedtls` folder and another from sources in the `lib/loclass` folder. For the `mbedtls` library, `fbt` will add `lib/mbedtls/include` to the list of include paths for the application and compile only the files specified in the `sources` list. Additionally, `fbt` will enable `MBEDTLS_ERROR_C` preprocessor definition for `mbedtls` sources. +For the `loclass` library, `fbt` will add `lib/loclass` to the list of the include paths for the application and build all sources in that folder. Also, `fbt` will disable treating compiler warnings as errors for the `loclass` library, which can be useful when compiling large 3rd-party codebases. Both libraries will be linked with the application. -## `.fam` file contents +## .fam file contents The `.fam` file contains one or more application definitions. For example, here's a part of `applications/service/bt/application.fam`: diff --git a/documentation/AppsOnSDCard.md b/documentation/AppsOnSDCard.md index 3f6d51acf06..cb8106fc6cf 100644 --- a/documentation/AppsOnSDCard.md +++ b/documentation/AppsOnSDCard.md @@ -1,4 +1,4 @@ -# FAP (Flipper Application Package) +# FAP (Flipper Application Package) {#apps_on_sd_card} [fbt](./fbt.md) supports building applications as FAP files. FAPs are essentially `.elf` executables with extra metadata and resources bundled in. @@ -6,7 +6,7 @@ FAPs are built with the `faps` target. They can also be deployed to the `dist` f FAPs do not depend on being run on a specific firmware version. Compatibility is determined by the FAP's metadata, which includes the required [API version](#api-versioning). -## How to set up an application to be built as a FAP +## How to set up an application to be built as a FAP {#fap-howto} FAPs are created and developed the same way as internal applications that are part of the firmware. @@ -21,15 +21,15 @@ To build your application as a FAP, create a folder with your app's source code FAPs can include static and animated images as private assets. They will be automatically compiled alongside application sources and can be referenced the same way as assets from the main firmware. -To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](./AppManifests.md#application-definition) for more details. +To use that feature, put your images in a subfolder inside your application's folder, then reference that folder in your application's manifest in the `fap_icon_assets` field. See [Application Manifests](AppManifests.md) for more details. To use these assets in your application, put `#include "{APPID}_icons.h"` in your application's source code, where `{APPID}` is the `appid` value field from your application's manifest. Then you can use all icons from your application's assets the same way as if they were a part of `assets_icons.h` of the main firmware. -Images and animated icons should follow the same [naming convention](../assets/ReadMe.md#asset-naming-rules) as those from the main firmware. +Images and animated icons should follow the same [naming convention](../assets/ReadMe.md) as those from the main firmware. ## Debugging FAPs -**`fbt`** includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by **`fbt`** and stock VS Code configurations. +`fbt` includes a script for gdb-py to provide debugging support for FAPs, `debug/flipperapps.py`. It is loaded in default debugging configurations by `fbt` and stock VS Code configurations. With it, you can debug FAPs as if they were a part of the main firmware — inspect variables, set breakpoints, step through the code, etc. @@ -43,7 +43,7 @@ To debug FAPs, do the following: 1. Build firmware with `./fbt` 2. Flash it with `./fbt flash` -3. [Build your FAP](#how-to-set-up-an-application-to-be-built-as-a-fap) and run it on Flipper +3. [Build your FAP](#fap-howto) and run it on Flipper After that, you can attach with `./fbt debug` or VS Code and use all debug features. @@ -59,25 +59,25 @@ Applications are built for a specific API version. It is a part of the hardware The App Loader allocates memory for the application and copies it to RAM, processing relocations and providing concrete addresses for imported symbols using the [symbol table](#symbol-table). Then it starts the application. -## API versioning +## API versioning {#api-versioning} Not all parts of firmware are available for external applications. A subset of available functions and variables is defined in the "api_symbols.csv" file, which is a part of the firmware target definition in the `targets/` directory. -**`fbt`** uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. +`fbt` uses semantic versioning for the API. The major version is incremented when there are breaking changes in the API. The minor version is incremented when new features are added. Breaking changes include: - Removing a function or a global variable - Changing the signature of a function -API versioning is mostly automated by **`fbt`**. When rebuilding the firmware, **`fbt`** checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version, and asks the user to go through the changes in the `.csv` file. New entries are marked with a "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, or to "`-`" for it to be unavailable. +API versioning is mostly automated by `fbt`. When rebuilding the firmware, `fbt` checks if there are any changes in the API exposed by headers gathered from `SDK_HEADERS`. If so, it stops the build, adjusts the API version, and asks the user to go through the changes in the `.csv` file. New entries are marked with a "`?`" mark, and the user is supposed to change the mark to "`+`" for the entry to be exposed for FAPs, or to "`-`" for it to be unavailable. -**`fbt`** will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". +`fbt` will not allow building a firmware until all "`?`" entries are changed to "`+`" or "`-`". -**NB:** **`fbt`** automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". +**NB:** `fbt` automatically manages the API version. The only case where manually incrementing the major API version is allowed (and required) is when existing "`+`" entries are to be changed to "`-`". -### Symbol table +### Symbol table {#symbol-table} -The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by **`fbt`** from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. +The symbol table is a list of symbols exported by firmware and available for external applications. It is generated by `fbt` from the API symbols file and is used by the App Loader to resolve addresses of imported symbols. It is build as a part of the `fap_loader` application. -**`fbt`** also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. +`fbt` also checks if all imported symbols are present in the symbol table. If there are any missing symbols, it will issue a warning listing them. The application won't be able to run on the device until all required symbols are provided in the symbol table. diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index c757c0d2b42..470564e5743 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -1,4 +1,4 @@ -# Expansion Module Protocol - Draft +# Expansion Module Protocol {#expansion_protocol} ## Terms and definitions diff --git a/documentation/FuriCheck.md b/documentation/FuriCheck.md index 02f3fc9173b..77a44ca84bf 100644 --- a/documentation/FuriCheck.md +++ b/documentation/FuriCheck.md @@ -1,4 +1,4 @@ -# Run time checks and forced system crash +# Run time checks and forced system crash {#furi_check} The best way to protect system integrity is to reduce amount cases that we must handle and crash the system as early as possible. For that purpose we have bunch of helpers located in Furi Core check.h. diff --git a/documentation/FuriHalBus.md b/documentation/FuriHalBus.md index 7880c041f6c..12c5a70ece1 100644 --- a/documentation/FuriHalBus.md +++ b/documentation/FuriHalBus.md @@ -1,4 +1,4 @@ -# Using FuriHalBus API +# Using FuriHalBus API {#furi_hal_bus} ## Basic info diff --git a/documentation/FuriHalDebuging.md b/documentation/FuriHalDebuging.md index da00cbdfb73..5104a999829 100644 --- a/documentation/FuriHalDebuging.md +++ b/documentation/FuriHalDebuging.md @@ -1,4 +1,4 @@ -# Furi HAL Debugging +# Furi HAL Debugging {#furi_hal_debugging} Some Furi subsystems got additional debugging features that can be enabled by adding additional defines to firmware compilation. Usually they are used for low level tracing and profiling or signal redirection/duplication. diff --git a/documentation/HardwareTargets.md b/documentation/HardwareTargets.md index b3213d4f506..9c36088eac8 100644 --- a/documentation/HardwareTargets.md +++ b/documentation/HardwareTargets.md @@ -1,4 +1,4 @@ -## What a Firmware Target is +## What a Firmware Target is {#hardware_targets} Flipper's firmware is modular and supports different hardware configurations in a common code base. It encapsulates hardware-specific differences in `furi_hal`, board initialization code, linker files, SDK data and other information in a _target definition_. @@ -29,7 +29,7 @@ A target definition file, `target.json`, is a JSON file that can contain the fol Not all applications are available on different hardware targets. -* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md#firmware-application-set) for details on build configurations. +* For applications built into the firmware, you have to specify a compatible application set using `FIRMWARE_APP_SET=...` fbt option. See [fbt docs](./fbt.md) for details on build configurations. * For applications built as external .faps, you have to explicitly specify compatible targets in application's manifest, `application.fam`. For example, to limit application to a single target, add `targets=["f7"],` to the manifest. It won't be built for other targets. diff --git a/documentation/KeyCombo.md b/documentation/KeyCombo.md index 6db5b411354..e3c5e000432 100644 --- a/documentation/KeyCombo.md +++ b/documentation/KeyCombo.md @@ -1,4 +1,4 @@ -# Key Combos +# Key Combos {#key_combos} There are times when your Flipper feels blue and doesn't respond to any of your commands due to a software issue. This guide will help you solve this problem. diff --git a/documentation/LFRFIDRaw.md b/documentation/LFRFIDRaw.md index 5a8cbde60d7..526b9a3cbaa 100644 --- a/documentation/LFRFIDRaw.md +++ b/documentation/LFRFIDRaw.md @@ -1,4 +1,4 @@ -# Reading RAW RFID data +# Reading RAW RFID data {#lfrfid_raw} Flipper Zero has the option to read RAW data from 125 kHz cards that allows you to record the card's data and save it, similar to how a dictaphone records sound. diff --git a/documentation/OTA.md b/documentation/OTA.md index ed75560cfe6..9028eff714b 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -1,20 +1,22 @@ -# Executing code from RAM +# Flipper Zero OTA update process {#ota_updates} + +## Executing code from RAM In Flipper firmware, we have a special boot mode that loads a specially crafted system image into RAM and transfers control to it. System image executing in RAM has full write access to Flipper's entire flash memory — something that's not possible when running main code from the same flash. We leverage that boot mode to perform OTA firmware updates, including operations on a radio stack running on the second MCU core. -# How does Flipper OTA work? +## How does Flipper OTA work? Installation of OTA updates goes through 3 stages: -## 1. Backing up internal storage (`/int`) +### 1. Backing up internal storage (/int) It is a special partition of Flipper's flash memory, taking up all available space not used by the firmware code. Newer versions of firmware may be of different size, and simply installing them would cause flash repartitioning and data loss. So, before taking any action on the firmware, we back up the current configuration from `/int` into a plain tar archive on the SD card. -## 2. Performing device update +### 2. Performing device update The main firmware loads an updater image — a customized build of the main Flipper firmware — into RAM and runs it. Updater performs operations on system flash as described by an Update manifest file. @@ -24,17 +26,17 @@ Then, updater validates and corrects Option Bytes — a special memory region co After that, updater loads a `.dfu` file with firmware to be flashed, checks its integrity using CRC32, writes it to system flash and validates written data. -## 3. Restoring internal storage and updating resources +### 3. Restoring internal storage and updating resources After performing operations on flash memory, the system restarts into newly flashed firmware. Then it performs restoration of previously backed up `/int` contents. If the update package contains an additional resources archive, it is extracted onto the SD card. -# Update manifest +## Update manifest An update package comes with a manifest that contains a description of its contents. The manifest is in Flipper File Format — a simple text file, comprised of key-value pairs. -## Mandatory fields +### Mandatory fields An update manifest must contain the following keys in the given order: @@ -50,7 +52,7 @@ An update manifest must contain the following keys in the given order: - **Loader CRC**: CRC32 of loader file. Note that it is represented in little-endian hex. -## Optional fields +### Optional fields Other fields may have empty values. In this case, updater skips all operations related to these values. @@ -66,7 +68,7 @@ Other fields may have empty values. In this case, updater skips all operations r - **OB reference**, **OB mask**, **OB write mask**: reference values for validating and correcting option bytes. -# OTA update error codes +## OTA update error codes We designed the OTA update process to be as fail-safe as possible. We don't start any risky operations before validating all related pieces of data to ensure we don't leave the device in a partially updated, or bricked, state. @@ -102,21 +104,21 @@ Even if something goes wrong, updater allows you to retry failed operations and | Restoring LFS | **12** | **0-100** | FS read/write error | | Updating resources | **13** | **0-100** | SD card read/write error | -# Building update packages +## Building update packages -## Full package +### Full package To build a full update package, including firmware, radio stack and resources for the SD card, run: `./fbt COMPACT=1 DEBUG=0 updater_package` -## Minimal package +### Minimal package To build a minimal update package, including only firmware, run: `./fbt COMPACT=1 DEBUG=0 updater_minpackage` -## Customizing update bundles +### Customizing update bundles Default update packages are built with Bluetooth Light stack. You can pick a different stack if your firmware version supports it, and build a bundle with it by passing the stack type and binary name to `fbt`: @@ -127,7 +129,7 @@ Note that `COPRO_OB_DATA` must point to a valid file in the `scripts` folder con In certain cases, you might have to confirm your intentions by adding `COPRO_DISCLAIMER=...` to the build command line. -## Building partial update packages +### Building partial update packages You can customize package contents by calling `scripts/update.py` directly. For example, to build a package only for installing BLE FULL stack: diff --git a/documentation/UnitTests.md b/documentation/UnitTests.md index 9352917cdc2..b77cd56c6ec 100644 --- a/documentation/UnitTests.md +++ b/documentation/UnitTests.md @@ -1,11 +1,11 @@ -# Unit tests +# Unit tests {#unit_tests} ## Intro Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they are correct. They are crucial for writing robust, bug-free code. -Flipper Zero firmware includes a separate application called [unit_tests](/applications/debug/unit_tests). +Flipper Zero firmware includes a separate application called [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests). It is run directly on Flipper devices in order to employ their hardware features and rule out any platform-related differences. When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features. @@ -20,7 +20,7 @@ To run the unit tests, follow these steps: 3. Launch the CLI session and run the `unit_tests` command. **NOTE:** To run a particular test (and skip all others), specify its name as the command argument. -See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete list of test names. +See [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/test_index.c) for the complete list of test names. ## Adding unit tests @@ -28,11 +28,11 @@ See [test_index.c](/applications/debug/unit_tests/test_index.c) for the complete #### Entry point -The common entry point for all tests is the [unit_tests](/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](/applications/debug/unit_tests/test_index.c) source file. +The common entry point for all tests is the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/test_index.c) source file. #### Test assets -Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). +Some unit tests require external data in order to function. These files (commonly called assets) reside in the [unit_tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat (FFF), binary, etc.). ### Application-specific @@ -41,9 +41,9 @@ Some unit tests require external data in order to function. These files (commonl Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol. To add unit tests for your protocol, follow these steps: -1. Create a file named `test_.irtest` in the [assets](/applications/debug/unit_tests/resources/unit_tests/infrared) directory. +1. Create a file named `test_.irtest` in the [assets](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) directory. 2. Fill it with the test data (more on it below). -3. Add the test code to [infrared_test.c](/applications/debug/unit_tests/infrared/infrared_test.c). +3. Add the test code to [infrared_test.c](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/debug/unit_tests/infrared/infrared_test.c). 4. Build and install firmware with resources, install it on your Flipper and run the tests to see if they pass. ##### Test data format diff --git a/documentation/UniversalRemotes.md b/documentation/UniversalRemotes.md index 213709afbfe..360d8a0abb2 100644 --- a/documentation/UniversalRemotes.md +++ b/documentation/UniversalRemotes.md @@ -1,4 +1,4 @@ -# Universal Remotes +# Universal Remotes {#universal_remotes} ## Televisions @@ -13,7 +13,7 @@ Each signal is recorded using the following algorithm: The signal names are self-explanatory. Remember to make sure that every recorded signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [TV universal remote file](/applications/main/infrared/resources/infrared/assets/tv.ir). +If everything checks out, append these signals **to the end** of the [TV universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/tv.ir). ## Audio players @@ -23,7 +23,7 @@ The signal names are self-explanatory. On many remotes, the `Play` button doubles as `Pause`. In this case, record it as `Play` omitting the `Pause`. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [audio player universal remote file](/applications/main/infrared/resources/infrared/assets/audio.ir). +If everything checks out, append these signals **to the end** of the [audio player universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/audio.ir). ## Projectors @@ -67,7 +67,7 @@ Finally, record the `Off` signal: The resulting remote file should now contain 6 signals. You can omit any of them, but you then won't be able to use their functionality. Test the file against the actual device. Make sure that every signal does what it's supposed to. -If everything checks out, append these signals **to the end** of the [A/C universal remote file](/applications/main/infrared/resources/infrared/assets/ac.ir). +If everything checks out, append these signals **to the end** of the [A/C universal remote file](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/ac.ir). ## Final steps diff --git a/documentation/devboard/Firmware update on Developer Board.md b/documentation/devboard/Firmware update on Developer Board.md new file mode 100644 index 00000000000..c62c653444a --- /dev/null +++ b/documentation/devboard/Firmware update on Developer Board.md @@ -0,0 +1,246 @@ +# Firmware update on Developer Board {#dev_board_fw_update} + +It's important to regularly update your Developer Board to keep it up to date. This tutorial will guide you through the necessary steps to successfully update the firmware of your Developer Board. + +This tutorial assumes that you're familiar with the basics of the command line. If you’re unfamiliar with the command line, please refer to the [Windows](https://www.digitalcitizen.life/command-prompt-how-use-basic-commands/) or [MacOS/Linux](https://ubuntu.com/tutorials/command-line-for-beginners#1-overview) command line tutorials. + +*** + +## Downloading the latest firmware + +The first thing you need to do is to download the latest Developer Board firmware. + +To get the latest pre-built firmware, do the following: + +1. Go to the [Update Server page](https://update.flipperzero.one/builds/blackmagic-firmware). +![The Update Server page hosts different versions of the Developer Board firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/gIXVO9VrE4LK05CmcMSSD_monosnap-miro-2023-07-19-17-36-23.jpg) + + There, you can find the following version of the Developer Board firmware: + + * **Release:** The most stable version of the firmware, which went through rigorous testing. The Release firmware version has the following format: **X.Y.Z/**, where X, Y, and Z are the build numbers. We recommend installing this version of the firmware. + + * **Release-candidate:** The firmware version that hasn't been tested yet and may contain bugs. The Release-candidate firmware version has the following format: **X.Y.Z-rc/**, where X, Y, and Z are the build numbers. + + * **Development:** The firmware version which builds every day and contains the latest features but might be unstable. + +2. Open the folder with the latest Release firmware and download the `blackmagic-firmware-s2-full-X.Y.Z.tgz` file. + +*** + +## Extracting the firmware + +After downloading the firmware archive, extract it into a folder: + +* On Windows, you can use any archive manager for this, for example, [7-Zip](https://www.7-zip.org/). + +* On MacOS and Linux, you can use the `tar` command: + + ```text + tar -xzf blackmagic-firmware-s2-full-X.Y.Z.tgz -C + ``` + +Don't forget to replace `X.Y.Z` with the actual version number and set the destination directory! + +*** + +## Installing the prerequisites for flashing + +Install the tools below if you haven't already. + +### Python + +Download and install [Python3](https://www.python.org/downloads/). Make sure to check the “Add Python to PATH” option during installation. + +### pip + +To install the pip package manager, run the following command in the Terminal: + +```text +python3 -m ensurepip --upgrade +``` + +If this command fails, please refer to the [official pip documentation](https://pip.pypa.io/en/stable/installation/) for alternative installation methods. + +### esptool + +esptool is a command-line utility for flashing ESP8266 and ESP32 microcontrollers, including the ESP32-S2 in your Developer Board. + +To install esptool, run the following command in the Terminal: + +```text +pip3 install esptool +``` + +If this command fails, try using **pip** instead of **pip3**. If this didn’t help, please refer to the [official esptool installation manual](https://docs.espressif.com/projects/esptool/en/latest/esp32/installation.html). + +*** + +## Connecting the Developer Board to your computer + +1. List all of the serial devices on your computer. + + * ***Windows*** + + On Windows, go to Device Manager and expand the Ports (COM & LPT) section. + + * ***macOS*** + + On macOS, you can run the following command in the Terminal: + + ```text + ls /dev/cu.* + ``` + + * ***Linux*** + + On Linux, you can run the following command in the Terminal: + + ```text + ls /dev/tty* + ``` + + View the devices in the list. + +2. Connect the Developer Board to your computer using a USB-C cable.\ +![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/Aq7gfMI-m_5H6sGGjwb4I_monosnap-miro-2023-07-19-19-47-39.jpg) + +3. Switch your Developer Board to Bootloader mode: + + 3.1. Press and hold the **BOOT** button. + + 3.2. Press the **RESET** button while holding the **BOOT** button. + + 3.3. Release the **BOOT** button. +![You can easily switch the Dev Board to Bootloader mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KynP9iT6sJ3mXLaLyI82__image.png) + +4. Repeat Step 1 and view the name of your Developer Board that appeared in the list. + + For example, on macOS: + + ```text + /dev/cu.usbmodem01 + ``` + +*** + +## Flashing the firmware + +### Getting the flash command + +1. Run the Terminal and navigate to the folder with the extracted firmware. + +2. Run the following command to read the file with the flash command: + + ```text + cat flash.command + ``` + + If you see a similar output, you can proceed to the Flashing step: + + ```text + esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x10000 blackmagic.bin 0x8000 partition-table.bin + ``` + + Don't use the exact command above for your Developer Board in the next step since it's just an example and may not match your firmware version! + + If you get an error, ensure you’re in the correct directory and extracted the firmware archive correctly. + +*** + +### Flashing + +1. Copy the command you got from the previous step and replace the `(PORT)` part with the name of the serial device you learned earlier. + + For Windows, replace `(PORT)` with the COM port number—for example, `COM3`. + +2. Run the command in the Terminal. + + Your command should look similar to this: + + ```text + esptool.py -p /dev/cu.usbmodem01 -b 460800 --before default_reset --after hard_reset --chip esp32s2 write_flash --flash_mode dio --flash_freq 80m --flash_size 4MB 0x1000 bootloader.bin 0x10000 blackmagic.bin 0x8000 partition-table.bin + ``` + + If you get an error, ensure that you’ve entered the correct serial device name and that the Developer Board is in Bootloader mode. + +3. Wait till the firmware flashing is over. The flashing process takes about 30 seconds. + + The Terminal output should look similar to this: + + ```text + esptool.py v4.6.1 + Serial port /dev/cu.usbmodem01 + Connecting... + Chip is ESP32-S2 (revision v0.0) + Features: WiFi, No Embedded Flash, No Embedded PSRAM, ADC and temperature sensor + calibration in BLK2 of efuse V2 + Crystal is 40MHz + MAC: 00:11:22:33:44:55 + Uploading stub... + Running stub... + Stub running... + Changing baud rate to 460800 + Changed. + Configuring flash size... + Flash will be erased from 0x00001000 to 0x00004fff... + Flash will be erased from 0x00010000 to 0x000ecfff... + Flash will be erased from 0x00008000 to 0x00008fff... + Compressed 13248 bytes to 9298... + Wrote 13248 bytes (9298 compressed) at 0x00001000 in 0.3 seconds (effective 402.7 kbit/s)... + Hash of data verified. + Compressed 904288 bytes to 562550... + Wrote 904288 bytes (562550 compressed) at 0x00010000 in 6.7 seconds (effective 1076.5 kbit/s)... + Hash of data verified. + Compressed 3072 bytes to 124... + Wrote 3072 bytes (124 compressed) at 0x00008000 in 0.1 seconds (effective 360.8 kbit/s)... + Hash of data verified. + Leaving... + Hard resetting via RTS pin... + ``` + + If the Terminal output has these two lines at the end, your Developer Board has been successfully updated: + + ```text + Leaving... + Hard resetting via RTS pin... + ``` + + If you get this warning, you can safely ignore it: + + ```text + WARNING: ESP32-S2 (revision v0.0) chip was placed into download mode using GPIO0. + esptool.py can not exit the download mode over USB. To run the app, reset the chip manually. + To suppress this note, set --after option to 'no_reset + ``` + +#### If flashing failed + +If you get an error message during the flashing process, such as: + +```text +A fatal error occurred: Serial data stream stopped: Possible serial noise or corruption. +``` + +or + +```text +FileNotFoundError: [Errno 2] No such file or directory: '/dev/cu.usbmodem01' +``` + +Try doing the following: + +* Disconnect the Developer Board from your computer, then reconnect it. + +* Use a different USB port on your computer. + +* Use a different USB-C cable. + +*** + +## Finishing the installation + +After flashing the firmware, you can reboot the Developer Board by pressing the **RESET** button. + +![Reset the Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/rcQeKARgrVwa51tLoo-qY_monosnap-miro-2023-07-20-18-29-33.jpg) + +The Developer Board should appear as a serial device on your computer. Now, you can use it with the Black Magic Debug client of your choice. diff --git a/documentation/devboard/Get started with the Dev Board.md b/documentation/devboard/Get started with the Dev Board.md new file mode 100644 index 00000000000..eb230663e52 --- /dev/null +++ b/documentation/devboard/Get started with the Dev Board.md @@ -0,0 +1,175 @@ +# Get started with the Dev Board {#dev_board_get_started} + +The Wi-Fi Developer Board serves as a tool to debug the Flipper Zero firmware. To debug the firmware, the initial step involves compiling the firmware from its source code. This process enables the debugging functionality within the firmware and generates all the necessary files required for debugging purposes. + +> **NOTE:** Building and debugging the Flipper Zero firmware is fully supported on MacOS and Linux. Support for Windows is in beta test. + +*** + +## Updating the firmware of your Developer Board + +Update the firmware of your Developer Board before using it. For more information, visit [Firmware update on Developer Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/update). + +*** + +## Installing Git + +You’ll need Git installed on your computer to clone the firmware repository. If you don’t have Git, install it by doing the following: + +* **MacOS** + + On MacOS, install the **Xcode Command Line Tools** package, which includes Git as one of the pre-installed command-line utilities, by running in the Terminal the following command: + + ```text + xcode-select --install + ``` + +* **Linux** + + On Linux, you can install Git using your package manager. For example, on Ubuntu, run in the Terminal the following command: + + ```text + sudo apt install git + ``` + +For other distributions, refer to your package manager documentation. + +*** + +## Building the firmware + +First, clone the firmware repository: + +```text +git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git +cd flipperzero-firmware +``` + +Then, run the **Flipper Build Tool** (FBT) to build the firmware: + +```text +./fbt +``` + +*** + +## Connecting the Developer Board + +The Developer Board can work in the **Wired** mode and two **Wireless** modes: **Wi-Fi access point (AP)** mode and **Wi-Fi client (STA)** mode. The Wired mode is the simplest to set up, but requires a USB Type-C cable. The Wireless modes are more complex to set up, but they allow you to debug your Flipper Zero wirelessly. + +> **NOTE:** Use the following credentials when connecting to the Developer Board in **Wi-Fi access point** mode: Name: **blackmagic**, Password: **iamwitcher** + +## Wired + +![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/jZdVlRTPVdSQVegzCyXp7_monosnap-miro-2023-06-22-16-28-06.jpg) + +To connect the Developer Board in **Wired** mode, do the following: + +1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. + +2. On your computer, open the **Terminal** and run the following: + + * **MacOS** + + ```text + ls /dev/cu.* + ``` + + * **Linux** + + ```text + ls /dev/tty* + ``` + + Note the list of devices. + +3. Connect the Developer Board to your computer via a USB-C cable. + +4. Rerun the command. Two new devices have to appear: this is the Developer Board. + +> **NOTE:** If the Developer Board doesn’t appear in the list of devices, try using a different cable, USB port, or computer. +> +> **NOTE:** Flipper Zero logs can only be viewed when the Developer Board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. For more information, visit [Reading logs via the Dev Board](https://docs.flipperzero.one/development/hardware/wifi-debugger-module/reading-logs). + +## Wireless + +### Wi-Fi access point (AP) mode + +![The Developer Board in Wi-Fi access point mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/tKRTMHAuruiLSEce2a8Ve_monosnap-miro-2023-06-22-16-39-17.jpg) + +Out of the box, the Developer Board is configured to work as a **Wi-Fi access point**. This means it will create its own Wi-Fi network to which you can connect. If your Developer Board doesn’t create a Wi-Fi network, it is probably configured to work in **Wi-Fi client** mode. To reset your Developer Board back to **Wi-Fi access point** mode, press and hold the **BOOT** button for 10 seconds, then wait for the module to reboot. + +![You can reconfigure the Developer Board mode by pressing and holding the BOOT button](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/57eELJsAwMxeZCEA1NMJw_monosnap-miro-2023-06-22-20-33-27.jpg) + +To connect the Developer Board in **Wi-Fi access point** mode, do the following: + +1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. + +2. Open Wi-Fi settings on your client device (phone, laptop, or other). + +3. Connect to the network: + + * Name: **blackmagic** + * Password: **iamwitcher** + +4. To configure the Developer Board, open a browser and go to `http://192.168.4.1`. + +#### Wi-Fi client (STA) mode + +![The Developer Board in Wi-Fi client mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/xLQpFyYPfUS5Cx0uQhrNd_monosnap-miro-2023-06-23-12-34-36.jpg) + +To connect the Developer Board in **Wi-Fi client** mode, you need to configure it to connect to your Wi-Fi network by doing the following: + +1. Cold-plug the Developer Board by turning off your Flipper Zero and connecting the Developer Board, and then turning it back on. + +2. Connect to the Developer Board in **Wi-Fi access point** mode. + +3. In a browser, go to the configuration page on `http://192.168.4.1`. + +4. Select the **STA** mode and enter your network’s **SSID** (name) and **password**. For convenience, you can click the **+** button to see the list of nearby networks. + +5. Save the configuration and reboot the Developer Board. + +![In the Wi-Fi tab, you can set the Developer Board mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/klbLVj8lz2bEvm7j4wRaj_monosnap-miro-2023-06-23-13-06-32.jpg) + +After rebooting, the Developer Board connects to your Wi-Fi network. You can connect to the device using the mDNS name [blackmagic.local](http://blackmagic.local) or the IP address it got from your router (you’ll have to figure this out yourself, every router is different). + +After connecting to your debugger via [blackmagic.local](http://blackmagic.local), you can find its IP address in the **SYS** tab. You can also change the debugger’s mode to **AP** or **STA** there. + +![In the SYS tab, you can view the IP address of your Developer Board](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/5XbUptlfqzlV0p6hRUqiG_monosnap-miro-2023-06-22-18-11-30.jpg) + +*** + +## Debugging the firmware + +Open the **Terminal** in the **flipperzero-firmware** directory that you cloned earlier and run the following command: + +```text +./fbt flash_blackmagic +``` + +This will upload the firmware you’ve just built to your Flipper Zero via the Developer Board. After that, you can start debugging the firmware using the [GDB](https://www.gnu.org/software/gdb/) debugger. We recommend using **VSCode** with the recommended extensions, and we have pre-made configurations for it. + +To debug in **VSCode**, do the following: + +1. In VSCode, open the **flipperzero-firmware** directory. + +2. You should see a notification about recommended extensions. Install them. + + If there were no notifications, open the **Extensions** tab, enter `@recommended` in the search bar, and install the workspace recommendations. + +3. In the **Terminal**, run the `./fbt vscode_dist` command. This will generate the VSCode configuration files needed for debugging. + +4. In VSCode, open the **Run and Debug** tab and select **Attach FW (blackmagic)** from the dropdown menu. + +5. If needed, flash your Flipper Zero with the `./fbt flash_blackmagic` command, then click the **Play** button in the debug sidebar to start the debugging session. + +6. Note that starting a debug session halts the execution of the firmware, so you’ll need to click the **Continue** button on the toolbar at the top of your VSCode window to continue execution. + +![Click Continue in the toolbar to continue execution of the firmware](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/lp8ygGaZ3DvWD3OSI9yGO_monosnap-miro-2023-06-23-17-58-09.jpg) + +To learn about debugging, visit the following pages: + +* [Debugging with GDB](https://sourceware.org/gdb/current/onlinedocs/gdb.pdf) + +* [Debugging in VS Code](https://code.visualstudio.com/docs/editor/debugging) diff --git a/documentation/devboard/Reading logs via the Dev Board.md b/documentation/devboard/Reading logs via the Dev Board.md new file mode 100644 index 00000000000..112e59a19bf --- /dev/null +++ b/documentation/devboard/Reading logs via the Dev Board.md @@ -0,0 +1,152 @@ +# Reading logs via the Dev Board {#dev_board_reading_logs} + +The Developer Board allows you to read Flipper Zero logs via UART. Unlike reading logs via the command-line interface (CLI), the Developer Board enables you to collect logs from the device directly to a serial console independently from the operating system of Flipper Zero. It allows you to see the device's logs when it's loading, updating, or crashing. It's useful for debugging and troubleshooting during software development. + +> **NOTE:** Flipper Zero logs can only be viewed when the developer board is connected via USB. The option to view logs over Wi-Fi will be added in future updates. + +## Setting the log level + +Depending on your needs, you can set the log level by going to Main Menu -> Settings -> Log Level. To learn more about logging levels, visit [Settings](https://docs.flipperzero.one/basics/settings#d5TAt). + +![You can manually set the preferred log level](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/INzQMw8QUsG9PXi30WFS0_monosnap-miro-2023-07-11-13-29-47.jpg) + +*** + +## Viewing Flipper Zero logs + +Depending on your operating system, you need to install an additional application on your computer to read logs via the Developer Board: + +### MacOS + +On MacOS, you need to install the **minicom** communication program by doing the following: + +1. [Install Homebrew](https://brew.sh/) by running in the Terminal the following command: + + ```text + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + ``` + +2. After installation of Homebrew, run the following command to install minicom: + + ```text + brew install minicom + ``` + +After installation of minicom on your macOS computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following: + +1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. + +2. On your computer, open the Terminal and run the following command: + + ```text + ls /dev/cu.* + ``` + + Note the list of devices. + +3. Connect the developer board to your computer using a USB Type-C cable.\ +![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) + +4. Rerun the command. Two new devices have to appear: this is the Developer Board. + + ```text + /dev/cu.usbmodemblackmagic1 + /dev/cu.usbmodemblackmagic3 + ``` + + Your Developer Board might have different names. + +5. Run the following command: + + ```text + minicom -D /dev/ -b 230400 + ``` + + Where `` is the name of your device with a bigger number. + + Example: + + ```text + minicom -D /dev/cu.usbmodemblackmagic3 -b 230400 + ``` + +6. View logs of your Flipper Zero in the Terminal. + +7. To quit, close the minicom window or quit via the minicom menu. + +### Linux + +On Linux, you need to install the **minicom** communication program. For example, on Ubuntu, run in the Terminal the following command: + +```text +sudo apt install minicom +``` + +After installation of minicom on your Linux computer, you can connect to the Developer Board to read Flipper Zero logs by doing the following: + +1. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. + +2. On your computer, open the Terminal and run the following command: + + ```text + ls /dev/tty* + ``` + + Note the list of devices. + +3. Connect the developer board to your computer using a USB Type-C cable. +![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) + +4. Rerun the command. Two new devices have to appear: this is the Developer Board. + + ```text + /dev/ttyACM0 + /dev/ttyACM1 + ``` + + Your Developer Board might have different names. + +5. Run the following command: + + ```text + minicom -D /dev/ \-b 230400 + ``` + + Where `` is the name of your device with a bigger number. + + Example: + + ```text + minicom -D /dev/ttyACM1 \-b 230400 + ``` + +6. View logs of your Flipper Zero in the Terminal. + + > **NOTE:** If no logs are shown in the Terminal, try running the command from Step 5 with another device name. + +7. To quit, close the minicom window or quit via the minicom menu. + +### Windows + +On Windows, do the following: + +1. On your computer, [install the PuTTY application](https://www.chiark.greenend.org.uk/\~sgtatham/putty/latest.html). + +2. Cold-plug the Developer Board into your Flipper Zero by turning off the Flipper Zero, connecting the developer board, and then turning it back on. + +3. Connect the developer board to your computer using a USB Type-C cable. +![The Developer Board in Wired mode](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/iPpsMt2-is4aIjiVeFu5t_hjxs2i1oovrnps74v5jgsimage.png) + +4. Find the serial port that the developer board is connected to by going to **Device Manager -> Ports (COM & LPT)** and looking for a new port that appears when you connect the Wi-Fi developer board. +![Go to Device Manager -> Ports (COM & LPT)](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/KKLQJK1lvqmI5iab3d__C_image.png) + +5. Run the PuTTY application and select **Serial** as the connection type. + +6. Enter the port number you found in the previous step into the **Serial line** field. + +7. Set the **Speed** parameter to **230400** and click **Open**. +![Set the required parameters](https://archbee-image-uploads.s3.amazonaws.com/3StCFqarJkJQZV-7N79yY/ROBSJyfQ_CXiy4GUZcPbs_monosnap-miro-2023-07-12-13-56-47.jpg) + +8. View logs of your Flipper Zero in the PuTTY terminal window. + +9. To quit, close the PuTTY window. diff --git a/documentation/doxygen/Doxyfile-awesome.cfg b/documentation/doxygen/Doxyfile-awesome.cfg new file mode 100644 index 00000000000..e4c4c95cd70 --- /dev/null +++ b/documentation/doxygen/Doxyfile-awesome.cfg @@ -0,0 +1,11 @@ +@INCLUDE = doxygen/Doxyfile.cfg +GENERATE_TREEVIEW = YES # required! +DISABLE_INDEX = NO +FULL_SIDEBAR = NO +HTML_EXTRA_STYLESHEET = doxygen/doxygen-awesome-css/doxygen-awesome.css \ + doxygen/doxygen-awesome-css/doxygen-awesome-sidebar-only.css \ + doxygen/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css +HTML_COLORSTYLE = LIGHT # required with Doxygen >= 1.9.5 +HTML_HEADER = doxygen/header.html +HTML_EXTRA_FILES = doxygen/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js + diff --git a/documentation/Doxyfile b/documentation/doxygen/Doxyfile.cfg similarity index 98% rename from documentation/Doxyfile rename to documentation/doxygen/Doxyfile.cfg index f31cbb9d868..28ac19e0294 100644 --- a/documentation/Doxyfile +++ b/documentation/doxygen/Doxyfile.cfg @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "FlipperZero Firmware" +PROJECT_NAME = "Flipper Zero Firmware" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -51,14 +51,19 @@ PROJECT_BRIEF = # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = +PROJECT_LOGO = doxygen/logo.png + +# With the PROJECT_ICON tag one can specify an icon that is included in the tabs +# when the HTML document is shown. Doxygen will copy the logo to the output directory. + +PROJECT_ICON = doxygen/favicon.ico # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = documentation +OUTPUT_DIRECTORY = doxygen/build # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -150,7 +155,7 @@ INLINE_INHERITED_MEMB = NO # shortest path that makes the file name unique will be used # The default value is: YES. -FULL_PATH_NAMES = YES +FULL_PATH_NAMES = NO # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand @@ -162,7 +167,7 @@ FULL_PATH_NAMES = YES # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. -STRIP_FROM_PATH = +STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which @@ -171,7 +176,7 @@ STRIP_FROM_PATH = # specify the list of include paths that are normally passed to the compiler # using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't @@ -347,7 +352,7 @@ TOC_INCLUDE_HEADINGS = 5 # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. -AUTOLINK_SUPPORT = YES +AUTOLINK_SUPPORT = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this @@ -463,7 +468,7 @@ LOOKUP_CACHE_SIZE = 0 # DOT_NUM_THREADS setting. # Minimum value: 0, maximum value: 32, default value: 1. -NUM_PROC_THREADS = 1 +NUM_PROC_THREADS = 4 #--------------------------------------------------------------------------- # Build related configuration options @@ -748,7 +753,7 @@ SHOW_FILES = YES # Folder Tree View (if specified). # The default value is: YES. -SHOW_NAMESPACES = YES +SHOW_NAMESPACES = NO # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from @@ -871,10 +876,13 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = applications \ - lib \ - firmware \ - furi +INPUT = ../applications \ + ../documentation \ + ../targets \ + ../assets \ + ../lib \ + ../furi \ + ../.vscode \ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -912,7 +920,9 @@ FILE_PATTERNS = *.c \ *.hh \ *.hxx \ *.hpp \ - *.h++ + *.h++ \ + *.md \ + *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -928,18 +938,46 @@ RECURSIVE = YES # run. EXCLUDE = \ - lib/mlib \ - lib/stm32wb_cmsis \ - lib/stm32wb_copro \ - lib/stm32wb_hal_driver \ - lib/littlefs \ - lib/nanopb \ - assets/protobuf \ - lib/libusb_stm32 \ - lib/FreeRTOS-Kernel \ - lib/microtar \ - lib/mbedtls \ - lib/cxxheaderparser + ../lib/mlib \ + ../lib/STM32CubeWB \ + ../lib/littlefs \ + ../lib/nanopb \ + ../assets/protobuf \ + ../lib/libusb_stm32 \ + ../lib/FreeRTOS-Kernel \ + ../lib/microtar \ + ../lib/mbedtls \ + ../lib/cxxheaderparser \ + ../lib/ST25RFAL002 \ + ../lib/fatfs \ + ../lib/mlib \ + ../lib/stm32wb_cmsis \ + ../lib/stm32wb_copro \ + ../lib/stm32wb_hal_driver \ + ../lib/stm32wb_hal \ + ../lib/cmsis_core \ + ../targets/f7/fatfs/ \ + ../applications/plugins/dap_link/lib/free-dap \ + ../applications/debug \ + ../applications/main \ + ../applications/settings \ + ../lib/micro-ecc \ + ../lib/ReadMe.md \ + ../lib/callback-connector \ + ../lib/app-scened-template \ + ../applications/ReadMe.md \ + ../targets/ReadMe.md \ + ../web \ + ../assets/protobuf \ + ../lib/libusb_stm32 \ + ../lib/FreeRTOS-Kernel \ + ../lib/microtar \ + ../lib/mbedtls \ + ../lib/cxxheaderparser \ + ../applications/external/dap_link/lib/free-dap \ + ../lib/heatshrink \ + ./doxygen/doxygen-awesome-css + # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/documentation/doxygen/applications.dox b/documentation/doxygen/applications.dox new file mode 100644 index 00000000000..ad0dfba8d80 --- /dev/null +++ b/documentation/doxygen/applications.dox @@ -0,0 +1,12 @@ +/** +@page applications Application Programming + +Flipper Zero features full support for custom applications which (usually) do not require any changes to the firmware. + +For easy application development, a software tool called [uFBT](https://github.com/flipperdevices/flipperzero-ufbt) is available. + +- @subpage vscode - Flipper Zero integration for VS Code +- @subpage apps_on_sd_card - Creating apps that can be dynamically loaded from the SD card +- @subpage app_manifests - How applications announce themselves to the system +- @subpage app_examples - Various application examples, complete with the source code +*/ diff --git a/documentation/doxygen/dev_board.dox b/documentation/doxygen/dev_board.dox new file mode 100644 index 00000000000..f9363ed0690 --- /dev/null +++ b/documentation/doxygen/dev_board.dox @@ -0,0 +1,10 @@ +/** +@page dev_board Developer Board + +[ESP32-based development board](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/wifi-devboard). + +- @subpage dev_board_get_started - Quick start for new users +- @subpage dev_board_reading_logs - Find out what is currently happening on the system +- @subpage dev_board_fw_update - Keep the developer board up to date + +*/ diff --git a/documentation/doxygen/dev_tools.dox b/documentation/doxygen/dev_tools.dox new file mode 100644 index 00000000000..bd7a5c704fc --- /dev/null +++ b/documentation/doxygen/dev_tools.dox @@ -0,0 +1,9 @@ +/** +@page dev_tools Developer Tools + +Hardware and software tools for all kinds of programming. + +- @subpage fbt - Official build and deployment tool for Flipper Zero +- @subpage dev_board - ESP32-based development board +- @subpage ota_updates - Standalone firmware self-update mechanism +*/ diff --git a/documentation/doxygen/doxygen-awesome-css b/documentation/doxygen/doxygen-awesome-css new file mode 160000 index 00000000000..df88fe4fdd9 --- /dev/null +++ b/documentation/doxygen/doxygen-awesome-css @@ -0,0 +1 @@ +Subproject commit df88fe4fdd97714fadfd3ef17de0b4401f804052 diff --git a/documentation/doxygen/examples.dox b/documentation/doxygen/examples.dox new file mode 100644 index 00000000000..9743549a2df --- /dev/null +++ b/documentation/doxygen/examples.dox @@ -0,0 +1,10 @@ +/** +@page app_examples Application Examples + +A collection of examples covering various aspects of application programming for Flipper Zero. + +- @subpage example_app_images - Using images and icons in an application +- @subpage example_app_assets - Using application-specific asset folders +- @subpage example_app_data - Using application-specific data folders +- @subpage example_thermo - Reading data from a 1-Wire thermometer +*/ diff --git a/documentation/doxygen/expansion_modules.dox b/documentation/doxygen/expansion_modules.dox new file mode 100644 index 00000000000..c38bb2923f6 --- /dev/null +++ b/documentation/doxygen/expansion_modules.dox @@ -0,0 +1,8 @@ +/** +@page expansion Expansion Modules + +Expansion modules are special pieces of hardware designed to interface with Flipper's GPIO connector, such as the [Video Game Module](https://shop.flipperzero.one/collections/flipper-zero-accessories/products/video-game-module-for-flipper-zero). + +- @subpage expansion_protocol - Transport protocol for smart expansion modules + +*/ diff --git a/documentation/doxygen/favicon.ico b/documentation/doxygen/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3fbdd33f20fdf6e7db8049f78c7eda63a67fe3fa GIT binary patch literal 2734 zcmeH}J(3ea429M57F$I$eFC~B+Hj+di8uyD$T55diUav54mkpu`JU9>9vfqzD2PyP zTP^A7r=^~mH7mBWWm(`4_EgMX%&fJPzqr|ZbkENze|%;3?!oK{%px31e)s70=aZdI zr+bhe+g7Mmb8)LnFsoI3ZI&F%+K|lK>b^E=_J)g<3U6%SZa@lqcUz4>-kP@xd@W0< zI!vv#u%JbI$PPQRJ?H;jl8=1Vr^b5A`dOdqr$6XZb|~6XbQX`TewvPBQ1Tl)nhUQZ$#UU^ik<_a+0nlC;{GG`L z#he&k!y#14Pb6TPH6a+@kV64LGS`B^LMscg2pB5>IKZ8IBY^IS@DOR% zmHUF0nz(SKW<`P_Vha)=;3=x-WLIq>fi$K8?mTQ^8oT5ZnBvA$3dFBwZ;ob<2eaKC zw3huP8u1V0I+9blb_FkIcD( z{^GqA4tnj#8I9AP$c=&i_c(C&!=Sl1OK(M4_)JTRdErL=*j@H&Lk|z7%ata8()%!p zgbKt>NQ!WnR6neDADbth@hJCz&nJfVfo?sD*dh;|2Zu9U1`3dEjBxtM08RPc<|5)5 z$cOwUjqmxj@w*xlph|8;Le0C5x1HHh{E=_G_>TV(#ChWmF7ZG8dI$D`Zu)nSOka=U I^m+9C8=LaGd;kCd literal 0 HcmV?d00001 diff --git a/documentation/doxygen/file_formats.dox b/documentation/doxygen/file_formats.dox new file mode 100644 index 00000000000..47c2362cf75 --- /dev/null +++ b/documentation/doxygen/file_formats.dox @@ -0,0 +1,13 @@ +/** +@page file_formats File Formats + +Descriptions of various file formats used in Flipper Zero, grouped by applications that use them. + +- @subpage badusb_file_format +- @subpage ibutton_file_format +- @subpage infrared_file_format +- @subpage lfrfid_file_format +- @subpage nfc_file_format +- @subpage subghz_file_format + +*/ diff --git a/documentation/doxygen/header.html b/documentation/doxygen/header.html new file mode 100644 index 00000000000..cd3ea49e75b --- /dev/null +++ b/documentation/doxygen/header.html @@ -0,0 +1,84 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/documentation/doxygen/index.dox b/documentation/doxygen/index.dox new file mode 100644 index 00000000000..6fb4d5a7177 --- /dev/null +++ b/documentation/doxygen/index.dox @@ -0,0 +1,25 @@ +/** +@mainpage Overview + +Welcome to the Flipper Zero Firmware Developer Documentation! + +This documentation is intended for developers who want to modify the firmware of the Flipper Zero. + +If you are looking for the user manual, please visit the [User Documentation](https://docs.flipperzero.one/) instead. + +The documentation is divided into several sections, with all of them accessible from the sidebar on the left: + +- @ref applications - Writing applications for Flipper Zero +- @ref system - Understanding the firmware's internals +- @ref file_formats - Saving and loading data to and from files +- @ref dev_tools - Hardware and software tools for all kinds of programming +- @ref expansion - Additional modules to expand Flipper's consciousness +- @ref misc - Various useful pieces of information + +Aside from the manually-written documentation files, there's also a few automatically-generated ones at the bottom of the sidebar: + +- [Data Structures](annotated.html) - Every data structure in a list +- [Files](files.html) - Source file tree with easy navigation + +These are generated from the source code and are useful for quickly finding the source code or API documentation for a particular function or data structure. +*/ diff --git a/documentation/doxygen/logo.png b/documentation/doxygen/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a38cdab4d6448d06e9d3576bcdbfaaa9e912aa7b GIT binary patch literal 873 zcmV-v1D5=WP)EX>4Tx04R}tkv&MmKpe$iKcpfR1uKX+WT;LSMMWI73Pq?8YK2xEOfLO`CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4P#J2)x2NQwVT3N2zhIPS;0dyl(!fWKa5su>&yRLwF{ ziMWu-t_q=71Q0|o`Y|RkQ=b#XBs|C0J$!tC`-NgjguFvE0V2XsEe3JSuIyt^Pc>L;heUz%ypVWNMI355FtQD6(y8mAx5i4iis5M$36Umj$b5~Os*0b zITlcX3d!+<|H1EW&HUtqn-q=%9WS>1F${!ufkw@?zmILZaRLOMfh(=$uhfB=Ptt2G zEqVm>Yy%h9Elu77E_Z-|CtWfmM+(sN=kvh(8GTb0=(`2F*4*Bj`#607($rP*1~@nb zMhcX@?(y!<_TK(I)9mjDS44800{@mh00006VoOIv0RI600RN!9r;`8x010qNS#tmY z3ljhU3ljkVnw%H_000McNliru=mP{0CJ(Vj;|%}+02y>eSad^gZEa<4bO1wgWnpw> zWFU8GbZ8()Nlj2!fese{00BWsL_t(o!^Kxi7Q-M26rXp^nLI6Br1lX60X5c5Ka$Qc znIRRDu&^dfFD$qcbl;r-9ua}l;G6{f1Duj5&cflsx?byPd@{|G#QZq zTw0is39^=aaf&4$J~?8$_#7ET;ka$~(cEHzFR%VC5!YOzB^;buP#|Z^6v; z1adLHVM6s8@I9JkQVhr9C>>6jl=y}U(((KQ($Fa@gMO#X00000NkvXXu0mjfI;w3s literal 0 HcmV?d00001 diff --git a/documentation/doxygen/misc.dox b/documentation/doxygen/misc.dox new file mode 100644 index 00000000000..0ef232ba249 --- /dev/null +++ b/documentation/doxygen/misc.dox @@ -0,0 +1,9 @@ +/** +@page misc Miscellaneous + +Various pieces of information that do not fall into other categories. + +- @subpage lfrfid_raw - Collecting raw data from LFRFID tags +- @subpage key_combos - Different key combination shortcuts for Flipper Zero +- @subpage universal_remotes - Creating and improving IR universal remote libraries +*/ diff --git a/documentation/doxygen/system.dox b/documentation/doxygen/system.dox new file mode 100644 index 00000000000..328717ea21e --- /dev/null +++ b/documentation/doxygen/system.dox @@ -0,0 +1,13 @@ +/** +@page system System Programming + +Lower level aspects of software development for Flipper Zero. + +- @subpage unit_tests - Automated testing, a crucial part of the development process +- @subpage furi_check - Hard checks for exceptional situations +- @subpage furi_hal_bus - Access the on-chip peripherals in a safe way +- @subpage furi_hal_debugging - Low level debugging features +- @subpage hardware_targets - Support for different hardware platforms +- @subpage firmware_assets - Various files required for building the firmware +- @subpage dolphin_assets - Animations for the Dolphin game +*/ diff --git a/documentation/fbt.md b/documentation/fbt.md index 0220178a2da..a7df5615b91 100644 --- a/documentation/fbt.md +++ b/documentation/fbt.md @@ -1,4 +1,4 @@ -# Flipper Build Tool +# Flipper Build Tool {#fbt} FBT is the entry point for firmware-related commands and utilities. It is invoked by `./fbt` in the firmware project root directory. Internally, it is a wrapper around [scons](https://scons.org/) build system. @@ -58,7 +58,7 @@ To use language servers other than the default VS Code C/C++ language server, us ## FBT targets -**`fbt`** keeps track of internal dependencies, so you only need to build the highest-level target you need, and **`fbt`** will make sure everything they depend on is up-to-date. +`fbt` keeps track of internal dependencies, so you only need to build the highest-level target you need, and `fbt` will make sure everything they depend on is up-to-date. ### High-level (what you most likely need) @@ -83,8 +83,8 @@ To use language servers other than the default VS Code C/C++ language server, us ### Firmware targets -- `faps` - build all external & plugin apps as [`.faps`](./AppsOnSDCard.md#fap-flipper-application-package). -- **`fbt`** also defines per-app targets. For example, for an app with `appid=snake_game` target names are: +- `faps` - build all external & plugin apps as [`.faps`](AppsOnSDCard.md). +- `fbt` also defines per-app targets. For example, for an app with `appid=snake_game` target names are: - `fap_snake_game`, etc. - build single app as `.fap` by its application ID. - Check out [`--extra-ext-apps`](#command-line-parameters) for force adding extra apps to external build. - `fap_snake_game_list`, etc - generate source + assembler listing for app's `.fap`. @@ -103,7 +103,7 @@ To use language servers other than the default VS Code C/C++ language server, us - `proto_ver` - generate `.h` with a protobuf version - `dolphin_internal`, `dolphin_blocking` - generate `.c+.h` for corresponding dolphin assets -## Command-line parameters +## Command-line parameters {#command-line-parameters} - `--options optionfile.py` (default value `fbt_options.py`) - load a file with multiple configuration values - `--extra-int-apps=app1,app2,appN` - force listed apps to be built as internal with the `firmware` target diff --git a/documentation/file_formats/BadUsbScriptFormat.md b/documentation/file_formats/BadUsbScriptFormat.md index 1eb8eb5180b..3bda3061723 100644 --- a/documentation/file_formats/BadUsbScriptFormat.md +++ b/documentation/file_formats/BadUsbScriptFormat.md @@ -1,21 +1,23 @@ -# Command syntax +# BadUSB File Format {#badusb_file_format} + +## Command syntax BadUsb app uses extended Duckyscript syntax. It is compatible with classic USB Rubber Ducky 1.0 scripts but provides some additional commands and features, such as custom USB ID, ALT+Numpad input method, SYSRQ command, and more functional keys. -# Script file format +## Script file format BadUsb app can execute only text scripts from `.txt` files, no compilation is required. Both `\n` and `\r\n` line endings are supported. Empty lines are allowed. You can use spaces or tabs for line indentation. -# Command set +## Command set -## Comment line +### Comment line Just a single comment line. The interpreter will ignore all text after the REM command. | Command | Parameters | Notes | | ------- | ------------ | ----- | | REM | Comment text | | -## Delay +### Delay Pause script execution by a defined time. | Command | Parameters | Notes | @@ -24,7 +26,7 @@ Pause script execution by a defined time. | DEFAULT_DELAY | Delay value in ms | Add delay before every next command | | DEFAULTDELAY | Delay value in ms | Same as DEFAULT_DELAY | -## Special keys +### Special keys | Command | Notes | | ------------------ | ---------------- | @@ -53,7 +55,7 @@ Pause script execution by a defined time. | APP | Same as MENU | | Fx | F1-F12 keys | -## Modifier keys +### Modifier keys Can be combined with a special key command or a single character. | Command | Notes | @@ -85,7 +87,7 @@ Will wait indefinitely for a button to be pressed | WAIT_FOR_BUTTON_PRESS | None | Will wait for the user to press a button to continue script execution | -## String +### String | Command | Parameters | Notes | | ------- | ----------- | ----------------- | @@ -100,13 +102,13 @@ Delay between keypresses. | STRING_DELAY | Delay value in ms | Applied once to next appearing STRING command | | STRINGDELAY | Delay value in ms | Same as STRING_DELAY | -## Repeat +### Repeat | Command | Parameters | Notes | | ------- | ---------------------------- | ----------------------- | | REPEAT | Number of additional repeats | Repeat previous command | -## ALT+Numpad input +### ALT+Numpad input On Windows and some Linux systems, you can print characters by holding `ALT` key and entering its code on Numpad. | Command | Parameters | Notes | @@ -115,14 +117,14 @@ On Windows and some Linux systems, you can print characters by holding `ALT` key | ALTSTRING | Text string | Print text string using ALT+Numpad method | | ALTCODE | Text string | Same as ALTSTRING, presents in some Duckyscript implementations | -## SysRq +### SysRq Send [SysRq command](https://en.wikipedia.org/wiki/Magic_SysRq_key) | Command | Parameters | Notes | | ------- | ---------------- | ----- | | SYSRQ | Single character | | -## USB device ID +### USB device ID You can set the custom ID of the Flipper USB HID device. ID command should be in the **first line** of script, it is executed before script run. diff --git a/documentation/file_formats/InfraredFileFormats.md b/documentation/file_formats/InfraredFileFormats.md index 4d43bd5b8e0..a416a5894c3 100644 --- a/documentation/file_formats/InfraredFileFormats.md +++ b/documentation/file_formats/InfraredFileFormats.md @@ -1,7 +1,7 @@ -# Infrared Flipper File Formats +# Infrared Flipper File Formats {#infrared_file_format} +## Supported protocols list for "type: parsed" -## Supported protocols list for `type: parsed` ``` NEC NECext @@ -17,6 +17,7 @@ Kaseikyo RCA ``` + ## Infrared Remote File Format ### Example @@ -51,7 +52,7 @@ Each button is separated from others by a comment character (`#`) for better rea Known protocols are represented in the `parsed` form, whereas non-recognized signals may be saved and re-transmitted as `raw` data. -#### Version history: +#### Version history 1. Initial version. @@ -72,19 +73,19 @@ Known protocols are represented in the `parsed` form, whereas non-recognized sig ### Examples -- [TV Universal Library](/applications/main/infrared/resources/infrared/assets/tv.ir) -- [A/C Universal Library](/applications/main/infrared/resources/infrared/assets/ac.ir) -- [Audio Universal Library](/applications/main/infrared/resources/infrared/assets/audio.ir) +- [TV Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/tv.ir) +- [A/C Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/ac.ir) +- [Audio Universal Library](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/applications/main/infrared/resources/infrared/assets/audio.ir) ### Description Filename extension: `.ir` -This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field.\ +This file format is used to store universal remote libraries. It is identical to the previous format, differing only in the `Filetype` field. It also has predefined button names for each universal library type, so that the universal remote application can understand them. -See [Universal Remotes](/documentation/UniversalRemotes.md) for more information. +See [Universal Remotes](../UniversalRemotes.md) for more information. -### Version history: +### Version history 1. Initial version. @@ -92,7 +93,7 @@ See [Universal Remotes](/documentation/UniversalRemotes.md) for more information ### Examples -See [Infrared Unit Tests](/applications/debug/unit_tests/resources/unit_tests/infrared/) for various examples. +See [Infrared Unit Tests](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/debug/unit_tests/resources/unit_tests/infrared) for various examples. ### Description @@ -103,10 +104,10 @@ It is mostly similar to the two previous formats, with the main difference being Each infrared protocol must have corresponding unit tests complete with an `.irtest` file. -Known protocols are represented in the `parsed_array` form, whereas raw data has the `raw` type.\ +Known protocols are represented in the `parsed_array` form, whereas raw data has the `raw` type. Note: a single parsed signal must be represented as an array of size 1. -### Version history: +### Version history 1. Initial version. @@ -141,4 +142,4 @@ and the number is a sequential integer: 1, 2, 3, etc., which produces names like | decoder_expected | parsed_array | An array of parsed signals containing the expected decoder output. Also used as the encoder input. | | encoder_decoder_input | parsed_array | An array of parsed signals containing both the encoder-decoder input and expected output. | -See [Unit Tests](/documentation/UnitTests.md#infrared) for more info. +See [Unit Tests](../UnitTests.md) for more info. diff --git a/documentation/file_formats/LfRfidFileFormat.md b/documentation/file_formats/LfRfidFileFormat.md index 5143d8bc1e7..2463195e407 100644 --- a/documentation/file_formats/LfRfidFileFormat.md +++ b/documentation/file_formats/LfRfidFileFormat.md @@ -1,4 +1,4 @@ -# LF RFID key file format +# LF RFID key file format {#lfrfid_file_format} ## Example diff --git a/documentation/file_formats/NfcFileFormats.md b/documentation/file_formats/NfcFileFormats.md index f752cdb901b..5b08c3471fd 100644 --- a/documentation/file_formats/NfcFileFormats.md +++ b/documentation/file_formats/NfcFileFormats.md @@ -1,4 +1,4 @@ -# NFC Flipper File Formats +# NFC Flipper File Formats {#nfc_file_format} ## UID + Header (General format) diff --git a/documentation/file_formats/SubGhzFileFormats.md b/documentation/file_formats/SubGhzFileFormats.md index c22f97f8df1..c4d63835e9b 100644 --- a/documentation/file_formats/SubGhzFileFormats.md +++ b/documentation/file_formats/SubGhzFileFormats.md @@ -1,20 +1,20 @@ -# File Formats for Flipper's SubGhz Subsystem +# SubGhz Subsystem File Formats {#subghz_file_format} -## `.sub` File Format +## .sub File Format -Flipper uses `.sub` files to store SubGhz transmissions. These are text files in Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. +Flipper uses `.sub` files to store SubGhz signals. These files use the Flipper File Format. `.sub` files can contain either a SubGhz Key with a certain protocol or SubGhz RAW data. -A `.sub` files consist of 3 parts: +A `.sub` file consist of 3 parts: -- **header**, contains file type, version, and frequency +- **header**, contains the file type, version, and frequency - **preset information**, preset type and, in case of a custom preset, transceiver configuration data - **protocol and its data**, contains protocol name and its specific data, such as key, bit length, etc., or RAW data -Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) for more details. +Flipper's SubGhz subsystem uses presets to configure the radio transceiver. Presets are used to configure modulation, bandwidth, filters, etc. There are several presets available in stock firmware, and there is a way to create custom presets. See [SubGhz Presets](#adding-a-custom-preset) section for more details. ## Header format -Header is a mandatory part of `.sub` file. It contains file type, version, and frequency. +Header is a mandatory part of a `.sub` file. It contains the file type, version, and frequency. | Field | Type | Description | | ----------- | ------ | ----------------------------------------------------------------- | @@ -41,7 +41,7 @@ Built-in presets: - `FuriHalSubGhzPreset2FSKDev238Async` — 2 Frequency Shift Keying, deviation 2kHz, 270kHz bandwidth, async(IO throw GP0) - `FuriHalSubGhzPreset2FSKDev476Async` — 2 Frequency Shift Keying, deviation 47kHz, 270kHz bandwidth, async(IO throw GP0) -### Transceiver Configuration Data +### Transceiver Configuration Data {#transceiver-configuration-data} Transceiver configuration data is a string of bytes, encoded in hex format, separated by spaces. For CC1101 data structure is: `XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ`, where: @@ -54,7 +54,7 @@ You can find more details in the [CC1101 datasheet](https://www.ti.com/lit/ds/sy ## File Data -`.sub` file data section contains either key data — protocol name and its specific data, bit length, etc., or RAW data — an array of signal timings, recorded without any protocol-specific processing. +`.sub` file data section can either contain key data, consisting of a protocol name and its specific data, bit length, etc., or RAW data, which consists of an array of signal timings, recorded without any protocol-specific processing. ### Key Files @@ -88,20 +88,20 @@ RAW `.sub` files contain raw signal data that is not processed through protocol- For RAW files, 2 fields are required: - **Protocol**, must be `RAW` -- **RAW_Data**, contains an array of timings, specified in microseconds Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. +- **RAW_Data**, contains an array of timings, specified in microseconds. Values must be non-zero, start with a positive number, and interleaved (change sign with each value). Up to 512 values per line. Can be specified multiple times to store multiple lines of data. Example of RAW data: Protocol: RAW RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 ... -Long payload not fitting into internal memory buffer and consisting of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. +A long payload that doesn't fit into the internal memory buffer and consists of short duration timings (< 10us) may not be read fast enough from the SD card. That might cause the signal transmission to stop before reaching the end of the payload. Ensure that your SD Card has good performance before transmitting long or complex RAW payloads. ### BIN_RAW Files BinRAW `.sub` files and `RAW` files both contain data that has not been decoded by any protocol. However, unlike `RAW`, `BinRAW` files only record a useful repeating sequence of durations with a restored byte transfer rate and without broadcast noise. These files can emulate nearly all static protocols, whether Flipper knows them or not. -- Usually, you have to receive the signal a little longer so that Flipper accumulates sufficient data for correct analysis. +- Usually, you have to receive the signal a little longer so that Flipper accumulates sufficient data to analyze it correctly. For `BinRAW` files, the following parameters are required and must be aligned to the left: @@ -188,7 +188,7 @@ Data_RAW: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 DE 02 D3 54 D5 4C D2 C SubGhz application provides support for adding extra radio presets and additional keys for decoding transmissions in certain protocols. -## SubGhz `keeloq_mfcodes_user` file +## SubGhz keeloq_mfcodes_user file This file contains additional manufacturer keys for Keeloq protocol. It is used to decode Keeloq transmissions. This file is loaded at subghz application start and is located at path `/ext/subghz/assets/keeloq_mfcodes_user`. @@ -228,7 +228,7 @@ For each key, a name and encryption method must be specified, according to comme AABBCCDDEEFFAABB:1:Test1 AABBCCDDEEFFAABB:1:Test2 -## SubGhz `setting_user` file +## SubGhz setting_user file This file contains additional radio presets and frequencies for SubGhz application. It is used to add new presets and frequencies for existing presets. This file is being loaded on subghz application start and is located at path `/ext/subghz/assets/setting_user`. @@ -256,7 +256,7 @@ Header must contain the following fields: Repeating the same frequency will cause Flipper to listen to this frequency more often. -#### Adding a Custom Preset +#### Adding a Custom Preset {#adding-a-custom-preset} You can have as many presets as you want. Presets are embedded into `.sub` files, so another Flipper can load them directly from that file. Each preset is defined by the following fields: diff --git a/documentation/file_formats/iButtonFileFormat.md b/documentation/file_formats/iButtonFileFormat.md index 63743f06372..414d73045f1 100644 --- a/documentation/file_formats/iButtonFileFormat.md +++ b/documentation/file_formats/iButtonFileFormat.md @@ -1,4 +1,4 @@ -# iButton key file format +# iButton key file format {#ibutton_file_format} ## Example diff --git a/furi/core/check.h b/furi/core/check.h index 2d5df4cf6c4..e782380fdda 100644 --- a/furi/core/check.h +++ b/furi/core/check.h @@ -43,7 +43,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Crash system * - * @param optional message (const char*) + * @param ... optional message (const char*) */ #define furi_crash(...) M_APPLY(__furi_crash, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) @@ -57,7 +57,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Halt system * - * @param optional message (const char*) + * @param ... optional message (const char*) */ #define furi_halt(...) M_APPLY(__furi_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) @@ -71,8 +71,7 @@ FURI_NORETURN void __furi_halt_implementation(); /** Check condition and crash if failed * - * @param condition to check - * @param optional message (const char*) + * @param ... condition to check and optional message (const char*) */ #define furi_check(...) \ M_APPLY(__furi_check, M_DEFAULT_ARGS(2, (__FURI_CHECK_MESSAGE_FLAG), __VA_ARGS__)) @@ -97,8 +96,7 @@ FURI_NORETURN void __furi_halt_implementation(); * * @warning only will do check if firmware compiled in debug mode * - * @param condition to check - * @param optional message (const char*) + * @param ... condition to check and optional message (const char*) */ #define furi_assert(...) \ M_APPLY(__furi_assert, M_DEFAULT_ARGS(2, (__FURI_ASSERT_MESSAGE_FLAG), __VA_ARGS__)) diff --git a/furi/core/kernel.h b/furi/core/kernel.h index c962402efd6..592f01d57d2 100644 --- a/furi/core/kernel.h +++ b/furi/core/kernel.h @@ -79,7 +79,7 @@ void furi_delay_tick(uint32_t ticks); * * @warning This should never be called in interrupt request context. * - * @param[in] ticks The tick until which kerel should delay task execution + * @param[in] tick The tick until which kerel should delay task execution * * @return The furi status. */ diff --git a/furi/core/log.h b/furi/core/log.h index a587d8ab275..3ce88db5b2e 100644 --- a/furi/core/log.h +++ b/furi/core/log.h @@ -51,7 +51,7 @@ void furi_log_init(void); /** Add log TX callback * - * @param[in] callback The callback + * @param[in] handler The callback and its context * * @return true on success, false otherwise */ @@ -59,7 +59,7 @@ bool furi_log_add_handler(FuriLogHandler handler); /** Remove log TX callback * - * @param[in] callback The callback + * @param[in] handler The callback and its context * * @return true on success, false otherwise */ @@ -112,15 +112,16 @@ FuriLogLevel furi_log_get_level(void); /** Log level to string * * @param[in] level The level + * @param[out] str String representation of the level * - * @return The string + * @return True if success, False otherwise */ bool furi_log_level_to_string(FuriLogLevel level, const char** str); /** Log level from string * * @param[in] str The string - * @param level The level + * @param[out] level The level * * @return True if success, False otherwise */ diff --git a/furi/core/memmgr_heap.h b/furi/core/memmgr_heap.h index 9aacba1ca79..660c5c6bf00 100644 --- a/furi/core/memmgr_heap.h +++ b/furi/core/memmgr_heap.h @@ -18,13 +18,13 @@ extern "C" { * * @param thread_id - thread id to track */ -void memmgr_heap_enable_thread_trace(FuriThreadId taks_handle); +void memmgr_heap_enable_thread_trace(FuriThreadId thread_id); /** Memmgr heap disable thread allocation tracking * * @param thread_id - thread id to track */ -void memmgr_heap_disable_thread_trace(FuriThreadId taks_handle); +void memmgr_heap_disable_thread_trace(FuriThreadId thread_id); /** Memmgr heap get allocatred thread memory * @@ -32,7 +32,7 @@ void memmgr_heap_disable_thread_trace(FuriThreadId taks_handle); * * @return bytes allocated right now */ -size_t memmgr_heap_get_thread_memory(FuriThreadId taks_handle); +size_t memmgr_heap_get_thread_memory(FuriThreadId thread_id); /** Memmgr heap get the max contiguous block size on the heap * diff --git a/furi/core/message_queue.h b/furi/core/message_queue.h index 392a145f101..8d7f389e127 100644 --- a/furi/core/message_queue.h +++ b/furi/core/message_queue.h @@ -32,7 +32,6 @@ void furi_message_queue_free(FuriMessageQueue* instance); * @param instance pointer to FuriMessageQueue instance * @param[in] msg_ptr The message pointer * @param[in] timeout The timeout - * @param[in] msg_prio The message prio * * @return The furi status. */ @@ -43,7 +42,6 @@ FuriStatus * * @param instance pointer to FuriMessageQueue instance * @param msg_ptr The message pointer - * @param msg_prio The message prioority * @param[in] timeout The timeout * * @return The furi status. diff --git a/furi/core/string.h b/furi/core/string.h index 0e3e6a88e6a..77ae9da6dcf 100644 --- a/furi/core/string.h +++ b/furi/core/string.h @@ -102,7 +102,7 @@ void furi_string_reserve(FuriString* string, size_t size); /** * @brief Reset string. * Make the string empty. - * @param s + * @param string */ void furi_string_reset(FuriString* string); diff --git a/furi/core/thread.h b/furi/core/thread.h index 83c051cc224..489a4684480 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -235,8 +235,6 @@ int32_t furi_thread_get_return_code(FuriThread* thread); /** Thread related methods that doesn't involve FuriThread directly */ /** Get FreeRTOS FuriThreadId for current thread - * - * @param thread FuriThread instance * * @return FuriThreadId or NULL */ @@ -263,7 +261,7 @@ uint32_t furi_thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeo * @brief Enumerate threads * * @param thread_array array of FuriThreadId, where thread ids will be stored - * @param array_items array size + * @param array_item_count array size * @return uint32_t threads count */ uint32_t furi_thread_enumerate(FuriThreadId* thread_array, uint32_t array_item_count); diff --git a/lib/flipper_application/elf/elf_file.h b/lib/flipper_application/elf/elf_file.h index 631fe122f93..921b506bdbb 100644 --- a/lib/flipper_application/elf/elf_file.h +++ b/lib/flipper_application/elf/elf_file.h @@ -98,8 +98,7 @@ bool elf_file_is_init_complete(ELFFile* elf); /** * @brief Get actual entry point for ELF file * @param elf_file - * @param args - * @return int32_t + * @return void* */ void* elf_file_get_entry_point(ELFFile* elf_file); @@ -148,4 +147,4 @@ ElfProcessSectionResult elf_process_section( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/lib/nfc/nfc.h b/lib/nfc/nfc.h index 4f7980b0260..6f3e25a5d03 100644 --- a/lib/nfc/nfc.h +++ b/lib/nfc/nfc.h @@ -170,7 +170,7 @@ void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc); * @brief Set mask receive time. * * @param[in,out] instance pointer to the instance to be modified. - * @param[in] mask_rx_time mask receive time, in carrier cycles. + * @param[in] mask_rx_time_fc mask receive time, in carrier cycles. */ void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc); diff --git a/lib/nfc/nfc_scanner.h b/lib/nfc/nfc_scanner.h index a1b4aabcda3..c13a58b741f 100644 --- a/lib/nfc/nfc_scanner.h +++ b/lib/nfc/nfc_scanner.h @@ -72,14 +72,14 @@ NfcScanner* nfc_scanner_alloc(Nfc* nfc); /** * @brief Delete an NfcScanner instance. * - * @param[in,out] pointer to the instance to be deleted. + * @param[in,out] instance pointer to the instance to be deleted. */ void nfc_scanner_free(NfcScanner* instance); /** * @brief Start an NfcScanner. * - * @param[in,out] pointer to the instance to be started. + * @param[in,out] instance pointer to the instance to be started. * @param[in] callback pointer to the callback function (will be called upon a detection event). * @param[in] context pointer to the caller-specific context (will be passed to the callback). */ @@ -88,7 +88,7 @@ void nfc_scanner_start(NfcScanner* instance, NfcScannerCallback callback, void* /** * @brief Stop an NfcScanner. * - * @param[in,out] pointer to the instance to be stopped. + * @param[in,out] instance pointer to the instance to be stopped. */ void nfc_scanner_stop(NfcScanner* instance); diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index c871d5060a7..353bd349464 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -199,7 +199,7 @@ FuriHalRtcHeapTrackMode furi_hal_rtc_get_heap_track_mode(void); /** Set locale units * - * @param[in] mode The RTC Locale Units + * @param[in] value The RTC Locale Units */ void furi_hal_rtc_set_locale_units(FuriHalRtcLocaleUnits value); diff --git a/targets/furi_hal_include/furi_hal_bt.h b/targets/furi_hal_include/furi_hal_bt.h index 939c9a30ddc..266db858885 100644 --- a/targets/furi_hal_include/furi_hal_bt.h +++ b/targets/furi_hal_include/furi_hal_bt.h @@ -97,11 +97,12 @@ void furi_hal_bt_reinit(); /** Change BLE app * Restarts 2nd core * - * @param profile FuriHalBleProfileTemplate instance - * @param event_cb GapEventCallback instance - * @param context pointer to context + * @param profile_template FuriHalBleProfileTemplate instance + * @param profile_params Parameters to pass to the profile. Can be NULL + * @param event_cb GapEventCallback instance + * @param context pointer to context * - * @return instance of profile, NULL on failure + * @return instance of profile, NULL on failure */ FURI_WARN_UNUSED FuriHalBleProfileBase* furi_hal_bt_change_app( const FuriHalBleProfileTemplate* profile_template, diff --git a/targets/furi_hal_include/furi_hal_i2c.h b/targets/furi_hal_include/furi_hal_i2c.h index f493655b4d2..44f647cefcc 100644 --- a/targets/furi_hal_include/furi_hal_i2c.h +++ b/targets/furi_hal_include/furi_hal_i2c.h @@ -91,7 +91,7 @@ bool furi_hal_i2c_tx( * @param size Size of data buffer * @param begin How to begin the transaction * @param end How to end the transaction - * @param timer Timeout timer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ @@ -131,7 +131,7 @@ bool furi_hal_i2c_rx( * @param size Size of data buffer * @param begin How to begin the transaction * @param end How to end the transaction - * @param timer Timeout timer + * @param timeout Timeout in milliseconds * * @return true on successful transfer, false otherwise */ diff --git a/targets/furi_hal_include/furi_hal_infrared.h b/targets/furi_hal_include/furi_hal_infrared.h index 5fcea06615e..ce2e4328e6a 100644 --- a/targets/furi_hal_include/furi_hal_infrared.h +++ b/targets/furi_hal_include/furi_hal_infrared.h @@ -36,15 +36,15 @@ typedef void (*FuriHalInfraredTxSignalSentISRCallback)(void* context); /** Signature of callback function for receiving continuous INFRARED rx signal. * - * @param ctx[in] context to pass to callback - * @param level[in] level of input INFRARED rx signal - * @param duration[in] duration of continuous rx signal level in us + * @param[in] ctx context to pass to callback + * @param[in] level level of input INFRARED rx signal + * @param[in] duration duration of continuous rx signal level in us */ typedef void (*FuriHalInfraredRxCaptureCallback)(void* ctx, bool level, uint32_t duration); /** Signature of callback function for reaching silence timeout on INFRARED port. * - * @param ctx[in] context to pass to callback + * @param[in] ctx context to pass to callback */ typedef void (*FuriHalInfraredRxTimeoutCallback)(void* ctx); diff --git a/targets/furi_hal_include/furi_hal_nfc.h b/targets/furi_hal_include/furi_hal_nfc.h index 3d145d10026..3d8ff394efb 100644 --- a/targets/furi_hal_include/furi_hal_nfc.h +++ b/targets/furi_hal_include/furi_hal_nfc.h @@ -228,9 +228,9 @@ FuriHalNfcError furi_hal_nfc_poller_tx(const uint8_t* tx_data, size_t tx_bits); * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_poller_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -249,9 +249,9 @@ FuriHalNfcError furi_hal_nfc_listener_tx(const uint8_t* tx_data, size_t tx_bits) * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[out] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[out] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError furi_hal_nfc_listener_rx(uint8_t* rx_data, size_t rx_data_size, size_t* rx_bits); @@ -395,9 +395,9 @@ FuriHalNfcError furi_hal_nfc_iso14443a_tx_sdd_frame(const uint8_t* tx_data, size * * The receive buffer must be big enough to accomodate all of the expected data. * - * @param rx_data[out] pointer to a byte array to be filled with received data. - * @param rx_data_size[in] maximum received data size, in bytes. - * @param rx_bits[out] pointer to the variable to hold received data size, in bits. + * @param[in] rx_data pointer to a byte array to be filled with received data. + * @param[in] rx_data_size maximum received data size, in bytes. + * @param[in] rx_bits pointer to the variable to hold received data size, in bits. * @returns FuriHalNfcErrorNone on success, any other error code on failure. */ FuriHalNfcError diff --git a/targets/furi_hal_include/furi_hal_power.h b/targets/furi_hal_include/furi_hal_power.h index ebe0fe6149b..fa5e179c003 100644 --- a/targets/furi_hal_include/furi_hal_power.h +++ b/targets/furi_hal_include/furi_hal_power.h @@ -135,9 +135,7 @@ float furi_hal_power_get_battery_charge_voltage_limit(); * * Invalid values will be clamped downward to the nearest valid value. * - * @param voltage[in] voltage in V - * - * @return voltage in V + * @param[in] voltage voltage in V */ void furi_hal_power_set_battery_charge_voltage_limit(float voltage); @@ -161,7 +159,7 @@ uint32_t furi_hal_power_get_battery_design_capacity(); /** Get battery voltage in V * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return voltage in V */ @@ -169,7 +167,7 @@ float furi_hal_power_get_battery_voltage(FuriHalPowerIC ic); /** Get battery current in A * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return current in A */ @@ -177,7 +175,7 @@ float furi_hal_power_get_battery_current(FuriHalPowerIC ic); /** Get temperature in C * - * @param ic FuriHalPowerIc to get measurment + * @param[in] ic FuriHalPowerIc to get measurment * * @return temperature in C */ From 4f7eb770711f0f6d07c66a1018618918a604806f Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Thu, 7 Mar 2024 08:57:32 +0200 Subject: [PATCH 56/58] Fix troika 4K keys (#3499) --- .../main/nfc/plugins/supported_cards/troika.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index 50806e137d3..758f824b70b 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -73,14 +73,14 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0x7A38E3511A38, .b = 0xAB16584C972A}, //30 {.a = 0x7545DF809202, .b = 0xECF751084A80}, //31 {.a = 0x5125974CD391, .b = 0xD3EAFB5DF46D}, //32 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //33 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //34 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //35 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //36 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //37 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //38 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //39 - {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //40 + {.a = 0x7A86AA203788, .b = 0xE41242278CA2}, //33 + {.a = 0xAFCEF64C9913, .b = 0x9DB96DCA4324}, //34 + {.a = 0x04EAA462F70B, .b = 0xAC17B93E2FAE}, //35 + {.a = 0xE734C210F27E, .b = 0x29BA8C3E9FDA}, //36 + {.a = 0xD5524F591EED, .b = 0x5DAF42861B4D}, //37 + {.a = 0xE4821A377B75, .b = 0xE8709E486465}, //38 + {.a = 0x518DC6EEA089, .b = 0x97C64AC98CA4}, //39 + {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { From adbe4d44f4c8019d31740909216998c6b848b286 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=82=E3=81=8F?= Date: Mon, 11 Mar 2024 22:09:39 +0900 Subject: [PATCH 57/58] DateTime: fix missing weekday in datetime_timestamp_to_datetime conversion (#3508) --- .../unit_tests/datetimelib/datetimelib_test.c | 20 +++++++++++++++++-- lib/datetime/datetime.c | 1 + 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/applications/debug/unit_tests/datetimelib/datetimelib_test.c b/applications/debug/unit_tests/datetimelib/datetimelib_test.c index 42bc7dbed72..bf8e6fabd71 100644 --- a/applications/debug/unit_tests/datetimelib/datetimelib_test.c +++ b/applications/debug/unit_tests/datetimelib/datetimelib_test.c @@ -112,7 +112,7 @@ MU_TEST_SUITE(test_datetime_validate_datetime) { MU_TEST(test_datetime_timestamp_to_datetime_min) { uint32_t test_value = 0; - DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 0}; + DateTime min_datetime_expected = {0, 0, 0, 1, 1, 1970, 4}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -122,7 +122,7 @@ MU_TEST(test_datetime_timestamp_to_datetime_min) { MU_TEST(test_datetime_timestamp_to_datetime_max) { uint32_t test_value = UINT32_MAX; - DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 0}; + DateTime max_datetime_expected = {6, 28, 15, 7, 2, 2106, 7}; DateTime result = {0}; datetime_timestamp_to_datetime(test_value, &result); @@ -141,10 +141,26 @@ MU_TEST(test_datetime_timestamp_to_datetime_to_timestamp) { mu_assert_int_eq(test_value, result); } +MU_TEST(test_datetime_timestamp_to_datetime_weekday) { + uint32_t test_value = 1709748421; // Wed Mar 06 18:07:01 2024 UTC + + DateTime datetime = {0}; + datetime_timestamp_to_datetime(test_value, &datetime); + + mu_assert_int_eq(datetime.hour, 18); + mu_assert_int_eq(datetime.minute, 7); + mu_assert_int_eq(datetime.second, 1); + mu_assert_int_eq(datetime.day, 6); + mu_assert_int_eq(datetime.month, 3); + mu_assert_int_eq(datetime.weekday, 3); + mu_assert_int_eq(datetime.year, 2024); +} + MU_TEST_SUITE(test_datetime_timestamp_to_datetime_suite) { MU_RUN_TEST(test_datetime_timestamp_to_datetime_min); MU_RUN_TEST(test_datetime_timestamp_to_datetime_max); MU_RUN_TEST(test_datetime_timestamp_to_datetime_to_timestamp); + MU_RUN_TEST(test_datetime_timestamp_to_datetime_weekday); } MU_TEST(test_datetime_datetime_to_timestamp_min) { diff --git a/lib/datetime/datetime.c b/lib/datetime/datetime.c index 7b1f73dd0f1..73044fae60d 100644 --- a/lib/datetime/datetime.c +++ b/lib/datetime/datetime.c @@ -71,6 +71,7 @@ void datetime_timestamp_to_datetime(uint32_t timestamp, DateTime* datetime) { uint32_t seconds_in_day = timestamp % SECONDS_PER_DAY; datetime->year = EPOCH_START_YEAR; + datetime->weekday = ((days + 3) % 7) + 1; while(days >= datetime_get_days_per_year(datetime->year)) { days -= datetime_get_days_per_year(datetime->year); From 022fccf0d79bb2c3b68e5d55d12f67c073ad7f32 Mon Sep 17 00:00:00 2001 From: Georgii Surkov <37121527+gsurkov@users.noreply.github.com> Date: Mon, 11 Mar 2024 20:35:51 +0300 Subject: [PATCH 58/58] [FL-3783] Asynchronous Infrared remote manipulation (#3503) * Introduce ConcurrentRunner, load universal and regular remotes concurrently * Perform all lengthy operations in a ConcurrentRunner * Fix python formatting * Clean up code * Add usage warning * Remove ConcurrentRunner, use a plain FuriThread instead * Load remotes asynchronously in RPC mode as well * Reorder code for clarity * Clean up, use thread return code to report errors * Improve wording * Fix logical error --- applications/main/infrared/infrared_app.c | 36 ++++---- applications/main/infrared/infrared_app_i.h | 39 +++++--- .../main/infrared/infrared_custom_event.h | 1 + .../common/infrared_scene_universal_common.c | 35 ++++++-- .../scenes/infrared_scene_edit_delete.c | 60 ++++++++----- .../scenes/infrared_scene_edit_move.c | 38 ++++---- .../scenes/infrared_scene_edit_rename.c | 58 +++++++----- .../scenes/infrared_scene_remote_list.c | 60 ++++++++----- .../main/infrared/scenes/infrared_scene_rpc.c | 90 ++++++++++++------- .../scenes/infrared_scene_universal_ac.c | 13 +-- .../scenes/infrared_scene_universal_audio.c | 13 +-- .../infrared_scene_universal_projector.c | 13 +-- .../scenes/infrared_scene_universal_tv.c | 13 +-- 13 files changed, 269 insertions(+), 200 deletions(-) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 645659bbc5d..50534c660d2 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -6,7 +6,8 @@ #define TAG "InfraredApp" -#define INFRARED_TX_MIN_INTERVAL_MS 50U +#define INFRARED_TX_MIN_INTERVAL_MS (50U) +#define INFRARED_TASK_STACK_SIZE (2048UL) static const NotificationSequence* infrared_notification_sequences[InfraredNotificationMessageCount] = { @@ -128,6 +129,8 @@ static void infrared_find_vacant_remote_name(FuriString* name, const char* path) static InfraredApp* infrared_alloc() { InfraredApp* infrared = malloc(sizeof(InfraredApp)); + infrared->task_thread = + furi_thread_alloc_ex("InfraredTask", INFRARED_TASK_STACK_SIZE, NULL, infrared); infrared->file_path = furi_string_alloc(); infrared->button_name = furi_string_alloc(); @@ -203,6 +206,10 @@ static InfraredApp* infrared_alloc() { static void infrared_free(InfraredApp* infrared) { furi_assert(infrared); + + furi_thread_join(infrared->task_thread); + furi_thread_free(infrared->task_thread); + ViewDispatcher* view_dispatcher = infrared->view_dispatcher; InfraredAppState* app_state = &infrared->app_state; @@ -377,6 +384,18 @@ void infrared_tx_stop(InfraredApp* infrared) { infrared->app_state.last_transmit_time = furi_get_tick(); } +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback) { + view_stack_add_view(infrared->view_stack, loading_get_view(infrared->loading)); + furi_thread_set_callback(infrared->task_thread, callback); + furi_thread_start(infrared->task_thread); +} + +bool infrared_blocking_task_finalize(InfraredApp* infrared) { + furi_thread_join(infrared->task_thread); + view_stack_remove_view(infrared->view_stack, loading_get_view(infrared->loading)); + return furi_thread_get_return_code(infrared->task_thread); +} + void infrared_text_store_set(InfraredApp* infrared, uint32_t bank, const char* fmt, ...) { va_list args; va_start(args, fmt); @@ -397,21 +416,6 @@ void infrared_play_notification_message( notification_message(infrared->notifications, infrared_notification_sequences[message]); } -void infrared_show_loading_popup(const InfraredApp* infrared, bool show) { - ViewStack* view_stack = infrared->view_stack; - Loading* loading = infrared->loading; - - if(show) { - // Raise timer priority so that animations can play - furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated); - view_stack_add_view(view_stack, loading_get_view(loading)); - } else { - view_stack_remove_view(view_stack, loading_get_view(loading)); - // Restore default timer priority - furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal); - } -} - void infrared_show_error_message(const InfraredApp* infrared, const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 7a9202b28ed..bccd58608a5 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -19,12 +19,13 @@ #include #include +#include #include #include #include -#include +#include #include "infrared_app.h" #include "infrared_remote.h" @@ -36,8 +37,6 @@ #include "views/infrared_debug_view.h" #include "views/infrared_move_view.h" -#include "rpc/rpc_app.h" - #define INFRARED_FILE_NAME_SIZE 100 #define INFRARED_TEXT_STORE_NUM 2 #define INFRARED_TEXT_STORE_SIZE 128 @@ -120,6 +119,7 @@ struct InfraredApp { Loading* loading; /**< Standard view for informing about long operations. */ InfraredProgressView* progress; /**< Custom view for showing brute force progress. */ + FuriThread* task_thread; /**< Pointer to a FuriThread instance for concurrent tasks. */ FuriString* file_path; /**< Full path to the currently loaded file. */ FuriString* button_name; /**< Name of the button requested in RPC mode. */ /** Arbitrary text storage for various inputs. */ @@ -210,6 +210,28 @@ void infrared_tx_start_button_index(InfraredApp* infrared, size_t button_index); */ void infrared_tx_stop(InfraredApp* infrared); +/** + * @brief Start a blocking task in a separate thread. + * + * If a ViewStack is currently on screen, a busy "Hourglass" animation + * will be shown and no input will be accepted until completion. + * + * @param[in,out] infrared pointer to the application instance. + * @param[in] callback pointer to the function to be run in the thread. + */ +void infrared_blocking_task_start(InfraredApp* infrared, FuriThreadCallback callback); + +/** + * @brief Wait for a blocking task to finish and receive the result. + * + * Upon the completion of a blocking task, the busy animation will be hidden + * and input will be accepted again. + * + * @param[in,out] infrared pointer to the application instance. + * @return true if the blocking task finished successfully, false otherwise. + */ +bool infrared_blocking_task_finalize(InfraredApp* infrared); + /** * @brief Set the internal text store with formatted text. * @@ -239,17 +261,6 @@ void infrared_play_notification_message( const InfraredApp* infrared, InfraredNotificationMessage message); -/** - * @brief Show a loading pop-up screen. - * - * In order for this to work, a Stack view must be currently active and - * the main view must be added to it. - * - * @param[in] infrared pointer to the application instance. - * @param[in] show whether to show or hide the pop-up. - */ -void infrared_show_loading_popup(const InfraredApp* infrared, bool show); - /** * @brief Show a formatted error messsage. * diff --git a/applications/main/infrared/infrared_custom_event.h b/applications/main/infrared/infrared_custom_event.h index 30bb0f729cd..b53e52a2f3e 100644 --- a/applications/main/infrared/infrared_custom_event.h +++ b/applications/main/infrared/infrared_custom_event.h @@ -14,6 +14,7 @@ enum InfraredCustomEventType { InfraredCustomEventTypePopupClosed, InfraredCustomEventTypeButtonSelected, InfraredCustomEventTypeBackPressed, + InfraredCustomEventTypeTaskFinished, InfraredCustomEventTypeRpcLoadFile, InfraredCustomEventTypeRpcExit, diff --git a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c index 4967d195664..9fc48bd46bd 100644 --- a/applications/main/infrared/scenes/common/infrared_scene_universal_common.c +++ b/applications/main/infrared/scenes/common/infrared_scene_universal_common.c @@ -32,9 +32,24 @@ static void infrared_scene_universal_common_hide_popup(InfraredApp* infrared) { infrared_play_notification_message(infrared, InfraredNotificationMessageBlinkStop); } +static int32_t infrared_scene_universal_common_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_brute_force_calculate_messages(infrared->brute_force); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, + infrared_custom_event_pack(InfraredCustomEventTypeTaskFinished, 0)); + + return success; +} + void infrared_scene_universal_common_on_enter(void* context) { InfraredApp* infrared = context; + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); view_stack_add_view(infrared->view_stack, button_panel_get_view(infrared->button_panel)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); + + // Load universal remote data in background + infrared_blocking_task_start(infrared, infrared_scene_universal_common_task_callback); } bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent event) { @@ -58,26 +73,34 @@ bool infrared_scene_universal_common_on_event(void* context, SceneManagerEvent e if(infrared_custom_event_get_type(event.event) == InfraredCustomEventTypeBackPressed) { infrared_brute_force_stop(brute_force); infrared_scene_universal_common_hide_popup(infrared); - consumed = true; } + consumed = true; } } else { if(event.type == SceneManagerEventTypeBack) { scene_manager_previous_scene(scene_manager); consumed = true; } else if(event.type == SceneManagerEventTypeCustom) { - if(infrared_custom_event_get_type(event.event) == - InfraredCustomEventTypeButtonSelected) { + uint16_t event_type; + int16_t event_value; + infrared_custom_event_unpack(event.event, &event_type, &event_value); + + if(event_type == InfraredCustomEventTypeButtonSelected) { uint32_t record_count; - if(infrared_brute_force_start( - brute_force, infrared_custom_event_get_value(event.event), &record_count)) { + if(infrared_brute_force_start(brute_force, event_value, &record_count)) { dolphin_deed(DolphinDeedIrSend); infrared_scene_universal_common_show_popup(infrared, record_count); } else { scene_manager_next_scene(scene_manager, InfraredSceneErrorDatabases); } - consumed = true; + } else if(event_type == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); + } } + consumed = true; } } diff --git a/applications/main/infrared/scenes/infrared_scene_edit_delete.c b/applications/main/infrared/scenes/infrared_scene_edit_delete.c index 0cb88efdb66..8dc4ab6f9f8 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_delete.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_delete.c @@ -6,12 +6,33 @@ static void view_dispatcher_send_custom_event(infrared->view_dispatcher, result); } +static int32_t infrared_scene_edit_delete_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_delete_signal(infrared->remote, app_state->current_button_index); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_remote_remove(infrared->remote); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_delete_on_enter(void* context) { InfraredApp* infrared = context; DialogEx* dialog_ex = infrared->dialog_ex; InfraredRemote* remote = infrared->remote; - const InfraredEditTarget edit_target = infrared->app_state.edit_target; + if(edit_target == InfraredEditTargetButton) { dialog_ex_set_header(dialog_ex, "Delete Button?", 64, 0, AlignCenter, AlignTop); @@ -84,39 +105,30 @@ bool infrared_scene_edit_delete_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultLeft) { scene_manager_previous_scene(scene_manager); - consumed = true; } else if(event.event == DialogExResultRight) { - bool success = false; - InfraredRemote* remote = infrared->remote; + // Delete a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_delete_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; - const InfraredEditTarget edit_target = app_state->edit_target; - - if(edit_target == InfraredEditTargetButton) { - furi_assert(app_state->current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_delete_signal(remote, app_state->current_button_index); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_remote_remove(remote); - app_state->current_button_index = InfraredButtonIndexNone; - } else { - furi_crash(); - } - if(success) { + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditDeleteDone); } else { - infrared_show_error_message( - infrared, - "Failed to\ndelete %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\ndelete %s", edit_target_text); + const uint32_t possible_scenes[] = {InfraredSceneRemoteList, InfraredSceneStart}; scene_manager_search_and_switch_to_previous_scene_one_of( scene_manager, possible_scenes, COUNT_OF(possible_scenes)); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_move.c b/applications/main/infrared/scenes/infrared_scene_edit_move.c index 4959a831095..500f3d791ed 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_move.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_move.c @@ -1,5 +1,17 @@ #include "../infrared_app_i.h" +static int32_t infrared_scene_edit_move_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = infrared_remote_move_signal( + infrared->remote, + infrared->app_state.prev_button_index, + infrared->app_state.current_button_index); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + static void infrared_scene_edit_move_button_callback( uint32_t index_old, uint32_t index_new, @@ -38,25 +50,21 @@ bool infrared_scene_edit_move_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeButtonSelected) { - infrared_show_loading_popup(infrared, true); - const bool button_moved = infrared_remote_move_signal( - infrared->remote, - infrared->app_state.prev_button_index, - infrared->app_state.current_button_index); - infrared_show_loading_popup(infrared, false); - - if(!button_moved) { - infrared_show_error_message( - infrared, - "Failed to move\n\"%s\"", - infrared_remote_get_signal_name( - infrared->remote, infrared->app_state.current_button_index)); + // Move the button in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_move_task_callback); + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(!task_success) { + const char* signal_name = infrared_remote_get_signal_name( + infrared->remote, infrared->app_state.current_button_index); + infrared_show_error_message(infrared, "Failed to move\n\"%s\"", signal_name); scene_manager_search_and_switch_to_previous_scene( infrared->scene_manager, InfraredSceneRemoteList); } - - consumed = true; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_edit_rename.c b/applications/main/infrared/scenes/infrared_scene_edit_rename.c index 178690926d4..2763c277738 100644 --- a/applications/main/infrared/scenes/infrared_scene_edit_rename.c +++ b/applications/main/infrared/scenes/infrared_scene_edit_rename.c @@ -3,6 +3,28 @@ #include #include +static int32_t infrared_scene_edit_rename_task_callback(void* context) { + InfraredApp* infrared = context; + InfraredAppState* app_state = &infrared->app_state; + const InfraredEditTarget edit_target = app_state->edit_target; + + bool success; + if(edit_target == InfraredEditTargetButton) { + furi_assert(app_state->current_button_index != InfraredButtonIndexNone); + success = infrared_remote_rename_signal( + infrared->remote, app_state->current_button_index, infrared->text_store[0]); + } else if(edit_target == InfraredEditTargetRemote) { + success = infrared_rename_current_remote(infrared, infrared->text_store[0]); + } else { + furi_crash(); + } + + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + + return success; +} + void infrared_scene_edit_rename_on_enter(void* context) { InfraredApp* infrared = context; InfraredRemote* remote = infrared->remote; @@ -61,41 +83,31 @@ void infrared_scene_edit_rename_on_enter(void* context) { bool infrared_scene_edit_rename_on_event(void* context, SceneManagerEvent event) { InfraredApp* infrared = context; - InfraredRemote* remote = infrared->remote; SceneManager* scene_manager = infrared->scene_manager; - InfraredAppState* app_state = &infrared->app_state; bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { if(event.event == InfraredCustomEventTypeTextEditDone) { - bool success = false; - const InfraredEditTarget edit_target = app_state->edit_target; - if(edit_target == InfraredEditTargetButton) { - const int32_t current_button_index = app_state->current_button_index; - furi_assert(current_button_index != InfraredButtonIndexNone); - infrared_show_loading_popup(infrared, true); - success = infrared_remote_rename_signal( - remote, current_button_index, infrared->text_store[0]); - infrared_show_loading_popup(infrared, false); - app_state->current_button_index = InfraredButtonIndexNone; - } else if(edit_target == InfraredEditTargetRemote) { - success = infrared_rename_current_remote(infrared, infrared->text_store[0]); - } else { - furi_crash(); - } + // Rename a button or a remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_edit_rename_task_callback); - if(success) { + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + InfraredAppState* app_state = &infrared->app_state; + + if(task_success) { scene_manager_next_scene(scene_manager, InfraredSceneEditRenameDone); } else { - infrared_show_error_message( - infrared, - "Failed to\nrename %s", - edit_target == InfraredEditTargetButton ? "button" : "file"); + const char* edit_target_text = + app_state->edit_target == InfraredEditTargetButton ? "button" : "file"; + infrared_show_error_message(infrared, "Failed to\nrename %s", edit_target_text); scene_manager_search_and_switch_to_previous_scene( scene_manager, InfraredSceneRemoteList); } - consumed = true; + + app_state->current_button_index = InfraredButtonIndexNone; } + consumed = true; } return consumed; diff --git a/applications/main/infrared/scenes/infrared_scene_remote_list.c b/applications/main/infrared/scenes/infrared_scene_remote_list.c index 2276e270a0c..744409a7ab2 100644 --- a/applications/main/infrared/scenes/infrared_scene_remote_list.c +++ b/applications/main/infrared/scenes/infrared_scene_remote_list.c @@ -1,41 +1,59 @@ #include "../infrared_app_i.h" -void infrared_scene_remote_list_on_enter(void* context) { +static int32_t infrared_scene_remote_list_task_callback(void* context) { InfraredApp* infrared = context; - SceneManager* scene_manager = infrared->scene_manager; - ViewDispatcher* view_dispatcher = infrared->view_dispatcher; - - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(view_dispatcher, InfraredViewStack); + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} +static void infrared_scene_remote_list_select_and_load(InfraredApp* infrared) { DialogsFileBrowserOptions browser_options; dialog_file_browser_set_basic_options(&browser_options, INFRARED_APP_EXTENSION, &I_ir_10px); browser_options.base_path = INFRARED_APP_FOLDER; - while(dialog_file_browser_show( - infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options)) { - const char* file_path = furi_string_get_cstr(infrared->file_path); + const bool file_selected = dialog_file_browser_show( + infrared->dialogs, infrared->file_path, infrared->file_path, &browser_options); - infrared_show_loading_popup(infrared, true); - const bool remote_loaded = infrared_remote_load(infrared->remote, file_path); - infrared_show_loading_popup(infrared, false); + if(file_selected) { + view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - if(remote_loaded) { - scene_manager_next_scene(scene_manager, InfraredSceneRemote); - return; - } else { - infrared_show_error_message(infrared, "Failed to load\n\"%s\"", file_path); - } + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_remote_list_task_callback); + + } else { + scene_manager_previous_scene(infrared->scene_manager); } +} - scene_manager_previous_scene(scene_manager); +void infrared_scene_remote_list_on_enter(void* context) { + InfraredApp* infrared = context; + infrared_scene_remote_list_select_and_load(infrared); } bool infrared_scene_remote_list_on_event(void* context, SceneManagerEvent event) { - UNUSED(context); - UNUSED(event); + InfraredApp* infrared = context; + bool consumed = false; + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + scene_manager_next_scene(infrared->scene_manager, InfraredSceneRemote); + } else { + infrared_show_error_message( + infrared, "Failed to load\n\"%s\"", furi_string_get_cstr(infrared->file_path)); + infrared_scene_remote_list_select_and_load(infrared); + } + } + consumed = true; + } + return consumed; } diff --git a/applications/main/infrared/scenes/infrared_scene_rpc.c b/applications/main/infrared/scenes/infrared_scene_rpc.c index f3408fba4dd..03a2bff0107 100644 --- a/applications/main/infrared/scenes/infrared_scene_rpc.c +++ b/applications/main/infrared/scenes/infrared_scene_rpc.c @@ -9,6 +9,15 @@ typedef enum { InfraredRpcStateSending, } InfraredRpcState; +static int32_t infrared_scene_rpc_task_callback(void* context) { + InfraredApp* infrared = context; + const bool success = + infrared_remote_load(infrared->remote, furi_string_get_cstr(infrared->file_path)); + view_dispatcher_send_custom_event( + infrared->view_dispatcher, InfraredCustomEventTypeTaskFinished); + return success; +} + void infrared_scene_rpc_on_enter(void* context) { InfraredApp* infrared = context; Popup* popup = infrared->popup; @@ -21,7 +30,8 @@ void infrared_scene_rpc_on_enter(void* context) { popup_set_context(popup, context); popup_set_callback(popup, infrared_popup_closed_callback); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewPopup); + view_stack_add_view(infrared->view_stack, popup_get_view(infrared->popup)); + view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); scene_manager_set_scene_state(infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateIdle); @@ -33,76 +43,88 @@ bool infrared_scene_rpc_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - InfraredRpcState state = + InfraredAppState* app_state = &infrared->app_state; + InfraredRpcState rpc_state = scene_manager_get_scene_state(infrared->scene_manager, InfraredSceneRpc); - if(event.event == InfraredCustomEventTypeBackPressed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypePopupClosed) { - view_dispatcher_stop(infrared->view_dispatcher); - } else if(event.event == InfraredCustomEventTypeRpcLoadFile) { - bool result = false; - if(state == InfraredRpcStateIdle) { - result = infrared_remote_load( - infrared->remote, furi_string_get_cstr(infrared->file_path)); - if(result) { - scene_manager_set_scene_state( - infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); - } + + if(event.event == InfraredCustomEventTypeRpcLoadFile) { + if(rpc_state == InfraredRpcStateIdle) { + // Load the remote in a separate thread + infrared_blocking_task_start(infrared, infrared_scene_rpc_task_callback); + } + + } else if(event.event == InfraredCustomEventTypeTaskFinished) { + const bool task_success = infrared_blocking_task_finalize(infrared); + + if(task_success) { + const char* remote_name = infrared_remote_get_name(infrared->remote); + infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); + scene_manager_set_scene_state( + infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); + + } else { + infrared_text_store_set( + infrared, 0, "failed to load\n%s", furi_string_get_cstr(infrared->file_path)); } - const char* remote_name = infrared_remote_get_name(infrared->remote); - infrared_text_store_set(infrared, 0, "loaded\n%s", remote_name); popup_set_text( infrared->popup, infrared->text_store[0], 89, 44, AlignCenter, AlignTop); - rpc_system_app_confirm(infrared->rpc_ctx, result); + rpc_system_app_confirm(infrared->rpc_ctx, task_success); + } else if( event.event == InfraredCustomEventTypeRpcButtonPressName || event.event == InfraredCustomEventTypeRpcButtonPressIndex) { bool result = false; - if(state == InfraredRpcStateLoaded) { + if(rpc_state == InfraredRpcStateLoaded) { if(event.event == InfraredCustomEventTypeRpcButtonPressName) { const char* button_name = furi_string_get_cstr(infrared->button_name); size_t index; const bool index_found = infrared_remote_get_signal_index(infrared->remote, button_name, &index); - infrared->app_state.current_button_index = - index_found ? (signed)index : InfraredButtonIndexNone; + app_state->current_button_index = index_found ? (signed)index : + InfraredButtonIndexNone; FURI_LOG_D(TAG, "Sending signal with name \"%s\"", button_name); } else { FURI_LOG_D( - TAG, - "Sending signal with index \"%ld\"", - infrared->app_state.current_button_index); + TAG, "Sending signal with index \"%ld\"", app_state->current_button_index); } if(infrared->app_state.current_button_index != InfraredButtonIndexNone) { - infrared_tx_start_button_index( - infrared, infrared->app_state.current_button_index); + infrared_tx_start_button_index(infrared, app_state->current_button_index); scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateSending); result = true; } } rpc_system_app_confirm(infrared->rpc_ctx, result); + } else if(event.event == InfraredCustomEventTypeRpcButtonRelease) { bool result = false; - if(state == InfraredRpcStateSending) { + + if(rpc_state == InfraredRpcStateSending) { infrared_tx_stop(infrared); result = true; scene_manager_set_scene_state( infrared->scene_manager, InfraredSceneRpc, InfraredRpcStateLoaded); } + rpc_system_app_confirm(infrared->rpc_ctx, result); - } else if(event.event == InfraredCustomEventTypeRpcExit) { - scene_manager_stop(infrared->scene_manager); - view_dispatcher_stop(infrared->view_dispatcher); - rpc_system_app_confirm(infrared->rpc_ctx, true); - } else if(event.event == InfraredCustomEventTypeRpcSessionClose) { + + } else if( + event.event == InfraredCustomEventTypeRpcExit || + event.event == InfraredCustomEventTypeRpcSessionClose || + event.event == InfraredCustomEventTypePopupClosed) { scene_manager_stop(infrared->scene_manager); view_dispatcher_stop(infrared->view_dispatcher); + + if(event.event == InfraredCustomEventTypeRpcExit) { + rpc_system_app_confirm(infrared->rpc_ctx, true); + } } + + consumed = true; } + return consumed; } @@ -112,5 +134,7 @@ void infrared_scene_rpc_on_exit(void* context) { InfraredRpcStateSending) { infrared_tx_stop(infrared); } + + view_stack_remove_view(infrared->view_stack, popup_get_view(infrared->popup)); popup_reset(infrared->popup); } diff --git a/applications/main/infrared/scenes/infrared_scene_universal_ac.c b/applications/main/infrared/scenes/infrared_scene_universal_ac.c index 764a9518909..b82bcc1f9af 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_ac.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_ac.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_ac_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -122,16 +120,7 @@ void infrared_scene_universal_ac_on_enter(void* context) { button_panel_add_label(button_panel, 4, 10, FontPrimary, "AC remote"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_ac_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_audio.c b/applications/main/infrared/scenes/infrared_scene_universal_audio.c index 241a22bcbb7..a15b2ce9949 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_audio.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_audio.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_audio_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -119,16 +117,7 @@ void infrared_scene_universal_audio_on_enter(void* context) { button_panel_add_label(button_panel, 1, 10, FontPrimary, "Mus. remote"); button_panel_add_icon(button_panel, 34, 56, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_audio_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_projector.c b/applications/main/infrared/scenes/infrared_scene_universal_projector.c index d8520deb39e..c665444fb11 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_projector.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_projector.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_projector_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -68,16 +66,7 @@ void infrared_scene_universal_projector_on_enter(void* context) { button_panel_add_label(button_panel, 3, 11, FontPrimary, "Proj. remote"); button_panel_add_icon(button_panel, 17, 72, &I_vol_ac_text_30x30); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_projector_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/main/infrared/scenes/infrared_scene_universal_tv.c b/applications/main/infrared/scenes/infrared_scene_universal_tv.c index 6031205f551..16633e29cbe 100644 --- a/applications/main/infrared/scenes/infrared_scene_universal_tv.c +++ b/applications/main/infrared/scenes/infrared_scene_universal_tv.c @@ -3,8 +3,6 @@ #include "common/infrared_scene_universal_common.h" void infrared_scene_universal_tv_on_enter(void* context) { - infrared_scene_universal_common_on_enter(context); - InfraredApp* infrared = context; ButtonPanel* button_panel = infrared->button_panel; InfraredBruteForce* brute_force = infrared->brute_force; @@ -95,16 +93,7 @@ void infrared_scene_universal_tv_on_enter(void* context) { button_panel_add_label(button_panel, 5, 10, FontPrimary, "TV remote"); - view_set_orientation(view_stack_get_view(infrared->view_stack), ViewOrientationVertical); - view_dispatcher_switch_to_view(infrared->view_dispatcher, InfraredViewStack); - - infrared_show_loading_popup(infrared, true); - bool success = infrared_brute_force_calculate_messages(brute_force); - infrared_show_loading_popup(infrared, false); - - if(!success) { - scene_manager_next_scene(infrared->scene_manager, InfraredSceneErrorDatabases); - } + infrared_scene_universal_common_on_enter(context); } bool infrared_scene_universal_tv_on_event(void* context, SceneManagerEvent event) {