Skip to content

Commit 757e4a2

Browse files
committed
Merge branch 'strftime'
2 parents 6620ee3 + 2fa257c commit 757e4a2

File tree

10 files changed

+135
-97
lines changed

10 files changed

+135
-97
lines changed

.ci/ci

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ if arm-none-eabi-nm build/bin/firmware.elf | grep -q "float_to_decimal_common_sh
5959
echo "Use something simpler like (float*10).round() as u64, then format with util::decimal::format"
6060
exit 1
6161
fi
62+
if arm-none-eabi-nm build/bin/firmware.elf | grep -q "strftime"; then
63+
echo "strftime adds significant binary bloat. Use custom formatting like in `format_dateimte()`."
64+
exit 1
65+
fi
6266

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

src/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,6 @@ add_custom_target(rust-bindgen
296296
--with-derive-default
297297
--ctypes-prefix util::c_types
298298
--allowlist-function bip32_derive_xpub
299-
--allowlist-function strftime
300299
--allowlist-function localtime
301300
--allowlist-function wally_free_string
302301
--allowlist-function mock_memory_factoryreset

src/rust/bitbox02-rust/src/backup.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,20 @@ pub fn create(
228228
let dir = id(seed);
229229
let files = bitbox02::sd::list_subdir(Some(&dir)).or(Err(Error::SdList))?;
230230

231+
let filename_datetime = {
232+
let tm = bitbox02::get_datetime(backup_create_timestamp).map_err(|_| Error::Generic)?;
233+
format!(
234+
"{}_{}T{}-{}-{}Z",
235+
tm.weekday(),
236+
tm.date(),
237+
tm.hour(),
238+
tm.minute(),
239+
tm.second()
240+
)
241+
};
242+
231243
for i in 0..3 {
232-
let filename = format!(
233-
"backup_{}_{}.bin",
234-
bitbox02::strftime(backup_create_timestamp, "%a_%Y-%m-%dT%H-%M-%SZ"),
235-
i,
236-
);
244+
let filename = format!("backup_{}_{}.bin", filename_datetime, i,);
237245
// Timestamp must be different from an existing backup when recreating a backup, otherwise
238246
// we might end up corrupting the existing backup.
239247
if files.contains(&filename) {

src/rust/bitbox02-rust/src/hww.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ mod tests {
373373
UI_COUNTER += 1;
374374
UI_COUNTER
375375
} {
376-
1 => assert_eq!(params.body, "<date>"),
376+
1 => assert_eq!(params.body, "Mon 2020-09-28"),
377377
2 => assert_eq!(params.body, "Proceed to upgrade?"),
378378
_ => panic!("too many dialogs"),
379379
}
@@ -467,7 +467,7 @@ mod tests {
467467
UI_COUNTER += 1;
468468
UI_COUNTER
469469
} {
470-
1 => assert_eq!(params.body, "<date>"),
470+
1 => assert_eq!(params.body, "Mon 2020-09-28"),
471471
2 => assert_eq!(params.title, "RESET"),
472472
3 => assert_eq!(params.body, "Restore backup?"),
473473
4 => assert!(params.body.starts_with("Name: test device name.")),

src/rust/bitbox02-rust/src/hww/api/backup.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,10 @@ pub async fn create(
7575
timezone_offset,
7676
}: &pb::CreateBackupRequest,
7777
) -> Result<Response, Error> {
78-
const MAX_EAST_UTC_OFFSET: i32 = 50400; // 14 hours in seconds
79-
const MAX_WEST_UTC_OFFSET: i32 = -43200; // 12 hours in seconds
80-
81-
if !(MAX_WEST_UTC_OFFSET..=MAX_EAST_UTC_OFFSET).contains(&timezone_offset) {
82-
return Err(Error::InvalidInput);
83-
}
84-
8578
confirm::confirm(&confirm::Params {
8679
title: "Is today?",
87-
body: &bitbox02::format_datetime(timestamp, timezone_offset, true),
80+
body: &bitbox02::format_datetime(timestamp, timezone_offset, true)
81+
.map_err(|_| Error::InvalidInput)?,
8882
..Default::default()
8983
})
9084
.await?;
@@ -174,7 +168,7 @@ mod tests {
174168
mock(Data {
175169
sdcard_inserted: Some(true),
176170
ui_confirm_create: Some(Box::new(|params| {
177-
assert_eq!(params.body, "<date>");
171+
assert_eq!(params.body, "Mon 2020-09-28");
178172
true
179173
})),
180174
..Default::default()

src/rust/bitbox02-rust/src/hww/api/restore.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ pub async fn from_file(request: &pb::RestoreBackupRequest) -> Result<Response, E
4747
#[cfg(feature = "app-u2f")]
4848
{
4949
let datetime_string =
50-
bitbox02::format_datetime(request.timestamp, request.timezone_offset, false);
50+
bitbox02::format_datetime(request.timestamp, request.timezone_offset, false)
51+
.map_err(|_| Error::InvalidInput)?;
5152
let params = confirm::Params {
5253
title: "Is now?",
5354
body: &datetime_string,
@@ -92,7 +93,8 @@ pub async fn from_mnemonic(
9293
) -> Result<Response, Error> {
9394
#[cfg(feature = "app-u2f")]
9495
{
95-
let datetime_string = bitbox02::format_datetime(timestamp, timezone_offset, false);
96+
let datetime_string = bitbox02::format_datetime(timestamp, timezone_offset, false)
97+
.map_err(|_| Error::InvalidInput)?;
9698
confirm::confirm(&confirm::Params {
9799
title: "Is now?",
98100
body: &datetime_string,

src/rust/bitbox02/src/lib.rs

Lines changed: 109 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ pub mod secp256k1;
4848
pub mod securechip;
4949
pub mod ui;
5050

51+
use ::util::c_types::c_int;
5152
use core::time::Duration;
5253

5354
pub use bitbox02_sys::buffer_t;
@@ -116,45 +117,103 @@ pub fn reset(status: bool) {
116117
unsafe { bitbox02_sys::reset_reset(status) }
117118
}
118119

119-
pub fn strftime(timestamp: u32, format: &str) -> String {
120-
let mut out = [0u8; 100];
121-
unsafe {
122-
bitbox02_sys::strftime(
123-
out.as_mut_ptr(),
124-
out.len() as _,
125-
crate::util::str_to_cstr_vec(format).unwrap().as_ptr(),
126-
bitbox02_sys::localtime(&(timestamp as bitbox02_sys::time_t)),
127-
);
128-
}
129-
crate::util::str_from_null_terminated(&out[..])
130-
.unwrap()
131-
.into()
120+
pub struct Tm {
121+
tm: bitbox02_sys::tm,
132122
}
133123

134-
#[cfg(not(feature = "testing"))]
135-
pub fn format_datetime(timestamp: u32, timezone_offset: i32, date_only: bool) -> String {
136-
let mut out = [0u8; 100];
137-
unsafe {
138-
bitbox02_sys::util_format_datetime(
139-
timestamp,
140-
timezone_offset,
141-
date_only,
142-
out.as_mut_ptr(),
143-
out.len() as _,
124+
fn range(low: c_int, item: c_int, high: c_int) -> c_int {
125+
core::cmp::max(low, core::cmp::min(item, high))
126+
}
127+
128+
impl Tm {
129+
/// Returns the weekday, one of "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
130+
pub fn weekday(&self) -> String {
131+
// Same as '%a' in strftime:
132+
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#435
133+
let wday = self.tm.tm_wday;
134+
if !(0..=6).contains(&wday) {
135+
return "?".into();
136+
}
137+
["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][wday as usize].into()
138+
}
139+
140+
/// Returns 'year-month-day', e.g. 2024-07-16, equivalent of '%Y-%m-%d' in strftime.
141+
pub fn date(&self) -> String {
142+
// Same as strftime:
143+
// %Y - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L712
144+
// %m - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L600
145+
// %d - https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L498
146+
format!(
147+
"{}-{:02}-{:02}",
148+
1900 + self.tm.tm_year,
149+
range(0, self.tm.tm_mon, 11) + 1,
150+
range(1, self.tm.tm_mday, 31)
144151
)
145152
}
146-
crate::util::str_from_null_terminated(&out[..])
147-
.unwrap()
148-
.into()
153+
154+
/// Returns the zero-padded hour from 00-23, e.g. "07".
155+
pub fn hour(&self) -> String {
156+
// Same as '%H' in strftime:
157+
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#582
158+
format!("{:02}", range(0, self.tm.tm_hour, 23))
159+
}
160+
161+
/// Returns the zero-padded minute from 00-59, e.g. "07".
162+
pub fn minute(&self) -> String {
163+
// Same as '%M' in strftime:
164+
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L605
165+
format!("{:02}", range(0, self.tm.tm_min, 59))
166+
}
167+
168+
/// Returns the zero-padded second from 00-60, e.g. "07".
169+
pub fn second(&self) -> String {
170+
// Same as '%S' in strftime:
171+
// https://github.com/arnoldrobbins/strftime/blob/2011b7e82365d25220b8949e252eb5f28c0994cd/strftime.c#L645
172+
format!("{:02}", range(0, self.tm.tm_sec, 60))
173+
}
149174
}
150175

151-
#[cfg(feature = "testing")]
152-
pub fn format_datetime(_timestamp: u32, _timezone_offset: i32, date_only: bool) -> String {
153-
if date_only {
154-
"<date>".into()
155-
} else {
156-
"<datetime>".into()
176+
pub fn get_datetime(timestamp: u32) -> Result<Tm, ()> {
177+
Ok(Tm {
178+
tm: unsafe {
179+
let localtime = bitbox02_sys::localtime(&(timestamp as bitbox02_sys::time_t));
180+
if localtime.is_null() {
181+
return Err(());
182+
}
183+
184+
*localtime
185+
},
186+
})
187+
}
188+
189+
/// Formats the timestamp in the local timezone.
190+
/// timestamp is the unix timestamp in seconds.
191+
/// timezone_offset is added to the timestamp, timezone part.
192+
/// date_only: if true, only the date is formatted. If false, both date and time are.
193+
pub fn format_datetime(
194+
timestamp: u32,
195+
timezone_offset: i32,
196+
date_only: bool,
197+
) -> Result<String, ()> {
198+
const MAX_EAST_UTC_OFFSET: i32 = 50400; // 14 hours in seconds
199+
const MAX_WEST_UTC_OFFSET: i32 = -43200; // 12 hours in seconds
200+
201+
if !(MAX_WEST_UTC_OFFSET..=MAX_EAST_UTC_OFFSET).contains(&timezone_offset) {
202+
return Err(());
157203
}
204+
let ts = ((timestamp as i64) + (timezone_offset as i64)) as u32;
205+
let tm = get_datetime(ts)?;
206+
Ok(if date_only {
207+
format!("{} {}", tm.weekday(), tm.date())
208+
} else {
209+
format!(
210+
"{} {}\n{}:{}",
211+
tm.weekday(),
212+
tm.date(),
213+
tm.hour(),
214+
tm.minute()
215+
)
216+
})
158217
}
159218

160219
#[cfg(not(feature = "testing"))]
@@ -201,10 +260,25 @@ mod tests {
201260
use super::*;
202261

203262
#[test]
204-
fn test_strftime() {
263+
fn test_format_datetime() {
205264
assert_eq!(
206-
strftime(1601281809, "%a %Y-%m-%d\n%H:%M").as_str(),
207-
"Mon 2020-09-28\n08:30",
265+
format_datetime(1601281809, 0, true),
266+
Ok("Mon 2020-09-28".into())
208267
);
268+
assert_eq!(
269+
format_datetime(1601281809, 0, false),
270+
Ok("Mon 2020-09-28\n08:30".into()),
271+
);
272+
assert_eq!(
273+
format_datetime(1601281809, 18000, false),
274+
Ok("Mon 2020-09-28\n13:30".into()),
275+
);
276+
assert_eq!(
277+
format_datetime(1601281809, -32400, false),
278+
Ok("Sun 2020-09-27\n23:30".into()),
279+
);
280+
281+
assert!(format_datetime(1601281809, 50401, false).is_err());
282+
assert!(format_datetime(1601281809, -43201, false).is_err());
209283
}
210284
}

src/util.c

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,3 @@ void util_cleanup_64(uint8_t** buf)
6868
{
6969
util_zero(*buf, 64);
7070
}
71-
72-
void util_format_datetime(
73-
uint32_t timestamp,
74-
int32_t timezone_offset,
75-
bool date_only,
76-
char* out,
77-
size_t out_size)
78-
{
79-
time_t local_timestamp = timestamp + timezone_offset;
80-
struct tm* local_time = localtime(&local_timestamp);
81-
strftime(out, out_size, date_only ? "%a %Y-%m-%d" : "%a %Y-%m-%d\n%H:%M", local_time);
82-
}

src/util.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,4 @@ typedef struct {
145145
*/
146146
typedef enum { ASYNC_OP_TRUE, ASYNC_OP_FALSE, ASYNC_OP_NOT_READY } async_op_result_t;
147147

148-
/**
149-
* Formats the timestamp in the local timezone.
150-
* @param[in] timestamp unix timestamp in seconds.
151-
* @param[in] timezone_offset is added to the timestamp, timezone part.
152-
* @param[in] date_only if true, only the date is formatted. If false, both date and time are
153-
* formatted.
154-
*/
155-
void util_format_datetime(
156-
uint32_t timestamp,
157-
int32_t timezone_offset,
158-
bool date_only,
159-
char* out,
160-
size_t out_size);
161-
162148
#endif

test/unit-test/test_util.c

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -50,27 +50,10 @@ static void test_minmax(void** state)
5050
assert_int_not_equal(res, 5);
5151
}
5252

53-
static void test_util_format_datetime(void** state)
54-
{
55-
char out[100];
56-
util_format_datetime(1601281809, 0, true, out, sizeof(out));
57-
assert_string_equal(out, "Mon 2020-09-28");
58-
59-
util_format_datetime(1601281809, 0, false, out, sizeof(out));
60-
assert_string_equal(out, "Mon 2020-09-28\n08:30");
61-
62-
util_format_datetime(1601281809, 18000, false, out, sizeof(out));
63-
assert_string_equal(out, "Mon 2020-09-28\n13:30");
64-
65-
util_format_datetime(1601281809, -32400, false, out, sizeof(out));
66-
assert_string_equal(out, "Sun 2020-09-27\n23:30");
67-
}
68-
6953
int main(void)
7054
{
7155
const struct CMUnitTest tests[] = {
7256
cmocka_unit_test(test_minmax),
73-
cmocka_unit_test(test_util_format_datetime),
7457
};
7558
return cmocka_run_group_tests(tests, NULL, NULL);
7659
}

0 commit comments

Comments
 (0)