Skip to content

Commit

Permalink
util/format_datetime: stop using strftime
Browse files Browse the repository at this point in the history
This function takes up ~4kB of binary space, which is huge. The reason
is that it handles every formatting char even though we only use a few
like '%Y', '%M', etc.

This reduces the firmware binary size by 3628 bytes.
  • Loading branch information
benma committed Jul 17, 2024
1 parent 0354821 commit 2fa257c
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 35 deletions.
4 changes: 4 additions & 0 deletions .ci/ci
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ if arm-none-eabi-nm build/bin/firmware.elf | grep -q "float_to_decimal_common_sh
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

(cd tools/atecc608; go test ./...)

Expand Down
1 change: 0 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,6 @@ add_custom_target(rust-bindgen
--with-derive-default
--ctypes-prefix util::c_types
--allowlist-function bip32_derive_xpub
--allowlist-function strftime
--allowlist-function localtime
--allowlist-function wally_free_string
--allowlist-function mock_memory_factoryreset
Expand Down
18 changes: 13 additions & 5 deletions src/rust/bitbox02-rust/src/backup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,12 +228,20 @@ pub fn create(
let dir = id(seed);
let files = bitbox02::sd::list_subdir(Some(&dir)).or(Err(Error::SdList))?;

let filename_datetime = {
let tm = bitbox02::get_datetime(backup_create_timestamp).map_err(|_| Error::Generic)?;
format!(
"{}_{}T{}-{}-{}Z",
tm.weekday(),
tm.date(),
tm.hour(),
tm.minute(),
tm.second()
)
};

for i in 0..3 {
let filename = format!(
"backup_{}_{}.bin",
bitbox02::strftime(backup_create_timestamp, "%a_%Y-%m-%dT%H-%M-%SZ"),
i,
);
let filename = format!("backup_{}_{}.bin", filename_datetime, i,);
// Timestamp must be different from an existing backup when recreating a backup, otherwise
// we might end up corrupting the existing backup.
if files.contains(&filename) {
Expand Down
109 changes: 80 additions & 29 deletions src/rust/bitbox02/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub mod secp256k1;
pub mod securechip;
pub mod ui;

use ::util::c_types::c_int;
use core::time::Duration;

pub use bitbox02_sys::buffer_t;
Expand Down Expand Up @@ -116,19 +117,73 @@ pub fn reset(status: bool) {
unsafe { bitbox02_sys::reset_reset(status) }
}

pub fn strftime(timestamp: u32, format: &str) -> String {
let mut out = [0u8; 100];
unsafe {
bitbox02_sys::strftime(
out.as_mut_ptr(),
out.len() as _,
crate::util::str_to_cstr_vec(format).unwrap().as_ptr(),
bitbox02_sys::localtime(&(timestamp as bitbox02_sys::time_t)),
);
pub struct Tm {
tm: bitbox02_sys::tm,
}

fn range(low: c_int, item: c_int, high: c_int) -> c_int {
core::cmp::max(low, core::cmp::min(item, high))
}

impl Tm {
/// Returns the weekday, one of "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
pub fn weekday(&self) -> String {
// Same as '%a' in strftime:
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#435
let wday = self.tm.tm_wday;
if !(0..=6).contains(&wday) {
return "?".into();
}
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][wday as usize].into()
}

/// Returns 'year-month-day', e.g. 2024-07-16, equivalent of '%Y-%m-%d' in strftime.
pub fn date(&self) -> String {
// Same as strftime:
// %Y - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L712
// %m - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L600
// %d - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L498
format!(
"{}-{:02}-{:02}",
1900 + self.tm.tm_year,
range(0, self.tm.tm_mon, 11) + 1,
range(1, self.tm.tm_mday, 31)
)
}

/// Returns the zero-padded hour from 00-23, e.g. "07".
pub fn hour(&self) -> String {
// Same as '%H' in strftime:
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#582
format!("{:02}", range(0, self.tm.tm_hour, 23))
}

/// Returns the zero-padded minute from 00-59, e.g. "07".
pub fn minute(&self) -> String {
// Same as '%M' in strftime:
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L605
format!("{:02}", range(0, self.tm.tm_min, 59))
}
crate::util::str_from_null_terminated(&out[..])
.unwrap()
.into()

/// Returns the zero-padded second from 00-60, e.g. "07".
pub fn second(&self) -> String {
// Same as '%S' in strftime:
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L645
format!("{:02}", range(0, self.tm.tm_sec, 60))
}
}

pub fn get_datetime(timestamp: u32) -> Result<Tm, ()> {
Ok(Tm {
tm: unsafe {
let localtime = bitbox02_sys::localtime(&(timestamp as bitbox02_sys::time_t));
if localtime.is_null() {
return Err(());
}

*localtime
},
})
}

/// Formats the timestamp in the local timezone.
Expand All @@ -146,15 +201,19 @@ pub fn format_datetime(
if !(MAX_WEST_UTC_OFFSET..=MAX_EAST_UTC_OFFSET).contains(&timezone_offset) {
return Err(());
}

Ok(strftime(
((timestamp as i64) + (timezone_offset as i64)) as u32,
if date_only {
"%a %Y-%m-%d"
} else {
"%a %Y-%m-%d\n%H:%M"
},
))
let ts = ((timestamp as i64) + (timezone_offset as i64)) as u32;
let tm = get_datetime(ts)?;
Ok(if date_only {
format!("{} {}", tm.weekday(), tm.date())
} else {
format!(
"{} {}\n{}:{}",
tm.weekday(),
tm.date(),
tm.hour(),
tm.minute()
)
})
}

#[cfg(not(feature = "testing"))]
Expand Down Expand Up @@ -200,14 +259,6 @@ pub fn println_stdout(msg: &str) {
mod tests {
use super::*;

#[test]
fn test_strftime() {
assert_eq!(
strftime(1601281809, "%a %Y-%m-%d\n%H:%M").as_str(),
"Mon 2020-09-28\n08:30",
);
}

#[test]
fn test_format_datetime() {
assert_eq!(
Expand Down

0 comments on commit 2fa257c

Please sign in to comment.