Merge remote-tracking branch 'OFW/dev' into dev

This commit is contained in:
MX 2024-09-05 22:10:41 +03:00
commit 56530ee61c
No known key found for this signature in database
GPG Key ID: 7CCC66B7DBDD1C83
52 changed files with 927 additions and 276 deletions

View File

@ -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 = [

View File

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

View File

@ -6,11 +6,17 @@
#include <furi.h>
#include <furi_hal.h>
#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);
}

View File

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

View File

@ -1,49 +1,48 @@
/* Implements rudimentary iso7816-3 support for APDU (T=0) */
#include <stdint.h>
#include <string.h>
#include <furi.h>
#include <furi_hal.h>
#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;

View File

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

View File

@ -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 <furi.h>
@ -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);

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

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

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

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.h>
#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
#include <lib/toolbox/strint.h>
#include <applications/services/locale/locale.h>
#include <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>
@ -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", "<Frequency: in Hz>", 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", "<Frequency: in Hz>", 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> <Frequency: in Hz> <Te us> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<Frequency: in Hz> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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", "<Frequency: in Hz>", 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:",
"<file_name: path_file> <Repeat count> <Device: 0 - CC1101_INT, 1 - CC1101_EXT>",
@ -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",
"<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"
@ -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", "<r|g|b|bl> <0-255>", furi_string_get_cstr(args));
return;
}

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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) {

View File

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

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

@ -2,6 +2,7 @@
#include "rpc_i.h"
#include "gpio.pb.h"
#include <furi_hal_gpio.h>
#include <furi_hal_power.h>
#include <furi_hal_resources.h>
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;
}

View File

@ -7,7 +7,7 @@
#include <storage/storage.h>
#include <lib/toolbox/md5_calc.h>
#include <lib/toolbox/path.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <toolbox/tar/tar_archive.h>
#include <pb_decode.h>
@ -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(

View File

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

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

@ -8,7 +8,7 @@
#include <toolbox/tar/tar_archive.h>
#include <toolbox/args.h>
#include <update_util/update_manifest.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <update_util/update_operation.h>
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");
}

View File

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

View File

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

View File

@ -7,7 +7,7 @@
#include <desktop/helpers/slideshow_filename.h>
#include <toolbox/path.h>
#include <update_util/dfu_file.h>
#include <update_util/lfs_backup.h>
#include <update_util/int_backup.h>
#include <update_util/update_operation.h>
#include <update_util/resources/manifest.h>
#include <toolbox/tar/tar_archive.h>
@ -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 = {

View File

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

@ -1 +1 @@
Subproject commit 816de200a4a43efc25c5b92d6a57fc982d7e988a
Subproject commit 6c7c0d55e82cb89223cf4890a540af4cff837fa7

View File

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

View File

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

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,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;
}

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,4 +1,4 @@
#include "lfs_backup.h"
#include "int_backup.h"
#include <toolbox/tar/tar_archive.h>
@ -9,7 +9,7 @@
#include <desktop/desktop_settings_filename.h>
#include <notification/notification_settings_filename.h>
#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;
}

View File

@ -0,0 +1,18 @@
#pragma once
#include <stdbool.h>
#include <storage/storage.h>
#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

View File

@ -1,18 +0,0 @@
#pragma once
#include <stdbool.h>
#include <storage/storage.h>
#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

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

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

View File

