[FL-3884] Proper integer parsing (#3839)

* feat: strint_to_uint32 and tests
* fix: permit explicit bases and prefixes
* feat: strint_to_{int32,uint16,int16}
* feat: strint_to_u?int64
* refactor: replace strtol, strtoul, sscanf with strint_to_*
* fix: api symbols
* docs: document parameter `end` of strint_to_uint_32
* style: apply changes requested by hedger
* refactor: fix pvs-studio diagnostic
* style: apply changes requested by CookiePLMonster
* fix: unused var
* fix: pointer type
* refactor: convert atoi to strint_to_*
* fix: strint_to_uint8 doesn't actually exist ._ .
* fix: memory leak
* style: address review comments
* Toolbox: couple small comments in the code and doxygen comment update. SubGhz, Loader: fix strint usage.
* Loader: fix incorrect cast

Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
porta 2024-09-05 20:02:42 +03:00 committed by GitHub
parent 6a48dd28f5
commit c9791a280a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 479 additions and 90 deletions

View File

@ -1,5 +1,7 @@
#include "../rpc_debug_app.h"
#include <lib/toolbox/strint.h>
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);

View File

@ -6,6 +6,8 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>
#include <lib/toolbox/strint.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
@ -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;
}

View File

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

View File

@ -0,0 +1,142 @@
#include <furi.h>
#include <furi_hal.h>
#include "../test.h" // IWYU pragma: keep
#include <toolbox/strint.h>
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)

View File

@ -3,6 +3,7 @@
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <storage/storage.h>
#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;
}

View File

@ -5,6 +5,7 @@
#include <furi_hal_infrared.h>
#include <flipper_format.h>
#include <toolbox/args.h>
#include <toolbox/strint.h>
#include <m-dict.h>
#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;

View File

@ -3,6 +3,7 @@
#include <flipper_application/flipper_application.h>
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
#include <lib/toolbox/strint.h>
#include <applications/services/locale/locale.h>
#include <datetime/datetime.h>
@ -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");

View File

@ -3,18 +3,20 @@
#include <furi.h>
#include <furi_hal.h>
#include <lib/toolbox/args.h>
#include <lib/subghz/subghz_keystore.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/subghz_keystore.h>
#include <lib/subghz/receiver.h>
#include <lib/subghz/transmitter.h>
#include <lib/subghz/subghz_file_encoder_worker.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include <lib/subghz/devices/devices.h>
#include <lib/subghz/devices/cc1101_configs.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include "helpers/subghz_chat.h"
#include <notification/notification_messages.h>
@ -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", "<Frequency: in Hz>", 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", "<Frequency: in Hz>", 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> <Frequency: in Hz> <Te us> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<Frequency: in Hz> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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", "<Frequency: in Hz>", 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:",
"<file_name: path_file> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<Frequency: in Hz> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",

View File

@ -9,6 +9,7 @@
#include <notification/notification_messages.h>
#include <loader/loader.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
// 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", "<r|g|b|bl> <0-255>", furi_string_get_cstr(args));
return;
}

View File

@ -3,6 +3,7 @@
#include <gui/elements.h>
#include <furi.h>
#include <assets_icons.h>
#include <lib/toolbox/strint.h>
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;
}

View File

@ -4,6 +4,7 @@
#include <cli/cli.h>
#include <applications.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/strint.h>
#include <notification/notification_messages.h>
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");
}
}

View File

@ -3,8 +3,9 @@
#include <cli/cli.h>
#include <lib/toolbox/args.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/dir_walk.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/strint.h>
#include <lib/toolbox/tar/tar_archive.h>
#include <storage/storage.h>
#include <storage/storage_sd_api.h>
@ -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)) {

View File

@ -1,5 +1,6 @@
#include <inttypes.h>
#include <toolbox/hex.h>
#include <toolbox/strint.h>
#include <core/check.h>
#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: {

View File

@ -4,6 +4,7 @@
#include <flipper_format/flipper_format.h>
#include <flipper_format/flipper_format_i.h>
#include <lib/subghz/devices/devices.h>
#include <lib/toolbox/strint.h>
#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;
}

View File

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

View File

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

121
lib/toolbox/strint.c Normal file
View File

@ -0,0 +1,121 @@
#include "strint.h"
#include <string.h>
// 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)

70
lib/toolbox/strint.h Normal file
View File

@ -0,0 +1,70 @@
/**
* @file strint.h
* Performs conversions between strings and integers.
*/
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#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

View File

@ -1,6 +1,7 @@
#include "manifest.h"
#include <toolbox/stream/buffered_file_stream.h>
#include <toolbox/strint.h>
#include <toolbox/hex.h>
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, ':');

View File

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

1 entry status name type params
170 Header + lib/toolbox/stream/file_stream.h
171 Header + lib/toolbox/stream/stream.h
172 Header + lib/toolbox/stream/string_stream.h
173 Header + lib/toolbox/strint.h
174 Header + lib/toolbox/tar/tar_archive.h
175 Header + lib/toolbox/value_index.h
176 Header + lib/toolbox/varint.h
2590 Function - strerror_l char* int, locale_t
2591 Function - strerror_r char* int, char*, size_t
2592 Function + string_stream_alloc Stream*
2593 Function + strint_to_int16 StrintParseError const char*, char**, int16_t*, uint8_t
2594 Function + strint_to_int32 StrintParseError const char*, char**, int32_t*, uint8_t
2595 Function + strint_to_int64 StrintParseError const char*, char**, int64_t*, uint8_t
2596 Function + strint_to_uint16 StrintParseError const char*, char**, uint16_t*, uint8_t
2597 Function + strint_to_uint32 StrintParseError const char*, char**, uint32_t*, uint8_t
2598 Function + strint_to_uint64 StrintParseError const char*, char**, uint64_t*, uint8_t
2599 Function - strlcat size_t char*, const char*, size_t
2600 Function + strlcpy size_t char*, const char*, size_t
2601 Function + strlen size_t const char*

View File

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

1 entry status name type params
242 Header + lib/toolbox/stream/file_stream.h
243 Header + lib/toolbox/stream/stream.h
244 Header + lib/toolbox/stream/string_stream.h
245 Header + lib/toolbox/strint.h
246 Header + lib/toolbox/tar/tar_archive.h
247 Header + lib/toolbox/value_index.h
248 Header + lib/toolbox/varint.h
3267 Function - strerror_l char* int, locale_t
3268 Function - strerror_r char* int, char*, size_t
3269 Function + string_stream_alloc Stream*
3270 Function + strint_to_int16 StrintParseError const char*, char**, int16_t*, uint8_t
3271 Function + strint_to_int32 StrintParseError const char*, char**, int32_t*, uint8_t
3272 Function + strint_to_int64 StrintParseError const char*, char**, int64_t*, uint8_t
3273 Function + strint_to_uint16 StrintParseError const char*, char**, uint16_t*, uint8_t
3274 Function + strint_to_uint32 StrintParseError const char*, char**, uint32_t*, uint8_t
3275 Function + strint_to_uint64 StrintParseError const char*, char**, uint64_t*, uint8_t
3276 Function - strlcat size_t char*, const char*, size_t
3277 Function + strlcpy size_t char*, const char*, size_t
3278 Function + strlen size_t const char*