What changed, and why it matters
This commit rewrites the BitBox02 hardware wallet's main firmware loop from C to Rust. It is a large refactoring change that moves core USB, Bluetooth/UART, and workflow handling into Rust. The change itself is not a security fix and does not introduce an obvious vulnerability in the diff, but any rewrite of security-critical firmware code carries a risk of subtle behavioral differences or memory-safety edge cases at the C/Rust boundary. The commit message and diff do not describe any security issue or credit a researcher.
Treat this as a high-risk refactoring rather than a vulnerability patch. Review the Rust FFI wrappers for pointer/lifetime correctness, ensure the main loop's ordering and timing behavior matches the C original (especially USB/UART interleaving and BLE power-down flushing), run full device and simulator regression tests, and audit the U2F simulator removal for any loss of test coverage. No immediate patching of a known flaw is indicated.
Security signals we found
Large language rewrite of security-critical main loop (C → Rust)
New unsafe FFI bindings and raw pointer conversions at C/Rust boundary
Use of MaybeUninit::zeroed().assume_init() for USB_FRAME structs
Removal of U2F workflow handling from simulator code paths
Compile-time size assertion added for USB report/frame consistency
Evidence from the diff
The patch deletes src/firmware_main_loop.c and replaces it with a Rust implementation in src/rust/bitbox02-rust/src/main_loop.rs, invoked via a new rust_main_loop() C ABI entry point. It adds thin Rust wrappers around existing C APIs for UART, USB HID (HWW/U2F), USB processing, DA14531 BLE protocol, queues, and U2F packet handling. Several modules are newly exposed outside the simulator-graphical feature flag. The C main() now calls rust_main_loop() instead of firmware_main_loop(). Simulators are adjusted: U2F workflows are removed from simulators, and graphical simulators use a new process_from_report() helper for raw 64-byte USB reports. A compile-time assertion enforces that USB_REPORT_SIZE equals sizeof(USB_FRAME).
Changed components
src/firmware_main_loop.c (deleted)src/rust/bitbox02-rust/src/main_loop.rs (new)src/rust/bitbox02/src/uart.rssrc/rust/bitbox02/src/usb_packet.rssrc/rust/bitbox02/src/usb_processing.rssrc/rust/bitbox02/src/hid_hww.rssrc/rust/bitbox02/src/hid_u2f.rssrc/rust/bitbox02/src/da14531_protocol.rssrc/rust/bitbox02/src/da14531_handler.rssrc/rust/bitbox02/src/queue.rssrc/rust/bitbox02/src/u2f_packet.rstest/simulator/simulator.ctest/simulator-graphical/src/main.rstest/simulator-graphical-bb03/src/main.rsInspect captured patch +442 / −265
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 80643a1..d4217e1 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,7 +3,6 @@
# Copyright (c) 2015-2016 Lucas Betschart, Douglas J. Bakkum
set(DBB-FIRMWARE-SOURCES
- ${CMAKE_SOURCE_DIR}/src/firmware_main_loop.c
${CMAKE_SOURCE_DIR}/src/delay.c
${CMAKE_SOURCE_DIR}/src/random.c
${CMAKE_SOURCE_DIR}/src/hardfault.c
diff --git a/src/firmware.c b/src/firmware.c
index cf99476..4ddc6f4 100644
--- a/src/firmware.c
+++ b/src/firmware.c
@@ -3,7 +3,6 @@
#include "common_main.h"
#include "da14531/da14531_protocol.h"
#include "driver_init.h"
-#include "firmware_main_loop.h"
#include "hardfault.h"
#include "memory/bitbox02_smarteeprom.h"
#include "memory/memory_shared.h"
@@ -15,6 +14,7 @@
#include "usb/usb_processing.h"
#include <hww.h>
#include <memory/memory_spi.h>
+#include <rust/rust.h>
#include <ui/oled/oled.h>
#if APP_U2F == 1
@@ -43,6 +43,6 @@ int main(void)
#if APP_U2F == 1
u2f_device_setup();
#endif
- firmware_main_loop();
+ rust_main_loop();
return 0;
}
diff --git a/src/firmware_main_loop.c b/src/firmware_main_loop.c
deleted file mode 100644
index faa58a9..0000000
--- a/src/firmware_main_loop.c
+++ /dev/null
@@ -1,191 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#include "firmware_main_loop.h"
-
-#include "da14531/da14531.h"
-#include "da14531/da14531_handler.h"
-#include "da14531/da14531_protocol.h"
-#include "driver_init.h"
-#include "hardfault.h"
-#include "hid_hww.h"
-#include "hww.h"
-#include "memory/memory.h"
-#include "memory/memory_shared.h"
-#include "touch/gestures.h"
-#include "uart.h"
-#include "ui/screen_process.h"
-#include "ui/screen_stack.h"
-#include "usb/class/hid/hww/hid_hww.h"
-#include "usb/usb.h"
-#include "usb/usb_frame.h"
-#include "usb/usb_processing.h"
-#include <platform/platform_init.h>
-#include <rust/rust.h>
-#include <ui/fonts/monogram_5X9.h>
-#include <utils_ringbuffer.h>
-#if APP_U2F == 1
- #include "u2f.h"
- #include "u2f/u2f_packet.h"
- #include "usb/class/hid/u2f/hid_u2f.h"
-#endif
-
-// Must be power of 2
-#define UART_OUT_BUF_LEN 2048
-
-static void _orientation_screen_poll(struct ringbuffer* uart_write_queue)
-{
- static bool orientation_set = false;
- bool _orientation;
- if (!orientation_set && rust_workflow_orientation_screen_poll(&_orientation)) {
- orientation_set = true;
- // hww handler in usb_process must be setup before we can allow ble connections
- if (memory_get_platform() == MEMORY_PLATFORM_BITBOX02_PLUS) {
- size_t len;
- da14531_handler_current_product = (const uint8_t*)platform_product(&len);
- da14531_handler_current_product_len = len;
- util_log("%s %d", da14531_handler_current_product, da14531_handler_current_product_len);
- da14531_set_product(
- da14531_handler_current_product,
- da14531_handler_current_product_len,
- uart_write_queue);
- }
- usb_start();
- }
-}
-
-void firmware_main_loop(void)
-{
- // Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read
- // out all bytes
- uint8_t uart_read_buf[USART_0_BUFFER_SIZE] = {0};
- uint16_t uart_read_buf_len = 0;
-
- struct ringbuffer uart_write_queue;
- uint8_t uart_write_buf[UART_OUT_BUF_LEN];
- ringbuffer_init(&uart_write_queue, &uart_write_buf, UART_OUT_BUF_LEN);
-
- /// If the bootloader has booted the BLE chip, the BLE chip isn't aware of the name according to
- /// the fw. Send it over.
- char buf[MEMORY_DEVICE_NAME_MAX_LEN] = {0};
- memory_get_device_name(buf);
- da14531_set_name(buf, &uart_write_queue);
-
- // This starts the async orientation screen workflow, which is processed by the loop below.
- rust_workflow_spawn_orientation_screen();
-
- const uint8_t* hww_data = NULL;
- USB_FRAME hww_frame = {0};
-
-#if APP_U2F == 1
- u2f_packet_init();
- const uint8_t* u2f_data = NULL;
- USB_FRAME u2f_frame = {0};
-#endif
-
- if (!memory_ble_enabled()) {
- rust_communication_mode_ble_disable();
- }
-
- while (1) {
- // Do UART I/O
- if (rust_communication_mode_ble_enabled()) {
- if (uart_read_buf_len < sizeof(uart_read_buf) ||
- ringbuffer_num(&uart_write_queue) > 0) {
- uart_poll(
- &uart_read_buf[0],
- sizeof(uart_read_buf),
- &uart_read_buf_len,
- &uart_write_queue);
- }
- }
-
- // Check if there is outgoing data
- if (!hww_data) {
- hww_data = queue_pull(queue_hww_queue());
- }
-#if APP_U2F == 1
- // Generate timeout packets
- uint32_t timeout_cid;
- while (u2f_packet_timeout_get(&timeout_cid)) {
- u2f_packet_timeout(timeout_cid);
- }
- if (!u2f_data) {
- u2f_data = queue_pull(queue_u2f_queue());
- // If USB stack was locked and there is no more messages to send out, time to
- // unlock it.
- if (!u2f_data && usb_processing_locked(usb_processing_u2f())) {
- usb_processing_unlock();
- }
- }
-#endif
- // Do USB Input
- if (!hww_data && hid_hww_read((uint8_t*)&hww_frame)) {
- if (usb_packet_process(&hww_frame)) {
- if (rust_communication_mode_ble_enabled()) {
- // Enqueue a power down command to the da14531
- da14531_power_down(&uart_write_queue);
- // Flush out the power down command. This will be the last UART communication we
- // do.
- while (ringbuffer_num(&uart_write_queue) > 0) {
- uart_poll(NULL, 0, NULL, &uart_write_queue);
- }
- rust_communication_mode_ble_disable();
- }
- } else {
- util_log("usb_packet_process: invalid");
- }
- }
-#if APP_U2F == 1
- if (!u2f_data && hid_u2f_read((uint8_t*)&u2f_frame)) {
- util_log("u2f data %s", util_dbg_hex((void*)&u2f_frame, 16));
- u2f_packet_process(&u2f_frame);
- }
-#endif
-
- // Do UART Output
- if (rust_communication_mode_ble_enabled()) {
- struct da14531_protocol_frame* frame = da14531_protocol_poll(
- &uart_read_buf[0], &uart_read_buf_len, &hww_data, &uart_write_queue);
-
- if (frame) {
- da14531_handler(frame, &uart_write_queue);
- }
- }
-
- // Do USB Output
- if (!rust_communication_mode_ble_enabled() && hww_data) {
- if (hid_hww_write_poll(hww_data)) {
- hww_data = NULL;
- }
- }
-#if APP_U2F == 1
- if (u2f_data) {
- if (hid_u2f_write_poll(u2f_data)) {
- util_log("u2f wrote %s", util_dbg_hex(u2f_data, 16));
- u2f_data = NULL;
- }
- }
-#endif
-
- /* First, process all the incoming USB traffic. */
- usb_processing_process(usb_processing_hww());
-#if APP_U2F == 1
- usb_processing_process(usb_processing_u2f());
-#endif
- /*
- * If USB has generated events at the application level,
- * process them now.
- */
-#if APP_U2F == 1
- u2f_process();
-#endif
-
- screen_process();
- /* And finally, run the high-level event processing. */
-
- rust_workflow_spin();
- rust_async_usb_spin();
-
- _orientation_screen_poll(&uart_write_queue);
- }
-}
diff --git a/src/firmware_main_loop.h b/src/firmware_main_loop.h
deleted file mode 100644
index 3864702..0000000
--- a/src/firmware_main_loop.h
+++ /dev/null
@@ -1,13 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#ifndef _FIRMWARE_MAIN_LOOP_H_
-#define _FIRMWARE_MAIN_LOOP_H_
-
-#include <stdbool.h>
-
-/**
- * Runs the main UI of the bitbox.
- */
-void firmware_main_loop(void);
-
-#endif
diff --git a/src/rust/bitbox02-rust-c/Cargo.toml b/src/rust/bitbox02-rust-c/Cargo.toml
index d91069f..2f8f6f6 100644
--- a/src/rust/bitbox02-rust-c/Cargo.toml
+++ b/src/rust/bitbox02-rust-c/Cargo.toml
@@ -117,4 +117,4 @@ app-cardano = [
factory-setup = []
-rtt = [ "util/rtt" ]
+rtt = ["util/rtt"]
diff --git a/src/rust/bitbox02-rust/src/lib.rs b/src/rust/bitbox02-rust/src/lib.rs
index 0d4b63d..3064b8a 100644
--- a/src/rust/bitbox02-rust/src/lib.rs
+++ b/src/rust/bitbox02-rust/src/lib.rs
@@ -25,6 +25,11 @@ pub mod hal;
pub mod hash;
pub mod hww;
pub mod keystore;
+#[cfg(all(
+ feature = "firmware",
+ not(any(feature = "c-unit-testing", feature = "simulator-graphical"))
+))]
+pub mod main_loop;
pub mod reset;
pub mod salt;
pub mod secp256k1;
diff --git a/src/rust/bitbox02-rust/src/main_loop.rs b/src/rust/bitbox02-rust/src/main_loop.rs
new file mode 100644
index 0000000..b14ec9b
--- /dev/null
+++ b/src/rust/bitbox02-rust/src/main_loop.rs
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use alloc::boxed::Box;
+use bitbox02::ringbuffer::RingBuffer;
+use bitbox02::uart::USART_0_BUFFER_SIZE;
+use bitbox02::usb_packet::USB_FRAME;
+use core::mem::MaybeUninit;
+use core::task::Poll;
+
+const UART_OUT_BUF_LEN: u32 = 2048;
+
+fn orientation_screen_poll(
+ orientation_task: &mut Option<util::bb02_async::Task<'static, bool>>,
+ uart_write_queue: &mut RingBuffer,
+) {
+ if let Some(task) = orientation_task {
+ if let Poll::Ready(_orientation) = util::bb02_async::spin(task) {
+ // hww handler in usb_process must be setup before we can allow ble connections
+ if let Ok(bitbox02::memory::Platform::BitBox02Plus) = bitbox02::memory::get_platform() {
+ let product = bitbox02::platform::product();
+ bitbox02::da14531_handler::set_product(product);
+ bitbox02::da14531::set_product(product, uart_write_queue)
+ }
+ bitbox02::usb::start();
+ *orientation_task = None;
+ }
+ }
+}
+
+fn main_loop(hal: &mut impl crate::hal::Hal) -> ! {
+ // Set the size of uart_read_buf to the size of the ringbuffer in the UART driver so we can read
+ // out all bytes
+ let mut uart_read_buf = [0u8; USART_0_BUFFER_SIZE as usize];
+ let mut uart_read_buf_len = 0u16;
+
+ let mut uart_write_buf = [0u8; UART_OUT_BUF_LEN as usize];
+ let mut uart_write_queue = RingBuffer::new(&mut uart_write_buf);
+
+ // If the bootloader has booted the BLE chip, the BLE chip isn't aware of the name according to
+ // the fw. Send it over.
+ let device_name = bitbox02::memory::get_device_name();
+ bitbox02::da14531::set_name(&device_name, &mut uart_write_queue);
+
+ // This starts the async orientation screen workflow, which is processed by the loop below.
+ let mut orientation_task: Option<util::bb02_async::Task<'static, bool>> = Some(Box::pin(
+ crate::workflow::orientation_screen::orientation_screen(),
+ ));
+
+ let mut hww_data = None;
+ let mut hww_frame: USB_FRAME = unsafe { MaybeUninit::zeroed().assume_init() };
+
+ #[cfg(feature = "app-u2f")]
+ bitbox02::u2f_packet::init();
+ #[cfg(feature = "app-u2f")]
+ let mut u2f_data = None;
+ #[cfg(feature = "app-u2f")]
+ let mut u2f_frame: USB_FRAME = unsafe { MaybeUninit::zeroed().assume_init() };
+
+ if !bitbox02::memory::ble_enabled() {
+ crate::communication_mode::ble_disable();
+ }
+
+ loop {
+ // Do UART I/O
+ if crate::communication_mode::ble_enabled(hal) {
+ if uart_read_buf_len < uart_read_buf.len() as u16 || uart_write_queue.len() > 0 {
+ bitbox02::uart::poll(
+ Some(&mut uart_read_buf),
+ Some(&mut uart_read_buf_len),
+ &mut uart_write_queue,
+ )
+ }
+ }
+
+ // Check if there is outgoing data
+ if hww_data.is_none() {
+ hww_data = bitbox02::queue::pull_hww();
+ }
+
+ // Generate u2f timeout packets
+ #[cfg(feature = "app-u2f")]
+ {
+ // Generate timeout packets
+ let mut timeout_cid = 0u32;
+ while bitbox02::u2f_packet::timeout_get(&mut timeout_cid) {
+ bitbox02::u2f_packet::timeout(timeout_cid);
+ }
+ if u2f_data.is_none() {
+ u2f_data = bitbox02::queue::pull_u2f();
+ // If USB stack was locked and there is no more messages to send out, time to
+ // unlock it.
+ if u2f_data.is_none() && bitbox02::usb_processing::locked_u2f() {
+ bitbox02::usb_processing::unlock();
+ }
+ }
+ }
+
+ // Do USB Input
+ if hww_data.is_none() && bitbox02::hid_hww::read(&mut hww_frame) {
+ if bitbox02::usb_packet::process(&hww_frame) {
+ if crate::communication_mode::ble_enabled(hal) {
+ // Enqueue a power down command to the da14531
+ bitbox02::da14531::power_down(&mut uart_write_queue);
+ // Flush out the power down command. This will be the last UART communication
+ // we do.
+ while uart_write_queue.len() > 0 {
+ bitbox02::uart::poll(None, None, &mut uart_write_queue);
+ }
+ crate::communication_mode::ble_disable();
+ }
+ } else {
+ // log!("usb_packet_process: invalid");
+ }
+ }
+ #[cfg(feature = "app-u2f")]
+ if u2f_data.is_none() && bitbox02::hid_u2f::read(&mut u2f_frame) {
+ bitbox02::u2f_packet::process(&u2f_frame);
+ }
+
+ // Do UART Output
+ if crate::communication_mode::ble_enabled(hal) {
+ if let Some(frame) = bitbox02::da14531_protocol::poll(
+ &mut uart_read_buf,
+ &mut uart_read_buf_len,
+ &mut hww_data,
+ &mut uart_write_queue,
+ ) {
+ // log!("got frame, calling handler");
+ bitbox02::da14531_handler::handler(frame, &mut uart_write_queue);
+ }
+ }
+
+ // Do USB Output
+ if let Some(data) = &mut hww_data
+ && !crate::communication_mode::ble_enabled(hal)
+ {
+ if bitbox02::hid_hww::write_poll(data) {
+ hww_data = None;
+ }
+ }
+ #[cfg(feature = "app-u2f")]
+ if let Some(data) = &mut u2f_data {
+ if bitbox02::hid_u2f::write_poll(data) {
+ u2f_data = None;
+ }
+ }
+
+ /* First, process all the incoming USB traffic. */
+ bitbox02::usb_processing::process_hww();
+ #[cfg(feature = "app-u2f")]
+ bitbox02::usb_processing::process_u2f();
+
+ /*
+ * If USB has generated events at the application level,
+ * process them now.
+ */
+ #[cfg(feature = "app-u2f")]
+ bitbox02::u2f::process();
+
+ bitbox02::screen::process();
+
+ /* And finally, run the high-level event processing. */
+ #[cfg(feature = "app-u2f")]
+ crate::workflow::u2f_c_api::workflow_spin();
+
+ crate::async_usb::spin();
+
+ orientation_screen_poll(&mut orientation_task, &mut uart_write_queue);
+ }
+}
+
+//
+// C interface
+//
+
+#[unsafe(no_mangle)]
+pub extern "C" fn rust_main_loop() -> ! {
+ main_loop(&mut crate::hal::BitBox02Hal::new())
+}
diff --git a/src/rust/bitbox02-rust/src/workflow.rs b/src/rust/bitbox02-rust/src/workflow.rs
index 4bd9446..2c3777c 100644
--- a/src/rust/bitbox02-rust/src/workflow.rs
+++ b/src/rust/bitbox02-rust/src/workflow.rs
@@ -18,7 +18,7 @@ pub mod testing;
pub mod transaction;
pub mod trinary_choice;
pub mod trinary_input_string;
-#[cfg(feature = "firmware")]
+#[cfg(feature = "app-u2f")]
pub mod u2f_c_api;
pub mod unlock;
pub mod unlock_animation;
diff --git a/src/rust/bitbox02-rust/src/workflow/u2f_c_api.rs b/src/rust/bitbox02-rust/src/workflow/u2f_c_api.rs
index 286fbf9..9c8ebb0 100644
--- a/src/rust/bitbox02-rust/src/workflow/u2f_c_api.rs
+++ b/src/rust/bitbox02-rust/src/workflow/u2f_c_api.rs
@@ -11,7 +11,7 @@
extern crate alloc;
-use crate::workflow::{confirm, orientation_screen};
+use crate::workflow::confirm;
use alloc::boxed::Box;
use alloc::string::String;
use core::task::Poll;
@@ -31,8 +31,6 @@ static mut CONFIRM_PARAMS: Option<confirm::Params> = None;
static mut CONFIRM_STATE: TaskState<'static, Result<(), confirm::UserAbort>> = TaskState::Nothing;
static mut BITBOX02_HAL: crate::hal::BitBox02Hal = crate::hal::BitBox02Hal::new();
-static mut ORIENTATION_SCREEN_STATE: TaskState<'static, bool> = TaskState::Nothing;
-
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_spawn_unlock() {
unsafe {
@@ -61,16 +59,7 @@ pub unsafe extern "C" fn rust_workflow_spawn_confirm(
}
}
-#[unsafe(no_mangle)]
-pub unsafe extern "C" fn rust_workflow_spawn_orientation_screen() {
- unsafe {
- ORIENTATION_SCREEN_STATE =
- TaskState::Running(Box::pin(orientation_screen::orientation_screen()));
- }
-}
-
-#[unsafe(no_mangle)]
-pub unsafe extern "C" fn rust_workflow_spin() {
+pub fn workflow_spin() {
unsafe {
match UNLOCK_STATE {
TaskState::Running(ref mut task) => {
@@ -90,15 +79,6 @@ pub unsafe extern "C" fn rust_workflow_spin() {
}
_ => (),
}
- match ORIENTATION_SCREEN_STATE {
- TaskState::Running(ref mut task) => {
- let result = spin(task);
- if let Poll::Ready(result) = result {
- ORIENTATION_SCREEN_STATE = TaskState::ResultAvailable(result);
- }
- }
- _ => (),
- }
}
}
@@ -138,21 +118,6 @@ pub unsafe extern "C" fn rust_workflow_confirm_poll(result_out: &mut bool) -> bo
}
}
-/// Returns true if there was a result.
-#[unsafe(no_mangle)]
-pub unsafe extern "C" fn rust_workflow_orientation_screen_poll(result_out: &mut bool) -> bool {
- unsafe {
- match ORIENTATION_SCREEN_STATE {
- TaskState::ResultAvailable(result) => {
- ORIENTATION_SCREEN_STATE = TaskState::Nothing;
- *result_out = result;
- true
- }
- _ => false,
- }
- }
-}
-
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_workflow_abort_current() {
unsafe {
@@ -162,7 +127,5 @@ pub unsafe extern "C" fn rust_workflow_abort_current() {
CONFIRM_BODY = None;
CONFIRM_PARAMS = None;
CONFIRM_STATE = TaskState::Nothing;
-
- ORIENTATION_SCREEN_STATE = TaskState::Nothing;
}
}
diff --git a/src/rust/bitbox02-sys/build.rs b/src/rust/bitbox02-sys/build.rs
index bb178c8..d3b949a 100644
--- a/src/rust/bitbox02-sys/build.rs
+++ b/src/rust/bitbox02-sys/build.rs
@@ -9,6 +9,8 @@ const ALLOWLIST_VARS: &[&str] = &[
"BASE58_CHECKSUM_LEN",
"BIP32_SERIALIZED_LEN",
"BIP39_WORDLIST_LEN",
+ "da14531_handler_current_product",
+ "da14531_handler_current_product_len",
"font_font_a_11X10",
"font_font_a_9X9",
"font_monogram_5X9",
@@ -32,6 +34,8 @@ const ALLOWLIST_VARS: &[&str] = &[
"secfalse_u8",
"SD_MAX_FILE_SIZE",
"SLIDER_POSITION_TWO_THIRD",
+ "USART_0_BUFFER_SIZE",
+ "USB_REPORT_SIZE",
];
const ALLOWLIST_TYPES: &[&str] = &[
@@ -39,6 +43,7 @@ const ALLOWLIST_TYPES: &[&str] = &[
"buffer_t",
"component_t",
"confirm_params_t",
+ "da14531_protocol_frame",
"delay_t",
"event_slider_data_t",
"event_types",
@@ -49,6 +54,8 @@ const ALLOWLIST_TYPES: &[&str] = &[
"upside_down_t",
];
+const OPAQUE_TYPES: &[&str] = &["da14531_protocol_frame"];
+
const ALLOWLIST_FNS: &[&str] = &[
"bip32_derive_xpub",
"bitbox02_smarteeprom_init",
@@ -64,6 +71,9 @@ const ALLOWLIST_FNS: &[&str] = &[
"delay_is_elapsed",
"delay_ms",
"delay_us",
+ "da14531_handler",
+ "da14531_power_down",
+ "da14531_protocol_poll",
"da14531_set_product",
"da14531_set_name",
"da14531_power_down",
@@ -71,6 +81,10 @@ const ALLOWLIST_FNS: &[&str] = &[
"empty_create",
"fake_memory_factoryreset",
"fake_memory_nova",
+ "hid_hww_read",
+ "hid_hww_write_poll",
+ "hid_u2f_read",
+ "hid_u2f_write_poll",
"hww_setup",
"keystore_bip39_mnemonic_to_seed",
"keystore_get_bip39_word",
@@ -117,11 +131,13 @@ const ALLOWLIST_FNS: &[&str] = &[
"memory_spi_get_active_ble_firmware_version",
"menu_create",
"orientation_arrows_create",
+ "platform_product",
"printf",
"progress_create",
"progress_set",
"queue_hww_queue",
"queue_pull",
+ "queue_u2f_queue",
"random_32_bytes_mcu",
"random_32_bytes",
"random_fake_reset",
@@ -168,6 +184,12 @@ const ALLOWLIST_FNS: &[&str] = &[
"trinary_choice_create",
"trinary_input_string_create",
"trinary_input_string_set_input",
+ "u2f_packet_init",
+ "u2f_packet_process",
+ "u2f_packet_timeout_get",
+ "u2f_packet_timeout",
+ "u2f_process",
+ "uart_poll",
"UG_ClearBuffer",
"UG_FontSelect",
"UG_PutString",
@@ -179,8 +201,12 @@ const ALLOWLIST_FNS: &[&str] = &[
"usb_packet_process",
"usb_processing_hww",
"usb_processing_init",
+ "usb_processing_locked",
"usb_processing_process",
"usb_processing_timeout_reset",
+ "usb_processing_u2f",
+ "usb_processing_unlock",
+ "usb_start",
"util_format_datetime",
];
@@ -398,6 +424,7 @@ pub fn main() -> Result<(), &'static str> {
.args(ALLOWLIST_TYPES.iter().flat_map(|s| ["--allowlist-type", s]))
.args(ALLOWLIST_VARS.iter().flat_map(|s| ["--allowlist-var", s]))
.args(RUSTIFIED_ENUMS.iter().flat_map(|s| ["--rustified-enum", s]))
+ .args(OPAQUE_TYPES.iter().flat_map(|s| ["--opaque-type", s]))
.arg("wrapper.h")
.arg("--")
.args(&definitions)
diff --git a/src/rust/bitbox02-sys/src/lib.rs b/src/rust/bitbox02-sys/src/lib.rs
index d17b718..e714f83 100644
--- a/src/rust/bitbox02-sys/src/lib.rs
+++ b/src/rust/bitbox02-sys/src/lib.rs
@@ -7,5 +7,6 @@
#![allow(non_snake_case)]
// Can be removed once https://github.com/rust-lang/rust-bindgen/issues/1651 is resolved.
#![allow(deref_nullptr)]
+
// include our generated bindings
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/src/rust/bitbox02-sys/wrapper.h b/src/rust/bitbox02-sys/wrapper.h
index a4dae44..8d166c6 100644
--- a/src/rust/bitbox02-sys/wrapper.h
+++ b/src/rust/bitbox02-sys/wrapper.h
@@ -1,13 +1,18 @@
// SPDX-License-Identifier: Apache-2.0
-#include <delay.h>
#include <da14531/da14531.h>
+#include <da14531/da14531_handler.h>
+#include <da14531/da14531_protocol.h>
+#include <delay.h>
+#include <hww.h>
#include <memory/bitbox02_smarteeprom.h>
#include <memory/memory.h>
#include <memory/memory_shared.h>
#include <memory/memory_spi.h>
#include <memory/smarteeprom.h>
#include <memory/spi_mem.h>
+#include <platform/driver_init.h>
+#include <platform/platform_init.h>
#include <random.h>
#include <reset.h>
#include <screen.h>
@@ -15,6 +20,9 @@
#include <securechip/securechip.h>
#include <system.h>
#include <time.h>
+#include <u2f.h>
+#include <u2f/u2f_packet.h>
+#include <uart.h>
#include <ui/components/confirm.h>
#include <ui/components/confirm_transaction.h>
#include <ui/components/empty.h>
@@ -36,19 +44,19 @@
#include <ui/screen_saver.h>
#include <ui/screen_stack.h>
#include <ui/ugui/ugui.h>
+#include <usb/class/hid/hww/hid_hww.h>
+#include <usb/class/hid/u2f/hid_u2f.h>
#include <usb/usb.h>
+#include <usb/usb_packet.h>
#include <usb/usb_processing.h>
#include <util.h>
#include <utils_ringbuffer.h>
#if defined(TESTING)
#include <fake_memory.h>
- #include <hww.h>
#include <touch/gestures.h>
#include <ui/event.h>
#include <ui/event_handler.h>
- #include <usb/usb_packet.h>
- #include <usb/usb_processing.h>
#endif
#if !defined(TESTING)
diff --git a/src/rust/bitbox02/src/da14531_handler.rs b/src/rust/bitbox02/src/da14531_handler.rs
new file mode 100644
index 0000000..cbcce06
--- /dev/null
+++ b/src/rust/bitbox02/src/da14531_handler.rs
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::ringbuffer::RingBuffer;
+use bitbox02_sys::da14531_protocol_frame;
+
+pub fn handler(frame: &'static da14531_protocol_frame, uart_write_queue: &mut RingBuffer) {
+ unsafe {
+ bitbox02_sys::da14531_handler(frame as *const _, &mut uart_write_queue.inner);
+ }
+}
+
+pub fn set_product(product: &'static str) {
+ let product = product.as_bytes();
+ unsafe {
+ bitbox02_sys::da14531_handler_current_product = product.as_ptr();
+ bitbox02_sys::da14531_handler_current_product_len = product.len() as u16;
+ }
+}
diff --git a/src/rust/bitbox02/src/da14531_protocol.rs b/src/rust/bitbox02/src/da14531_protocol.rs
new file mode 100644
index 0000000..ac105aa
--- /dev/null
+++ b/src/rust/bitbox02/src/da14531_protocol.rs
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::ringbuffer::RingBuffer;
+pub use bitbox02_sys::da14531_protocol_frame;
+
+pub fn poll(
+ uart_read_buf: &mut [u8],
+ uart_read_buf_len: &mut u16,
+ hww_data: &mut Option<[u8; 64]>,
+ uart_write_queue: &mut RingBuffer,
+) -> Option<&'static da14531_protocol_frame> {
+ let mut data: *const u8 = if let Some(data) = (*hww_data).as_ref() {
+ data.as_ptr() as *const _
+ } else {
+ core::ptr::null()
+ };
+ let frame = unsafe {
+ bitbox02_sys::da14531_protocol_poll(
+ uart_read_buf.as_mut_ptr() as *mut _,
+ uart_read_buf_len as *mut _,
+ &mut data as *mut _,
+ &mut uart_write_queue.inner as *mut _,
+ )
+ };
+ if data.is_null() {
+ *hww_data = None;
+ }
+ if frame.is_null() {
+ None
+ } else {
+ Some(unsafe { &*frame })
+ }
+}
diff --git a/src/rust/bitbox02/src/hid_hww.rs b/src/rust/bitbox02/src/hid_hww.rs
new file mode 100644
index 0000000..75008cf
--- /dev/null
+++ b/src/rust/bitbox02/src/hid_hww.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::usb_packet::USB_FRAME;
+
+pub fn write_poll(buf: &[u8; 64]) -> bool {
+ unsafe { bitbox02_sys::hid_hww_write_poll(buf.as_ptr() as *const _) }
+}
+
+pub fn read(frame: &mut USB_FRAME) -> bool {
+ unsafe { bitbox02_sys::hid_hww_read(frame as *mut USB_FRAME as *mut u8) }
+}
diff --git a/src/rust/bitbox02/src/hid_u2f.rs b/src/rust/bitbox02/src/hid_u2f.rs
new file mode 100644
index 0000000..f5370e2
--- /dev/null
+++ b/src/rust/bitbox02/src/hid_u2f.rs
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::usb_packet::USB_FRAME;
+
+pub fn write_poll(buf: &[u8; 64]) -> bool {
+ unsafe { bitbox02_sys::hid_u2f_write_poll(buf.as_ptr() as *const _) }
+}
+
+pub fn read(frame: &mut USB_FRAME) -> bool {
+ unsafe { bitbox02_sys::hid_u2f_read(frame as *mut USB_FRAME as *mut u8) }
+}
diff --git a/src/rust/bitbox02/src/lib.rs b/src/rust/bitbox02/src/lib.rs
index fb3a2ec..7b67270 100644
--- a/src/rust/bitbox02/src/lib.rs
+++ b/src/rust/bitbox02/src/lib.rs
@@ -23,25 +23,34 @@ pub mod testing;
extern crate bitbox02_rust;
pub mod da14531;
+pub mod da14531_handler;
+pub mod da14531_protocol;
pub mod delay;
#[cfg(feature = "simulator-graphical")]
pub mod event;
+pub mod hid_hww;
+#[cfg(feature = "app-u2f")]
+pub mod hid_u2f;
#[cfg(feature = "simulator-graphical")]
pub mod hww;
pub mod memory;
-#[cfg(feature = "simulator-graphical")]
+pub mod platform;
pub mod queue;
pub mod random;
pub mod ringbuffer;
-#[cfg(feature = "simulator-graphical")]
pub mod screen;
pub mod screen_saver;
pub mod sd;
pub mod securechip;
pub mod smarteeprom;
pub mod spi_mem;
+#[cfg(feature = "app-u2f")]
+pub mod u2f;
+#[cfg(feature = "app-u2f")]
+pub mod u2f_packet;
+pub mod uart;
pub mod ui;
-#[cfg(feature = "simulator-graphical")]
+pub mod usb;
pub mod usb_packet;
pub mod usb_processing;
diff --git a/src/rust/bitbox02/src/platform.rs b/src/rust/bitbox02/src/platform.rs
new file mode 100644
index 0000000..76217a5
--- /dev/null
+++ b/src/rust/bitbox02/src/platform.rs
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub fn product() -> &'static str {
+ unsafe {
+ let mut len = 0;
+ let s = bitbox02_sys::platform_product(&mut len as *mut _) as *const u8;
+ let s = core::slice::from_raw_parts(s, len);
+ str::from_utf8_unchecked(s)
+ }
+}
diff --git a/src/rust/bitbox02/src/queue.rs b/src/rust/bitbox02/src/queue.rs
index badd414..d7657f8 100644
--- a/src/rust/bitbox02/src/queue.rs
+++ b/src/rust/bitbox02/src/queue.rs
@@ -9,3 +9,14 @@ pub fn pull_hww() -> Option<[u8; 64]> {
unsafe { core::ptr::copy_nonoverlapping(hww_data, data.as_mut_ptr(), 64) }
Some(data)
}
+
+#[cfg(feature = "app-u2f")]
+pub fn pull_u2f() -> Option<[u8; 64]> {
+ let u2f_data = unsafe { bitbox02_sys::queue_pull(bitbox02_sys::queue_u2f_queue()) };
+ if u2f_data.is_null() {
+ return None;
+ }
+ let mut data: [u8; 64] = [0; 64];
+ unsafe { core::ptr::copy_nonoverlapping(u2f_data, data.as_mut_ptr(), 64) }
+ Some(data)
+}
diff --git a/src/rust/bitbox02/src/u2f.rs b/src/rust/bitbox02/src/u2f.rs
new file mode 100644
index 0000000..b3d02ba
--- /dev/null
+++ b/src/rust/bitbox02/src/u2f.rs
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub fn process() {
+ unsafe {
+ bitbox02_sys::u2f_process();
+ }
+}
diff --git a/src/rust/bitbox02/src/u2f_packet.rs b/src/rust/bitbox02/src/u2f_packet.rs
new file mode 100644
index 0000000..2d97d92
--- /dev/null
+++ b/src/rust/bitbox02/src/u2f_packet.rs
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub use bitbox02_sys::USB_FRAME;
+
+pub fn init() {
+ unsafe {
+ bitbox02_sys::u2f_packet_init();
+ }
+}
+
+pub fn timeout_get(cid: &mut u32) -> bool {
+ unsafe { bitbox02_sys::u2f_packet_timeout_get(cid as *mut _) }
+}
+
+pub fn timeout(cid: u32) {
+ unsafe { bitbox02_sys::u2f_packet_timeout(cid) }
+}
+
+pub fn process(frame: &USB_FRAME) -> bool {
+ unsafe { bitbox02_sys::u2f_packet_process(frame as *const _) }
+}
diff --git a/src/rust/bitbox02/src/uart.rs b/src/rust/bitbox02/src/uart.rs
new file mode 100644
index 0000000..c24738c
--- /dev/null
+++ b/src/rust/bitbox02/src/uart.rs
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::ringbuffer::RingBuffer;
+pub use bitbox02_sys::USART_0_BUFFER_SIZE;
+
+pub fn poll(
+ uart_read_buf: Option<&mut [u8]>,
+ uart_read_buf_len: Option<&mut u16>,
+ uart_write_queue: &mut RingBuffer,
+) {
+ let (uart_read_buf, cap) = if let Some(uart_read_buf) = uart_read_buf {
+ (
+ uart_read_buf as *mut _ as *mut _,
+ uart_read_buf.len() as u16,
+ )
+ } else {
+ (core::ptr::null_mut(), 0u16)
+ };
+ let uart_read_buf_len = if let Some(len) = uart_read_buf_len {
+ len as *mut _
+ } else {
+ core::ptr::null_mut()
+ };
+
+ unsafe {
+ bitbox02_sys::uart_poll(
+ uart_read_buf,
+ cap,
+ uart_read_buf_len,
+ &mut uart_write_queue.inner,
+ );
+ }
+}
diff --git a/src/rust/bitbox02/src/usb.rs b/src/rust/bitbox02/src/usb.rs
new file mode 100644
index 0000000..40635b3
--- /dev/null
+++ b/src/rust/bitbox02/src/usb.rs
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: Apache-2.0
+
+pub use bitbox02_sys::USB_REPORT_SIZE;
+
+pub fn start() {
+ unsafe {
+ bitbox02_sys::usb_start();
+ }
+}
diff --git a/src/rust/bitbox02/src/usb_packet.rs b/src/rust/bitbox02/src/usb_packet.rs
index fc3d9c4..792723b 100644
--- a/src/rust/bitbox02/src/usb_packet.rs
+++ b/src/rust/bitbox02/src/usb_packet.rs
@@ -2,6 +2,24 @@
pub use bitbox02_sys::USB_FRAME;
-pub fn process(packet: &[u8; 64]) -> bool {
- unsafe { bitbox02_sys::usb_packet_process(packet.as_ptr() as *const _) }
+// Compile-time assertion: a single USB report is exactly one `USB_FRAME`.
+// This is relevant because some code paths receive raw `[u8; 64]` reports and copy them into an
+// aligned `USB_FRAME` before passing a `USB_FRAME*` into C.
+const _: [u8; bitbox02_sys::USB_REPORT_SIZE as usize] = [0u8; core::mem::size_of::<USB_FRAME>()];
+
+pub fn process(frame: &USB_FRAME) -> bool {
+ unsafe { bitbox02_sys::usb_packet_process(frame as *const _) }
+}
+
+#[cfg(feature = "simulator-graphical")]
+pub fn process_from_report(packet: &[u8; 64]) -> bool {
+ let mut frame = core::mem::MaybeUninit::<USB_FRAME>::uninit();
+ unsafe {
+ core::ptr::copy_nonoverlapping(
+ packet.as_ptr(),
+ frame.as_mut_ptr().cast::<u8>(),
+ packet.len(),
+ );
+ bitbox02_sys::usb_packet_process(frame.as_ptr())
+ }
}
diff --git a/src/rust/bitbox02/src/usb_processing.rs b/src/rust/bitbox02/src/usb_processing.rs
index 23c3e75..a199115 100644
--- a/src/rust/bitbox02/src/usb_processing.rs
+++ b/src/rust/bitbox02/src/usb_processing.rs
@@ -12,7 +12,20 @@ pub fn init() {
unsafe { bitbox02_sys::usb_processing_init() }
}
-#[cfg(feature = "simulator-graphical")]
pub fn process_hww() {
unsafe { bitbox02_sys::usb_processing_process(bitbox02_sys::usb_processing_hww()) }
}
+
+#[cfg(feature = "app-u2f")]
+pub fn process_u2f() {
+ unsafe { bitbox02_sys::usb_processing_process(bitbox02_sys::usb_processing_u2f()) }
+}
+
+#[cfg(feature = "app-u2f")]
+pub fn locked_u2f() -> bool {
+ unsafe { bitbox02_sys::usb_processing_locked(bitbox02_sys::usb_processing_u2f()) }
+}
+
+pub fn unlock() {
+ unsafe { bitbox02_sys::usb_processing_unlock() }
+}
diff --git a/test/simulator-graphical-bb03/src/main.rs b/test/simulator-graphical-bb03/src/main.rs
index dba5285..f3e412e 100644
--- a/test/simulator-graphical-bb03/src/main.rs
+++ b/test/simulator-graphical-bb03/src/main.rs
@@ -493,7 +493,7 @@ impl ApplicationHandler<UserEvent> for App {
loop {
match inbound_out.try_recv() {
Ok(data) => {
- bitbox02::usb_packet::process(&data);
+ bitbox02::usb_packet::process_from_report(&data);
}
Err(TryRecvError::Disconnected) => {
// Drop the outbound channel
@@ -524,7 +524,6 @@ impl ApplicationHandler<UserEvent> for App {
}
}
// Business logic
- unsafe { bitbox02_rust::workflow::u2f_c_api::rust_workflow_spin() }
bitbox02_rust::async_usb::spin();
bitbox02::usb_processing::process_hww();
bitbox02::screen::process();
diff --git a/test/simulator-graphical/src/main.rs b/test/simulator-graphical/src/main.rs
index b5ddb19..85cb0a4 100644
--- a/test/simulator-graphical/src/main.rs
+++ b/test/simulator-graphical/src/main.rs
@@ -624,7 +624,7 @@ impl ApplicationHandler<UserEvent> for App {
loop {
match inbound_out.try_recv() {
Ok(data) => {
- bitbox02::usb_packet::process(&data);
+ bitbox02::usb_packet::process_from_report(&data);
}
Err(TryRecvError::Disconnected) => {
// Drop the outbound channel
@@ -655,7 +655,6 @@ impl ApplicationHandler<UserEvent> for App {
}
}
// Business logic
- unsafe { bitbox02_rust::workflow::u2f_c_api::rust_workflow_spin() }
bitbox02_rust::async_usb::spin();
bitbox02::usb_processing::process_hww();
bitbox02::screen::process();
diff --git a/test/simulator/simulator.c b/test/simulator/simulator.c
index 1d7a300..a5088aa 100644
--- a/test/simulator/simulator.c
+++ b/test/simulator/simulator.c
@@ -57,8 +57,6 @@ static void simulate_firmware_execution(const uint8_t* input)
USB_FRAME frame;
memcpy(&frame, input, sizeof(frame));
usb_packet_process(&frame);
- rust_workflow_spin();
- rust_async_usb_spin();
usb_processing_process(usb_processing_hww());
}
diff --git a/test/unit-test/test_simulator.c b/test/unit-test/test_simulator.c
index 83e6623..b932ea1 100644
--- a/test/unit-test/test_simulator.c
+++ b/test/unit-test/test_simulator.c
@@ -46,7 +46,6 @@ void send_usb_message_socket(void)
void simulate_firmware_execution(const uint8_t* input)
{
usb_packet_process((const USB_FRAME*)input);
- rust_workflow_spin();
rust_async_usb_spin();
usb_processing_process(usb_processing_hww());
}
Why this scored 31/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.