diff --git a/.ci/check-unwanted-symbols b/.ci/check-unwanted-symbols index 40d6a70a11..87c2d3bfa6 100755 --- a/.ci/check-unwanted-symbols +++ b/.ci/check-unwanted-symbols @@ -1,23 +1,67 @@ -#!/bin/bash +#!/usr/bin/env bash -set -x -set -e +set -euo pipefail -# Disallow some symbols in the final binary that we don't want. -if arm-none-eabi-nm build/bin/firmware.elf | grep -q "float_to_decimal_common_shortest"; then - echo "Rust fmt float formatting like {.1} adds significant binary bloat." - echo "Use something simpler like (float*10).round() as u64, then format with util::decimal::format" - exit 1 -fi -if arm-none-eabi-nm build/bin/firmware.elf | grep -q "strftime"; then - echo "strftime adds significant binary bloat. Use custom formatting like in `format_dateimte()`." - exit 1 -fi -if arm-none-eabi-nm build/bin/firmware.elf | grep -q "sha26sha512"; then - # sha26sha512 is a mangled Rust symbol standing for `sha2::sha512`. - # One can use rustfilt to see the demangled symbols: - # cargo install rustfilt; arm-none-eabi-nm build/bin/firmware.elf | rustfilt - echo "sha2::Sha512 adds significant binary bloat." - echo "Only use it if there is no other sha512 impl available that is smaller." - exit 1 +elf=build/bin/firmware.elf +if [[ ! -f "$elf" ]]; then + echo "ELF file not found: $elf" >&2 + exit 2 fi + +# Disallow symbols in the final linked ELF that indicate expensive code paths. +symbols=$(arm-none-eabi-nm -C "$elf") +failed=0 + +check_symbols() { + local name=$1 + local pattern=$2 + shift 2 + + local matches + matches=$(grep -E "$pattern" <<<"$symbols" || true) + if [[ -z "$matches" ]]; then + return + fi + + echo "Found unwanted symbols for: $name" >&2 + echo "$matches" | sed -n '1,20p' >&2 + for line in "$@"; do + echo "$line" >&2 + done + echo >&2 + failed=1 +} + +check_symbols \ + "Rust float formatting" \ + "float_to_decimal_common_shortest" \ + "Rust fmt float formatting like {:.1} adds significant binary bloat." \ + "Use integer arithmetic and format the resulting integer parts explicitly." + +check_symbols \ + "strftime" \ + "(^|[[:space:]])strftime($|[[:space:]])" \ + "strftime adds significant binary bloat." \ + "Use custom formatting like in format_datetime()." + +check_symbols \ + "sha2::Sha512" \ + "sha26sha512|sha2::sha512" \ + "sha2::Sha512 adds significant binary bloat." \ + "Only use it if there is no other sha512 implementation available that is smaller." + +# Some f64 helpers are already pulled in by the existing C snprintf/newlib path. +# Keep this check to helpers that are absent after the integer fee/progress changes. +check_symbols \ + "software f64 comparison/conversion helpers" \ + "(__aeabi_dcmp[[:alnum:]_]*|__aeabi_d2(i|l|ul)z|compiler_builtins::float::cmp)" \ + "Software f64 comparison/conversion helpers add significant binary bloat." \ + "Use integer arithmetic in firmware code instead." + +check_symbols \ + "software f32 arithmetic helpers" \ + "(__aeabi_f(add|sub|mul|div)|__(add|sub|mul|div|neg)sf3|compiler_builtins::float::(add|sub|mul|div)::.*f32)" \ + "Software f32 arithmetic helpers add significant binary bloat." \ + "Use integer arithmetic in firmware code instead." + +exit "$failed" diff --git a/src/rust/bitbox-hal/src/ui.rs b/src/rust/bitbox-hal/src/ui.rs index 1c300b0ecd..29eb0ab383 100644 --- a/src/rust/bitbox-hal/src/ui.rs +++ b/src/rust/bitbox-hal/src/ui.rs @@ -62,8 +62,9 @@ pub enum CanCancel { } pub trait Progress { - /// Set progress. `progress` should be in the range `[0.0, 1.0]`. - fn set(&mut self, progress: f32); + /// Set progress as a fraction. `denominator` must be non-zero and + /// `numerator <= denominator`. + fn set_fraction(&mut self, numerator: u32, denominator: u32); } pub trait Empty {} diff --git a/src/rust/bitbox02-rust/src/hal/testing/ui.rs b/src/rust/bitbox02-rust/src/hal/testing/ui.rs index 6437fe459a..2fdbe412d0 100644 --- a/src/rust/bitbox02-rust/src/hal/testing/ui.rs +++ b/src/rust/bitbox02-rust/src/hal/testing/ui.rs @@ -73,7 +73,7 @@ pub struct TestingUi<'a> { pub struct NoopProgress; impl Progress for NoopProgress { - fn set(&mut self, _progress: f32) {} + fn set_fraction(&mut self, _numerator: u32, _denominator: u32) {} } pub struct NoopEmpty; diff --git a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs index 3f3b3a66a6..e376e8738c 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bitcoin/signtx.rs @@ -359,15 +359,15 @@ async fn handle_prevtx( let mut hasher = Sha256::new(); hasher.update(prevtx_init.version.to_le_bytes()); + let prevtx_total_ios = prevtx_init.num_inputs + prevtx_init.num_outputs; + hasher.update(serialize(&VarInt(prevtx_init.num_inputs as u64))); for prevtx_input_index in 0..prevtx_init.num_inputs { // Update progress. - progress_component.set({ - let step = 1f32 / (num_inputs as f32); - let subprogress: f32 = (prevtx_input_index as f32) - / (prevtx_init.num_inputs + prevtx_init.num_outputs) as f32; - (input_index as f32 + subprogress) * step - }); + progress_component.set_fraction( + input_index * prevtx_total_ios + prevtx_input_index, + num_inputs * prevtx_total_ios, + ); let prevtx_input = get_prevtx_input(input_index, prevtx_input_index, next_response).await?; hasher.update(prevtx_input.prev_out_hash.as_slice()); @@ -382,12 +382,10 @@ async fn handle_prevtx( hasher.update(serialize(&VarInt(prevtx_init.num_outputs as u64))); for prevtx_output_index in 0..prevtx_init.num_outputs { // Update progress. - progress_component.set({ - let step = 1f32 / (num_inputs as f32); - let subprogress: f32 = (prevtx_init.num_inputs + prevtx_output_index) as f32 - / (prevtx_init.num_inputs + prevtx_init.num_outputs) as f32; - (input_index as f32 + subprogress) * step - }); + progress_component.set_fraction( + input_index * prevtx_total_ios + prevtx_init.num_inputs + prevtx_output_index, + num_inputs * prevtx_total_ios, + ); let prevtx_output = get_prevtx_output(input_index, prevtx_output_index, next_response).await?; @@ -759,7 +757,7 @@ async fn _process( progress_component .as_mut() .unwrap() - .set((input_index as f32) / (request.num_inputs as f32)); + .set_fraction(input_index, request.num_inputs); let tx_input = get_tx_input(input_index, &mut next_response).await?; let script_config_account = validated_script_configs @@ -846,7 +844,7 @@ async fn _process( } // The progress for loading the inputs is 100%. - progress_component.as_mut().unwrap().set(1.); + progress_component.as_mut().unwrap().set_fraction(1, 1); let hash_prevouts = hasher_prevouts.finalize(); let hash_sequence = hasher_sequence.finalize(); @@ -1146,16 +1144,12 @@ async fn _process( let fee: u64 = total_out .checked_sub(outputs_sum_out) .ok_or(Error::InvalidInput)?; - let fee_percentage: Option = if outputs_sum_out == 0 { - None - } else { - Some(100. * (fee as f64) / (outputs_sum_out as f64)) - }; + let fee_percentage = transaction::warning_fee_percentage(fee, outputs_sum_out); transaction::verify_total_fee_maybe_warn( hal, &format_amount(coin_params, format_unit, total_out)?, &format_amount(coin_params, format_unit, fee)?, - fee_percentage, + fee_percentage.as_deref(), ) .await?; hal.ui().status("Transaction\nconfirmed", true).await; @@ -1316,7 +1310,7 @@ async fn _process( // Update progress. if let Some(ref mut c) = progress_component { - c.set((input_index + 1) as f32 / (request.num_inputs as f32)); + c.set_fraction(input_index + 1, request.num_inputs); } } diff --git a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs index 38c5258236..4c55f7daf3 100644 --- a/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs +++ b/src/rust/bitbox02-rust/src/hww/api/bluetooth.rs @@ -97,7 +97,7 @@ async fn process_upgrade_helper( memory.ble_firmware_flash_chunk(inactive_slot, chunk_index, &chunk)?; // Update progress. - progress.set((chunk_index + 1) as f32 / (num_chunks as f32)); + progress.set_fraction(chunk_index + 1, num_chunks); } let firmware_hash: [u8; 32] = firmware_hasher.finalize().into(); @@ -242,8 +242,8 @@ mod tests { } impl Progress for TestProgress { - fn set(&mut self, progress: f32) { - self.values.push(progress); + fn set_fraction(&mut self, numerator: u32, denominator: u32) { + self.values.push(numerator as f32 / denominator as f32); } } diff --git a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs index bcae3c444f..37f9b98583 100644 --- a/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs +++ b/src/rust/bitbox02-rust/src/hww/api/cardano/sign_transaction.rs @@ -278,12 +278,12 @@ async fn _process( }) .await?; } else { - let fee_percentage: f64 = 100. * (request.fee as f64) / (total as f64); + let fee_percentage = transaction::warning_fee_percentage(request.fee, total); transaction::verify_total_fee_maybe_warn( hal, &format_value(params, total + request.fee), &format_value(params, request.fee), - Some(fee_percentage), + fee_percentage.as_deref(), ) .await?; } diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/amount.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/amount.rs index dc8b7b57fa..9bce27094b 100644 --- a/src/rust/bitbox02-rust/src/hww/api/ethereum/amount.rs +++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/amount.rs @@ -2,7 +2,7 @@ use alloc::string::String; use num_bigint::BigUint; -use num_traits::{ToPrimitive, Zero}; +use num_traits::Zero; pub struct Amount<'a> { pub unit: &'a str, @@ -34,13 +34,17 @@ impl Amount<'_> { } } -/// Computes the percentage of the fee of the amount, up to one decimal point. -/// Returns None if the amount is 0 or either fee or amount cannot be represented by `f64`. -pub fn calculate_percentage(fee: &BigUint, amount: &BigUint) -> Option { +/// Computes the warning percentage string shown to the user, up to one decimal point. +/// Returns None if the amount is 0 or the raw fee percentage is below 10%. +pub fn calculate_percentage(fee: &BigUint, amount: &BigUint) -> Option { if amount.is_zero() { return None; } - Some(100. * fee.to_f64()? / amount.to_f64()?) + if fee * 10u8 < *amount { + return None; + } + let tenths = (fee * 1000u16 + amount / 2u8) / amount; + Some(util::decimal::format_no_trim(&tenths, 1)) } #[cfg(test)] @@ -139,13 +143,14 @@ mod tests { pub fn test_calculate_percentage() { let p = |f: u64, a: u64| calculate_percentage(&f.into(), &a.into()); assert_eq!(p(1, 0), None); - assert_eq!(p(3, 4), Some(75.)); - assert_eq!(p(0, 100), Some(0.)); - assert_eq!(p(1, 100), Some(1.)); - assert_eq!(p(9, 100), Some(9.)); - assert_eq!(p(10, 100), Some(10.)); - assert_eq!(p(99, 100), Some(99.)); - assert_eq!(p(909, 1000), Some(90.9)); + assert_eq!(p(3, 4), Some("75.0".into())); + assert_eq!(p(0, 100), None); + assert_eq!(p(1, 100), None); + assert_eq!(p(9, 100), None); + assert_eq!(p(10, 100), Some("10.0".into())); + assert_eq!(p(99, 100), Some("99.0".into())); + assert_eq!(p(909, 1000), Some("90.9".into())); + assert_eq!(p(995, 10000), None); assert_eq!( calculate_percentage( // 63713280000000000 @@ -153,7 +158,7 @@ mod tests { // 530564000000000000 &BigUint::from_bytes_be(b"\x07\x5c\xf1\x25\x9e\x9c\x40\x00"), ), - Some(12.008594627603833) + Some("12.0".into()) ); } } diff --git a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs index d28995a41e..9411f0edb3 100644 --- a/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs +++ b/src/rust/bitbox02-rust/src/hww/api/ethereum/sign.rs @@ -163,8 +163,13 @@ async fn verify_standard_total_fee( value: amount_value.add(&fee.value), }; let percentage = calculate_percentage(&fee.value, amount_value); - transaction::verify_total_fee_maybe_warn(hal, &total.format(), &fee.format(), percentage) - .await?; + transaction::verify_total_fee_maybe_warn( + hal, + &total.format(), + &fee.format(), + percentage.as_deref(), + ) + .await?; Ok(()) } diff --git a/src/rust/bitbox02-rust/src/workflow/transaction.rs b/src/rust/bitbox02-rust/src/workflow/transaction.rs index ad772712a6..6f254c0b7b 100644 --- a/src/rust/bitbox02-rust/src/workflow/transaction.rs +++ b/src/rust/bitbox02-rust/src/workflow/transaction.rs @@ -4,21 +4,97 @@ use crate::hal::Ui; use crate::hal::ui::ConfirmParams; use crate::hal::ui::UserAbort; -use alloc::string::String; +use alloc::string::{String, ToString}; -fn format_percentage(p: f64) -> String { - let int: u64 = num_traits::float::FloatCore::round(p * 10.) as _; - util::decimal::format_no_trim(int, 1) +fn next_decimal_digit(remainder: u64, amount: u64) -> (u8, u64) { + // Long-division step: compute floor(remainder * 10 / amount) without ever multiplying by 10, + // as `remainder * 10` can overflow for arbitrary u64 inputs. + let mut digit = 0; + let mut next_remainder = 0; + for _ in 0..10 { + if next_remainder >= amount - remainder { + next_remainder -= amount - remainder; + digit += 1; + } else { + next_remainder += remainder; + } + } + (digit, next_remainder) +} + +fn rounded_fractional_tenths(mut remainder: u64, amount: u64) -> u16 { + // Percent with one decimal is stored as tenths of a percent: + // fee / amount * 100% * 10 = fee / amount * 1000. + // The integer quotient is handled separately, so extract exactly the three fractional decimal + // digits contributed by `remainder / amount`, then round from the leftover remainder. + let mut result = 0; + for _ in 0..3 { + let (digit, next_remainder) = next_decimal_digit(remainder, amount); + result = result * 10 + u16::from(digit); + remainder = next_remainder; + } + if remainder >= amount - remainder { + result += 1; + } + result +} + +fn push_digit(out: &mut String, digit: u16) { + out.push((b'0' + digit as u8) as char); +} + +fn format_percentage(quotient: u64, fractional_tenths: u16) -> String { + // `quotient * 100 + fractional_tenths / 10` can overflow u64 for extreme inputs. Format it as + // decimal text instead: append two percent digits to `quotient`, then append the decimal digit. + let mut whole = if fractional_tenths / 10 == 100 { + // Rounding can turn 99.95% of the fractional part into an additional full `quotient`. + let mut whole = (quotient + 1).to_string(); + whole.push('0'); + whole.push('0'); + whole + } else { + let tens = fractional_tenths / 100; + let ones = (fractional_tenths / 10) % 10; + let mut whole = if quotient == 0 { + String::new() + } else { + quotient.to_string() + }; + if quotient != 0 || tens != 0 { + push_digit(&mut whole, tens); + } + push_digit(&mut whole, ones); + whole + }; + push_digit(&mut whole, fractional_tenths % 10); + whole.insert(whole.len() - 1, '.'); + whole +} + +pub fn warning_fee_percentage(fee: u64, amount: u64) -> Option { + if amount == 0 { + return None; + } + let warning_threshold = amount / 10 + u64::from(amount % 10 != 0); + if fee < warning_threshold { + return None; + } + Some(format_percentage( + fee / amount, + rounded_fractional_tenths(fee % amount, amount), + )) +} + +fn format_percentage_text(fee_percentage: &str) -> String { + format!("The fee is {}%\nthe send amount.\nProceed?", fee_percentage) } pub async fn verify_total_fee_maybe_warn( hal: &mut impl crate::hal::Hal, total: &str, fee: &str, - fee_percentage: Option, + fee_percentage: Option<&str>, ) -> Result<(), UserAbort> { - const FEE_WARNING_THRESHOLD: f64 = 10.; - let fee_percentage = fee_percentage.filter(|&f| f >= FEE_WARNING_THRESHOLD); let longtouch = fee_percentage.is_none(); hal.ui().verify_total_fee(total, fee, longtouch).await?; @@ -26,10 +102,7 @@ pub async fn verify_total_fee_maybe_warn( hal.ui() .confirm(&ConfirmParams { title: "High fee", - body: &format!( - "The fee is {}%\nthe send amount.\nProceed?", - format_percentage(fee_percentage) - ), + body: &format_percentage_text(fee_percentage), longtouch: true, ..Default::default() }) @@ -43,11 +116,32 @@ mod tests { use super::*; #[test] - fn test_format_percentage() { - assert_eq!(format_percentage(0.), "0.0"); - assert_eq!(format_percentage(10.0), "10.0"); - assert_eq!(format_percentage(10.1), "10.1"); - assert_eq!(format_percentage(10.14), "10.1"); - assert_eq!(format_percentage(10.15), "10.2"); + fn test_warning_fee_percentage() { + assert_eq!(warning_fee_percentage(1, 0), None); + assert_eq!(warning_fee_percentage(0, 100), None); + assert_eq!(warning_fee_percentage(9, 100), None); + assert_eq!(warning_fee_percentage(3, 4), Some("75.0".into())); + assert_eq!(warning_fee_percentage(10, 100), Some("10.0".into())); + assert_eq!(warning_fee_percentage(101, 1000), Some("10.1".into())); + assert_eq!(warning_fee_percentage(1014, 10000), Some("10.1".into())); + assert_eq!(warning_fee_percentage(1015, 10000), Some("10.2".into())); + assert_eq!(warning_fee_percentage(995, 10000), None); + assert_eq!(warning_fee_percentage(909, 1000), Some("90.9".into())); + assert_eq!( + warning_fee_percentage(u64::MAX, 1), + Some("1844674407370955161500.0".into()) + ); + assert_eq!( + warning_fee_percentage(u64::MAX, 2), + Some("922337203685477580750.0".into()) + ); + assert_eq!( + warning_fee_percentage(u64::MAX, u64::MAX), + Some("100.0".into()) + ); + assert_eq!( + warning_fee_percentage(u64::MAX - 1, u64::MAX), + Some("100.0".into()) + ); } } diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs index 67998c8494..c74fc6871e 100644 --- a/src/rust/bitbox02-sys/build.rs +++ b/src/rust/bitbox02-sys/build.rs @@ -131,7 +131,7 @@ const ALLOWLIST_FNS: &[&str] = &[ "platform_product", "printf", "progress_create", - "progress_set", + "progress_set_fraction", "random_32_bytes_mcu", "random_32_bytes", "random_fake_reset", diff --git a/src/rust/bitbox02/src/hal/ui.rs b/src/rust/bitbox02/src/hal/ui.rs index 440bfbf70c..3dd251ba17 100644 --- a/src/rust/bitbox02/src/hal/ui.rs +++ b/src/rust/bitbox02/src/hal/ui.rs @@ -19,8 +19,8 @@ pub struct BitBox02Progress { } impl HalProgress for BitBox02Progress { - fn set(&mut self, progress: f32) { - crate::ui::progress_set(&mut self.component, progress); + fn set_fraction(&mut self, numerator: u32, denominator: u32) { + crate::ui::progress_set_fraction(&mut self.component, numerator, denominator); } } diff --git a/src/rust/bitbox02/src/ui/ui.rs b/src/rust/bitbox02/src/ui/ui.rs index 32904ca22a..9063e014de 100644 --- a/src/rust/bitbox02/src/ui/ui.rs +++ b/src/rust/bitbox02/src/ui/ui.rs @@ -781,8 +781,8 @@ pub fn progress_create(title: &str) -> Component { } } -pub fn progress_set(component: &mut Component, progress: f32) { - unsafe { bitbox02_sys::progress_set(component.component, progress) } +pub fn progress_set_fraction(component: &mut Component, numerator: u32, denominator: u32) { + unsafe { bitbox02_sys::progress_set_fraction(component.component, numerator, denominator) } } pub fn empty_create() -> Component { diff --git a/src/rust/bitbox02/src/ui/ui_stub.rs b/src/rust/bitbox02/src/ui/ui_stub.rs index c10d4ac726..de758cac63 100644 --- a/src/rust/bitbox02/src/ui/ui_stub.rs +++ b/src/rust/bitbox02/src/ui/ui_stub.rs @@ -93,7 +93,7 @@ pub fn progress_create(_title: &str) -> Component { Component { is_pushed: false } } -pub fn progress_set(_component: &mut Component, _progress: f32) {} +pub fn progress_set_fraction(_component: &mut Component, _numerator: u32, _denominator: u32) {} pub fn empty_create() -> Component { Component { is_pushed: false } diff --git a/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs index e53ff8ebad..2958911806 100644 --- a/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs +++ b/src/rust/bitbox02/src/ui/ui_stub_c_unit_tests.rs @@ -116,7 +116,7 @@ pub fn progress_create(_title: &str) -> Component { Component { is_pushed: false } } -pub fn progress_set(_component: &mut Component, _progress: f32) {} +pub fn progress_set_fraction(_component: &mut Component, _numerator: u32, _denominator: u32) {} pub fn empty_create() -> Component { Component { is_pushed: false } diff --git a/src/rust/bitbox03/src/ui.rs b/src/rust/bitbox03/src/ui.rs index f6830ee783..fcf842b3c0 100644 --- a/src/rust/bitbox03/src/ui.rs +++ b/src/rust/bitbox03/src/ui.rs @@ -38,7 +38,7 @@ impl Drop for ScreenGuard<'_, Timer> { } impl hal::ui::Progress for BitBox03UiProgress { - fn set(&mut self, _progress: f32) { + fn set_fraction(&mut self, _numerator: u32, _denominator: u32) { todo!() } } diff --git a/src/ui/components/progress.c b/src/ui/components/progress.c index 8e8ff327d2..7a17274fa6 100644 --- a/src/ui/components/progress.c +++ b/src/ui/components/progress.c @@ -10,15 +10,14 @@ #include typedef struct { - float progress; + uint16_t filled_width; } data_t; static void _render(component_t* component) { const data_t* data = (const data_t*)component->data; const uint16_t bar_height = 5; - UG_FillFrame( - 0, SCREEN_HEIGHT - bar_height, SCREEN_WIDTH * data->progress, SCREEN_HEIGHT, C_WHITE); + UG_FillFrame(0, SCREEN_HEIGHT - bar_height, data->filled_width, SCREEN_HEIGHT, C_WHITE); ui_util_component_render_subcomponents(component); } @@ -49,8 +48,8 @@ component_t* progress_create(const char* title) return component; } -void progress_set(component_t* component, float progress) +void progress_set_fraction(component_t* component, uint32_t numerator, uint32_t denominator) { data_t* data = (data_t*)component->data; - data->progress = progress; + data->filled_width = (uint16_t)((uint32_t)SCREEN_WIDTH * numerator / denominator); } diff --git a/src/ui/components/progress.h b/src/ui/components/progress.h index 9ea2425327..caec5b3f80 100644 --- a/src/ui/components/progress.h +++ b/src/ui/components/progress.h @@ -11,9 +11,9 @@ component_t* progress_create(const char* title); /** - * Set the progress. - * @param[in] progress value must be in [0, 1]. + * Set the progress as an exact fraction. + * @param[in] denominator must be non-zero. */ -void progress_set(component_t* component, float progress); +void progress_set_fraction(component_t* component, uint32_t numerator, uint32_t denominator); #endif