Skip to content

Commit 2765f56

Browse files
committed
Merge branch 'random-name'
2 parents 436195d + fc21385 commit 2765f56

File tree

4 files changed

+142
-10
lines changed

4 files changed

+142
-10
lines changed

src/memory/memory.c

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,16 +250,55 @@ bool memory_set_device_name(const char* name)
250250
_read_chunk(CHUNK_1, chunk_bytes);
251251
util_zero(chunk.fields.device_name, sizeof(chunk.fields.device_name));
252252
snprintf((char*)&chunk.fields.device_name, MEMORY_DEVICE_NAME_MAX_LEN, "%s", name);
253+
254+
if (!rust_util_is_name_valid(chunk.fields.device_name, MEMORY_DEVICE_NAME_MAX_LEN)) {
255+
return false;
256+
}
253257
return _write_chunk(CHUNK_1, chunk.bytes);
254258
}
255259

260+
static void _random_name(char* name_out)
261+
{
262+
static char cached_name[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
263+
264+
if (cached_name[0] == 0x00) {
265+
// Generate 4 random uppercase letters
266+
uint8_t random[32] = {0};
267+
_interface_functions->random_32_bytes(random);
268+
uint8_t letters[4];
269+
for (size_t i = 0; i < sizeof(letters); i++) {
270+
letters[i] = 'A' + (random[i] % 26);
271+
}
272+
273+
// Format into cached name
274+
snprintf(
275+
cached_name,
276+
MEMORY_DEVICE_NAME_MAX_LEN,
277+
"BitBox %c%c%c%c",
278+
letters[0],
279+
letters[1],
280+
letters[2],
281+
letters[3]);
282+
}
283+
284+
// Copy cached result to output
285+
snprintf(name_out, MEMORY_DEVICE_NAME_MAX_LEN, "%s", cached_name);
286+
}
287+
256288
void memory_get_device_name(char* name_out)
257289
{
258290
chunk_1_t chunk = {0};
259291
CLEANUP_CHUNK(chunk);
260292
_read_chunk(CHUNK_1, chunk_bytes);
261-
if (chunk.fields.device_name[0] == 0xFF) {
262-
snprintf(name_out, MEMORY_DEVICE_NAME_MAX_LEN, "%s", MEMORY_DEFAULT_DEVICE_NAME);
293+
if (chunk.fields.device_name[0] == 0xFF ||
294+
!rust_util_is_name_valid(chunk.fields.device_name, MEMORY_DEVICE_NAME_MAX_LEN)) {
295+
if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
296+
// For Bluetooth, we want to use an unambiguous default name so this BitBox can be
297+
// identified if multiple BitBoxes are advertising at the same time.
298+
_random_name(name_out);
299+
} else {
300+
snprintf(name_out, MEMORY_DEVICE_NAME_MAX_LEN, "%s", MEMORY_DEFAULT_DEVICE_NAME);
301+
}
263302
} else {
264303
snprintf(name_out, MEMORY_DEVICE_NAME_MAX_LEN, "%s", chunk.fields.device_name);
265304
}

src/memory/memory.h

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,17 @@ extern const char* MEMORY_DEFAULT_DEVICE_NAME;
6666
// memory.c)
6767
#define MEMORY_DEVICE_NAME_MAX_LEN (64)
6868

69-
// set device name. name is an utf8-encoded string, and null terminated. The max
70-
// size (including the null terminator) is MEMORY_DEVICE_NAME_MAX_LEN bytes.
69+
// set device name. name is null terminated. The name must be smaller or equal to
70+
// MEMORY_DEVICE_NAME_MAX_LEN (including the null terminator) and larger than 0 in size, consist of
71+
// printable ASCII characters only (and space), not start or end with whitespace, and contain no
72+
// whitespace other than space.
7173
USE_RESULT bool memory_set_device_name(const char* name);
7274

73-
// name_out must have MEMORY_DEVICE_NAME_MAX_LEN bytes in size. Returns `MEMORY_DEFAULT_DEVICE_NAME`
74-
// if no device name is set.
75+
// name_out must have MEMORY_DEVICE_NAME_MAX_LEN bytes in size. If no device name is set, or if it
76+
// is invalid, we return:
77+
// - `MEMORY_DEFAULT_DEVICE_NAME` for non-bluetooth enabled BitBoxes
78+
// - "BitBox ABCD" for Bluetooth-enabled BitBoxes, where ABCD are four random uppercase letters.
79+
// The name is cached in RAM, so the same random name is returned until reboot.
7580
void memory_get_device_name(char* name_out);
7681

7782
/**

src/rust/bitbox02-rust-c/src/util.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,24 @@ pub extern "C" fn rust_util_zero(mut dst: BytesMut) {
2525
util::zero(dst.as_mut())
2626
}
2727

28+
/// Calls `util::name::validate()` on the provided C string.
29+
/// SAFETY:
30+
/// `buf` must point to a valid buffer of size `max_len`.
31+
#[no_mangle]
32+
pub unsafe extern "C" fn rust_util_is_name_valid(buf: *const u8, max_len: usize) -> bool {
33+
if max_len == 0 {
34+
return false;
35+
}
36+
let slice = core::slice::from_raw_parts(buf, max_len);
37+
match core::ffi::CStr::from_bytes_until_nul(slice) {
38+
Ok(cstr) => match cstr.to_str() {
39+
Ok(s) => util::name::validate(s, max_len - 1),
40+
Err(_) => false,
41+
},
42+
Err(_) => false,
43+
}
44+
}
45+
2846
/// Convert bytes to hex representation
2947
///
3048
/// * `buf` - bytes to convert to hex.
@@ -202,4 +220,22 @@ mod tests {
202220
let expected = b"LUC1eAJa5jW\0";
203221
assert_eq!(&result_buf[..expected.len()], expected);
204222
}
223+
224+
#[test]
225+
// For clarity we want explicit null terminators in the strings below, not CStr literals
226+
// `c"..."`.
227+
#[allow(clippy::manual_c_str_literals)]
228+
fn test_rust_util_is_name_valid() {
229+
unsafe {
230+
// Valid
231+
assert!(rust_util_is_name_valid("foo\0".as_ptr(), 4));
232+
assert!(rust_util_is_name_valid("foo\0........".as_ptr(), 12));
233+
234+
// Invalid
235+
assert!(!rust_util_is_name_valid("fo\no\0".as_ptr(), 5));
236+
assert!(!rust_util_is_name_valid("".as_ptr(), 0));
237+
assert!(!rust_util_is_name_valid("foo\0".as_ptr(), 3));
238+
assert!(!rust_util_is_name_valid("foo".as_ptr(), 3));
239+
}
240+
}
205241
}

test/unit-test/test_memory.c

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,13 @@
1818
#include <cmocka.h>
1919

2020
#include <memory/memory.h>
21+
#include <memory/memory_shared.h>
2122

2223
#include <stdint.h>
2324
#include <stdio.h>
2425
#include <string.h>
2526

26-
#define CHUNK_SIZE (16 * 512) // 8kB.
27-
2827
#define FLASH_APP_DATA_LEN (0x000010000)
29-
#define FLASH_SHARED_DATA_START (0xe000)
3028

3129
// chunk 0
3230
static const int _addr_factory_setup_done = 0;
@@ -386,18 +384,70 @@ static void _test_memory_get_device_name_default(void** state)
386384
EMPTYCHUNK(empty_chunk);
387385
expect_value(__wrap_memory_read_chunk_mock, chunk_num, 1);
388386
will_return(__wrap_memory_read_chunk_mock, empty_chunk);
387+
388+
EMPTYCHUNK(empty_shared_chunk);
389+
will_return(__wrap_memory_read_shared_bootdata_mock, empty_shared_chunk);
390+
389391
memory_get_device_name(name_out);
390392
assert_string_equal(MEMORY_DEFAULT_DEVICE_NAME, name_out);
391393
}
392394

393-
static void _test_memory_get_device_name(void** state)
395+
// For Bluetooth devices (BitBox02+), the default name is "BitBox ABCD" where ABCD are four random
396+
// uppercase letters.
397+
static void _test_memory_get_device_name_default_bluetooth(void** state)
398+
{
399+
uint8_t entropy[32] = {0};
400+
memcpy(entropy, "\x00\x19\xFE\xFF", 4);
401+
402+
char name_out[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
403+
EMPTYCHUNK(empty_chunk);
404+
expect_value(__wrap_memory_read_chunk_mock, chunk_num, 1);
405+
will_return(__wrap_memory_read_chunk_mock, empty_chunk);
406+
407+
EMPTYCHUNK(empty_shared_chunk);
408+
chunk_shared_t shared_chunk = {0};
409+
memcpy(shared_chunk.bytes, empty_shared_chunk, CHUNK_SIZE);
410+
shared_chunk.fields.platform = MEMORY_PLATFORM_BITBOX02_PLUS;
411+
will_return(__wrap_memory_read_shared_bootdata_mock, shared_chunk.bytes);
412+
413+
will_return(_mock_random_32_bytes, entropy);
414+
415+
memory_get_device_name(name_out);
416+
assert_string_equal("BitBox AZUV", name_out);
417+
418+
// Calling it again does not re-generate a new random name but reuses the already generated one.
419+
expect_value(__wrap_memory_read_chunk_mock, chunk_num, 1);
420+
will_return(__wrap_memory_read_chunk_mock, empty_chunk);
421+
will_return(__wrap_memory_read_shared_bootdata_mock, shared_chunk.bytes);
422+
memory_get_device_name(name_out);
423+
assert_string_equal("BitBox AZUV", name_out);
424+
}
425+
426+
static void _test_memory_get_device_name_invalid(void** state)
394427
{
395428
char name_out[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
396429
EMPTYCHUNK(chunk);
397430
memset(chunk + _addr_device_name, 0, MEMORY_DEVICE_NAME_MAX_LEN);
398431
const char* device_name = "Äxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxx 漢字xxxxxxxxxxxxxxxxx";
399432
snprintf((char*)chunk + _addr_device_name, MEMORY_DEVICE_NAME_MAX_LEN, "%s", device_name);
400433

434+
EMPTYCHUNK(empty_shared_chunk);
435+
will_return(__wrap_memory_read_shared_bootdata_mock, empty_shared_chunk);
436+
437+
expect_value(__wrap_memory_read_chunk_mock, chunk_num, 1);
438+
will_return(__wrap_memory_read_chunk_mock, chunk);
439+
memory_get_device_name(name_out);
440+
assert_string_equal(MEMORY_DEFAULT_DEVICE_NAME, name_out);
441+
}
442+
443+
static void _test_memory_get_device_name(void** state)
444+
{
445+
char name_out[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
446+
EMPTYCHUNK(chunk);
447+
memset(chunk + _addr_device_name, 0, MEMORY_DEVICE_NAME_MAX_LEN);
448+
const char* device_name = "foo bar";
449+
snprintf((char*)chunk + _addr_device_name, MEMORY_DEVICE_NAME_MAX_LEN, "%s", device_name);
450+
401451
expect_value(__wrap_memory_read_chunk_mock, chunk_num, 1);
402452
will_return(__wrap_memory_read_chunk_mock, chunk);
403453
memory_get_device_name(name_out);
@@ -527,6 +577,8 @@ int main(void)
527577
cmocka_unit_test(_test_memory_set_mnemonic_passphrase_enabled),
528578
cmocka_unit_test(_test_memory_reset_hww),
529579
cmocka_unit_test(_test_memory_get_device_name_default),
580+
cmocka_unit_test(_test_memory_get_device_name_default_bluetooth),
581+
cmocka_unit_test(_test_memory_get_device_name_invalid),
530582
cmocka_unit_test(_test_memory_get_device_name),
531583
cmocka_unit_test(_test_memory_device_name),
532584
cmocka_unit_test(_test_memory_set_seed_birthdate),

0 commit comments

Comments
 (0)