diff --git a/README.md b/README.md index 3e630d0258e..43d1fab8014 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,8 @@ Latest Updates: - TOFIX - FAP Loader disabled so the FW compiles. Compiling requires api_symbols.csv manipulation and some FAPs are not working, we are aware. -- Latest OFW updates: [#1524](https://github.com/flipperdevices/flipperzero-firmware/pull/1524), [#1492](https://github.com/flipperdevices/flipperzero-firmware/pull/1492), [#1496](https://github.com/flipperdevices/flipperzero-firmware/pull/1496), [#1520](https://github.com/flipperdevices/flipperzero-firmware/pull/1520), [#1523](https://github.com/flipperdevices/flipperzero-firmware/pull/1523), [#1513](https://github.com/flipperdevices/flipperzero-firmware/pull/1513), [#1462](https://github.com/flipperdevices/flipperzero-firmware/pull/1462) & [#1528](https://github.com/flipperdevices/flipperzero-firmware/pull/1528) +- Latest OFW updates: [#1524](https://github.com/flipperdevices/flipperzero-firmware/pull/1524), [#1492](https://github.com/flipperdevices/flipperzero-firmware/pull/1492), [#1496](https://github.com/flipperdevices/flipperzero-firmware/pull/1496), [#1520](https://github.com/flipperdevices/flipperzero-firmware/pull/1520), [#1523](https://github.com/flipperdevices/flipperzero-firmware/pull/1523), [#1513](https://github.com/flipperdevices/flipperzero-firmware/pull/1513), [#1462](https://github.com/flipperdevices/flipperzero-firmware/pull/1462), [#1528](https://github.com/flipperdevices/flipperzero-firmware/pull/1528) & [Make printf great again #1438 (By DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/pull/1438) - Excluded [App RPC Bug Fixes and redesign #1491](https://github.com/flipperdevices/flipperzero-firmware/pull/1491) since it breaks compile -- Excluded [Make printf great again #1438 (By DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/pull/1438) @@ -170,6 +169,5 @@ $ ./fbt plugin_dist FIRMWARE_APP_SET=ext_apps ## Conflicting PRs Not Merged: - [Fixed building for users with space in windows username #1437 (By SzymonLisowiec)](https://github.com/flipperdevices/flipperzero-firmware/pull/1437) - [App RPC Bug Fixes and redesign #1491 (By skotopes)](https://github.com/flipperdevices/flipperzero-firmware/pull/1491) -- [Make printf great again #1438 (By DrZlo13)](https://github.com/flipperdevices/flipperzero-firmware/pull/1438)

This software is for experimental purposes only and is not meant for any illegal activity/purposes. We do not condone illegal activity and strongly encourage keeping transmissions to legal/valid uses allowed by law.

diff --git a/applications/arkanoid/arkanoid_game.c b/applications/arkanoid/arkanoid_game.c index 0affadc5adf..b9c5b78cebd 100644 --- a/applications/arkanoid/arkanoid_game.c +++ b/applications/arkanoid/arkanoid_game.c @@ -115,7 +115,7 @@ void move_ball(Canvas* canvas) { released = false; lives--; - sprintf(text, "LIVES:%u", lives); + snprintf(text, sizeof(text), "LIVES:%u", lives); canvas_draw_str(canvas, 0, 90, text); // arduboy.tunes.tone(175, 250); @@ -167,7 +167,7 @@ void move_ball(Canvas* canvas) { leftBall <= rightBrick && rightBall >= leftBrick) { // Draw score score += (level * 10); - sprintf(text, "SCORE:%u", score); + snprintf(text, sizeof(text), "SCORE:%u", score); canvas_draw_str(canvas, 80, 90, text); brickCount++; diff --git a/applications/cli/cli.c b/applications/cli/cli.c index 4d9b8a5f067..e554ac898e3 100644 --- a/applications/cli/cli.c +++ b/applications/cli/cli.c @@ -439,9 +439,9 @@ void cli_session_open(Cli* cli, void* session) { cli->session = session; if(cli->session != NULL) { cli->session->init(); - furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout); } else { - furi_stdglue_set_thread_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL); } furi_semaphore_release(cli->idle_sem); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); @@ -455,7 +455,7 @@ void cli_session_close(Cli* cli) { cli->session->deinit(); } cli->session = NULL; - furi_stdglue_set_thread_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL); furi_check(furi_mutex_release(cli->mutex) == FuriStatusOk); } @@ -469,9 +469,9 @@ int32_t cli_srv(void* p) { furi_record_create(RECORD_CLI, cli); if(cli->session != NULL) { - furi_stdglue_set_thread_stdout_callback(cli->session->tx_stdout); + furi_thread_set_stdout_callback(cli->session->tx_stdout); } else { - furi_stdglue_set_thread_stdout_callback(NULL); + furi_thread_set_stdout_callback(NULL); } if(furi_hal_rtc_get_boot_mode() == FuriHalRtcBootModeNormal) { diff --git a/applications/cli/cli_i.h b/applications/cli/cli_i.h index be1cd288ee7..ba4582d0dfc 100755 --- a/applications/cli/cli_i.h +++ b/applications/cli/cli_i.h @@ -29,7 +29,7 @@ struct CliSession { void (*deinit)(void); size_t (*rx)(uint8_t* buffer, size_t size, uint32_t timeout); void (*tx)(const uint8_t* buffer, size_t size); - void (*tx_stdout)(void* _cookie, const char* data, size_t size); + void (*tx_stdout)(const char* data, size_t size); bool (*is_connected)(void); }; diff --git a/applications/cli/cli_vcp.c b/applications/cli/cli_vcp.c index 113ef01c7d0..f2893a48b85 100644 --- a/applications/cli/cli_vcp.c +++ b/applications/cli/cli_vcp.c @@ -277,8 +277,7 @@ static void cli_vcp_tx(const uint8_t* buffer, size_t size) { #endif } -static void cli_vcp_tx_stdout(void* _cookie, const char* data, size_t size) { - UNUSED(_cookie); +static void cli_vcp_tx_stdout(const char* data, size_t size) { cli_vcp_tx((const uint8_t*)data, size); } diff --git a/applications/clock_app/clock_app.c b/applications/clock_app/clock_app.c index c5eaa636894..d93cdd3fb1d 100644 --- a/applications/clock_app/clock_app.c +++ b/applications/clock_app/clock_app.c @@ -39,19 +39,21 @@ static void clock_render_callback(Canvas* const canvas, void* ctx) { char strings[3][20]; int curMin = (timerSecs / 60); int curSec = timerSecs - (curMin * 60); - sprintf( + snprintf( strings[0], + sizeof(strings[0]), "%.4d-%.2d-%.2d", state->datetime.year, state->datetime.month, state->datetime.day); - sprintf( + snprintf( strings[1], + sizeof(strings[1]), "%.2d:%.2d:%.2d", state->datetime.hour, state->datetime.minute, state->datetime.second); - sprintf(strings[2], "%.2d:%.2d", curMin, curSec); + snprintf(strings[2], sizeof(strings[2]), "%.2d:%.2d", curMin, curSec); release_mutex((ValueMutex*)ctx, state); canvas_set_font(canvas, FontBigNumbers); canvas_draw_str_aligned(canvas, 64, 8, AlignCenter, AlignCenter, strings[1]); diff --git a/applications/desktop/views/desktop_view_debug.c b/applications/desktop/views/desktop_view_debug.c index 053bde39289..a289bd0bfdc 100644 --- a/applications/desktop/views/desktop_view_debug.c +++ b/applications/desktop/views/desktop_view_debug.c @@ -77,7 +77,6 @@ void desktop_debug_render(Canvas* canvas, void* model) { canvas_draw_str(canvas, 5, 50 + STATUS_BAR_Y_SHIFT, buffer); } else { - char buffer[64]; Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN); DolphinStats stats = dolphin_stats(dolphin); furi_record_close(RECORD_DOLPHIN); @@ -86,18 +85,20 @@ void desktop_debug_render(Canvas* canvas, void* model) { uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter); canvas_set_font(canvas, FontSecondary); - snprintf(buffer, 64, "Icounter: %ld Butthurt %ld", m->icounter, m->butthurt); + snprintf(buffer, sizeof(buffer), "Icounter: %ld Butthurt %ld", m->icounter, m->butthurt); canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer); snprintf( buffer, - 64, + sizeof(buffer), "Level: %ld To level up: %ld", current_lvl, (remaining == (uint32_t)(-1) ? remaining : 0)); canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer); - snprintf(buffer, 64, "%s", asctime(localtime((const time_t*)&m->timestamp))); + // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t + snprintf(buffer, sizeof(buffer), "%ld", (uint32_t)m->timestamp); + canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer); canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value [ok] save"); } diff --git a/applications/dice/dice.c b/applications/dice/dice.c index 1a993b1c322..4265bd14fba 100644 --- a/applications/dice/dice.c +++ b/applications/dice/dice.c @@ -64,8 +64,9 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { srand(furi_get_tick()); rand_generator_inited = true; } - sprintf( + snprintf( rollTime[0], + sizeof(rollTime[0]), "%.2d:%.2d:%.2d", state->datetime.hour, state->datetime.minute, @@ -93,10 +94,10 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { "Very doubtful", "My reply is no"}; diceRoll = ((rand() % diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG - sprintf(diceType[0], "%s", "8BALL"); - sprintf(strings[0], "%s at %s", diceType[0], rollTime[0]); + snprintf(diceType[0], sizeof(diceType[0]), "%s", "8BALL"); + snprintf(strings[0], sizeof(strings[0]), "%s at %s", diceType[0], rollTime[0]); uint8_t d1_i = rand() % COUNT_OF(eightBall); - sprintf(strings[1], "%s", eightBall[d1_i]); + snprintf(strings[1], sizeof(strings[1]), "%s", eightBall[d1_i]); } else if(diceSelect == 230) { const char* diceOne[] = { "Nibble", @@ -122,11 +123,11 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { "???", "Genitals"}; diceRoll = ((rand() % diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG - sprintf(diceType[0], "%s", "SEX?"); - sprintf(strings[0], "%s at %s", diceType[0], rollTime[0]); + snprintf(diceType[0], sizeof(diceType[0]), "%s", "SEX?"); + snprintf(strings[0], sizeof(strings[0]), "%s at %s", diceType[0], rollTime[0]); uint8_t d1_i = rand() % COUNT_OF(diceOne); uint8_t d2_i = rand() % COUNT_OF(diceTwo); - sprintf(strings[1], "%s %s", diceOne[d1_i], diceTwo[d2_i]); + snprintf(strings[1], sizeof(strings[1]), "%s %s", diceOne[d1_i], diceTwo[d2_i]); } else if(diceSelect == 231) { const char* deckOne[] = {"2H", "2C", "2D", "2S", "3H", "3C", "3D", "3S", "4H", "4C", "4D", "4S", "5H", "5C", "5D", "5S", "6H", "6C", @@ -141,24 +142,24 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { "JH", "JC", "JD", "JS", "QH", "QC", "QD", "QS", "KH", "KC", "KD", "KS", "AH", "AC", "AD"}; // ONE LESS SINCE ONE WILL BE REMOVED diceRoll = ((rand() % diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG - sprintf(diceType[0], "%s", "WAR!"); - sprintf(strings[0], "%s at %s", diceType[0], rollTime[0]); + snprintf(diceType[0], sizeof(diceType[0]), "%s", "WAR!"); + snprintf(strings[0], sizeof(strings[0]), "%s at %s", diceType[0], rollTime[0]); uint8_t d1_i = rand() % COUNT_OF(deckOne); // INITIALIZE WITH PLACEHOLDERS TO AVOID MAYBE UNINITIALIZED ERROR for(int i = 0; i < COUNT_OF(deckOne); i++) { if(i < d1_i) { - sprintf(deckTwo[i], "%s", deckOne[i]); + snprintf(deckTwo[i], 8, "%s", deckOne[i]); } else if(i > d1_i) { - sprintf(deckTwo[i - 1], "%s", deckOne[i]); + snprintf(deckTwo[i - 1], 8, "%s", deckOne[i]); } } uint8_t d2_i = rand() % COUNT_OF(deckTwo); if(d1_i > d2_i) { playerOneScore++; - sprintf(strings[1], "%s > %s", deckOne[d1_i], deckTwo[d2_i]); + snprintf(strings[1], sizeof(strings[1]), "%s > %s", deckOne[d1_i], deckTwo[d2_i]); } else { playerTwoScore++; - sprintf(strings[1], "%s < %s", deckOne[d1_i], deckTwo[d2_i]); + snprintf(strings[1], sizeof(strings[1]), "%s < %s", deckOne[d1_i], deckTwo[d2_i]); } } else if(diceSelect == 232) { const char* diceOne[] = { @@ -185,42 +186,42 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { "then sing a song", "then do a dance"}; diceRoll = ((rand() % diceSelect) + 1); // JUST TO GET IT GOING? AND FIX BUG - sprintf(diceType[0], "%s", "WEED!"); - sprintf(strings[0], "%s at %s", diceType[0], rollTime[0]); + snprintf(diceType[0], sizeof(diceType[0]), "%s", "WEED!"); + snprintf(strings[0], sizeof(strings[0]), "%s at %s", diceType[0], rollTime[0]); uint8_t d1_i = rand() % COUNT_OF(diceOne); uint8_t d2_i = rand() % COUNT_OF(diceTwo); uint8_t d3_i = rand() % COUNT_OF(diceThree); uint8_t d4_i = rand() % COUNT_OF(diceFour); - sprintf(strings[1], "%s", diceOne[d1_i]); - sprintf(strings[2], "%s", diceTwo[d2_i]); - sprintf(strings[3], "%s", diceThree[d3_i]); - sprintf(strings[4], "%s", diceFour[d4_i]); + snprintf(strings[1], sizeof(strings[1]), "%s", diceOne[d1_i]); + snprintf(strings[2], sizeof(strings[2]), "%s", diceTwo[d2_i]); + snprintf(strings[3], sizeof(strings[3]), "%s", diceThree[d3_i]); + snprintf(strings[4], sizeof(strings[4]), "%s", diceFour[d4_i]); } else { diceRoll = ((rand() % diceSelect) + 1); - sprintf(diceType[0], "%s%d", "d", diceSelect); - sprintf(strings[0], "%d%s at %s", diceQty, diceType[0], rollTime[0]); + snprintf(diceType[0], sizeof(diceType[0]), "%s%d", "d", diceSelect); + snprintf(strings[0], sizeof(strings[0]), "%d%s at %s", diceQty, diceType[0], rollTime[0]); if(diceQty == 1) { - sprintf(strings[1], "%d", diceRoll); + snprintf(strings[1], sizeof(strings[1]), "%d", diceRoll); } else if(diceQty == 2) { - sprintf(strings[1], "%d %d", diceRoll, ((rand() % diceSelect) + 1)); + snprintf(strings[1], sizeof(strings[1]), "%d %d", diceRoll, ((rand() % diceSelect) + 1)); } else if(diceQty == 3) { - sprintf( - strings[1], + snprintf( + strings[1], sizeof(strings[1]), "%d %d %d", diceRoll, ((rand() % diceSelect) + 1), ((rand() % diceSelect) + 1)); } else if(diceQty == 4) { - sprintf( - strings[1], + snprintf( + strings[1], sizeof(strings[1]), "%d %d %d %d", diceRoll, ((rand() % diceSelect) + 1), ((rand() % diceSelect) + 1), ((rand() % diceSelect) + 1)); } else if(diceQty == 5) { - sprintf( - strings[1], + snprintf( + strings[1], sizeof(strings[1]), "%d %d %d %d %d", diceRoll, ((rand() % diceSelect) + 1), @@ -228,8 +229,8 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { ((rand() % diceSelect) + 1), ((rand() % diceSelect) + 1)); } else if(diceQty == 6) { - sprintf( - strings[1], + snprintf( + strings[1], sizeof(strings[1]), "%d %d %d %d %d %d", diceRoll, ((rand() % diceSelect) + 1), @@ -258,8 +259,8 @@ static void dice_render_callback(Canvas* const canvas, void* ctx) { } if(diceSelect == 231 && !(playerOneScore == 0 && playerTwoScore == 0)) { canvas_set_font(canvas, FontSecondary); - sprintf( - theScores[0], + snprintf( + theScores[0], sizeof(theScores[0]), "%d %d", playerOneScore, playerTwoScore); diff --git a/applications/infrared/infrared_cli.c b/applications/infrared/infrared_cli.c index c190aad3e83..aae02e8fd14 100644 --- a/applications/infrared/infrared_cli.c +++ b/applications/infrared/infrared_cli.c @@ -27,7 +27,7 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv if(infrared_worker_signal_is_decoded(received_signal)) { const InfraredMessage* message = infrared_worker_get_decoded_signal(received_signal); - buf_cnt = sniprintf( + buf_cnt = snprintf( buf, sizeof(buf), "%s, A:0x%0*lX, C:0x%0*lX%s\r\n", @@ -43,13 +43,13 @@ static void signal_received_callback(void* context, InfraredWorkerSignal* receiv size_t timings_cnt; infrared_worker_get_raw_signal(received_signal, &timings, &timings_cnt); - buf_cnt = sniprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt); + buf_cnt = snprintf(buf, sizeof(buf), "RAW, %d samples:\r\n", timings_cnt); cli_write(cli, (uint8_t*)buf, buf_cnt); for(size_t i = 0; i < timings_cnt; ++i) { - buf_cnt = sniprintf(buf, sizeof(buf), "%lu ", timings[i]); + buf_cnt = snprintf(buf, sizeof(buf), "%lu ", timings[i]); cli_write(cli, (uint8_t*)buf, buf_cnt); } - buf_cnt = sniprintf(buf, sizeof(buf), "\r\n"); + buf_cnt = snprintf(buf, sizeof(buf), "\r\n"); cli_write(cli, (uint8_t*)buf, buf_cnt); } } diff --git a/applications/mousejacker/mousejacker.c b/applications/mousejacker/mousejacker.c index 9637fd0483e..e525ee5a723 100644 --- a/applications/mousejacker/mousejacker.c +++ b/applications/mousejacker/mousejacker.c @@ -52,7 +52,7 @@ static void render_callback(Canvas* const canvas, void* ctx) { canvas_draw_frame(canvas, 0, 0, 128, 64); canvas_set_font(canvas, FontSecondary); - sprintf(target_text, target_fmt_text, target_address_str); + snprintf(target_text, sizeof(target_text), target_fmt_text, target_address_str); canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, target_text); canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, "Press Ok button to "); canvas_draw_str_aligned(canvas, 10, 40, AlignLeft, AlignBottom, "browse for ducky script"); @@ -74,7 +74,7 @@ static void mousejacker_state_init(PluginState* const plugin_state) { static void hexlify(uint8_t* in, uint8_t size, char* out) { memset(out, 0, size * 2); - for(int i = 0; i < size; i++) sprintf(out + strlen(out), "%02X", in[i]); + for(int i = 0; i < size; i++) snprintf(out + strlen(out), sizeof(out + strlen(out)), "%02X", in[i]); } static bool open_ducky_script(Storage* storage, Stream* stream) { diff --git a/applications/nrfsniff/nrfsniff.c b/applications/nrfsniff/nrfsniff.c index f2608cf97ca..cf5c40f7e26 100644 --- a/applications/nrfsniff/nrfsniff.c +++ b/applications/nrfsniff/nrfsniff.c @@ -124,11 +124,11 @@ static void render_callback(Canvas* const canvas, void* ctx) { if(!sniffing_state) strcpy(sniffing, "No"); - sprintf(rate_text, rate_text_fmt, (int)rate); - sprintf(channel_text, channel_text_fmt, (int)target_channel); - sprintf(preamble_text, preamble_text_fmt, target_preamble[0]); - sprintf(sniff_text, sniff_text_fmt, sniffing); - sprintf(sniffed_address, sniffed_address_fmt, top_address, (int)rate); + snprintf(rate_text, sizeof(rate_text), rate_text_fmt, (int)rate); + snprintf(channel_text, sizeof(channel_text), channel_text_fmt, (int)target_channel); + snprintf(preamble_text, sizeof(preamble_text), preamble_text_fmt, target_preamble[0]); + snprintf(sniff_text, sizeof(sniff_text), sniff_text_fmt, sniffing); + snprintf(sniffed_address, sizeof(sniffed_address), sniffed_address_fmt, top_address, (int)rate); canvas_draw_str_aligned(canvas, 10, 10, AlignLeft, AlignBottom, rate_text); canvas_draw_str_aligned(canvas, 10, 20, AlignLeft, AlignBottom, channel_text); canvas_draw_str_aligned(canvas, 10, 30, AlignLeft, AlignBottom, preamble_text); @@ -148,7 +148,7 @@ static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queu static void hexlify(uint8_t* in, uint8_t size, char* out) { memset(out, 0, size * 2); - for(int i = 0; i < size; i++) sprintf(out + strlen(out), "%02X", in[i]); + for(int i = 0; i < size; i++) snprintf(out + strlen(out), sizeof(out + strlen(out)),"%02X", in[i]); } static bool save_addr_to_file(Storage* storage, uint8_t* data, uint8_t size) { @@ -162,7 +162,7 @@ static bool save_addr_to_file(Storage* storage, uint8_t* data, uint8_t size) { Stream* stream = file_stream_alloc(storage); if(target_rate == 8) rate = 2; - sprintf(ending, ",%d\n", rate); + snprintf(ending, sizeof(ending), ",%d\n", rate); hexlify(data, size, addrline); strcat(addrline, ending); linesize = strlen(addrline); diff --git a/applications/power/power_service/power.c b/applications/power/power_service/power.c index 2390b9e20e3..f496abae05e 100644 --- a/applications/power/power_service/power.c +++ b/applications/power/power_service/power.c @@ -16,7 +16,7 @@ void power_draw_battery_callback(Canvas* canvas, void* context) { if(power->info.gauge_is_ok) { char batteryPercentile[5]; - sprintf(batteryPercentile, "%d", power->info.charge); + snprintf(batteryPercentile, sizeof(batteryPercentile), "%d", power->info.charge); strcat(batteryPercentile, "%"); if((power->displayBatteryPercentage == 1) && diff --git a/applications/rpc/rpc_storage.c b/applications/rpc/rpc_storage.c index d2b43ff6628..1e2920f5b43 100644 --- a/applications/rpc/rpc_storage.c +++ b/applications/rpc/rpc_storage.c @@ -541,7 +541,7 @@ static void rpc_system_storage_md5sum_process(const PB_Main* request, void* cont (void)md5sum_size; furi_assert(hash_size <= ((md5sum_size - 1) / 2)); for(uint8_t i = 0; i < hash_size; i++) { - md5sum += sprintf(md5sum, "%02x", hash[i]); + md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]); } free(hash); diff --git a/applications/subghz/scenes/subghz_scene_read_raw.c b/applications/subghz/scenes/subghz_scene_read_raw.c index 495f030d5b7..a4d40cbda02 100644 --- a/applications/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/subghz/scenes/subghz_scene_read_raw.c @@ -258,7 +258,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { uint32_t time = LL_RTC_TIME_Get(RTC); // 0x00HHMMSS uint32_t date = LL_RTC_DATE_Get(RTC); // 0xWWDDMMYY char strings[1][25]; - sprintf(strings[0], "%s%.4d%.2d%.2d%.2d%.2d", "R" + snprintf(strings[0], sizeof(strings[0]), "%s%.4d%.2d%.2d%.2d%.2d", "R" , __LL_RTC_CONVERT_BCD2BIN((date >> 0) & 0xFF) + 2000 // YEAR , __LL_RTC_CONVERT_BCD2BIN((date >> 8) & 0xFF) // MONTH , __LL_RTC_CONVERT_BCD2BIN((date >> 16) & 0xFF) // DAY @@ -290,7 +290,7 @@ bool subghz_scene_read_raw_on_event(void* context, SceneManagerEvent event) { uint32_t time = LL_RTC_TIME_Get(RTC); // 0x00HHMMSS uint32_t date = LL_RTC_DATE_Get(RTC); // 0xWWDDMMYY char strings[1][25]; - sprintf(strings[0], "%s%.4d%.2d%.2d%.2d%.2d", "R" + snprintf(strings[0], sizeof(strings[0]), "%s%.4d%.2d%.2d%.2d%.2d", "R" , __LL_RTC_CONVERT_BCD2BIN((date >> 0) & 0xFF) + 2000 // YEAR , __LL_RTC_CONVERT_BCD2BIN((date >> 8) & 0xFF) // MONTH , __LL_RTC_CONVERT_BCD2BIN((date >> 16) & 0xFF) // DAY diff --git a/applications/subghz/scenes/subghz_scene_receiver_config.c b/applications/subghz/scenes/subghz_scene_receiver_config.c index f2db5f47e68..2620395ca25 100644 --- a/applications/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/subghz/scenes/subghz_scene_receiver_config.c @@ -100,8 +100,9 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) { if(subghz->txrx->hopper_state == SubGhzHopperStateOFF) { char text_buf[10] = {0}; - sprintf( + snprintf( text_buf, + sizeof(text_buf), "%lu.%02lu", subghz_setting_get_frequency(subghz->setting, index) / 1000000, (subghz_setting_get_frequency(subghz->setting, index) % 1000000) / 10000); @@ -145,8 +146,9 @@ static void subghz_scene_receiver_config_set_hopping_runing(VariableItem* item) variable_item_set_current_value_text(item, hopping_text[index]); if(hopping_value[index] == SubGhzHopperStateOFF) { char text_buf[10] = {0}; - sprintf( + snprintf( text_buf, + sizeof(text_buf), "%lu.%02lu", subghz_setting_get_default_frequency(subghz->setting) / 1000000, (subghz_setting_get_default_frequency(subghz->setting) % 1000000) / 10000); @@ -199,8 +201,9 @@ void subghz_scene_receiver_config_on_enter(void* context) { subghz->scene_manager, SubGhzSceneReceiverConfig, (uint32_t)item); variable_item_set_current_value_index(item, value_index); char text_buf[10] = {0}; - sprintf( + snprintf( text_buf, + sizeof(text_buf), "%lu.%02lu", subghz_setting_get_frequency(subghz->setting, value_index) / 1000000, (subghz_setting_get_frequency(subghz->setting, value_index) % 1000000) / 10000); diff --git a/applications/tictactoe_game/tictactoe_game.c b/applications/tictactoe_game/tictactoe_game.c index beded349f30..d655dd67651 100644 --- a/applications/tictactoe_game/tictactoe_game.c +++ b/applications/tictactoe_game/tictactoe_game.c @@ -103,12 +103,12 @@ void tictactoe_draw(Canvas* canvas) { canvas_draw_str(canvas, 75, 24, "X:"); char scoreXBuffer[10]; - sprintf(scoreXBuffer, "%d", scoreX); + snprintf(scoreXBuffer, sizeof(scoreXBuffer), "%d", scoreX); canvas_draw_str(canvas, 88, 24, scoreXBuffer); canvas_draw_str(canvas, 75, 35, "O:"); char scoreOBuffer[10]; - sprintf(scoreOBuffer, "%d", scoreO); + snprintf(scoreOBuffer, sizeof(scoreOBuffer), "%d", scoreO); canvas_draw_str(canvas, 88, 35, scoreOBuffer); canvas_set_font(canvas, FontSecondary); diff --git a/applications/unit_tests/rpc/rpc_test.c b/applications/unit_tests/rpc/rpc_test.c index 69a0c434ae5..d31311af6c4 100644 --- a/applications/unit_tests/rpc/rpc_test.c +++ b/applications/unit_tests/rpc/rpc_test.c @@ -189,8 +189,9 @@ static void clean_directory(Storage* fs_api, const char* clean_dir) { FileInfo fileinfo; char* name = malloc(MAX_NAME_LENGTH + 1); while(storage_dir_read(dir, &fileinfo, name, MAX_NAME_LENGTH)) { - char* fullname = malloc(strlen(clean_dir) + strlen(name) + 1 + 1); - sprintf(fullname, "%s/%s", clean_dir, name); + size_t size = strlen(clean_dir) + strlen(name) + 1 + 1; + char* fullname = malloc(size); + snprintf(fullname, size, "%s/%s", clean_dir, name); if(fileinfo.flags & FSF_DIRECTORY) { clean_directory(fs_api, fullname); } @@ -1226,7 +1227,7 @@ MU_TEST(test_storage_mkdir) { mu_check(test_is_exists(TEST_DIR "dir2")); } -static void test_storage_calculate_md5sum(const char* path, char* md5sum) { +static void test_storage_calculate_md5sum(const char* path, char* md5sum, size_t md5sum_size) { Storage* api = furi_record_open(RECORD_STORAGE); File* file = storage_file_alloc(api); @@ -1247,7 +1248,7 @@ static void test_storage_calculate_md5sum(const char* path, char* md5sum) { free(md5_ctx); for(uint8_t i = 0; i < hash_size; i++) { - md5sum += sprintf(md5sum, "%02x", hash[i]); + md5sum += snprintf(md5sum, md5sum_size, "%02x", hash[i]); } free(hash); @@ -1299,9 +1300,9 @@ MU_TEST(test_storage_md5sum) { test_create_file(TEST_DIR "file1.txt", 0); test_create_file(TEST_DIR "file2.txt", 1); test_create_file(TEST_DIR "file3.txt", 512); - test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1); - test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2); - test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3); + test_storage_calculate_md5sum(TEST_DIR "file1.txt", md5sum1, MD5SUM_SIZE * 2 + 1); + test_storage_calculate_md5sum(TEST_DIR "file2.txt", md5sum2, MD5SUM_SIZE * 2 + 1); + test_storage_calculate_md5sum(TEST_DIR "file3.txt", md5sum3, MD5SUM_SIZE * 2 + 1); test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK); test_storage_md5sum_run(TEST_DIR "file1.txt", ++command_id, md5sum1, PB_CommandStatus_OK); diff --git a/firmware.scons b/firmware.scons index 96c7c79981b..cdb6977ee42 100644 --- a/firmware.scons +++ b/firmware.scons @@ -185,8 +185,6 @@ fwenv.AppendUnique( "-Wl,--wrap,_free_r", "-Wl,--wrap,_calloc_r", "-Wl,--wrap,_realloc_r", - "-u", - "_printf_float", "-n", "-Xlinker", "-Map=${TARGET}.map", @@ -201,6 +199,7 @@ fwelf = fwenv["FW_ELF"] = fwenv.Program( "${FIRMWARE_BUILD_CFG}", sources, LIBS=[ + "print", "flipper${TARGET_HW}", "furi", "freertos", diff --git a/furi/core/stdglue.c b/furi/core/stdglue.c deleted file mode 100644 index 573277aa535..00000000000 --- a/furi/core/stdglue.c +++ /dev/null @@ -1,102 +0,0 @@ -#include "stdglue.h" -#include "check.h" -#include "memmgr.h" - -#include -#include - -#include -#include - -DICT_DEF2( - FuriStdglueCallbackDict, - uint32_t, - M_DEFAULT_OPLIST, - FuriStdglueWriteCallback, - M_PTR_OPLIST) - -typedef struct { - FuriMutex* mutex; - FuriStdglueCallbackDict_t thread_outputs; -} FuriStdglue; - -static FuriStdglue* furi_stdglue = NULL; - -static ssize_t stdout_write(void* _cookie, const char* data, size_t size) { - furi_assert(furi_stdglue); - bool consumed = false; - FuriThreadId task_id = furi_thread_get_current_id(); - if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING && task_id && - furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk) { - // We are in the thread context - // Handle thread callbacks - FuriStdglueWriteCallback* callback_ptr = - FuriStdglueCallbackDict_get(furi_stdglue->thread_outputs, (uint32_t)task_id); - if(callback_ptr) { - (*callback_ptr)(_cookie, data, size); - consumed = true; - } - furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk); - } - // Flush - if(data == 0) { - /* - * This means that we should flush internal buffers. Since we - * don't we just return. (Remember, "handle" == -1 means that all - * handles should be flushed.) - */ - return 0; - } - // Debug uart - if(!consumed) furi_hal_console_tx((const uint8_t*)data, size); - // All data consumed - return size; -} - -void furi_stdglue_init() { - furi_stdglue = malloc(sizeof(FuriStdglue)); - // Init outputs structures - furi_stdglue->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - furi_check(furi_stdglue->mutex); - FuriStdglueCallbackDict_init(furi_stdglue->thread_outputs); - // Prepare and set stdout descriptor - FILE* fp = fopencookie( - NULL, - "w", - (cookie_io_functions_t){ - .read = NULL, - .write = stdout_write, - .seek = NULL, - .close = NULL, - }); - setvbuf(fp, NULL, _IOLBF, 0); - stdout = fp; -} - -bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback) { - furi_assert(furi_stdglue); - FuriThreadId task_id = furi_thread_get_current_id(); - if(task_id) { - furi_check(furi_mutex_acquire(furi_stdglue->mutex, FuriWaitForever) == FuriStatusOk); - if(callback) { - FuriStdglueCallbackDict_set_at( - furi_stdglue->thread_outputs, (uint32_t)task_id, callback); - } else { - FuriStdglueCallbackDict_erase(furi_stdglue->thread_outputs, (uint32_t)task_id); - } - furi_check(furi_mutex_release(furi_stdglue->mutex) == FuriStatusOk); - return true; - } else { - return false; - } -} - -void __malloc_lock(struct _reent* REENT) { - UNUSED(REENT); - vTaskSuspendAll(); -} - -void __malloc_unlock(struct _reent* REENT) { - UNUSED(REENT); - xTaskResumeAll(); -} diff --git a/furi/core/stdglue.h b/furi/core/stdglue.h deleted file mode 100644 index 800fcf92817..00000000000 --- a/furi/core/stdglue.h +++ /dev/null @@ -1,36 +0,0 @@ -/** - * @file stdglue.h - * Furi: stdlibc glue - */ - -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** Write callback - * @param _cookie pointer to cookie (see stdio gnu extension) - * @param data pointer to data - * @param size data size @warnign your handler must consume everything - */ -typedef void (*FuriStdglueWriteCallback)(void* _cookie, const char* data, size_t size); - -/** Initialized std library glue code */ -void furi_stdglue_init(); - -/** Set STDOUT callback for your thread - * - * @param callback callback or NULL to clear - * - * @return true on success, otherwise fail - * @warning function is thread aware, use this API from the same thread - */ -bool furi_stdglue_set_thread_stdout_callback(FuriStdglueWriteCallback callback); - -#ifdef __cplusplus -} -#endif diff --git a/furi/core/thread.c b/furi/core/thread.c index 3b6708f669c..044f83711b6 100644 --- a/furi/core/thread.c +++ b/furi/core/thread.c @@ -4,12 +4,21 @@ #include "memmgr_heap.h" #include "check.h" #include "common_defines.h" +#include "mutex.h" #include #include +#include #define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers +typedef struct FuriThreadStdout FuriThreadStdout; + +struct FuriThreadStdout { + FuriThreadStdoutWriteCallback write_callback; + string_t buffer; +}; + struct FuriThread { FuriThreadState state; int32_t ret; @@ -27,8 +36,13 @@ struct FuriThread { TaskHandle_t task_handle; bool heap_trace_enabled; size_t heap_size; + + FuriThreadStdout output; }; +static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size); +static int32_t __furi_thread_stdout_flush(FuriThread* thread); + /** Catch threads that are trying to exit wrong way */ __attribute__((__noreturn__)) void furi_thread_catch() { asm volatile("nop"); // extra magic @@ -47,6 +61,10 @@ static void furi_thread_body(void* context) { furi_assert(context); FuriThread* thread = context; + // store thread instance to thread local storage + furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) == NULL); + vTaskSetThreadLocalStoragePointer(NULL, 0, thread); + furi_assert(thread->state == FuriThreadStateStarting); furi_thread_set_state(thread, FuriThreadStateRunning); @@ -66,12 +84,18 @@ static void furi_thread_body(void* context) { furi_assert(thread->state == FuriThreadStateRunning); furi_thread_set_state(thread, FuriThreadStateStopped); + // clear thread local storage + __furi_thread_stdout_flush(thread); + furi_assert(pvTaskGetThreadLocalStoragePointer(NULL, 0) != NULL); + vTaskSetThreadLocalStoragePointer(NULL, 0, NULL); + vTaskDelete(thread->task_handle); furi_thread_catch(); } FuriThread* furi_thread_alloc() { FuriThread* thread = malloc(sizeof(FuriThread)); + string_init(thread->output.buffer); return thread; } @@ -81,6 +105,8 @@ void furi_thread_free(FuriThread* thread) { furi_assert(thread->state == FuriThreadStateStopped); if(thread->name) free((void*)thread->name); + string_clear(thread->output.buffer); + free(thread); } @@ -199,6 +225,12 @@ FuriThreadId furi_thread_get_current_id() { return xTaskGetCurrentTaskHandle(); } +FuriThread* furi_thread_get_current() { + FuriThread* thread = pvTaskGetThreadLocalStoragePointer(NULL, 0); + furi_assert(thread != NULL); + return thread; +} + void furi_thread_yield() { furi_assert(!FURI_IS_IRQ_MODE()); taskYIELD(); @@ -408,3 +440,59 @@ uint32_t furi_thread_get_stack_space(FuriThreadId thread_id) { return (sz); } + +static size_t __furi_thread_stdout_write(FuriThread* thread, const char* data, size_t size) { + if(thread->output.write_callback != NULL) { + thread->output.write_callback(data, size); + } else { + furi_hal_console_tx((const uint8_t*)data, size); + } + return size; +} + +static int32_t __furi_thread_stdout_flush(FuriThread* thread) { + string_ptr buffer = thread->output.buffer; + size_t size = string_size(buffer); + if(size > 0) { + __furi_thread_stdout_write(thread, string_get_cstr(buffer), size); + string_reset(buffer); + } + return 0; +} + +bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) { + FuriThread* thread = furi_thread_get_current(); + + __furi_thread_stdout_flush(thread); + thread->output.write_callback = callback; + + return true; +} + +size_t furi_thread_stdout_write(const char* data, size_t size) { + FuriThread* thread = furi_thread_get_current(); + + if(size == 0 || data == NULL) { + return __furi_thread_stdout_flush(thread); + } else { + if(data[size - 1] == '\n') { + // if the last character is a newline, we can flush buffer and write data as is, wo buffers + __furi_thread_stdout_flush(thread); + __furi_thread_stdout_write(thread, data, size); + } else { + // string_cat doesn't work here because we need to write the exact size data + for(size_t i = 0; i < size; i++) { + string_push_back(thread->output.buffer, data[i]); + if(data[i] == '\n') { + __furi_thread_stdout_flush(thread); + } + } + } + } + + return size; +} + +int32_t furi_thread_stdout_flush() { + return __furi_thread_stdout_flush(furi_thread_get_current()); +} \ No newline at end of file diff --git a/furi/core/thread.h b/furi/core/thread.h index 34eb39f03da..7f746f03faa 100644 --- a/furi/core/thread.h +++ b/furi/core/thread.h @@ -42,6 +42,12 @@ typedef void* FuriThreadId; */ typedef int32_t (*FuriThreadCallback)(void* context); +/** Write to stdout callback + * @param data pointer to data + * @param size data size @warning your handler must consume everything + */ +typedef void (*FuriThreadStdoutWriteCallback)(const char* data, size_t size); + /** FuriThread state change calback called upon thread state change * @param state new thread state * @param context callback context @@ -177,6 +183,12 @@ int32_t furi_thread_get_return_code(FuriThread* thread); */ FuriThreadId furi_thread_get_current_id(); +/** Get FuriThread instance for current thread + * + * @return FuriThread* + */ +FuriThread* furi_thread_get_current(); + /** Return control to scheduler */ void furi_thread_yield(); @@ -194,6 +206,29 @@ const char* furi_thread_get_name(FuriThreadId thread_id); uint32_t furi_thread_get_stack_space(FuriThreadId thread_id); +/** Set STDOUT callback for thread + * + * @param callback callback or NULL to clear + * + * @return true on success, otherwise fail + */ +bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback); + +/** Write data to buffered STDOUT + * + * @param data input data + * @param size input data size + * + * @return size_t written data size + */ +size_t furi_thread_stdout_write(const char* data, size_t size); + +/** Flush data to STDOUT + * + * @return int32_t error code + */ +int32_t furi_thread_stdout_flush(); + #ifdef __cplusplus } #endif diff --git a/furi/furi.c b/furi/furi.c index e6848624bc6..76aed024fa3 100644 --- a/furi/furi.c +++ b/furi/furi.c @@ -8,7 +8,6 @@ void furi_init() { furi_log_init(); furi_record_init(); - furi_stdglue_init(); } void furi_run() { diff --git a/furi/furi.h b/furi/furi.h index d78129a8238..68914b502fd 100644 --- a/furi/furi.h +++ b/furi/furi.h @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/SConscript b/lib/SConscript index 67b3f09d0bc..e6637c04242 100644 --- a/lib/SConscript +++ b/lib/SConscript @@ -69,6 +69,7 @@ libs = env.BuildModules( [ "STM32CubeWB", "freertos", + "print", "microtar", "toolbox", "ST25RFAL002", diff --git a/lib/print/SConscript b/lib/print/SConscript new file mode 100644 index 00000000000..412d17a660b --- /dev/null +++ b/lib/print/SConscript @@ -0,0 +1,107 @@ +Import("env") + +wrapped_fn_list = [ + # + # used by our firmware, so we provide their realizations + # + "fflush", + "printf", + "putc", # fallback from printf, thanks gcc + "putchar", # storage cli + "puts", # fallback from printf, thanks gcc + "snprintf", + "vsnprintf", # m-string + "__assert", # ??? + "__assert_func", # ??? + # + # wrap other functions to make sure they are not called + # realization is not provided + # + "setbuf", + "setvbuf", + "fprintf", + "vfprintf", + "vprintf", + "fputc", + "fputs", + "sprintf", # specially, because this function is dangerous + "asprintf", + "vasprintf", + "asiprintf", + "asniprintf", + "asnprintf", + "diprintf", + "fiprintf", + "iprintf", + "siprintf", + "sniprintf", + "vasiprintf", + "vasniprintf", + "vasnprintf", + "vdiprintf", + "vfiprintf", + "viprintf", + "vsiprintf", + "vsniprintf", + # + # Scanf is not implemented 4 now + # + # "fscanf", + # "scanf", + # "sscanf", + # "vsprintf", + # "fgetc", + # "fgets", + # "getc", + # "getchar", + # "gets", + # "ungetc", + # "vfscanf", + # "vscanf", + # "vsscanf", + # "fiscanf", + # "iscanf", + # "siscanf", + # "vfiscanf", + # "viscanf", + # "vsiscanf", + # + # File management + # + # "fclose", + # "freopen", + # "fread", + # "fwrite", + # "fgetpos", + # "fseek", + # "fsetpos", + # "ftell", + # "rewind", + # "feof", + # "ferror", + # "fopen", + # "remove", + # "rename", + # "fseeko", + # "ftello", +] + +for wrapped_fn in wrapped_fn_list: + env.Append( + LINKFLAGS=[ + "-Wl,--wrap," + wrapped_fn, + "-Wl,--wrap," + wrapped_fn + "_unlocked", + "-Wl,--wrap,_" + wrapped_fn + "_r", + "-Wl,--wrap,_" + wrapped_fn + "_unlocked_r", + ] + ) + +libenv = env.Clone(FW_LIB_NAME="print") +libenv.ApplyLibFlags() +libenv.Append(CCFLAGS=["-Wno-double-promotion"]) + +sources = libenv.GlobRecursive("*.c*", ".") + +lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources) +libenv.Install("${LIB_DIST_DIR}", lib) +Return("lib") diff --git a/lib/print/printf_tiny.c b/lib/print/printf_tiny.c new file mode 100644 index 00000000000..0db11922d71 --- /dev/null +++ b/lib/print/printf_tiny.c @@ -0,0 +1,1037 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf_tiny.h" + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + +// wrapper (used as buffer) for output function type +typedef struct { + void (*fct)(char character, void* arg); + void* arg; +} out_fct_wrap_type; + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) { + if(idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) { + (void)character; + (void)buffer; + (void)idx; + (void)maxlen; +} + +// internal _putchar wrapper +static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen) { + (void)buffer; + (void)idx; + (void)maxlen; + if(character) { + _putchar(character); + } +} + +// internal output function wrapper +static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen) { + (void)idx; + (void)maxlen; + if(character) { + // buffer is the output fct pointer + ((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg); + } +} + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) { + const char* s; + for(s = str; *s && maxsize--; ++s) + ; + return (unsigned int)(s - str); +} + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) { + return (ch >= '0') && (ch <= '9'); +} + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) { + unsigned int i = 0U; + while(_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + const char* buf, + size_t len, + unsigned int width, + unsigned int flags) { + const size_t start_idx = idx; + + // pad spaces up to given width + if(!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for(size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while(len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if(flags & FLAGS_LEFT) { + while(idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + +// internal itoa format +static size_t _ntoa_format( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + char* buf, + size_t len, + bool negative, + unsigned int base, + unsigned int prec, + unsigned int width, + unsigned int flags) { + // pad leading zeros + if(!(flags & FLAGS_LEFT)) { + if(width && (flags & FLAGS_ZEROPAD) && + (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if(flags & FLAGS_HASH) { + if(!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if(len && (base == 16U)) { + len--; + } + } + if((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } else if((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } else if((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if(len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if(len < PRINTF_NTOA_BUFFER_SIZE) { + if(negative) { + buf[len++] = '-'; + } else if(flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if(flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + +// internal itoa for 'long' type +static size_t _ntoa_long( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + unsigned long value, + bool negative, + unsigned long base, + unsigned int prec, + unsigned int width, + unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if(!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if(!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : + (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while(value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format( + out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + unsigned long long value, + bool negative, + unsigned long long base, + unsigned int prec, + unsigned int width, + unsigned int flags) { + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if(!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if(!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : + (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while(value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format( + out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags); +#endif + +// internal ftoa for fixed decimal floating point +static size_t _ftoa( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags) { + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + + // test for special values + if(value != value) return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if(value < -DBL_MAX) return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if(value > DBL_MAX) + return _out_rev( + out, + buffer, + idx, + maxlen, + (flags & FLAGS_PLUS) ? "fni+" : "fni", + (flags & FLAGS_PLUS) ? 4U : 3U, + width, + flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if(value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if(!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if(diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if(frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } else if(diff < 0.5) { + } else if((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if(prec == 0U) { + diff = value - (double)whole; + if((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while(len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if(!(frac /= 10U)) { + break; + } + } + // add extra 0s + while((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if(len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while(len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if(!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if(!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if(width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if(len < PRINTF_FTOA_BUFFER_SIZE) { + if(negative) { + buf[len++] = '-'; + } else if(flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } else if(flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa( + out_fct_type out, + char* buffer, + size_t idx, + size_t maxlen, + double value, + unsigned int prec, + unsigned int width, + unsigned int flags) { + // check for NaN and special values + if((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if(negative) { + value = -value; + } + + // default precision + if(!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | + (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = + (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if(value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if(flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if((value >= 1e-4) && (value < 1e6)) { + if((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } else { + // we use one sigfig for the whole part + if((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if(width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if(expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa( + out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if(minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long( + out, + buffer, + idx, + maxlen, + (expval < 0) ? -expval : expval, + expval < 0, + 10, + 0, + minwidth - 1, + FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if(flags & FLAGS_LEFT) { + while(idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + +// internal vsnprintf +static int + _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) { + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if(!buffer) { + // use null output function + out = _out_null; + } + + while(*format) { + // format specifier? %[flags][width][.precision][length] + if(*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch(*format) { + case '0': + flags |= FLAGS_ZEROPAD; + format++; + n = 1U; + break; + case '-': + flags |= FLAGS_LEFT; + format++; + n = 1U; + break; + case '+': + flags |= FLAGS_PLUS; + format++; + n = 1U; + break; + case ' ': + flags |= FLAGS_SPACE; + format++; + n = 1U; + break; + case '#': + flags |= FLAGS_HASH; + format++; + n = 1U; + break; + default: + n = 0U; + break; + } + } while(n); + + // evaluate width field + width = 0U; + if(_is_digit(*format)) { + width = _atoi(&format); + } else if(*format == '*') { + const int w = va_arg(va, int); + if(w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if(*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if(_is_digit(*format)) { + precision = _atoi(&format); + } else if(*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch(*format) { + case 'l': + flags |= FLAGS_LONG; + format++; + if(*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h': + flags |= FLAGS_SHORT; + format++; + if(*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't': + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j': + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z': + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default: + break; + } + + // evaluate specifier + switch(*format) { + case 'd': + case 'i': + case 'u': + case 'x': + case 'X': + case 'o': + case 'b': { + // set the base + unsigned int base; + if(*format == 'x' || *format == 'X') { + base = 16U; + } else if(*format == 'o') { + base = 8U; + } else if(*format == 'b') { + base = 2U; + } else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if(*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if(flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if((*format == 'i') || (*format == 'd')) { + // signed + if(flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long( + out, + buffer, + idx, + maxlen, + (unsigned long long)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); +#endif + } else if(flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long( + out, + buffer, + idx, + maxlen, + (unsigned long)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); + } else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : + (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : + va_arg(va, int); + idx = _ntoa_long( + out, + buffer, + idx, + maxlen, + (unsigned int)(value > 0 ? value : 0 - value), + value < 0, + base, + precision, + width, + flags); + } + } else { + // unsigned + if(flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long( + out, + buffer, + idx, + maxlen, + va_arg(va, unsigned long long), + false, + base, + precision, + width, + flags); +#endif + } else if(flags & FLAGS_LONG) { + idx = _ntoa_long( + out, + buffer, + idx, + maxlen, + va_arg(va, unsigned long), + false, + base, + precision, + width, + flags); + } else { + const unsigned int value = + (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : + (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : + va_arg(va, unsigned int); + idx = _ntoa_long( + out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f': + case 'F': + if(*format == 'F') flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if((*format == 'g') || (*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if((*format == 'E') || (*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c': { + unsigned int l = 1U; + // pre padding + if(!(flags & FLAGS_LEFT)) { + while(l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if(flags & FLAGS_LEFT) { + while(l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's': { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if(flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if(!(flags & FLAGS_LEFT)) { + while(l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if(flags & FLAGS_LEFT) { + while(l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p': { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if(is_ll) { + idx = _ntoa_long_long( + out, + buffer, + idx, + maxlen, + (uintptr_t)va_arg(va, void*), + false, + 16U, + precision, + width, + flags); + } else { +#endif + idx = _ntoa_long( + out, + buffer, + idx, + maxlen, + (unsigned long)((uintptr_t)va_arg(va, void*)), + false, + 16U, + precision, + width, + flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%': + out('%', buffer, idx++, maxlen); + format++; + break; + + default: + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + +/////////////////////////////////////////////////////////////////////////////// + +int printf_(const char* format, ...) { + va_list va; + va_start(va, format); + char buffer[1]; + const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + +int sprintf_(char* buffer, const char* format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + +int snprintf_(char* buffer, size_t count, const char* format, ...) { + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, count, format, va); + va_end(va); + return ret; +} + +int vprintf_(const char* format, va_list va) { + char buffer[1]; + return _vsnprintf(_out_char, buffer, (size_t)-1, format, va); +} + +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va) { + return _vsnprintf(_out_buffer, buffer, count, format, va); +} + +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...) { + va_list va; + va_start(va, format); + const out_fct_wrap_type out_fct_wrap = {out, arg}; + const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va); + va_end(va); + return ret; +} diff --git a/lib/print/printf_tiny.h b/lib/print/printf_tiny.h new file mode 100644 index 00000000000..8f292819e0a --- /dev/null +++ b/lib/print/printf_tiny.h @@ -0,0 +1,103 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Output a character to a custom device like UART, used by the printf() function + * This function is declared here only. You have to write your custom implementation somewhere + * \param character Character to output + */ +void _putchar(char character); + +/** + * Tiny printf implementation + * You have to implement _putchar if you use printf() + * To avoid conflicts with the regular printf() API it is overridden by macro defines + * and internal underscore-appended functions like printf_() are used + * \param format A string that specifies the format of the output + * \return The number of characters that are written into the array, not counting the terminating null character + */ +int printf_(const char* format, ...); + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +int sprintf_(char* buffer, const char* format, ...); + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +int snprintf_(char* buffer, size_t count, const char* format, ...); +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +int vprintf_(const char* format, va_list va); + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + +#ifdef __cplusplus +} +#endif + +#endif // _PRINTF_H_ diff --git a/lib/print/wrappers.c b/lib/print/wrappers.c new file mode 100644 index 00000000000..3fe446657c4 --- /dev/null +++ b/lib/print/wrappers.c @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "printf_tiny.h" + +void _putchar(char character) { + furi_thread_stdout_write(&character, 1); +} + +int __wrap_printf(const char* format, ...) { + va_list args; + va_start(args, format); + int ret = vprintf_(format, args); + va_end(args); + + return ret; +} + +int __wrap_vsnprintf(char* str, size_t size, const char* format, va_list args) { + return vsnprintf_(str, size, format, args); +} + +int __wrap_puts(const char* str) { + size_t size = furi_thread_stdout_write(str, strlen(str)); + size += furi_thread_stdout_write("\n", 1); + return size; +} + +int __wrap_putchar(int ch) { + size_t size = furi_thread_stdout_write((char*)&ch, 1); + return size; +} + +int __wrap_putc(int ch, FILE* stream) { + UNUSED(stream); + size_t size = furi_thread_stdout_write((char*)&ch, 1); + return size; +} + +int __wrap_snprintf(char* str, size_t size, const char* format, ...) { + va_list args; + va_start(args, format); + int ret = __wrap_vsnprintf(str, size, format, args); + va_end(args); + + return ret; +} + +int __wrap_fflush(FILE* stream) { + UNUSED(stream); + furi_thread_stdout_flush(); + return 0; +} + +__attribute__((__noreturn__)) void __wrap___assert(const char* file, int line, const char* e) { + UNUSED(file); + UNUSED(line); + // TODO: message file and line number + furi_crash(e); +} + +__attribute__((__noreturn__)) void + __wrap___assert_func(const char* file, int line, const char* func, const char* e) { + UNUSED(file); + UNUSED(line); + UNUSED(func); + // TODO: message file and line number + furi_crash(e); +} \ No newline at end of file diff --git a/lib/toolbox/random_name.c b/lib/toolbox/random_name.c index 5d362a2bb0b..1c5c213e4ed 100644 --- a/lib/toolbox/random_name.c +++ b/lib/toolbox/random_name.c @@ -9,8 +9,9 @@ void set_random_name(char* name, uint8_t max_name_size) { uint32_t time = LL_RTC_TIME_Get(RTC); // 0x00HHMMSS uint32_t date = LL_RTC_DATE_Get(RTC); // 0xWWDDMMYY char strings[1][25]; - sprintf( + snprintf( strings[0], + 18, "%s%.4d%.2d%.2d%.2d%.2d", "s", __LL_RTC_CONVERT_BCD2BIN((date >> 0) & 0xFF) + 2000 // YEAR @@ -21,9 +22,9 @@ void set_random_name(char* name, uint8_t max_name_size) { , __LL_RTC_CONVERT_BCD2BIN((time >> 16) & 0xFF) // HOUR , - __LL_RTC_CONVERT_BCD2BIN((time >> 8) & 0xFF) // DAY + __LL_RTC_CONVERT_BCD2BIN((time >> 8) & 0xFF) // MIN ); - sniprintf(name, max_name_size, "%s", strings[0]); + snprintf(name, max_name_size, "%s", strings[0]); // Set first symbol to upper case name[0] = name[0] - 0x20; }