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 be807168a..4298dc33d 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 @@ -320,7 +322,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/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 2ee66955c..ccc3caa81 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 169f0c63d..00246a90f 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 1b61c26e7..a9be0a1f9 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 4f5c4cb62..6375f2eee 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 @@ -71,9 +73,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; } @@ -115,9 +116,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; } @@ -181,23 +181,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> ", @@ -314,10 +305,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", " ", @@ -401,9 +393,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; } @@ -622,9 +613,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:", " ", @@ -936,10 +929,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 b4eeebbe6..c3539813b 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" @@ -361,9 +362,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/gui/modules/number_input.c b/applications/services/gui/modules/number_input.c index 777e55747..5a5e56c1c 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/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/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 1ccde73a4..60737250b 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,27 +46,26 @@ void subghz_file_encoder_worker_add_level_duration( } bool subghz_file_encoder_worker_data_parse(SubGhzFileEncoderWorker* instance, const char* strStart) { - char* str1; - 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; - subghz_file_encoder_worker_add_level_duration(instance, atoi(str1)); + // 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 11e01a8c9..03b8999c4 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/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/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 2c1a56565..b603813a7 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -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,, @@ -2589,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 e5292d936..1d5b98fe3 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -242,6 +242,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,, @@ -3266,6 +3267,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*