diff --git a/SConstruct b/SConstruct index 298954fd6..597e42c61 100644 --- a/SConstruct +++ b/SConstruct @@ -234,7 +234,7 @@ firmware_debug = distenv.PhonyTarget( ) distenv.Depends(firmware_debug, firmware_flash) -distenv.PhonyTarget( +firmware_blackmagic = distenv.PhonyTarget( "blackmagic", "${GDBPYCOM}", source=firmware_env["FW_ELF"], @@ -242,6 +242,7 @@ distenv.PhonyTarget( GDBREMOTE="${BLACKMAGIC_ADDR}", FBT_FAP_DEBUG_ELF_ROOT=firmware_env["FBT_FAP_DEBUG_ELF_ROOT"], ) +distenv.Depends(firmware_blackmagic, firmware_flash) # Debug alien elf debug_other_opts = [ diff --git a/applications/debug/ccid_test/ccid_test_app.c b/applications/debug/ccid_test/ccid_test_app.c index abb8ad3dd..4158c1a60 100644 --- a/applications/debug/ccid_test/ccid_test_app.c +++ b/applications/debug/ccid_test/ccid_test_app.c @@ -105,7 +105,7 @@ void ccid_test_app_free(CcidTestApp* app) { furi_record_close(RECORD_GUI); app->gui = NULL; - free(app->iso7816_handler); + iso7816_handler_free(app->iso7816_handler); // Free rest free(app); @@ -121,8 +121,7 @@ int32_t ccid_test_app(void* p) { furi_hal_usb_unlock(); furi_check(furi_hal_usb_set_config(&usb_ccid, &app->ccid_cfg) == true); - furi_hal_usb_ccid_set_callbacks( - (CcidCallbacks*)&app->iso7816_handler->ccid_callbacks, app->iso7816_handler); + iso7816_handler_set_usb_ccid_callbacks(); furi_hal_usb_ccid_insert_smartcard(); //handle button events @@ -142,7 +141,7 @@ int32_t ccid_test_app(void* p) { } //tear down USB - furi_hal_usb_ccid_set_callbacks(NULL, NULL); + iso7816_handler_reset_usb_ccid_callbacks(); furi_hal_usb_set_config(usb_mode_prev, NULL); //teardown view diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.c b/applications/debug/ccid_test/iso7816/iso7816_handler.c index 97214d1b2..8f0f758b2 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.c +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.c @@ -6,11 +6,17 @@ #include #include +#include "iso7816_handler.h" + #include "iso7816_t0_apdu.h" #include "iso7816_atr.h" -#include "iso7816_handler.h" #include "iso7816_response.h" +static Iso7816Handler* iso7816_handler; +static CcidCallbacks* ccid_callbacks; +static uint8_t* command_apdu_buffer; +static uint8_t* response_apdu_buffer; + void iso7816_icc_power_on_callback(uint8_t* atr_data, uint32_t* atr_data_len, void* context) { furi_check(context); @@ -40,12 +46,11 @@ void iso7816_xfr_datablock_callback( Iso7816Handler* handler = (Iso7816Handler*)context; - ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)&handler->response_apdu_buffer; - - ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)&handler->command_apdu_buffer; + ISO7816_Response_APDU* response_apdu = (ISO7816_Response_APDU*)response_apdu_buffer; + ISO7816_Command_APDU* command_apdu = (ISO7816_Command_APDU*)command_apdu_buffer; uint8_t result = iso7816_read_command_apdu( - command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len); + command_apdu, pc_to_reader_datablock, pc_to_reader_datablock_len, CCID_SHORT_APDU_SIZE); if(result == ISO7816_READ_COMMAND_APDU_OK) { handler->iso7816_process_command(command_apdu, response_apdu); @@ -61,8 +66,31 @@ void iso7816_xfr_datablock_callback( } Iso7816Handler* iso7816_handler_alloc() { - Iso7816Handler* handler = malloc(sizeof(Iso7816Handler)); - handler->ccid_callbacks.icc_power_on_callback = iso7816_icc_power_on_callback; - handler->ccid_callbacks.xfr_datablock_callback = iso7816_xfr_datablock_callback; - return handler; + iso7816_handler = malloc(sizeof(Iso7816Handler)); + + command_apdu_buffer = malloc(sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE); + response_apdu_buffer = malloc(sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE); + + ccid_callbacks = malloc(sizeof(CcidCallbacks)); + ccid_callbacks->icc_power_on_callback = iso7816_icc_power_on_callback; + ccid_callbacks->xfr_datablock_callback = iso7816_xfr_datablock_callback; + + return iso7816_handler; +} + +void iso7816_handler_set_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(ccid_callbacks, iso7816_handler); +} + +void iso7816_handler_reset_usb_ccid_callbacks() { + furi_hal_usb_ccid_set_callbacks(NULL, NULL); +} + +void iso7816_handler_free(Iso7816Handler* handler) { + free(ccid_callbacks); + + free(command_apdu_buffer); + free(response_apdu_buffer); + + free(handler); } diff --git a/applications/debug/ccid_test/iso7816/iso7816_handler.h b/applications/debug/ccid_test/iso7816/iso7816_handler.h index d67118ce6..4f9703e46 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_handler.h +++ b/applications/debug/ccid_test/iso7816/iso7816_handler.h @@ -5,14 +5,14 @@ #include "iso7816_t0_apdu.h" typedef struct { - CcidCallbacks ccid_callbacks; void (*iso7816_answer_to_reset)(Iso7816Atr* atr); void (*iso7816_process_command)( const ISO7816_Command_APDU* command, ISO7816_Response_APDU* response); - - uint8_t command_apdu_buffer[sizeof(ISO7816_Command_APDU) + CCID_SHORT_APDU_SIZE]; - uint8_t response_apdu_buffer[sizeof(ISO7816_Response_APDU) + CCID_SHORT_APDU_SIZE]; } Iso7816Handler; Iso7816Handler* iso7816_handler_alloc(); + +void iso7816_handler_free(Iso7816Handler* handler); +void iso7816_handler_set_usb_ccid_callbacks(); +void iso7816_handler_reset_usb_ccid_callbacks(); diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c index 216f2582f..023daebe2 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.c @@ -1,49 +1,48 @@ /* Implements rudimentary iso7816-3 support for APDU (T=0) */ #include #include -#include -#include #include "iso7816_t0_apdu.h" -//reads dataBuffer with dataLen size, translate it into a ISO7816_Command_APDU type +//reads pc_to_reader_datablock_len with pc_to_reader_datablock_len size, translate it into a ISO7816_Command_APDU type //extra data will be pointed to commandDataBuffer uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, - const uint8_t* dataBuffer, - uint32_t dataLen) { - command->CLA = dataBuffer[0]; - command->INS = dataBuffer[1]; - command->P1 = dataBuffer[2]; - command->P2 = dataBuffer[3]; + const uint8_t* pc_to_reader_datablock, + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size) { + command->CLA = pc_to_reader_datablock[0]; + command->INS = pc_to_reader_datablock[1]; + command->P1 = pc_to_reader_datablock[2]; + command->P2 = pc_to_reader_datablock[3]; - if(dataLen == 4) { + if(pc_to_reader_datablock_len == 4) { command->Lc = 0; command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == 5) { + } else if(pc_to_reader_datablock_len == 5) { //short le command->Lc = 0; - command->Le = dataBuffer[4]; + command->Le = pc_to_reader_datablock[4]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen > 5 && dataBuffer[4] != 0x00) { + } else if(pc_to_reader_datablock_len > 5 && pc_to_reader_datablock[4] != 0x00) { //short lc - command->Lc = dataBuffer[4]; - if(command->Lc > 0 && command->Lc < CCID_SHORT_APDU_SIZE) { //-V560 - memcpy(command->Data, &dataBuffer[5], command->Lc); + command->Lc = pc_to_reader_datablock[4]; + if(command->Lc > 0 && command->Lc < max_apdu_size) { //-V560 + memcpy(command->Data, &pc_to_reader_datablock[5], command->Lc); //does it have a short le too? - if(dataLen == (uint32_t)(command->Lc + 5)) { + if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 5)) { command->Le = 0; command->LePresent = false; return ISO7816_READ_COMMAND_APDU_OK; - } else if(dataLen == (uint32_t)(command->Lc + 6)) { - command->Le = dataBuffer[dataLen - 1]; + } else if(pc_to_reader_datablock_len == (uint32_t)(command->Lc + 6)) { + command->Le = pc_to_reader_datablock[pc_to_reader_datablock_len - 1]; command->LePresent = true; return ISO7816_READ_COMMAND_APDU_OK; diff --git a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h index a21dfbafc..bbd2a1b23 100644 --- a/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h +++ b/applications/debug/ccid_test/iso7816/iso7816_t0_apdu.h @@ -34,7 +34,8 @@ typedef struct { uint8_t iso7816_read_command_apdu( ISO7816_Command_APDU* command, const uint8_t* pc_to_reader_datablock, - uint32_t pc_to_reader_datablock_len); + uint32_t pc_to_reader_datablock_len, + uint32_t max_apdu_size); void iso7816_write_response_apdu( const ISO7816_Response_APDU* response, uint8_t* reader_to_pc_datablock, diff --git a/applications/debug/expansion_test/expansion_test.c b/applications/debug/expansion_test/expansion_test.c index e3bcf4e9c..665874f47 100644 --- a/applications/debug/expansion_test/expansion_test.c +++ b/applications/debug/expansion_test/expansion_test.c @@ -6,18 +6,27 @@ * 13 -> 16 (USART TX to LPUART RX) * 14 -> 15 (USART RX to LPUART TX) * + * Optional: Connect an LED with an appropriate series resistor + * between pins 1 and 8. It will always be on if the device is + * connected to USB power, so unplug it before running the app. + * * What this application does: * * - Enables module support and emulates the module on a single device * (hence the above connection), * - Connects to the expansion module service, sets baud rate, + * - Enables OTG (5V) on GPIO via plain expansion protocol, + * - Waits 5 cycles of idle loop (1 second), * - Starts the RPC session, + * - Disables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Creates a directory at `/ext/ExpansionTest` and writes a file * named `test.txt` under it, * - Plays an audiovisual alert (sound and blinking display), - * - Waits 10 cycles of idle loop, + * - Enables OTG (5V) on GPIO via RPC messages, + * - Waits 5 cycles of idle loop (1 second), * - Stops the RPC session, - * - Waits another 10 cycles of idle loop, + * - Disables OTG (5V) on GPIO via plain expansion protocol, * - Exits (plays a sound if any of the above steps failed). */ #include @@ -302,6 +311,22 @@ static bool expansion_test_app_handshake(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + do { + const ExpansionFrameControlCommand command = enable ? + ExpansionFrameControlCommandEnableOtg : + ExpansionFrameControlCommandDisableOtg; + if(!expansion_test_app_send_control_request(instance, command)) break; + if(!expansion_test_app_receive_frame(instance, &instance->frame)) break; + if(!expansion_test_app_is_success_response(&instance->frame)) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_start_rpc(ExpansionTestApp* instance) { bool success = false; @@ -396,6 +421,27 @@ static bool expansion_test_app_rpc_alert(ExpansionTestApp* instance) { return success; } +static bool expansion_test_app_rpc_enable_otg(ExpansionTestApp* instance, bool enable) { + bool success = false; + + instance->msg.command_id++; + instance->msg.command_status = PB_CommandStatus_OK; + instance->msg.which_content = PB_Main_gpio_set_otg_mode_tag; + instance->msg.content.gpio_set_otg_mode.mode = enable ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + instance->msg.has_next = false; + + do { + if(!expansion_test_app_send_rpc_request(instance, &instance->msg)) break; + if(!expansion_test_app_receive_rpc_request(instance, &instance->msg)) break; + if(instance->msg.which_content != PB_Main_empty_tag) break; + if(instance->msg.command_status != PB_CommandStatus_OK) break; + success = true; + } while(false); + + return success; +} + static bool expansion_test_app_idle(ExpansionTestApp* instance, uint32_t num_cycles) { uint32_t num_cycles_done; for(num_cycles_done = 0; num_cycles_done < num_cycles; ++num_cycles_done) { @@ -434,13 +480,18 @@ int32_t expansion_test_app(void* p) { if(!expansion_test_app_send_presence(instance)) break; if(!expansion_test_app_wait_ready(instance)) break; if(!expansion_test_app_handshake(instance)) break; + if(!expansion_test_app_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_start_rpc(instance)) break; + if(!expansion_test_app_rpc_enable_otg(instance, false)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_rpc_mkdir(instance)) break; if(!expansion_test_app_rpc_write(instance)) break; if(!expansion_test_app_rpc_alert(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_rpc_enable_otg(instance, true)) break; + if(!expansion_test_app_idle(instance, 5)) break; if(!expansion_test_app_stop_rpc(instance)) break; - if(!expansion_test_app_idle(instance, 10)) break; + if(!expansion_test_app_enable_otg(instance, false)) break; success = true; } while(false); diff --git a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c index 367ca7a4f..be7774881 100644 --- a/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c +++ b/applications/debug/rpc_debug_app/scenes/rpc_debug_app_scene_input_error_code.c @@ -1,5 +1,7 @@ #include "../rpc_debug_app.h" +#include + static bool rpc_debug_app_scene_input_error_code_validator_callback( const char* text, FuriString* error, @@ -44,9 +46,8 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv if(event.type == SceneManagerEventTypeCustom) { if(event.event == RpcDebugAppCustomEventInputErrorCode) { - char* end; - int error_code = strtol(app->text_store, &end, 10); - if(!*end) { + uint32_t error_code; + if(strint_to_uint32(app->text_store, NULL, &error_code, 10) == StrintParseNoError) { rpc_system_app_set_error_code(app->rpc, error_code); } scene_manager_previous_scene(app->scene_manager); diff --git a/applications/debug/uart_echo/uart_echo.c b/applications/debug/uart_echo/uart_echo.c index bf38ba4c2..1d5a473ca 100644 --- a/applications/debug/uart_echo/uart_echo.c +++ b/applications/debug/uart_echo/uart_echo.c @@ -6,6 +6,8 @@ #include #include +#include + #include #include @@ -321,7 +323,7 @@ int32_t uart_echo_app(void* p) { uint32_t baudrate = DEFAULT_BAUD_RATE; if(p) { const char* baudrate_str = p; - if(sscanf(baudrate_str, "%lu", &baudrate) != 1) { + if(strint_to_uint32(baudrate_str, NULL, &baudrate, 10) != StrintParseNoError) { FURI_LOG_E(TAG, "Invalid baudrate: %s", baudrate_str); baudrate = DEFAULT_BAUD_RATE; } diff --git a/applications/debug/unit_tests/application.fam b/applications/debug/unit_tests/application.fam index f5f84ead7..c87305847 100644 --- a/applications/debug/unit_tests/application.fam +++ b/applications/debug/unit_tests/application.fam @@ -220,3 +220,11 @@ App( entry_point="get_api", requires=["unit_tests"], ) + +App( + appid="test_strint", + sources=["tests/common/*.c", "tests/strint/*.c"], + apptype=FlipperAppType.PLUGIN, + entry_point="get_api", + requires=["unit_tests"], +) diff --git a/applications/debug/unit_tests/tests/strint/strint_test.c b/applications/debug/unit_tests/tests/strint/strint_test.c new file mode 100644 index 000000000..d8fd9113d --- /dev/null +++ b/applications/debug/unit_tests/tests/strint/strint_test.c @@ -0,0 +1,142 @@ +#include +#include + +#include "../test.h" // IWYU pragma: keep + +#include + +MU_TEST(strint_test_basic) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_junk) { + uint32_t result = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32(" 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n\r\n 123456 ", NULL, &result, 10)); + mu_assert_int_eq(123456, result); +} + +MU_TEST(strint_test_tail) { + uint32_t result = 0; + char* tail; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); + mu_assert_int_eq( + StrintParseNoError, strint_to_uint32(" \r\n 123456tail", &tail, &result, 10)); + mu_assert_int_eq(123456, result); + mu_assert_string_eq("tail", tail); +} + +MU_TEST(strint_test_errors) { + uint32_t result = 123; + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32("", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseAbsentError, strint_to_uint32(" asd\r\n", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("+++123456", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseSignError, strint_to_uint32("-1", NULL, &result, 10)); + mu_assert_int_eq(123, result); + mu_assert_int_eq( + StrintParseOverflowError, + strint_to_uint32("0xAAAAAAAAAAAAAAAADEADBEEF!!!!!!", NULL, &result, 0)); + mu_assert_int_eq(123, result); + mu_assert_int_eq(StrintParseOverflowError, strint_to_uint32("4294967296", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + int32_t result_i32 = 123; + mu_assert_int_eq( + StrintParseOverflowError, strint_to_int32("-2147483649", NULL, &result_i32, 0)); + mu_assert_int_eq(123, result_i32); +} + +MU_TEST(strint_test_bases) { + uint32_t result = 0; + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0x123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0X123", NULL, &result, 0)); + mu_assert_int_eq(0x123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 0)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0xDEADBEEF", NULL, &result, 16)); + mu_assert_int_eq(0xDEADBEEF, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 16)); + mu_assert_int_eq(0x123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 0)); + mu_assert_int_eq(123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 0)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("123", NULL, &result, 8)); + mu_assert_int_eq(0123, result); + + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0b101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("0B101", NULL, &result, 0)); + mu_assert_int_eq(0b101, result); + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("101", NULL, &result, 2)); + mu_assert_int_eq(0b101, result); +} + +MU_TEST_SUITE(strint_test_limits) { + uint64_t result_u64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_uint64("18446744073709551615", NULL, &result_u64, 0)); + // `mu_assert_int_eq' does not support longs :( + mu_assert(UINT64_MAX == result_u64, "result does not equal UINT64_MAX"); + + int64_t result_i64 = 0; + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("9223372036854775807", NULL, &result_i64, 0)); + mu_assert(INT64_MAX == result_i64, "result does not equal INT64_MAX"); + mu_assert_int_eq( + StrintParseNoError, strint_to_int64("-9223372036854775808", NULL, &result_i64, 0)); + mu_assert(INT64_MIN == result_i64, "result does not equal INT64_MIN"); + + uint32_t result_u32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint32("4294967295", NULL, &result_u32, 0)); + mu_assert_int_eq(UINT32_MAX, result_u32); + + int32_t result_i32 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int32("2147483647", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MAX, result_i32); + mu_assert_int_eq(StrintParseNoError, strint_to_int32("-2147483648", NULL, &result_i32, 0)); + mu_assert_int_eq(INT32_MIN, result_i32); + + uint16_t result_u16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_uint16("65535", NULL, &result_u16, 0)); + mu_assert_int_eq(UINT16_MAX, result_u16); + + int16_t result_i16 = 0; + mu_assert_int_eq(StrintParseNoError, strint_to_int16("32767", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MAX, result_i16); + mu_assert_int_eq(StrintParseNoError, strint_to_int16("-32768", NULL, &result_i16, 0)); + mu_assert_int_eq(INT16_MIN, result_i16); +} + +MU_TEST_SUITE(test_strint_suite) { + MU_RUN_TEST(strint_test_basic); + MU_RUN_TEST(strint_test_junk); + MU_RUN_TEST(strint_test_tail); + MU_RUN_TEST(strint_test_errors); + MU_RUN_TEST(strint_test_bases); + MU_RUN_TEST(strint_test_limits); +} + +int run_minunit_test_strint(void) { + MU_RUN_SUITE(test_strint_suite); + return MU_EXIT_CODE; +} + +TEST_API_DEFINE(run_minunit_test_strint) diff --git a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c index 2afdaf5c1..d10d97c24 100644 --- a/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c +++ b/applications/examples/example_number_input/scenes/example_number_input_scene_show_number.c @@ -13,8 +13,7 @@ static void example_number_input_scene_update_view(void* context) { dialog_ex_set_header(dialog_ex, "The number is", 64, 0, AlignCenter, AlignTop); - static char buffer[12]; //needs static for extended lifetime - + char buffer[12] = {}; snprintf(buffer, sizeof(buffer), "%ld", app->current_number); dialog_ex_set_text(dialog_ex, buffer, 64, 29, AlignCenter, AlignCenter); diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index f098d9da3..8408b96ae 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "ducky_script.h" #include "ducky_script_i.h" @@ -64,7 +65,7 @@ uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept bool ducky_get_number(const char* param, uint32_t* val) { uint32_t value = 0; - if(sscanf(param, "%lu", &value) == 1) { + if(strint_to_uint32(param, NULL, &value, 10) == StrintParseNoError) { *val = value; return true; } diff --git a/applications/main/infrared/infrared_cli.c b/applications/main/infrared/infrared_cli.c index 2763d2345..aa24694ae 100644 --- a/applications/main/infrared/infrared_cli.c +++ b/applications/main/infrared/infrared_cli.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "infrared_signal.h" @@ -176,25 +177,28 @@ static bool infrared_cli_parse_raw(const char* str, InfraredSignal* signal) { return false; } - uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); - uint32_t frequency = atoi(frequency_str); - float duty_cycle = (float)atoi(duty_cycle_str) / 100; + uint32_t frequency; + uint32_t duty_cycle_u32; + if(strint_to_uint32(frequency_str, NULL, &frequency, 10) != StrintParseNoError || + strint_to_uint32(duty_cycle_str, NULL, &duty_cycle_u32, 10) != StrintParseNoError) + return false; + float duty_cycle = duty_cycle_u32 / 100.0f; str += strlen(frequency_str) + strlen(duty_cycle_str) + INFRARED_CLI_BUF_SIZE; + uint32_t* timings = malloc(sizeof(uint32_t) * MAX_TIMINGS_AMOUNT); size_t timings_size = 0; while(1) { while(*str == ' ') { ++str; } - char timing_str[INFRARED_CLI_BUF_SIZE]; - if(sscanf(str, "%9s", timing_str) != 1) { + uint32_t timing; + char* next_token; + if(strint_to_uint32(str, &next_token, &timing, 10) != StrintParseNoError) { break; } - - str += strlen(timing_str); - uint32_t timing = atoi(timing_str); + str = next_token; if((timing <= 0) || (timings_size >= MAX_TIMINGS_AMOUNT)) { break; diff --git a/applications/main/nfc/plugins/supported_cards/itso.c b/applications/main/nfc/plugins/supported_cards/itso.c index 92dbbd7b6..4e9264de4 100644 --- a/applications/main/nfc/plugins/supported_cards/itso.c +++ b/applications/main/nfc/plugins/supported_cards/itso.c @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -72,7 +73,10 @@ static bool itso_parse(const NfcDevice* device, FuriString* parsed_data) { 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 dateStamp; + if(strint_to_uint32(datep, NULL, &dateStamp, 16) != StrintParseNoError) { + return false; + } uint32_t unixTimestamp = dateStamp * 24 * 60 * 60 + 852076800U; furi_string_set(parsed_data, "\e#ITSO Card\n"); diff --git a/applications/main/subghz/subghz_cli.c b/applications/main/subghz/subghz_cli.c index 43ba9c644..e23b9c4db 100644 --- a/applications/main/subghz/subghz_cli.c +++ b/applications/main/subghz/subghz_cli.c @@ -3,18 +3,20 @@ #include #include -#include -#include +#include +#include #include #include #include #include -#include #include #include #include +#include +#include + #include "helpers/subghz_chat.h" #include @@ -76,9 +78,8 @@ void subghz_cli_command_tx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz tx_carrier", "", furi_string_get_cstr(args)); return; } @@ -120,9 +121,8 @@ void subghz_cli_command_rx_carrier(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx_carrier", "", furi_string_get_cstr(args)); return; } @@ -186,23 +186,14 @@ void subghz_cli_command_tx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf( - furi_string_get_cstr(args), - "%lx %lu %lu %lu %lu", - &key, - &frequency, - &te, - &repeat, - &device_ind); - if(ret != 5) { - printf( - "sscanf returned %d, key: %lx, frequency: %lu, te: %lu, repeat: %lu, device: %lu\r\n ", - ret, - key, - frequency, - te, - repeat, - device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &key, 16); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &te, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &repeat, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx", "<3 Byte Key: in hex> ", @@ -318,10 +309,11 @@ void subghz_cli_command_rx(Cli* cli, FuriString* args, void* context) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf( - "sscanf returned %d, frequency: %lu device: %lu\r\n", ret, frequency, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz rx", " ", @@ -405,9 +397,8 @@ void subghz_cli_command_rx_raw(Cli* cli, FuriString* args, void* context) { uint32_t frequency = 433920000; if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu", &frequency); - if(ret != 1) { - printf("sscanf returned %d, frequency: %lu\r\n", ret, frequency); + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &frequency, 10) != + StrintParseNoError) { cli_print_usage("subghz rx", "", furi_string_get_cstr(args)); return; } @@ -624,9 +615,11 @@ void subghz_cli_command_tx_from_file(Cli* cli, FuriString* args, void* context) } if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &repeat, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, repeat: %lu device: %lu\r\n", ret, repeat, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz tx_from_file:", " ", @@ -939,10 +932,11 @@ static void subghz_cli_command_chat(Cli* cli, FuriString* args) { uint32_t device_ind = 0; // 0 - CC1101_INT, 1 - CC1101_EXT if(furi_string_size(args)) { - int ret = sscanf(furi_string_get_cstr(args), "%lu %lu", &frequency, &device_ind); - if(ret != 2) { - printf("sscanf returned %d, Frequency: %lu\r\n", ret, frequency); - printf("sscanf returned %d, Device: %lu\r\n", ret, device_ind); + char* args_cstr = (char*)furi_string_get_cstr(args); + StrintParseError parse_err = StrintParseNoError; + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &frequency, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &device_ind, 10); + if(parse_err) { cli_print_usage( "subghz chat", " ", diff --git a/applications/services/cli/cli_commands.c b/applications/services/cli/cli_commands.c index 8fe5643af..62a630f95 100644 --- a/applications/services/cli/cli_commands.c +++ b/applications/services/cli/cli_commands.c @@ -9,6 +9,7 @@ #include #include #include +#include // Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'` #define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d" @@ -371,9 +372,9 @@ void cli_command_led(Cli* cli, FuriString* args, void* context) { } furi_string_free(light_name); // Read light value from the rest of the string - char* end_ptr; - uint32_t value = strtoul(furi_string_get_cstr(args), &end_ptr, 0); - if(!(value < 256 && *end_ptr == '\0')) { + uint32_t value; + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &value, 0) != StrintParseNoError || + value >= 256) { cli_print_usage("led", " <0-255>", furi_string_get_cstr(args)); return; } diff --git a/applications/services/expansion/expansion_protocol.h b/applications/services/expansion/expansion_protocol.h index 6ed818f82..a8d682330 100644 --- a/applications/services/expansion/expansion_protocol.h +++ b/applications/services/expansion/expansion_protocol.h @@ -64,8 +64,28 @@ typedef enum { * @brief Enumeration of suported control commands. */ typedef enum { - ExpansionFrameControlCommandStartRpc = 0x00, /**< Start an RPC session. */ - ExpansionFrameControlCommandStopRpc = 0x01, /**< Stop an open RPC session. */ + /** @brief Start an RPC session. + * + * Must only be used while the RPC session is NOT active. + */ + ExpansionFrameControlCommandStartRpc = 0x00, + /** @brief Stop an open RPC session. + * + * Must only be used while the RPC session IS active. + */ + ExpansionFrameControlCommandStopRpc = 0x01, + /** @brief Enable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandEnableOtg = 0x02, + /** @brief Disable OTG (5V) on external GPIO. + * + * Must only be used while the RPC session is NOT active, + * otherwise OTG is to be controlled via RPC messages. + */ + ExpansionFrameControlCommandDisableOtg = 0x03, } ExpansionFrameControlCommand; #pragma pack(push, 1) diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index 449d02cff..c05b9cc85 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -245,9 +245,18 @@ static bool expansion_worker_handle_state_connected( 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; + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStartRpc) { + if(!expansion_worker_rpc_session_open(instance)) break; + instance->state = ExpansionWorkerStateRpcActive; + } else if(command == ExpansionFrameControlCommandEnableOtg) { + furi_hal_power_enable_otg(); + } else if(command == ExpansionFrameControlCommandDisableOtg) { + furi_hal_power_disable_otg(); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeHeartbeat) { @@ -279,9 +288,14 @@ static bool expansion_worker_handle_state_rpc_active( 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); + const uint8_t command = rx_frame->content.control.command; + if(command == ExpansionFrameControlCommandStopRpc) { + instance->state = ExpansionWorkerStateConnected; + expansion_worker_rpc_session_close(instance); + } else { + break; + } + if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; } else if(rx_frame->header.type == ExpansionFrameTypeStatus) { diff --git a/applications/services/gui/elements.c b/applications/services/gui/elements.c index d2dc9e438..9dbfb7b6e 100644 --- a/applications/services/gui/elements.c +++ b/applications/services/gui/elements.c @@ -185,6 +185,70 @@ void elements_button_right(Canvas* canvas, const char* str) { canvas_invert_color(canvas); } +void elements_button_up(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonUp_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + (int32_t)vertical_offset; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = 0; + const int32_t y = 0 + button_height; + + int32_t line_x = x + button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x + 0, line_y, line_x + 0, y - 1); + canvas_draw_line(canvas, line_x + 1, line_y, line_x + 1, y - 2); + canvas_draw_line(canvas, line_x + 2, line_y, line_x + 2, y - 3); + + canvas_invert_color(canvas); + canvas_draw_icon(canvas, x + horizontal_offset, y - icon_v_offset, icon); + canvas_draw_str( + canvas, x + horizontal_offset + icon_width_with_offset, y - vertical_offset, str); + canvas_invert_color(canvas); +} + +void elements_button_down(Canvas* canvas, const char* str) { + furi_check(canvas); + + const Icon* icon = &I_ButtonDown_7x4; + + const size_t button_height = 12; + const size_t vertical_offset = 3; + const size_t horizontal_offset = 3; + const size_t string_width = canvas_string_width(canvas, str); + const int32_t icon_h_offset = 3; + const int32_t icon_width_with_offset = icon_get_width(icon) + icon_h_offset; + const int32_t icon_v_offset = icon_get_height(icon) + vertical_offset + 1; + const size_t button_width = string_width + horizontal_offset * 2 + icon_width_with_offset; + + const int32_t x = canvas_width(canvas); + const int32_t y = button_height; + + int32_t line_x = x - button_width; + int32_t line_y = y - button_height; + + canvas_draw_box(canvas, line_x, line_y, button_width, button_height); + canvas_draw_line(canvas, line_x - 1, line_y, line_x - 1, y - 1); + canvas_draw_line(canvas, line_x - 2, line_y, line_x - 2, y - 2); + canvas_draw_line(canvas, line_x - 3, line_y, line_x - 3, y - 3); + + canvas_invert_color(canvas); + canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str); + canvas_draw_icon( + canvas, x - horizontal_offset - icon_get_width(icon), y - icon_v_offset, icon); + canvas_invert_color(canvas); +} + void elements_button_center(Canvas* canvas, const char* str) { furi_check(canvas); diff --git a/applications/services/gui/elements.h b/applications/services/gui/elements.h index 30d7e35ac..4f76f5c09 100644 --- a/applications/services/gui/elements.h +++ b/applications/services/gui/elements.h @@ -96,6 +96,28 @@ void elements_button_left(Canvas* canvas, const char* str); */ void elements_button_right(Canvas* canvas, const char* str); +/** + * @brief This function draws a button in the top left corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_up(Canvas* canvas, const char* str); + +/** + * @brief This function draws a button in the top right corner of the canvas with icon and string. + * + * The design and layout of the button is defined within this function. + * + * @param[in] canvas This is a pointer to the @c Canvas structure where the button will be drawn. + * @param[in] str This is a pointer to the character string that will be drawn within the button. + * + */ +void elements_button_down(Canvas* canvas, const char* str); + /** Draw button in center * * @param canvas Canvas instance diff --git a/applications/services/gui/modules/byte_input.c b/applications/services/gui/modules/byte_input.c index 1da36b2dc..d55e519f5 100644 --- a/applications/services/gui/modules/byte_input.c +++ b/applications/services/gui/modules/byte_input.c @@ -33,7 +33,7 @@ typedef struct { static const uint8_t keyboard_origin_x = 7; static const uint8_t keyboard_origin_y = 31; -static const uint8_t keyboard_row_count = 2; +static const int8_t keyboard_row_count = 2; static const uint8_t enter_symbol = '\r'; static const uint8_t backspace_symbol = '\b'; static const uint8_t max_drawable_bytes = 8; @@ -649,11 +649,11 @@ static void byte_input_view_draw_callback(Canvas* canvas, void* _model) { } canvas_set_font(canvas, FontKeyboard); // Draw keyboard - for(uint8_t row = 0; row < keyboard_row_count; row++) { + for(int8_t row = 0; row < keyboard_row_count; row++) { const uint8_t column_count = byte_input_get_row_size(row); const ByteInputKey* keys = byte_input_get_row(row); - for(size_t column = 0; column < column_count; column++) { + for(uint8_t column = 0; column < column_count; column++) { if(keys[column].value == enter_symbol) { canvas_set_color(canvas, ColorBlack); if(model->selected_row == row && model->selected_column == column) { diff --git a/applications/services/gui/modules/dialog_ex.c b/applications/services/gui/modules/dialog_ex.c index 7171f6892..75209a408 100644 --- a/applications/services/gui/modules/dialog_ex.c +++ b/applications/services/gui/modules/dialog_ex.c @@ -10,7 +10,7 @@ struct DialogEx { }; typedef struct { - const char* text; + FuriString* text; uint8_t x; uint8_t y; Align horizontal; @@ -28,16 +28,15 @@ typedef struct { TextElement text; IconElement icon; - const char* left_text; - const char* center_text; - const char* right_text; + FuriString* left_text; + FuriString* center_text; + FuriString* right_text; } DialogExModel; static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { DialogExModel* model = _model; // Prepare canvas - canvas_clear(canvas); canvas_set_color(canvas, ColorBlack); if(model->icon.icon != NULL) { @@ -46,94 +45,94 @@ static void dialog_ex_view_draw_callback(Canvas* canvas, void* _model) { // Draw header canvas_set_font(canvas, FontPrimary); - if(model->header.text != NULL) { + if(furi_string_size(model->header.text)) { elements_multiline_text_aligned( canvas, model->header.x, model->header.y, model->header.horizontal, model->header.vertical, - model->header.text); + furi_string_get_cstr(model->header.text)); } // Draw text canvas_set_font(canvas, FontSecondary); - if(model->text.text != NULL) { + if(furi_string_size(model->text.text)) { elements_multiline_text_aligned( canvas, model->text.x, model->text.y, model->text.horizontal, model->text.vertical, - model->text.text); + furi_string_get_cstr(model->text.text)); } // Draw buttons - if(model->left_text != NULL) { - elements_button_left(canvas, model->left_text); + if(furi_string_size(model->left_text)) { + elements_button_left(canvas, furi_string_get_cstr(model->left_text)); } - if(model->center_text != NULL) { - elements_button_center(canvas, model->center_text); + if(furi_string_size(model->center_text)) { + elements_button_center(canvas, furi_string_get_cstr(model->center_text)); } - if(model->right_text != NULL) { - elements_button_right(canvas, model->right_text); + if(furi_string_size(model->right_text)) { + elements_button_right(canvas, furi_string_get_cstr(model->right_text)); } } static bool dialog_ex_view_input_callback(InputEvent* event, void* context) { DialogEx* dialog_ex = context; bool consumed = false; - const char* left_text = NULL; - const char* center_text = NULL; - const char* right_text = NULL; + bool left_text_present = false; + bool center_text_present = false; + bool right_text_present = false; with_view_model( dialog_ex->view, DialogExModel * model, { - left_text = model->left_text; - center_text = model->center_text; - right_text = model->right_text; + left_text_present = furi_string_size(model->left_text); + center_text_present = furi_string_size(model->center_text); + right_text_present = furi_string_size(model->right_text); }, - true); + false); if(dialog_ex->callback) { if(event->type == InputTypeShort) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExResultLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExResultCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExResultRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypePress && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExPressLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExPressCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExPressRight, dialog_ex->context); consumed = true; } } if(event->type == InputTypeRelease && dialog_ex->enable_extended_events) { - if(event->key == InputKeyLeft && left_text != NULL) { + if(event->key == InputKeyLeft && left_text_present) { dialog_ex->callback(DialogExReleaseLeft, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyOk && center_text != NULL) { + } else if(event->key == InputKeyOk && center_text_present) { dialog_ex->callback(DialogExReleaseCenter, dialog_ex->context); consumed = true; - } else if(event->key == InputKeyRight && right_text != NULL) { + } else if(event->key == InputKeyRight && right_text_present) { dialog_ex->callback(DialogExReleaseRight, dialog_ex->context); consumed = true; } @@ -154,13 +153,13 @@ DialogEx* dialog_ex_alloc(void) { dialog_ex->view, DialogExModel * model, { - model->header.text = NULL; + model->header.text = furi_string_alloc(); model->header.x = 0; model->header.y = 0; model->header.horizontal = AlignLeft; model->header.vertical = AlignBottom; - model->text.text = NULL; + model->text.text = furi_string_alloc(); model->text.x = 0; model->text.y = 0; model->text.horizontal = AlignLeft; @@ -170,17 +169,28 @@ DialogEx* dialog_ex_alloc(void) { model->icon.y = 0; model->icon.icon = NULL; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->left_text = furi_string_alloc(); + model->center_text = furi_string_alloc(); + model->right_text = furi_string_alloc(); }, - true); + false); dialog_ex->enable_extended_events = false; return dialog_ex; } void dialog_ex_free(DialogEx* dialog_ex) { furi_check(dialog_ex); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { + furi_string_free(model->header.text); + furi_string_free(model->text.text); + furi_string_free(model->left_text); + furi_string_free(model->center_text); + furi_string_free(model->right_text); + }, + false); view_free(dialog_ex->view); free(dialog_ex); } @@ -212,7 +222,7 @@ void dialog_ex_set_header( dialog_ex->view, DialogExModel * model, { - model->header.text = text; + furi_string_set(model->header.text, text); model->header.x = x; model->header.y = y; model->header.horizontal = horizontal; @@ -233,7 +243,7 @@ void dialog_ex_set_text( dialog_ex->view, DialogExModel * model, { - model->text.text = text; + furi_string_set(model->text.text, text); model->text.x = x; model->text.y = y; model->text.horizontal = horizontal; @@ -257,34 +267,41 @@ void dialog_ex_set_icon(DialogEx* dialog_ex, uint8_t x, uint8_t y, const Icon* i void dialog_ex_set_left_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->left_text = text; }, true); + with_view_model( + dialog_ex->view, DialogExModel * model, { furi_string_set(model->left_text, text); }, true); } void dialog_ex_set_center_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->center_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->center_text, text); }, + true); } void dialog_ex_set_right_button_text(DialogEx* dialog_ex, const char* text) { furi_check(dialog_ex); - with_view_model(dialog_ex->view, DialogExModel * model, { model->right_text = text; }, true); + with_view_model( + dialog_ex->view, + DialogExModel * model, + { furi_string_set(model->right_text, text); }, + true); } void dialog_ex_reset(DialogEx* dialog_ex) { furi_check(dialog_ex); - TextElement clean_text_el = { - .text = NULL, .x = 0, .y = 0, .horizontal = AlignLeft, .vertical = AlignLeft}; - IconElement clean_icon_el = {.icon = NULL, .x = 0, .y = 0}; with_view_model( dialog_ex->view, DialogExModel * model, { - model->header = clean_text_el; - model->text = clean_text_el; - model->icon = clean_icon_el; - model->left_text = NULL; - model->center_text = NULL; - model->right_text = NULL; + model->icon.icon = NULL; + furi_string_reset(model->header.text); + furi_string_reset(model->text.text); + + furi_string_reset(model->left_text); + furi_string_reset(model->center_text); + furi_string_reset(model->right_text); }, true); dialog_ex->context = NULL; diff --git a/applications/services/gui/modules/number_input.c b/applications/services/gui/modules/number_input.c index 317f22f54..c29b37912 100644 --- a/applications/services/gui/modules/number_input.c +++ b/applications/services/gui/modules/number_input.c @@ -3,6 +3,7 @@ #include #include #include +#include struct NumberInput { View* view; @@ -163,7 +164,11 @@ static void number_input_handle_right(NumberInputModel* model) { } static bool is_number_too_large(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value > (int64_t)model->max_value) { return true; } @@ -171,7 +176,11 @@ static bool is_number_too_large(NumberInputModel* model) { } static bool is_number_too_small(NumberInputModel* model) { - int64_t value = strtoll(furi_string_get_cstr(model->text_buffer), NULL, 10); + int64_t value; + if(strint_to_int64(furi_string_get_cstr(model->text_buffer), NULL, &value, 10) != + StrintParseNoError) { + return true; + } if(value < (int64_t)model->min_value) { return true; } diff --git a/applications/services/loader/loader_cli.c b/applications/services/loader/loader_cli.c index a0254f0d0..f3ea30df2 100644 --- a/applications/services/loader/loader_cli.c +++ b/applications/services/loader/loader_cli.c @@ -4,6 +4,7 @@ #include #include #include +#include #include static void loader_cli_print_usage(void) { @@ -89,18 +90,22 @@ static void loader_cli_close(Loader* loader) { static void loader_cli_signal(FuriString* args, Loader* loader) { uint32_t signal; - void* arg = NULL; + uint32_t arg = 0; + StrintParseError parse_err = 0; + char* args_cstr = (char*)furi_string_get_cstr(args); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &signal, 10); + parse_err |= strint_to_uint32(args_cstr, &args_cstr, &arg, 16); - if(!sscanf(furi_string_get_cstr(args), "%lu %p", &signal, &arg)) { + if(parse_err) { printf("Signal must be a decimal number\r\n"); } else if(!loader_is_locked(loader)) { printf("No application is running\r\n"); } else { - const bool is_handled = loader_signal(loader, signal, arg); + const bool is_handled = loader_signal(loader, signal, (void*)arg); printf( "Signal %lu with argument 0x%p was %s\r\n", signal, - arg, + (void*)arg, is_handled ? "handled" : "ignored"); } } diff --git a/applications/services/rpc/rpc_gpio.c b/applications/services/rpc/rpc_gpio.c index 09e738505..40fc898a0 100644 --- a/applications/services/rpc/rpc_gpio.c +++ b/applications/services/rpc/rpc_gpio.c @@ -2,6 +2,7 @@ #include "rpc_i.h" #include "gpio.pb.h" #include +#include #include static const GpioPin* rpc_pin_to_hal_pin(PB_Gpio_GpioPin rpc_pin) { @@ -188,6 +189,44 @@ void rpc_system_gpio_set_input_pull(const PB_Main* request, void* context) { free(response); } +void rpc_system_gpio_get_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_get_otg_mode_tag); + + RpcSession* session = context; + + const bool otg_enabled = furi_hal_power_is_otg_enabled(); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + response->which_content = PB_Main_gpio_get_otg_mode_response_tag; + response->content.gpio_get_otg_mode_response.mode = otg_enabled ? PB_Gpio_GpioOtgMode_ON : + PB_Gpio_GpioOtgMode_OFF; + + rpc_send_and_release(session, response); + + free(response); +} + +void rpc_system_gpio_set_otg_mode(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_gpio_set_otg_mode_tag); + + RpcSession* session = context; + + const PB_Gpio_GpioOtgMode mode = request->content.gpio_set_otg_mode.mode; + + if(mode == PB_Gpio_GpioOtgMode_OFF) { + furi_hal_power_disable_otg(); + } else { + furi_hal_power_enable_otg(); + } + + rpc_send_and_release_empty(session, request->command_id, PB_CommandStatus_OK); +} + void* rpc_system_gpio_alloc(RpcSession* session) { furi_assert(session); @@ -212,5 +251,11 @@ void* rpc_system_gpio_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_gpio_set_input_pull; rpc_add_handler(session, PB_Main_gpio_set_input_pull_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_gpio_get_otg_mode; + rpc_add_handler(session, PB_Main_gpio_get_otg_mode_tag, &rpc_handler); + + rpc_handler.message_handler = rpc_system_gpio_set_otg_mode; + rpc_add_handler(session, PB_Main_gpio_set_otg_mode_tag, &rpc_handler); + return NULL; } diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index 89991aa86..aedcf8c94 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include @@ -656,7 +656,7 @@ static void rpc_system_storage_backup_create_process(const PB_Main* request, voi rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_create( + bool backup_ok = int_backup_create( rpc_storage->api, request->content.storage_backup_create_request.archive_path); rpc_send_and_release_empty( @@ -676,7 +676,7 @@ static void rpc_system_storage_backup_restore_process(const PB_Main* request, vo rpc_system_storage_reset_state(rpc_storage, session, true); - bool backup_ok = lfs_backup_unpack( + bool backup_ok = int_backup_unpack( rpc_storage->api, request->content.storage_backup_restore_request.archive_path); rpc_send_and_release_empty( diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 6dbeb0d36..072db1305 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -504,7 +504,7 @@ FS_Error storage_sd_info(Storage* storage, SDInfo* info); */ FS_Error storage_sd_status(Storage* storage); -/******************* Internal LFS Functions *******************/ +/************ Internal Storage Backup/Restore ************/ typedef void (*StorageNameConverter)(FuriString*); diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index a18b28940..441b58da6 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -3,8 +3,9 @@ #include #include -#include #include +#include +#include #include #include #include @@ -267,9 +268,8 @@ static void storage_cli_read_chunks(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else if(storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { uint64_t file_size = storage_file_size(file); @@ -307,9 +307,8 @@ static void storage_cli_write_chunk(Cli* cli, FuriString* path, FuriString* args File* file = storage_file_alloc(api); uint32_t buffer_size; - int parsed_count = sscanf(furi_string_get_cstr(args), "%lu", &buffer_size); - - if(parsed_count != 1) { + if(strint_to_uint32(furi_string_get_cstr(args), NULL, &buffer_size, 10) != + StrintParseNoError) { storage_cli_print_usage(); } else { if(storage_file_open(file, furi_string_get_cstr(path), FSAM_WRITE, FSOM_OPEN_APPEND)) { diff --git a/applications/system/updater/cli/updater_cli.c b/applications/system/updater/cli/updater_cli.c index 0b734c0f4..56a16bd9d 100644 --- a/applications/system/updater/cli/updater_cli.c +++ b/applications/system/updater/cli/updater_cli.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include typedef void (*cmd_handler)(FuriString* args); @@ -35,7 +35,7 @@ static void updater_cli_install(FuriString* manifest_path) { static void updater_cli_backup(FuriString* args) { printf("Backup /int to '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_create(storage, furi_string_get_cstr(args)); + bool success = int_backup_create(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } @@ -43,7 +43,7 @@ static void updater_cli_backup(FuriString* args) { static void updater_cli_restore(FuriString* args) { printf("Restore /int from '%s'\r\n", furi_string_get_cstr(args)); Storage* storage = furi_record_open(RECORD_STORAGE); - bool success = lfs_backup_unpack(storage, furi_string_get_cstr(args)); + bool success = int_backup_unpack(storage, furi_string_get_cstr(args)); furi_record_close(RECORD_STORAGE); printf("Result: %s\r\n", success ? "OK" : "FAIL"); } diff --git a/applications/system/updater/util/update_task.c b/applications/system/updater/util/update_task.c index 8f051ff77..cca488475 100644 --- a/applications/system/updater/util/update_task.c +++ b/applications/system/updater/util/update_task.c @@ -22,8 +22,8 @@ static const char* update_task_stage_descr[] = { [UpdateTaskStageRadioInstall] = "Installing radio FW", [UpdateTaskStageRadioBusy] = "Core 2 busy", [UpdateTaskStageOBValidation] = "Validating opt. bytes", - [UpdateTaskStageLfsBackup] = "Backing up LFS", - [UpdateTaskStageLfsRestore] = "Restoring LFS", + [UpdateTaskStageIntBackup] = "Backing up configuration", + [UpdateTaskStageIntRestore] = "Restoring configuration", [UpdateTaskStageResourcesFileCleanup] = "Cleaning up files", [UpdateTaskStageResourcesDirCleanup] = "Cleaning up directories", [UpdateTaskStageResourcesFileUnpack] = "Extracting resources", @@ -82,7 +82,7 @@ static const struct { }, #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsBackup, + .stage = UpdateTaskStageIntBackup, .percent_min = 0, .percent_max = 100, .descr = "FS R/W error", @@ -193,10 +193,10 @@ static const struct { #endif #ifndef FURI_RAM_EXEC { - .stage = UpdateTaskStageLfsRestore, + .stage = UpdateTaskStageIntRestore, .percent_min = 0, .percent_max = 100, - .descr = "LFS I/O error", + .descr = "SD card I/O error", }, { .stage = UpdateTaskStageResourcesFileCleanup, @@ -245,7 +245,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageProgress] = STAGE_DEF(UpdateTaskStageGroupMisc, 0), [UpdateTaskStageReadManifest] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 45), - [UpdateTaskStageLfsBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), + [UpdateTaskStageIntBackup] = STAGE_DEF(UpdateTaskStageGroupPreUpdate, 5), [UpdateTaskStageRadioImageValidate] = STAGE_DEF(UpdateTaskStageGroupRadio, 15), [UpdateTaskStageRadioErase] = STAGE_DEF(UpdateTaskStageGroupRadio, 25), @@ -259,7 +259,7 @@ static const UpdateTaskStageGroupMap update_task_stage_progress[] = { [UpdateTaskStageFlashWrite] = STAGE_DEF(UpdateTaskStageGroupFirmware, 100), [UpdateTaskStageFlashValidate] = STAGE_DEF(UpdateTaskStageGroupFirmware, 20), - [UpdateTaskStageLfsRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), + [UpdateTaskStageIntRestore] = STAGE_DEF(UpdateTaskStageGroupPostUpdate, 5), [UpdateTaskStageResourcesFileCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 100), [UpdateTaskStageResourcesDirCleanup] = STAGE_DEF(UpdateTaskStageGroupResources, 50), diff --git a/applications/system/updater/util/update_task.h b/applications/system/updater/util/update_task.h index 52bdfdbd2..c346c55fa 100644 --- a/applications/system/updater/util/update_task.h +++ b/applications/system/updater/util/update_task.h @@ -16,7 +16,7 @@ typedef enum { UpdateTaskStageProgress = 0, UpdateTaskStageReadManifest, - UpdateTaskStageLfsBackup, + UpdateTaskStageIntBackup, UpdateTaskStageRadioImageValidate, UpdateTaskStageRadioErase, @@ -30,7 +30,7 @@ typedef enum { UpdateTaskStageFlashWrite, UpdateTaskStageFlashValidate, - UpdateTaskStageLfsRestore, + UpdateTaskStageIntRestore, UpdateTaskStageResourcesFileCleanup, UpdateTaskStageResourcesDirCleanup, UpdateTaskStageResourcesFileUnpack, diff --git a/applications/system/updater/util/update_task_worker_backup.c b/applications/system/updater/util/update_task_worker_backup.c index 8d5039a16..1b5bea25b 100644 --- a/applications/system/updater/util/update_task_worker_backup.c +++ b/applications/system/updater/util/update_task_worker_backup.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -21,14 +21,14 @@ static bool update_task_pre_update(UpdateTask* update_task) { backup_file_path = furi_string_alloc(); path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, backup_file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsBackup, 0); + update_task_set_progress(update_task, UpdateTaskStageIntBackup, 0); /* to avoid bootloops */ furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeNormal); if((success = - lfs_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { + int_backup_create(update_task->storage, furi_string_get_cstr(backup_file_path)))) { furi_hal_rtc_set_boot_mode(FuriHalRtcBootModeUpdate); } @@ -145,12 +145,12 @@ static bool update_task_post_update(UpdateTask* update_task) { do { path_concat( furi_string_get_cstr(update_task->update_path), - LFS_BACKUP_DEFAULT_FILENAME, + INT_BACKUP_DEFAULT_FILENAME, file_path); - update_task_set_progress(update_task, UpdateTaskStageLfsRestore, 0); + update_task_set_progress(update_task, UpdateTaskStageIntRestore, 0); - CHECK_RESULT(lfs_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); + CHECK_RESULT(int_backup_unpack(update_task->storage, furi_string_get_cstr(file_path))); if(update_task->state.groups & UpdateTaskStageGroupResources) { TarUnpackProgress progress = { diff --git a/applications/system/updater/util/update_task_worker_flasher.c b/applications/system/updater/util/update_task_worker_flasher.c index e7e1bbbed..a464815f0 100644 --- a/applications/system/updater/util/update_task_worker_flasher.c +++ b/applications/system/updater/util/update_task_worker_flasher.c @@ -341,7 +341,7 @@ int32_t update_task_worker_flash_writer(void* context) { } furi_hal_rtc_set_boot_mode(FuriHalRtcBootModePostUpdate); - // Format LFS before restoring backup on next boot + // Clean up /int before restoring backup on next boot furi_hal_rtc_set_flag(FuriHalRtcFlagStorageFormatInternal); #ifdef FURI_NDEBUG // Production diff --git a/assets/protobuf b/assets/protobuf index 816de200a..6c7c0d55e 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a +Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7 diff --git a/documentation/ExpansionModules.md b/documentation/ExpansionModules.md index 470564e57..fd9703adc 100644 --- a/documentation/ExpansionModules.md +++ b/documentation/ExpansionModules.md @@ -73,7 +73,7 @@ If the requested baud rate is supported by the host, it SHALL respond with a STA ### Control frame -CONTROL frames are used to control various aspects of the communication. As of now, the sole purpose of CONTROL frames is to start and stop the RPC session. +CONTROL frames are used to control various aspects of the communication and enable/disable various device features. | Header (1 byte) | Contents (1 byte) | Checksum (1 byte) | |-----------------|-------------------|-------------------| @@ -81,10 +81,18 @@ CONTROL frames are used to control various aspects of the communication. As of n The `Command` field SHALL have one of the followind values: -| Command | Meaning | -|---------|-------------------| -| 0x00 | Start RPC session | -| 0x01 | Stop RPC session | +| Command | Meaning | Note | +|---------|--------------------------|:----:| +| 0x00 | Start RPC session | 1 | +| 0x01 | Stop RPC session | 2 | +| 0x02 | Enable OTG (5V) on GPIO | 3 | +| 0x03 | Disable OTG (5V) on GPIO | 3 | + +Notes: + +1. Must only be used while the RPC session NOT active. +2. Must only be used while the RPC session IS active. +3. See 1, otherwise OTG is to be controlled via RPC messages. ### Data frame diff --git a/documentation/OTA.md b/documentation/OTA.md index 0456eab1f..9783a7047 100644 --- a/documentation/OTA.md +++ b/documentation/OTA.md @@ -83,7 +83,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **50** | Package has mismatching HW target | | | | **60** | Missing DFU file | | | | **80** | Missing radio firmware file | -| Backing up LFS | **2** | **0-100** | FS read/write error | +| Backing up configuration| **2** | **0-100** | FS read/write error | | Checking radio FW | **3** | **0-99** | Error reading radio firmware file | | | | **100** | CRC mismatch | | Uninstalling radio FW | **4** | **0** | SHCI Delete command error | @@ -101,7 +101,7 @@ Even if something goes wrong, updater allows you to retry failed operations and | | | **99-100** | Corrupted DFU file | | Writing flash | **10** | **0-100** | Block read/write error | | Validating flash | **11** | **0-100** | Block read/write error | -| Restoring LFS | **12** | **0-100** | FS read/write error | +| Restoring configuration | **12** | **0-100** | FS read/write error | | Updating resources | **13-15** | **0-100** | SD card read/write error | ## Building update packages diff --git a/lib/flipper_format/flipper_format_stream.c b/lib/flipper_format/flipper_format_stream.c index b9d33169c..8a16dbfd9 100644 --- a/lib/flipper_format/flipper_format_stream.c +++ b/lib/flipper_format/flipper_format_stream.c @@ -1,5 +1,6 @@ #include #include +#include #include #include "flipper_format_stream.h" #include "flipper_format_stream_i.h" @@ -396,14 +397,16 @@ bool flipper_format_stream_read_value_line( #endif case FlipperStreamValueInt32: { int32_t* data = _data; - scan_values = sscanf(furi_string_get_cstr(value), "%" PRIi32, &data[i]); + if(strint_to_int32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; + } }; break; case FlipperStreamValueUint32: { uint32_t* data = _data; - // Minus sign is allowed in scanf() for unsigned numbers, resulting in unintentionally huge values with no error reported - if(!furi_string_start_with(value, "-")) { - scan_values = - sscanf(furi_string_get_cstr(value), "%" PRIu32, &data[i]); + if(strint_to_uint32(furi_string_get_cstr(value), NULL, &data[i], 10) == + StrintParseNoError) { + scan_values = 1; } }; break; case FlipperStreamValueHexUint64: { diff --git a/lib/subghz/subghz_file_encoder_worker.c b/lib/subghz/subghz_file_encoder_worker.c index 30654567e..af9376db5 100644 --- a/lib/subghz/subghz_file_encoder_worker.c +++ b/lib/subghz/subghz_file_encoder_worker.c @@ -4,6 +4,7 @@ #include #include #include +#include #define TAG "SubGhzFileEncoderWorker" @@ -45,39 +46,26 @@ void subghz_file_encoder_worker_add_level_duration( } bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { - char* str1; - int32_t temp_ds = 0; - bool res = false; // Line sample: "RAW_Data: -1, 2, -2..." - // Look for a key in the line - str1 = strstr(strStart, "RAW_Data: "); + // Look for the key in the line + char* str = strstr(strStart, "RAW_Data: "); + bool res = false; - if(str1 != NULL) { + if(str) { // Skip key - str1 = strchr(str1, ' '); + str = strchr(str, ' '); - // Check that there is still an element in the line - while(strchr(str1, ' ') != NULL) { - str1 = strchr(str1, ' '); - - // Skip space - str1 += 1; - // - temp_ds = atoi(str1); - if((temp_ds < -1000000) || (temp_ds > 1000000)) { - if(temp_ds > 0) { - subghz_file_encoder_worker_add_level_duration(instance, (int32_t)100); - } else { - subghz_file_encoder_worker_add_level_duration(instance, (int32_t)-100); - } - //FURI_LOG_I("PARSE", "Number overflow - %d", atoi(str1)); - } else { - subghz_file_encoder_worker_add_level_duration(instance, temp_ds); - } + // Parse next element + int32_t duration; + while(strint_to_int32(str, &str, &duration, 10) == StrintParseNoError) { + subghz_file_encoder_worker_add_level_duration(instance, duration); + if(*str == ',') str++; // could also be `\0` } + res = true; } + return res; } diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index b5909b6e9..d582c34c1 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -29,6 +29,7 @@ env.Append( File("stream/file_stream.h"), File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), + File("strint.h"), File("protocols/protocol_dict.h"), File("pretty_format.h"), File("hex.h"), diff --git a/lib/toolbox/args.c b/lib/toolbox/args.c index aa790ad68..914b093ba 100644 --- a/lib/toolbox/args.c +++ b/lib/toolbox/args.c @@ -1,5 +1,7 @@ #include "args.h" #include "hex.h" +#include "strint.h" +#include "m-core.h" size_t args_get_first_word_length(FuriString* args) { size_t ws = furi_string_search_char(args, ' '); @@ -21,7 +23,9 @@ bool args_read_int_and_trim(FuriString* args, int* value) { return false; } - if(sscanf(furi_string_get_cstr(args), "%d", value) == 1) { + int32_t temp; + if(strint_to_int32(furi_string_get_cstr(args), NULL, &temp, 10) == StrintParseNoError) { + *value = temp; furi_string_right(args, cmd_length); furi_string_trim(args); return true; diff --git a/lib/toolbox/strint.c b/lib/toolbox/strint.c new file mode 100644 index 000000000..8c7f36976 --- /dev/null +++ b/lib/toolbox/strint.c @@ -0,0 +1,121 @@ +#include "strint.h" + +#include + +// Splitting out the actual parser helps reduce code size. The manually +// monomorphized `strint_to_*`s are just wrappers around this generic +// implementation. +/** + * @brief Converts a string to a `uint64_t` and an auxillary sign bit, checking + * the bounds of the integer. + * @param [in] str Input string + * @param [out] end Pointer to first character after the number in input string + * @param [out] abs_out Absolute part of result + * @param [out] negative_out Sign part of result (true=negative, false=positive) + * @param [in] base Integer base + * @param [in] max_abs_negative Largest permissible absolute part of result if + * the sign is negative + * @param [in] max_positive Largest permissible absolute part of result if the + * sign is positive + */ +StrintParseError strint_to_uint64_internal( + const char* str, + char** end, + uint64_t* abs_out, + bool* negative_out, + uint8_t base, + uint64_t max_abs_negative, + uint64_t max_positive) { + // skip whitespace + while(((*str >= '\t') && (*str <= '\r')) || *str == ' ') { + str++; + } + + // read sign + bool negative = false; + if(*str == '+' || *str == '-') { + if(*str == '-') negative = true; + str++; + } + if(*str == '+' || *str == '-') return StrintParseSignError; + if(max_abs_negative == 0 && negative) return StrintParseSignError; + + // infer base + // not assigning directly to `base' to permit prefixes with explicit bases + uint8_t inferred_base = 0; + if(strncasecmp(str, "0x", 2) == 0) { + inferred_base = 16; + str += 2; + } else if(strncasecmp(str, "0b", 2) == 0) { + inferred_base = 2; + str += 2; + } else if(*str == '0') { + inferred_base = 8; + str++; + } else { + inferred_base = 10; + } + if(base == 0) base = inferred_base; + + // read digits + uint64_t limit = negative ? max_abs_negative : max_positive; + uint64_t mul_limit = limit / base; + uint64_t result = 0; + int read_total = 0; + while(*str != 0) { + int digit_value; + if(*str >= '0' && *str <= '9') { + digit_value = *str - '0'; + } else if(*str >= 'A' && *str <= 'Z') { + digit_value = *str - 'A' + 10; + } else if(*str >= 'a' && *str <= 'z') { + digit_value = *str - 'a' + 10; + } else { + break; + } + + if(digit_value >= base) { + break; + } + + if(result > mul_limit) return StrintParseOverflowError; + result *= base; + if(result > limit - digit_value) return StrintParseOverflowError; + result += digit_value; + + read_total++; + str++; + } + + if(read_total == 0) { + if(inferred_base == 8) { + // there's just a single zero + result = 0; + } else { + return StrintParseAbsentError; + } + } + + if(abs_out) *abs_out = result; + if(negative_out) *negative_out = negative; + if(end) *end = (char*)str; // rabbit hole: https://c-faq.com/ansi/constmismatch.html + return StrintParseNoError; +} + +#define STRINT_MONO(name, ret_type, neg_abs_limit, pos_limit) \ + StrintParseError name(const char* str, char** end, ret_type* out, uint8_t base) { \ + uint64_t absolute; \ + bool negative; \ + StrintParseError err = strint_to_uint64_internal( \ + str, end, &absolute, &negative, base, (neg_abs_limit), (pos_limit)); \ + if(err) return err; \ + if(out) *out = (negative ? (-(ret_type)absolute) : ((ret_type)absolute)); \ + return StrintParseNoError; \ + } + +STRINT_MONO(strint_to_uint64, uint64_t, 0, UINT64_MAX) +STRINT_MONO(strint_to_int64, int64_t, (uint64_t)INT64_MAX + 1, INT64_MAX) +STRINT_MONO(strint_to_uint32, uint32_t, 0, UINT32_MAX) +STRINT_MONO(strint_to_int32, int32_t, (uint64_t)INT32_MAX + 1, INT32_MAX) +STRINT_MONO(strint_to_uint16, uint16_t, 0, UINT16_MAX) +STRINT_MONO(strint_to_int16, int16_t, (uint64_t)INT16_MAX + 1, INT16_MAX) diff --git a/lib/toolbox/strint.h b/lib/toolbox/strint.h new file mode 100644 index 000000000..c27cdcb4e --- /dev/null +++ b/lib/toolbox/strint.h @@ -0,0 +1,70 @@ +/** + * @file strint.h + * Performs conversions between strings and integers. + */ +#pragma once + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** String to integer conversion error */ +typedef enum { + StrintParseNoError, //!< Conversion performed successfully + StrintParseSignError, //!< Multiple leading `+` or `-` characters, or leading `-` character if the type is unsigned + StrintParseAbsentError, //!< No valid digits after the leading whitespace, sign and prefix + StrintParseOverflowError, //!< Result does not fit in the requested type +} StrintParseError; + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint64(const char* str, char** end, uint64_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int64(const char* str, char** end, int64_t* out, uint8_t base); + +/** Converts a string to a `uint32_t` + * + * @param[in] str Input string + * @param[out] end Pointer to first character after the number in input string + * @param[out] out Parse result + * @param[in] base Integer base + * + * @return Parse error + * + * Parses the number in the input string. The number may be surrounded by + * whitespace characters to the left and any non-digit characters to the right. + * What's considered a digit is determined by the input base in the following + * order: `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`. The number may be prefixed + * with either a `+` or a `-` to indicate its sign. The pointer to the first + * character after the leading whitespace, allowed prefixes and digits is + * assigned to `end`. + * + * If the input base is 0, the base is inferred from the leading characters of + * the number: + * - If it starts with `0x`, it's read in base 16; + * - If it starts with a `0`, it's read in base 8; + * - If it starts with `0b`, it's read in base 2. + * - Otherwise, it's read in base 10. + * + * For a description of the return codes, see `StrintParseError`. If the return + * code is something other than `StrintParseNoError`, the values at `end` and + * `out` are unaltered. + */ +StrintParseError strint_to_uint32(const char* str, char** end, uint32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int32(const char* str, char** end, int32_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_uint16(const char* str, char** end, uint16_t* out, uint8_t base); + +/** See `strint_to_uint32` */ +StrintParseError strint_to_int16(const char* str, char** end, int16_t* out, uint8_t base); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/lfs_backup.c b/lib/update_util/int_backup.c similarity index 77% rename from lib/update_util/lfs_backup.c rename to lib/update_util/int_backup.c index 7786524ef..a904db247 100644 --- a/lib/update_util/lfs_backup.c +++ b/lib/update_util/int_backup.c @@ -1,4 +1,4 @@ -#include "lfs_backup.h" +#include "int_backup.h" #include @@ -9,7 +9,7 @@ #include #include -#define LFS_BACKUP_DEFAULT_LOCATION EXT_PATH(LFS_BACKUP_DEFAULT_FILENAME) +#define INT_BACKUP_DEFAULT_LOCATION EXT_PATH(INT_BACKUP_DEFAULT_FILENAME) static void backup_name_converter(FuriString* filename) { if(furi_string_empty(filename) || (furi_string_get_char(filename, 0) == '.')) { @@ -34,18 +34,18 @@ static void backup_name_converter(FuriString* filename) { } } -bool lfs_backup_create(Storage* storage, const char* destination) { +bool int_backup_create(Storage* storage, const char* destination) { const char* final_destination = - destination && strlen(destination) ? destination : LFS_BACKUP_DEFAULT_LOCATION; + destination && strlen(destination) ? destination : INT_BACKUP_DEFAULT_LOCATION; return storage_int_backup(storage, final_destination) == FSE_OK; } -bool lfs_backup_exists(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_exists(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_common_stat(storage, final_source, NULL) == FSE_OK; } -bool lfs_backup_unpack(Storage* storage, const char* source) { - const char* final_source = source && strlen(source) ? source : LFS_BACKUP_DEFAULT_LOCATION; +bool int_backup_unpack(Storage* storage, const char* source) { + const char* final_source = source && strlen(source) ? source : INT_BACKUP_DEFAULT_LOCATION; return storage_int_restore(storage, final_source, backup_name_converter) == FSE_OK; } diff --git a/lib/update_util/int_backup.h b/lib/update_util/int_backup.h new file mode 100644 index 000000000..168efda50 --- /dev/null +++ b/lib/update_util/int_backup.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +#define INT_BACKUP_DEFAULT_FILENAME "backup.tar" + +#ifdef __cplusplus +extern "C" { +#endif + +bool int_backup_create(Storage* storage, const char* destination); +bool int_backup_exists(Storage* storage, const char* source); +bool int_backup_unpack(Storage* storage, const char* source); + +#ifdef __cplusplus +} +#endif diff --git a/lib/update_util/lfs_backup.h b/lib/update_util/lfs_backup.h deleted file mode 100644 index 5a7738c86..000000000 --- a/lib/update_util/lfs_backup.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include -#include - -#define LFS_BACKUP_DEFAULT_FILENAME "backup.tar" - -#ifdef __cplusplus -extern "C" { -#endif - -bool lfs_backup_create(Storage* storage, const char* destination); -bool lfs_backup_exists(Storage* storage, const char* source); -bool lfs_backup_unpack(Storage* storage, const char* source); - -#ifdef __cplusplus -} -#endif diff --git a/lib/update_util/resources/manifest.c b/lib/update_util/resources/manifest.c index 580a76d45..92d84a779 100644 --- a/lib/update_util/resources/manifest.c +++ b/lib/update_util/resources/manifest.c @@ -1,6 +1,7 @@ #include "manifest.h" #include +#include #include struct ResourceManifestReader { @@ -97,7 +98,12 @@ ResourceManifestEntry* resource_manifest_reader_next(ResourceManifestReader* res furi_string_right( resource_manifest->linebuf, sizeof(resource_manifest->entry.hash) * 2 + 1); - resource_manifest->entry.size = atoi(furi_string_get_cstr(resource_manifest->linebuf)); + if(strint_to_uint32( + furi_string_get_cstr(resource_manifest->linebuf), + NULL, + &resource_manifest->entry.size, + 10) != StrintParseNoError) + break; /* Remove size */ size_t offs = furi_string_search_char(resource_manifest->linebuf, ':'); diff --git a/scripts/debug/flipperapps.py b/scripts/debug/flipperapps.py index 6dba89a56..81aa43c34 100644 --- a/scripts/debug/flipperapps.py +++ b/scripts/debug/flipperapps.py @@ -124,7 +124,7 @@ class SetFapDebugElfRoot(gdb.Command): print(f"Set '{arg}' as debug info lookup path for Flipper external apps") helper.attach_to_fw() gdb.events.stop.connect(helper.handle_stop) - gdb.events.exited.connect(helper.handle_exit) + gdb.events.gdb_exiting.connect(helper.handle_exit) except gdb.error as e: print(f"Support for Flipper external apps debug is not available: {e}") diff --git a/scripts/fbt_tools/pvsstudio.py b/scripts/fbt_tools/pvsstudio.py index 290531321..1a55278dc 100644 --- a/scripts/fbt_tools/pvsstudio.py +++ b/scripts/fbt_tools/pvsstudio.py @@ -47,6 +47,7 @@ def generate(env): PVSOPTIONS=[ "@.pvsoptions", "-j${PVSNCORES}", + "--disableLicenseExpirationCheck", # "--incremental", # kinda broken on PVS side ], PVSCONVOPTIONS=[ diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 57cbd1d62..b603813a7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, Header,+,applications/services/cli/cli.h,, @@ -13,13 +13,13 @@ Header,+,applications/services/gui/icon_i.h,, Header,+,applications/services/gui/modules/button_menu.h,, Header,+,applications/services/gui/modules/button_panel.h,, Header,+,applications/services/gui/modules/byte_input.h,, -Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/dialog_ex.h,, Header,+,applications/services/gui/modules/empty_screen.h,, Header,+,applications/services/gui/modules/file_browser.h,, Header,+,applications/services/gui/modules/file_browser_worker.h,, Header,+,applications/services/gui/modules/loading.h,, Header,+,applications/services/gui/modules/menu.h,, +Header,+,applications/services/gui/modules/number_input.h,, Header,+,applications/services/gui/modules/popup.h,, Header,+,applications/services/gui/modules/submenu.h,, Header,+,applications/services/gui/modules/text_box.h,, @@ -170,6 +170,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -723,11 +724,6 @@ Function,+,byte_input_free,void,ByteInput* Function,+,byte_input_get_view,View*,ByteInput* Function,+,byte_input_set_header_text,void,"ByteInput*, const char*" Function,+,byte_input_set_result_callback,void,"ByteInput*, ByteInputCallback, ByteChangedCallback, void*, uint8_t*, uint8_t" -Function,+,number_input_alloc,NumberInput*, -Function,+,number_input_free,void,NumberInput* -Function,+,number_input_get_view,View*,NumberInput* -Function,+,number_input_set_header_text,void,"NumberInput*, const char*" -Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,bzero,void,"void*, size_t" Function,+,calloc,void*,"size_t, size_t" Function,+,canvas_clear,void,Canvas* @@ -883,8 +879,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" @@ -2197,6 +2195,11 @@ Function,+,notification_internal_message_block,void,"NotificationApp*, const Not Function,+,notification_message,void,"NotificationApp*, const NotificationSequence*" Function,+,notification_message_block,void,"NotificationApp*, const NotificationSequence*" Function,-,nrand48,long,unsigned short[3] +Function,+,number_input_alloc,NumberInput*, +Function,+,number_input_free,void,NumberInput* +Function,+,number_input_get_view,View*,NumberInput* +Function,+,number_input_set_header_text,void,"NumberInput*, const char*" +Function,+,number_input_set_result_callback,void,"NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t" Function,-,on_exit,int,"void (*)(int, void*), void*" Function,+,onewire_host_alloc,OneWireHost*,const GpioPin* Function,+,onewire_host_free,void,OneWireHost* @@ -2587,6 +2590,12 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 0680de4ef..7176494d9 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,72.1,, +Version,+,72.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -245,6 +245,7 @@ Header,+,lib/toolbox/stream/buffered_file_stream.h,, Header,+,lib/toolbox/stream/file_stream.h,, Header,+,lib/toolbox/stream/stream.h,, Header,+,lib/toolbox/stream/string_stream.h,, +Header,+,lib/toolbox/strint.h,, Header,+,lib/toolbox/tar/tar_archive.h,, Header,+,lib/toolbox/value_index.h,, Header,+,lib/toolbox/varint.h,, @@ -974,8 +975,10 @@ Function,+,elements_bold_rounded_frame,void,"Canvas*, int32_t, int32_t, size_t, Function,+,elements_bubble,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_bubble_str,void,"Canvas*, int32_t, int32_t, const char*, Align, Align" Function,+,elements_button_center,void,"Canvas*, const char*" +Function,+,elements_button_down,void,"Canvas*, const char*" Function,+,elements_button_left,void,"Canvas*, const char*" Function,+,elements_button_right,void,"Canvas*, const char*" +Function,+,elements_button_up,void,"Canvas*, const char*" Function,+,elements_frame,void,"Canvas*, int32_t, int32_t, size_t, size_t" Function,+,elements_multiline_text,void,"Canvas*, int32_t, int32_t, const char*" Function,+,elements_multiline_text_aligned,void,"Canvas*, int32_t, int32_t, Align, Align, const char*" @@ -3311,6 +3314,12 @@ Function,-,strerror,char*,int Function,-,strerror_l,char*,"int, locale_t" Function,-,strerror_r,char*,"int, char*, size_t" Function,+,string_stream_alloc,Stream*, +Function,+,strint_to_int16,StrintParseError,"const char*, char**, int16_t*, uint8_t" +Function,+,strint_to_int32,StrintParseError,"const char*, char**, int32_t*, uint8_t" +Function,+,strint_to_int64,StrintParseError,"const char*, char**, int64_t*, uint8_t" +Function,+,strint_to_uint16,StrintParseError,"const char*, char**, uint16_t*, uint8_t" +Function,+,strint_to_uint32,StrintParseError,"const char*, char**, uint32_t*, uint8_t" +Function,+,strint_to_uint64,StrintParseError,"const char*, char**, uint64_t*, uint8_t" Function,-,strlcat,size_t,"char*, const char*, size_t" Function,+,strlcpy,size_t,"char*, const char*, size_t" Function,+,strlen,size_t,const char* diff --git a/targets/f7/furi_hal/furi_hal_rtc.h b/targets/f7/furi_hal/furi_hal_rtc.h index 030b464cf..c5eab12ec 100644 --- a/targets/f7/furi_hal/furi_hal_rtc.h +++ b/targets/f7/furi_hal/furi_hal_rtc.h @@ -9,6 +9,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -44,7 +45,7 @@ typedef enum { FuriHalRtcRegisterHeader, /**< RTC structure header */ FuriHalRtcRegisterSystem, /**< Various system bits */ FuriHalRtcRegisterVersion, /**< Pointer to Version */ - FuriHalRtcRegisterLfsFingerprint, /**< LFS geometry fingerprint */ + FuriHalRtcRegisterLfsFingerprint FURI_DEPRECATED, /**< LFS geometry fingerprint */ FuriHalRtcRegisterFaultData, /**< Pointer to last fault message */ FuriHalRtcRegisterPinFails, /**< Failed PINs count */ /* Index of FS directory entry corresponding to FW update to be applied */