@ -47,6 +47,7 @@ def generate(env):
PVSOPTIONS=[
"@.pvsoptions",
"-j${PVSNCORES}",
"--disableLicenseExpirationCheck",
# "--incremental", # kinda broken on PVS side
],
PVSCONVOPTIONS=[

View File

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

1 entry status name type params
2 Version + 72.1 72.2
3 Header + applications/services/bt/bt_service/bt.h
4 Header + applications/services/bt/bt_service/bt_keys_storage.h
5 Header + applications/services/cli/cli.h
13 Header + applications/services/gui/modules/button_menu.h
14 Header + applications/services/gui/modules/button_panel.h
15 Header + applications/services/gui/modules/byte_input.h
Header + applications/services/gui/modules/number_input.h
16 Header + applications/services/gui/modules/dialog_ex.h
17 Header + applications/services/gui/modules/empty_screen.h
18 Header + applications/services/gui/modules/file_browser.h
19 Header + applications/services/gui/modules/file_browser_worker.h
20 Header + applications/services/gui/modules/loading.h
21 Header + applications/services/gui/modules/menu.h
22 Header + applications/services/gui/modules/number_input.h
23 Header + applications/services/gui/modules/popup.h
24 Header + applications/services/gui/modules/submenu.h
25 Header + applications/services/gui/modules/text_box.h
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
724 Function + byte_input_get_view View* ByteInput*
725 Function + byte_input_set_header_text void ByteInput*, const char*
726 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
727 Function - bzero void void*, size_t
728 Function + calloc void* size_t, size_t
729 Function + canvas_clear void Canvas*
879 Function + elements_bubble void Canvas*, int32_t, int32_t, size_t, size_t
880 Function + elements_bubble_str void Canvas*, int32_t, int32_t, const char*, Align, Align
881 Function + elements_button_center void Canvas*, const char*
882 Function + elements_button_down void Canvas*, const char*
883 Function + elements_button_left void Canvas*, const char*
884 Function + elements_button_right void Canvas*, const char*
885 Function + elements_button_up void Canvas*, const char*
886 Function + elements_frame void Canvas*, int32_t, int32_t, size_t, size_t
887 Function + elements_multiline_text void Canvas*, int32_t, int32_t, const char*
888 Function + elements_multiline_text_aligned void Canvas*, int32_t, int32_t, Align, Align, const char*
2195 Function + notification_message void NotificationApp*, const NotificationSequence*
2196 Function + notification_message_block void NotificationApp*, const NotificationSequence*
2197 Function - nrand48 long unsigned short[3]
2198 Function + number_input_alloc NumberInput*
2199 Function + number_input_free void NumberInput*
2200 Function + number_input_get_view View* NumberInput*
2201 Function + number_input_set_header_text void NumberInput*, const char*
2202 Function + number_input_set_result_callback void NumberInput*, NumberInputCallback, void*, int32_t, int32_t, int32_t
2203 Function - on_exit int void (*)(int, void*), void*
2204 Function + onewire_host_alloc OneWireHost* const GpioPin*
2205 Function + onewire_host_free void OneWireHost*
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

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

1 entry status name type params
2 Version + 72.1 72.2
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/bt/bt_service/bt.h
5 Header + applications/services/bt/bt_service/bt_keys_storage.h
245 Header + lib/toolbox/stream/file_stream.h
246 Header + lib/toolbox/stream/stream.h
247 Header + lib/toolbox/stream/string_stream.h
248 Header + lib/toolbox/strint.h
249 Header + lib/toolbox/tar/tar_archive.h
250 Header + lib/toolbox/value_index.h
251 Header + lib/toolbox/varint.h
975 Function + elements_bubble void Canvas*, int32_t, int32_t, size_t, size_t
976 Function + elements_bubble_str void Canvas*, int32_t, int32_t, const char*, Align, Align
977 Function + elements_button_center void Canvas*, const char*
978 Function + elements_button_down void Canvas*, const char*
979 Function + elements_button_left void Canvas*, const char*
980 Function + elements_button_right void Canvas*, const char*
981 Function + elements_button_up void Canvas*, const char*
982 Function + elements_frame void Canvas*, int32_t, int32_t, size_t, size_t
983 Function + elements_multiline_text void Canvas*, int32_t, int32_t, const char*
984 Function + elements_multiline_text_aligned void Canvas*, int32_t, int32_t, Align, Align, const char*
3314 Function - strerror_l char* int, locale_t
3315 Function - strerror_r char* int, char*, size_t
3316 Function + string_stream_alloc Stream*
3317 Function + strint_to_int16 StrintParseError const char*, char**, int16_t*, uint8_t
3318 Function + strint_to_int32 StrintParseError const char*, char**, int32_t*, uint8_t
3319 Function + strint_to_int64 StrintParseError const char*, char**, int64_t*, uint8_t
3320 Function + strint_to_uint16 StrintParseError const char*, char**, uint16_t*, uint8_t
3321 Function + strint_to_uint32 StrintParseError const char*, char**, uint32_t*, uint8_t
3322 Function + strint_to_uint64 StrintParseError const char*, char**, uint64_t*, uint8_t
3323 Function - strlcat size_t char*, const char*, size_t
3324 Function + strlcpy size_t char*, const char*, size_t
3325 Function + strlen size_t const char*

View File

@ -9,6 +9,7 @@
#include <stdbool.h>
#include <datetime/datetime.h>
#include <core/common_defines.h>
#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 */