unleashed-firmware/lib/toolbox/strint.c
porta c9791a280a
[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>
2024-09-05 18:02:42 +01:00

121 lines
4.2 KiB
C

#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)