refactor(rust): rename FatPtr to CSlice, use more extensively
What changed, and why it matters
This commit is a code cleanup: it renames an internal helper type from FatPtr to CSlice, adds lifetime tracking and documentation, and updates call sites to use the new helper. The changes are intended to make Rust/C boundary handling safer and clearer, not to introduce a vulnerability. There is no public security advisory or incident tied to this commit.
No immediate action required. Treat as routine defensive refactor. If auditing, verify that all unsafe CSlice::from_ptr_and_len call sites still uphold the documented safety preconditions (valid pointer, correct length, lifetime bounded by the extern "C" call).
Security signals we found
Refactor of unsafe FFI slice helper with added lifetime parameter
Replacement of unbounded-lifetime from_c_array/from_c_str with CSlice accessors
Addition of SAFETY comments on extern "C" functions
Relaxation of 'static lifetime requirements on borrowed display strings/images
No vendor security disclosure or CVE referenced in commit
Evidence from the diff
The patch refactors the Rust FFI slice abstraction in Trezor firmware. FatPtr
Changed components
core/embed/rtl/src/cslice.rs (new)core/embed/rtl/src/util.rs (removed)core/embed/io/src/smp/api.rscore/embed/rust/src/ui/api/bootloader_c.rscore/embed/rust/src/ui/api/common_c.rscore/embed/rust/src/ui/api/prodtest_c.rscore/embed/rust/src/ui/layout_bolt/caesar/delizia/eckhart bootloader modulescore/embed/rust/src/ui/ui_bootloader.rscore/embed/sys/src/syslog.rsInspect captured patch +398 / −265
### core/embed/io/src/smp/api.rs
@@ -1,13 +1,13 @@
+use rtl::CSlice;
use rtl::error::unwrap;
-use rtl::util::{FatPtr, from_c_array};
use super::{echo, image_info, process_rx_byte, reset, upload};
#[unsafe(no_mangle)]
-extern "C" fn smp_echo(text: *const cty::c_char, text_len: u8) -> bool {
- let text = unwrap!(unsafe { from_c_array(text, text_len as usize) });
-
- echo::send(text)
+unsafe extern "C" fn smp_echo(text: *const cty::c_char, text_len: u8) -> bool {
+ // SAFETY: caller must provide a valid pointer + length
+ let text = unsafe { CSlice::from_ptr_and_len(text, text_len as usize) };
+ echo::send(unwrap!(text.as_ascii_str()))
}
#[unsafe(no_mangle)]
@@ -54,21 +54,20 @@ extern "C" fn smp_image_version_get(out: *mut NrfAppVersion) -> bool {
}
#[unsafe(no_mangle)]
-extern "C" fn smp_upload_app_image(
+unsafe extern "C" fn smp_upload_app_image(
data: *const cty::uint8_t,
len: cty::size_t,
image_hash: *const cty::uint8_t,
image_hash_len: cty::size_t,
) -> bool {
- let data = FatPtr::from_ptr_and_len(data, len);
- let image_hash = FatPtr::from_ptr_and_len(image_hash, image_hash_len);
-
- // SAFETY: we trust the caller with the validity of passed in pointers and
- // lengths
- let data_slice = unsafe { data.as_slice() }.unwrap_or(&[]);
- let hash_slice = unsafe { image_hash.as_slice() }.unwrap_or(&[]);
+ // SAFETY: caller must provide valid pointers + lengths
+ let data = unsafe { CSlice::from_ptr_and_len(data, len) };
+ let image_hash = unsafe { CSlice::from_ptr_and_len(image_hash, image_hash_len) };
- upload::upload_image(data_slice, hash_slice)
+ upload::upload_image(
+ data.as_slice().unwrap_or_default(),
+ image_hash.as_slice().unwrap_or_default(),
+ )
}
#[unsafe(no_mangle)]
### core/embed/rtl/src/cslice.rs
@@ -0,0 +1,300 @@
+use core::marker::PhantomData;
+
+/// Explicit manual representation of a slice.
+///
+/// Should be always used when converting between Rust slices and C ptr+len
+/// pairs.
+///
+/// A CSlice is a pointer + length pair, its behavior tuned to expectations of C
+/// code.
+///
+/// The internal pointer **can be NULL**, which C tends to conflate with the
+/// "empty slice" concept. C function will typically evaluate the pointer first,
+/// and only look at the length if the pointer is non-NULL. If we naively
+/// converted a Rust empty slice into a pointer, we would get a non-NULL
+/// dangling pointer (typically with an int value of `align_of<T>`, pointing to
+/// invalid memory).
+///
+/// Rust-side accessors always return an `Option<&[T]>` to cover the NULL case.
+///
+/// For this reason, CSlice coerces both zero-length slices (from Rust side) and
+/// NULL-pointer pairs (from C side) into the same [`CSlice::null()`] object.
+///
+/// # Safety
+///
+/// `CSlice` carries a lifetime parameter `'a`, which is required for soundness
+/// on Rust side.
+///
+/// Note, however, that `'a` does not figure in any return types. Views into the
+/// slice are either bounded by lifetime of `&self`, or (unsafely) unbounded.
+///
+/// There are two kinds of CSlices:
+///
+/// ## Created from a Rust slice
+///
+/// When a `CSlice` is safely constructed via `From<&'a [T]>`, the lifetime
+/// bound ensures that the CSlice does not outlive the original slice. Viewing
+/// via `as_slice()` / `as_ascii_str()` is safe because the view also cannot
+/// outlive the original.
+///
+/// ## Created from a C pointer
+///
+/// When unsafely constructed via `CSlice::from_ptr_and_len()`, the lifetime
+/// `'a` is unbounded. The caller must ensure that the `CSlice` object does not
+/// outlive pointer validity; typically, you will construct a `CSlice` in an
+/// `extern "C"` function and only keep it alive in its scope.
+///
+/// Given this assumption, viewing via `as_slice()` / `as_ascii_str()` is
+/// again safe because they don't outlive the owner object.
+pub struct CSlice<'a, T> {
+ ptr: *const T,
+ len: usize,
+ _marker: PhantomData<&'a T>,
+}
+
+impl<'a, T> CSlice<'a, T> {
+ /// Create a null CSlice with zero length
+ pub const fn null() -> Self {
+ Self {
+ ptr: core::ptr::null(),
+ len: 0,
+ _marker: PhantomData,
+ }
+ }
+
+ /// Create a CSlice from a ptr + len
+ ///
+ /// # Safety
+ ///
+ /// If `ptr` is NULL, or `len` is 0, the result is a null CSlice. Using a
+ /// null CSlice is safe.
+ ///
+ /// If the `ptr` is non-null and `len` is non-zero, then `ptr` must:
+ /// * be correctly aligned for type `T`
+ /// * point to a valid slice of `len` elements of type `T`
+ /// * be valid for the lifetime of the CSlice object (but not necessarily
+ /// for `'a`).
+ ///
+ /// The constructed CSlice object has an unbounded lifetime parameter `'a`,
+ /// but this never figures in any function return signatures, so it doesn't
+ /// actually affect safety.
+ pub const unsafe fn from_ptr_and_len(ptr: *const T, len: usize) -> Self {
+ if ptr.is_null() || len == 0 {
+ Self::null()
+ } else {
+ Self {
+ ptr,
+ len,
+ _marker: PhantomData,
+ }
+ }
+ }
+
+ /// Construct a slice with an unbounded lifetime from the ptr and len.
+ ///
+ /// # Safety
+ ///
+ /// Discards the owner's lifetime bound. Internal helper for both:
+ /// * `as_slice()`, which immediately binds the lifetime to &self, and
+ /// * `into_unbounded_slice()`, which consumes and discards self.
+ const unsafe fn make_unbounded_slice<'b>(&self) -> Option<&'b [T]> {
+ if self.ptr.is_null() {
+ None
+ } else if self.len == 0 {
+ Some(&[])
+ } else {
+ Some(unsafe { core::slice::from_raw_parts(self.ptr, self.len) })
+ }
+ }
+
+ /// Convert the CSlice into a slice with an unbounded lifetime.
+ ///
+ /// This is the escape hatch for CSlices created from C pointers whose
+ /// lifetime is longer than "scope of called function" (e.g., pointers to
+ /// static memory).
+ ///
+ /// # Safety
+ ///
+ /// By calling this, CSlice gives up any claim of lifetime management. It's
+ /// up to the caller to handle the lifetime of the returned slice manually
+ /// -- preferably by bounding it with an appropriate context.
+ pub const unsafe fn into_unbounded_slice<'b>(self) -> Option<&'b [T]> {
+ // SAFETY: responsibility of the caller
+ unsafe { self.make_unbounded_slice() }
+ }
+
+ /// View the CSlice as a slice
+ ///
+ /// Returns `None` if the CSlice is null, a possibly empty slice otherwise.
+ ///
+ /// The returned slice borrows from `self`, so its lifetime is capped by the
+ /// scope of the `CSlice` value. This is appropriate for a certain style of
+ /// C FFI calls, where you create a `CSlice` from an incoming ptr+len, then
+ /// pass the result of `as_slice()` into Rust code for processing. In such
+ /// case the lifetime guarantees that the slice will stop existing when we
+ /// return back to C.
+ pub fn as_slice(&self) -> Option<&[T]> {
+ // SAFETY: lifetime of returned slice is bounded by &self
+ unsafe { self.make_unbounded_slice() }
+ }
+
+ /// Check if the CSlice is null
+ pub const fn is_null(&self) -> bool {
+ self.ptr.is_null()
+ }
+
+ /// Get the raw pointer part
+ pub const fn ptr(&self) -> *const T {
+ self.ptr
+ }
+
+ /// Get the length of the slice
+ pub const fn len(&self) -> usize {
+ self.len
+ }
+
+ /// Check if the slice is empty
+ pub const fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+}
+
+macro_rules! impl_as_ascii_str {
+ ($ty:ty) => {
+ impl CSlice<'_, $ty> {
+ /// View a CSlice as an ASCII string
+ ///
+ /// Returns a `&str` representation if the pointer is non-null and
+ /// ASCII, `None` otherwise. Notably: if the string is valid but
+ /// non-ASCII, you also get `None`.
+ pub fn as_ascii_str(&self) -> Option<&str> {
+ // SAFETY: lifetime of returned slice is bounded by &self
+ unsafe { str_from_c_array(self.ptr as *const _, self.len) }
+ }
+
+ /// Convert the CSlice into an ASCII string with an unbounded lifetime.
+ ///
+ /// Escape hatch for CSlice created from C pointers whose lifetime
+ /// is longer than "scope of called function" (e.g., pointers to
+ /// static memory).
+ ///
+ /// # Safety
+ ///
+ /// By calling this, CSlice gives up any claim of lifetime
+ /// management. In this it is equivalent to
+ /// [`CSlice::into_unbounded_slice()`], see its safety notes for
+ /// details.
+ pub unsafe fn into_unbounded_ascii_str<'b>(self) -> Option<&'b str> {
+ // SAFETY: responsibility of the caller
+ unsafe { str_from_c_array(self.ptr as *const _, self.len) }
+ }
+ }
+ };
+}
+
+impl_as_ascii_str!(i8);
+impl_as_ascii_str!(u8);
+
+impl CSlice<'_, u8> {
+ /// Create a CSlice from a null-terminated C string
+ ///
+ /// Calculates the length of the string up to the first null byte
+ /// and converts it to a CSlice.
+ ///
+ /// # Safety
+ ///
+ /// The caller is responsible for ensuring that the underlying pointer
+ /// points to a valid null-terminated C string.
+ pub unsafe fn from_c_str(c_str: *const cty::c_char) -> Self {
+ if c_str.is_null() {
+ return Self::null();
+ }
+ // we use CStr to calculate length and make a slice for us
+ // SAFETY: caller should provide a valid C string
+ let cstr = unsafe { core::ffi::CStr::from_ptr(c_str as _) };
+ cstr.to_bytes().into()
+ }
+}
+
+impl<'a, T> From<&'a [T]> for CSlice<'a, T> {
+ fn from(s: &'a [T]) -> Self {
+ if s.is_empty() {
+ Self::null()
+ } else {
+ Self {
+ ptr: s.as_ptr(),
+ len: s.len(),
+ _marker: PhantomData,
+ }
+ }
+ }
+}
+
+// Helper for converting &str to (signed) char*
+impl<'a> From<&'a str> for CSlice<'a, cty::c_char> {
+ fn from(s: &str) -> Self {
+ let charptr = CSlice::from(s.as_bytes());
+ Self {
+ ptr: charptr.ptr() as *const cty::c_char,
+ len: charptr.len(),
+ _marker: PhantomData,
+ }
+ }
+}
+
+/// Construct str from a C array.
+///
+/// # Safety
+///
+/// The caller is responsible that the pointer is valid, which means that:
+/// (a) it points to a memory containing array of characters, with length `len`,
+/// and
+/// (b) that the pointer has appropriate lifetime.
+///
+/// The returned lifetime is unbounded and the caller is responsible for
+/// bounding it.
+unsafe fn str_from_c_array<'a>(c_str: *const cty::c_char, len: usize) -> Option<&'a str> {
+ if c_str.is_null() {
+ return None;
+ }
+ unsafe {
+ let slice = core::slice::from_raw_parts(c_str as *const u8, len);
+ if slice.is_ascii() {
+ Some(core::str::from_utf8_unchecked(slice))
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_fat_ptr() {
+ let s = "Hello, world!";
+ let fp = CSlice::from(s);
+ assert_eq!(fp.ptr() as usize, s.as_ptr() as usize);
+ assert_eq!(fp.len(), s.len());
+ }
+
+ #[test]
+ fn test_nullptr() {
+ let fp = CSlice::<i32>::null();
+ assert!(fp.is_null());
+ assert_eq!(fp.ptr(), core::ptr::null());
+ assert_eq!(fp.len(), 0);
+ assert!(fp.is_empty());
+ }
+
+ #[test]
+ fn test_empty_slice() {
+ let s: &[u64] = &[];
+ let fp = CSlice::from(s);
+ assert!(fp.is_null());
+ assert_eq!(fp.ptr(), core::ptr::null());
+ assert_eq!(fp.len(), 0);
+ assert!(fp.is_empty());
+ }
+}
### core/embed/rtl/src/lib.rs
@@ -1,9 +1,10 @@
#![no_std]
+mod cslice;
mod ffi;
pub mod error;
pub mod sysexit;
-pub mod util;
+pub use cslice::CSlice;
pub use sysexit::{system_exit_error, system_exit_fatal};
### core/embed/rtl/src/sysexit.rs
@@ -1,15 +1,14 @@
-use crate::ffi;
-use crate::util::FatPtr;
+use crate::{CSlice, ffi};
pub fn system_exit() -> ! {
// SAFETY: safe
unsafe { ffi::system_exit(0) }
}
pub fn system_exit_error(title: Option<&str>, message: &str, footer: Option<&str>) -> ! {
- let message_ptr = FatPtr::from(message);
- let title_ptr = title.map(FatPtr::from).unwrap_or_else(FatPtr::null);
- let footer_ptr = footer.map(FatPtr::from).unwrap_or_else(FatPtr::null);
+ let message_ptr = CSlice::from(message);
+ let title_ptr = title.map(CSlice::from).unwrap_or_else(CSlice::null);
+ let footer_ptr = footer.map(CSlice::from).unwrap_or_else(CSlice::null);
// SAFETY: safe
unsafe {
@@ -26,8 +25,8 @@ pub fn system_exit_error(title: Option<&str>, message: &str, footer: Option<&str
#[inline(never)] // saves few kilobytes of flash
pub fn system_exit_fatal(message: &str, file: &str, line: u32) -> ! {
- let message_ptr = FatPtr::from(message);
- let file_ptr = FatPtr::from(file);
+ let message_ptr = CSlice::from(message);
+ let file_ptr = CSlice::from(file);
// SAFETY: safe
unsafe {
### core/embed/rtl/src/util.rs
@@ -1,182 +0,0 @@
-/// Explicit fat pointer representation
-///
-/// Should be always used when passing slices into C.
-///
-/// Coerces the internal pointer to NULL in case length is zero, to make pointer
-/// validation safer and easier in C. Typically, C will allow either (a) NULL
-/// pointer or (b) pointer to valid memory. But Rust zero-length slices are
-/// pointers whose int value is `align_of<T>`, which is decidedly _not_ valid
-/// memory. This way, C side will be satisfied.
-///
-/// # Safety
-///
-/// A FatPtr is a fat representation of a Rust pointer type. It intentionally
-/// does not have an associated lifetime. When created from a slice that later
-/// goes out of scope, it may become invalid. Treat with care.
-#[repr(C)]
-pub struct FatPtr<T> {
- ptr: *const T,
- len: usize,
-}
-
-impl<T> FatPtr<T> {
- /// Create a null fatpointer with zero length
- pub fn null() -> Self {
- Self {
- ptr: core::ptr::null(),
- len: 0,
- }
- }
-
- /// Create a fat pointer from a ptr + len
- pub fn from_ptr_and_len(ptr: *const T, len: usize) -> Self {
- if ptr.is_null() {
- Self::null()
- } else {
- Self { ptr, len }
- }
- }
-
- /// View the fat pointer as a slice
- ///
- /// Returns `None` if the fat pointer is null, a possibly empty slice
- /// otherwise.
- ///
- /// The returned slice borrows from `self`, so its lifetime is capped by the
- /// scope of the `FatPtr` value. This is appropriate for a certain style of
- /// C FFI calls, where you create a `FatPtr` from an incoming ptr+len, then
- /// pass the result of `as_slice()` into Rust code for processing. In such
- /// case the lifetime guarantees that the slice will stop existing when we
- /// return back to C.
- ///
- /// # Safety
- ///
- /// If the pointer is non-null and length is non-zero, the call reduces to
- /// [`core::slice::from_raw_parts`], so all its safety properties apply; in
- /// short, the pointer must point to valid aligned memory of length `len`.
- pub unsafe fn as_slice(&self) -> Option<&[T]> {
- if self.ptr.is_null() {
- None
- } else if self.len == 0 {
- Some(&[])
- } else {
- Some(unsafe { core::slice::from_raw_parts(self.ptr, self.len) })
- }
- }
-
- pub fn is_null(&self) -> bool {
- self.ptr.is_null()
- }
-
- pub fn ptr(&self) -> *const T {
- self.ptr
- }
-
- pub fn len(&self) -> usize {
- self.len
- }
-
- pub fn is_empty(&self) -> bool {
- self.len == 0
- }
-}
-
-impl<T> From<&[T]> for FatPtr<T> {
- fn from(s: &[T]) -> Self {
- if s.is_empty() {
- Self::null()
- } else {
- Self {
- ptr: s.as_ptr(),
- len: s.len(),
- }
- }
- }
-}
-
-// Helper for converting &str to (signed) char*
-impl From<&str> for FatPtr<cty::c_char> {
- fn from(s: &str) -> Self {
- let charptr = FatPtr::from(s.as_bytes());
- Self {
- ptr: charptr.ptr() as *const cty::c_char,
- len: charptr.len(),
- }
- }
-}
-
-/// Constructs a string from a C string.
-///
-/// # Safety
-///
-/// The caller is responsible that the pointer is valid, which means that:
-/// (a) it points to a memory containing a valid C string (zero-terminated
-/// sequence of characters), and
-/// (b) that the pointer has appropriate lifetime.
-pub unsafe fn from_c_str<'a>(c_str: *const cty::c_char) -> Option<&'a str> {
- if c_str.is_null() {
- return None;
- }
- unsafe {
- let bytes = core::ffi::CStr::from_ptr(c_str as _).to_bytes();
- if bytes.is_ascii() {
- Some(core::str::from_utf8_unchecked(bytes))
- } else {
- None
- }
- }
-}
-
-/// Construct str from a C array.
-///
-/// # Safety
-///
-/// The caller is responsible that the pointer is valid, which means that:
-/// (a) it points to a memory containing array of characters, with length `len`,
-/// and
-/// (b) that the pointer has appropriate lifetime.
-pub unsafe fn from_c_array<'a>(c_str: *const cty::c_char, len: usize) -> Option<&'a str> {
- if c_str.is_null() {
- return None;
- }
- unsafe {
- let slice = core::slice::from_raw_parts(c_str as *const u8, len);
- if slice.is_ascii() {
- Some(core::str::from_utf8_unchecked(slice))
- } else {
- None
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_fat_ptr() {
- let s = "Hello, world!";
- let fp = FatPtr::from(s);
- assert_eq!(fp.ptr() as usize, s.as_ptr() as usize);
- assert_eq!(fp.len(), s.len());
- }
-
- #[test]
- fn test_nullptr() {
- let fp = FatPtr::<i32>::null();
- assert!(fp.is_null());
- assert_eq!(fp.ptr(), core::ptr::null());
- assert_eq!(fp.len(), 0);
- assert!(fp.is_empty());
- }
-
- #[test]
- fn test_empty_slice() {
- let s: &[u64] = &[];
- let fp = FatPtr::from(s);
- assert!(fp.is_null());
- assert_eq!(fp.ptr(), core::ptr::null());
- assert_eq!(fp.len(), 0);
- assert!(fp.is_empty());
- }
-}
### core/embed/rust/src/ui/api/bootloader_c.rs
@@ -1,4 +1,4 @@
-use rtl::util::{from_c_array, from_c_str};
+use rtl::CSlice;
use crate::strutil::hexlify;
use crate::ui::ui_bootloader::BootloaderUI;
@@ -38,8 +38,8 @@ extern "C" fn screen_install_confirm(
is_newinstall: bool,
version_cmp: cty::c_int,
) -> u32 {
- let text = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) });
- let version = unwrap!(unsafe { from_c_str(version) });
+ let text = unsafe { CSlice::from_ptr_and_len(vendor_str, vendor_str_len as usize) };
+ let version = unsafe { CSlice::from_c_str(version) };
let mut fingerprint_buffer: [u8; 64] = [0; 64];
let fingerprint_str = unsafe {
@@ -49,8 +49,8 @@ extern "C" fn screen_install_confirm(
};
ModelUI::screen_install_confirm(
- text,
- version,
+ unwrap!(text.as_ascii_str()),
+ unwrap!(version.as_ascii_str()),
fingerprint_str,
should_keep_seed,
is_newvendor,
@@ -95,11 +95,16 @@ extern "C" fn screen_intro(
version: *const cty::c_char,
fw_ok: bool,
) -> u32 {
- let vendor = unwrap!(unsafe { from_c_array(vendor_str, vendor_str_len as usize) });
- let version = unwrap!(unsafe { from_c_str(version) });
- let bld_version = unwrap!(unsafe { from_c_str(bld_version) });
-
- ModelUI::screen_intro(bld_version, vendor, version, fw_ok)
+ let vendor = unsafe { CSlice::from_ptr_and_len(vendor_str, vendor_str_len as usize) };
+ let version = unsafe { CSlice::from_c_str(version) };
+ let bld_version = unsafe { CSlice::from_c_str(bld_version) };
+
+ ModelUI::screen_intro(
+ unwrap!(bld_version.as_ascii_str()),
+ unwrap!(vendor.as_ascii_str()),
+ unwrap!(version.as_ascii_str()),
+ fw_ok,
+ )
}
#[no_mangle]
@@ -122,15 +127,23 @@ extern "C" fn screen_boot(
vendor_img_len: usize,
wait: i32,
) {
- let vendor_str = unsafe { from_c_array(vendor_str, vendor_str_len) };
+ let vendor_str = unsafe { CSlice::from_ptr_and_len(vendor_str, vendor_str_len as usize) };
+ // vendor_img MUST be a pointer to 'static memory, which is why we're
+ // sidestepping CSlice's rules
let vendor_img =
unsafe { core::slice::from_raw_parts(vendor_img as *const u8, vendor_img_len) };
// Splits a version stored as a u32 into four numbers
// starting with the major version.
let version = version.to_le_bytes();
- ModelUI::screen_boot(warning, vendor_str, version, vendor_img, wait);
+ ModelUI::screen_boot(
+ warning,
+ vendor_str.as_ascii_str(),
+ version,
+ vendor_img,
+ wait,
+ )
}
#[no_mangle]
@@ -185,9 +198,10 @@ extern "C" fn screen_pairing_mode(
name_len: usize,
ui_action_result: *mut u32,
) -> u32 {
- let name = unsafe { from_c_array(name, name_len).unwrap_or("") };
+ let name = unsafe { CSlice::from_ptr_and_len(name, name_len as usize) };
- let (res, ui_res) = ModelUI::screen_pairing_mode(initial_setup, name);
+ let (res, ui_res) =
+ ModelUI::screen_pairing_mode(initial_setup, name.as_ascii_str().unwrap_or_default());
unsafe {
*ui_action_result = ui_res;
}
@@ -201,9 +215,9 @@ extern "C" fn screen_wireless_setup(
name_len: usize,
ui_action_result: *mut u32,
) -> u32 {
- let name = unsafe { from_c_array(name, name_len).unwrap_or("") };
+ let name = unsafe { CSlice::from_ptr_and_len(name, name_len as usize) };
- let (res, ui_res) = ModelUI::screen_wireless_setup(name);
+ let (res, ui_res) = ModelUI::screen_wireless_setup(name.as_ascii_str().unwrap_or_default());
unsafe {
*ui_action_result = ui_res;
}
### core/embed/rust/src/ui/api/common_c.rs
@@ -1,7 +1,7 @@
//! Reexporting the `screens` module according to the
//! current feature (Trezor model)
-use rtl::util::from_c_str;
+use rtl::CSlice;
#[cfg(feature = "ui_debug")]
use crate::ui::util::set_animation_disabled;
@@ -13,9 +13,9 @@ extern "C" fn display_rsod_rust(
msg: *const cty::c_char,
footer: *const cty::c_char,
) {
- let title = unsafe { from_c_str(title) }.unwrap_or("");
- let msg = unsafe { from_c_str(msg) }.unwrap_or("");
- let footer = unsafe { from_c_str(footer) }.unwrap_or("");
+ let title = unsafe { CSlice::from_c_str(title) };
+ let msg = unsafe { CSlice::from_c_str(msg) };
+ let footer = unsafe { CSlice::from_c_str(footer) };
// SAFETY:
// This is the only situation we are allowed use this function
@@ -24,7 +24,11 @@ extern "C" fn display_rsod_rust(
// shut down.
unsafe { shape::unlock_bumps_on_failure() };
- ModelUI::screen_fatal_error(title, msg, footer);
+ ModelUI::screen_fatal_error(
+ title.as_ascii_str().unwrap_or_default(),
+ msg.as_ascii_str().unwrap_or_default(),
+ footer.as_ascii_str().unwrap_or_default(),
+ );
ModelUI::backlight_on();
}
### core/embed/rust/src/ui/api/prodtest_c.rs
@@ -2,7 +2,7 @@
use cty::int16_t;
#[cfg(feature = "touch")]
use heapless::Vec;
-use rtl::util::from_c_array;
+use rtl::CSlice;
use crate::trezorhal::layout_buf::{c_layout_t, LayoutBuffer};
use crate::trezorhal::sysevent::{parse_event, sysevents_t};
@@ -27,13 +27,11 @@ extern "C" fn screen_prodtest_event(layout: *mut c_layout_t, signalled: &syseven
#[no_mangle]
extern "C" fn screen_prodtest_welcome(layout: *mut c_layout_t, id: *const cty::c_char, id_len: u8) {
- let id = if id.is_null() {
- None
- } else {
- unsafe { from_c_array(id, id_len as usize) }
- };
+ let id = unsafe { CSlice::from_ptr_and_len(id, id_len as usize) };
- let mut screen = <ModelUI as ProdtestUI>::CLayoutType::init_welcome(id);
+ let mut screen = <ModelUI as ProdtestUI>::CLayoutType::init_welcome(unsafe {
+ id.into_unbounded_ascii_str()
+ });
screen.show();
// SAFETY: calling code is supposed to give us exclusive access to the layout
let mut layout = unsafe { LayoutBuffer::new(layout) };
@@ -42,9 +40,9 @@ extern "C" fn screen_prodtest_welcome(layout: *mut c_layout_t, id: *const cty::c
#[no_mangle]
extern "C" fn screen_prodtest_show_text(text: *const cty::c_char, text_len: u8) {
- let text = unwrap!(unsafe { from_c_array(text, text_len as usize) });
+ let text = unsafe { CSlice::from_ptr_and_len(text, text_len as usize) };
- ModelUI::screen_prodtest_show_text(text);
+ ModelUI::screen_prodtest_show_text(text.as_ascii_str().unwrap_or_default());
}
#[no_mangle]
@@ -54,8 +52,8 @@ extern "C" fn screen_prodtest_border() {
#[no_mangle]
extern "C" fn screen_prodtest_bars(colors: *const cty::c_char, colors_len: u8) {
- let colors: &str = unwrap!(unsafe { from_c_array(colors, colors_len as usize) });
- ModelUI::screen_prodtest_bars(colors);
+ let colors = unsafe { CSlice::from_ptr_and_len(colors, colors_len as usize) };
+ ModelUI::screen_prodtest_bars(colors.as_ascii_str().unwrap_or_default());
}
#[no_mangle]
### core/embed/rust/src/ui/layout_bolt/bootloader/mod.rs
@@ -361,7 +361,7 @@ impl BootloaderUI for UIBolt {
warning: bool,
vendor_str: Option<&str>,
version: [u8; 4],
- vendor_img: &'static [u8],
+ vendor_img: &[u8],
wait: i32,
) {
let bg_color = if warning {
@@ -442,7 +442,7 @@ impl BootloaderUI for UIBolt {
}
#[cfg(feature = "ble")]
- fn screen_pairing_mode(initial_setup: bool, _name: &'static str) -> (u32, u32) {
+ fn screen_pairing_mode(initial_setup: bool, _name: &str) -> (u32, u32) {
let bg = if initial_setup { WELCOME_COLOR } else { BLD_BG };
let btn = if initial_setup {
### core/embed/rust/src/ui/layout_caesar/bootloader/mod.rs
@@ -316,7 +316,7 @@ impl BootloaderUI for UICaesar {
_warning: bool,
vendor_str: Option<&str>,
version: [u8; 4],
- vendor_img: &'static [u8],
+ vendor_img: &[u8],
wait: i32,
) {
display::sync();
### core/embed/rust/src/ui/layout_delizia/bootloader/mod.rs
@@ -396,7 +396,7 @@ impl BootloaderUI for UIDelizia {
warning: bool,
vendor_str: Option<&str>,
version: [u8; 4],
- vendor_img: &'static [u8],
+ vendor_img: &[u8],
wait: i32,
) {
let bg_color = if warning {
### core/embed/rust/src/ui/layout_eckhart/bootloader/pairing_mode.rs
@@ -24,21 +24,21 @@ impl ReturnToC for PairingMsg {
}
}
-pub struct PairingModeScreen {
- header: BldHeader<'static>,
- name: Label<'static>,
- message: Label<'static>,
- footer: Label<'static>,
+pub struct PairingModeScreen<'a> {
+ header: BldHeader<'a>,
+ name: Label<'a>,
+ message: Label<'a>,
+ footer: Label<'a>,
screen_border: Option<ScreenBorder>,
}
-impl PairingModeScreen {
+impl<'a> PairingModeScreen<'a> {
const TEXT_NORMAL_GREEN_LIME: TextStyle = TextStyle {
text_color: theme::GREEN_LIME,
..theme::TEXT_NORMAL
};
- pub fn new(name: TString<'static>) -> Self {
+ pub fn new(name: TString<'a>) -> Self {
Self {
header: BldHeader::new("Pair new device".into()).with_close_button(),
name: Label::left_aligned(name, Self::TEXT_NORMAL_GREEN_LIME),
@@ -55,7 +55,7 @@ impl PairingModeScreen {
}
}
-impl Component for PairingModeScreen {
+impl Component for PairingModeScreen<'_> {
type Msg = PairingMsg;
fn place(&mut self, bounds: Rect) -> Rect {
@@ -104,7 +104,7 @@ impl Component for PairingModeScreen {
}
#[cfg(feature = "ui_debug")]
-impl crate::trace::Trace for PairingModeScreen {
+impl crate::trace::Trace for PairingModeScreen<'_> {
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
t.component("PairingMode");
t.string("name", *self.name.text());
### core/embed/rust/src/ui/layout_eckhart/bootloader/wireless_setup_screen.rs
@@ -11,17 +11,17 @@ use crate::ui::shape::{self, Renderer};
/// Full-screen component for the wireless setup screen. It shows instructions
/// for the user and QR code to download the Trezor Suite app.
-pub struct WirelessSetupScreen {
+pub struct WirelessSetupScreen<'a> {
/// Header with the device name
- header: BldHeader<'static>,
+ header: BldHeader<'a>,
/// Instruction text for the user
- instruction: Label<'static>,
+ instruction: Label<'a>,
/// Area for the QR code
qr_area: Rect,
/// Action bar to invoke more info
action_bar: BldActionBar,
/// More info section that can be toggled
- more_info: MoreInfo<'static>,
+ more_info: MoreInfo<'a>,
/// Flag to indicate if the more info section is currently showing
more_info_showing: bool,
}
@@ -33,8 +33,8 @@ struct MoreInfo<'a> {
action_bar: BldActionBar,
}
-impl WirelessSetupScreen {
- pub fn new(name: TString<'static>) -> Self {
+impl<'a> WirelessSetupScreen<'a> {
+ pub fn new(name: TString<'a>) -> Self {
let instruction = Label::left_aligned(
"Get the Trezor Suite app to begin setup.".into(),
theme::TEXT_NORMAL,
@@ -96,7 +96,7 @@ impl WirelessSetupScreen {
}
}
-impl Component for WirelessSetupScreen {
+impl Component for WirelessSetupScreen<'_> {
type Msg = PairingMsg;
fn place(&mut self, _bounds: Rect) -> Rect {
### core/embed/rust/src/ui/layout_eckhart/ui_bootloader.rs
@@ -112,7 +112,7 @@ impl BootloaderUI for UIEckhart {
}
#[cfg(feature = "ble")]
- fn screen_pairing_mode(initial_setup: bool, name: &'static str) -> (u32, u32) {
+ fn screen_pairing_mode(initial_setup: bool, name: &str) -> (u32, u32) {
let mut screen = PairingModeScreen::new(name.into());
if !initial_setup {
screen = screen.with_screen_border(SCREEN_BORDER_BLUE);
@@ -121,7 +121,7 @@ impl BootloaderUI for UIEckhart {
}
#[cfg(feature = "ble")]
- fn screen_wireless_setup(name: &'static str) -> (u32, u32) {
+ fn screen_wireless_setup(name: &str) -> (u32, u32) {
let mut screen = WirelessSetupScreen::new(name.into());
run(&mut screen, true, true)
}
@@ -397,7 +397,7 @@ impl BootloaderUI for UIEckhart {
warning: bool,
vendor_str: Option<&str>,
version: [u8; 4],
- vendor_img: &'static [u8],
+ vendor_img: &[u8],
wait: i32,
) {
let bg_color = if warning {
### core/embed/rust/src/ui/ui_bootloader.rs
@@ -6,12 +6,12 @@ pub trait BootloaderUI {
fn screen_connect(initial_setup: bool, show_menu: bool) -> (u32, u32);
#[cfg(feature = "ble")]
- fn screen_pairing_mode(_initial_setup: bool, _name: &'static str) -> (u32, u32) {
+ fn screen_pairing_mode(_initial_setup: bool, _name: &str) -> (u32, u32) {
unimplemented!();
}
#[cfg(feature = "ble")]
- fn screen_wireless_setup(_name: &'static str) -> (u32, u32) {
+ fn screen_wireless_setup(_name: &str) -> (u32, u32) {
unimplemented!();
}
@@ -60,7 +60,7 @@ pub trait BootloaderUI {
warning: bool,
vendor_str: Option<&str>,
version: [u8; 4],
- vendor_img: &'static [u8],
+ vendor_img: &[u8],
wait: i32,
);
### core/embed/sys/src/syslog.rs
@@ -1,4 +1,4 @@
-use rtl::util::FatPtr;
+use rtl::CSlice;
use super::ffi;
@@ -12,7 +12,7 @@ pub enum LogLevel {
impl From<&str> for ffi::log_source_t {
fn from(s: &str) -> Self {
- let ptr = FatPtr::from(s);
+ let ptr = CSlice::from(s);
ffi::log_source_t {
name: ptr.ptr(),
name_len: ptr.len(),
@@ -26,7 +26,7 @@ fn syslog_start_record(module: &str, level: LogLevel) -> bool {
}
fn syslog_write_chunk(text: &str, end_record: bool) -> Result<usize, ()> {
- let text = FatPtr::from(text);
+ let text = CSlice::from(text);
let bytes_written = unsafe { ffi::syslog_write_chunk(text.ptr(), text.len(), end_record) };
if bytes_written < 0 {
Err(())Why this scored 20/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.