You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
trezor-firmware/core/embed/rust/src/micropython/buffer.rs

260 lines
9.2 KiB

use core::{convert::TryFrom, ops::Deref, ptr, slice, str};
use crate::{error::Error, micropython::obj::Obj, strutil::hexlify};
use super::ffi;
/// Represents an immutable UTF-8 string managed by MicroPython GC.
/// This either means static data, or a valid GC object.
///
/// # Safety
///
/// We assume that MicroPython is handling strings according to Python
/// semantics, i.e., that string data is immutable.
/// Furthermore, we assume that string data is always either static or a GC head
/// pointer, i.e., we can never obtain a pointer into the middle of a GC string.
///
/// Given the above assumptions about MicroPython strings, working with
/// StrBuffers in Rust is safe.
///
/// The `off` field represents offset from the `ptr` and allows us to do
/// substring slices while keeping the head pointer as required by GC.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct StrBuffer {
ptr: *const u8,
len: u16,
off: u16,
}
impl StrBuffer {
pub fn empty() -> Self {
Self::from("")
}
// SAFETY:
// Caller is responsible for ensuring that data under `ptr` is valid for the
// whole lifetime of the result, plus possible copies/clones/offsets.
// This generally holds for GC-managed pointers and for static data.
// Dangerous with anything else.
pub unsafe fn from_ptr_and_len(ptr: *const u8, len: usize) -> Self {
Self {
ptr,
len: unwrap!(len.try_into()),
off: 0,
}
}
pub fn alloc(val: &str) -> Result<Self, Error> {
unsafe {
Self::alloc_with(val.len(), |buffer| {
// SAFETY: Memory should be freshly allocated and as such cannot overlap.
ptr::copy_nonoverlapping(val.as_ptr(), buffer.as_mut_ptr(), buffer.len())
})
}
}
pub fn alloc_with(len: usize, func: impl FnOnce(&mut [u8])) -> Result<Self, Error> {
// SAFETY:
// We assume that if `gc_alloc` returns successfully, the result is a valid
// pointer to GC-controlled memory of at least `val.len() + 1` bytes.
unsafe {
let raw = ffi::gc_alloc(len + 1, 0) as *mut u8;
if raw.is_null() {
return Err(Error::AllocationFailed);
}
// SAFETY: GC returns valid pointers, slice is discarded after `func`.
let bytes = slice::from_raw_parts_mut(raw, len);
// GC returns uninitialized memory which we must make sure to overwrite,
// otherwise leftover references may keep alive otherwise dead
// objects. Zero the entire buffer so we don't have to rely on
// `func` doing it.
bytes.fill(0);
func(bytes);
str::from_utf8(bytes).map_err(|_| Error::OutOfRange)?;
// Null-terminate the string for C ASCIIZ compatibility. This will not be
// reflected in Rust-visible slice, the zero byte is after the end.
raw.add(len).write(0);
// SAFETY: pointer is GC-managed.
Ok(Self::from_ptr_and_len(raw, len))
}
}
fn as_bytes(&self) -> &[u8] {
if self.ptr.is_null() {
&[]
} else {
unsafe { slice::from_raw_parts(self.ptr.add(self.off.into()), self.len.into()) }
}
}
pub fn skip_prefix(&self, skip_bytes: usize) -> Self {
let off: u16 = unwrap!(skip_bytes.try_into());
assert!(off <= self.len);
assert!(self.as_ref().is_char_boundary(skip_bytes));
Self {
ptr: self.ptr,
// Does not overflow because `off <= self.len`.
len: self.len - off,
// `self.off + off` could only overflow if `self.off + self.len` could overflow, and
// given that `off` only advances by as much as `len` decreases, that should not be
// possible either.
off: self.off + off,
}
}
}
impl Default for StrBuffer {
fn default() -> Self {
Self::empty()
}
}
impl TryFrom<Obj> for StrBuffer {
type Error = Error;
fn try_from(obj: Obj) -> Result<Self, Self::Error> {
if obj.is_str() {
let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?;
// SAFETY: bufinfo.buf should point to a GC head pointer or static data.
let new = unsafe { Self::from_ptr_and_len(bufinfo.buf as _, bufinfo.len) };
// MicroPython _should_ ensure that values of type `str` are UTF-8.
// Rust seems to be stricter in what it considers UTF-8 though.
// If there is a mismatch, we return an error.
let bytes = new.as_bytes();
if str::from_utf8(bytes).is_err() {
return Err(Error::TypeError);
}
Ok(new)
} else {
Err(Error::TypeError)
}
}
}
impl Deref for StrBuffer {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl AsRef<str> for StrBuffer {
fn as_ref(&self) -> &str {
// SAFETY:
// - If constructed from a Rust `&str`, this is safe.
// - If constructed from a MicroPython string, we check validity of UTF-8 at
// construction time. Python semantics promise not to mutate the underlying
// data from under us.
// - If constructed by `offset()`, we expect the input to be UTF-8 and check
// that we split the string at character boundary.
unsafe { str::from_utf8_unchecked(self.as_bytes()) }
}
}
impl From<&'static str> for StrBuffer {
fn from(val: &'static str) -> Self {
// SAFETY: Safe for &'static strs.
// Do not try to do it with arbitrary &'a str.
unsafe { Self::from_ptr_and_len(val.as_ptr(), val.len()) }
}
}
fn get_buffer_info(obj: Obj, flags: u32) -> Result<ffi::mp_buffer_info_t, Error> {
let mut bufinfo = ffi::mp_buffer_info_t {
buf: ptr::null_mut(),
len: 0,
typecode: 0,
};
// SAFETY: We assume that if `ffi::mp_get_buffer` returns successfully,
// `bufinfo.buf` contains a pointer to data of `bufinfo.len` bytes.
// EXCEPTION: Does not raise for Micropython's builtin types, and we don't
// implement custom buffer protocols.
if unsafe { ffi::mp_get_buffer(obj, &mut bufinfo, flags as _) } {
Ok(bufinfo)
} else {
Err(Error::TypeError)
}
}
/// Get an immutable reference to a buffer from a MicroPython object.
///
/// SAFETY:
/// The caller is responsible for ensuring immutability of the returned buffer,
/// in particular that:
/// (a) no mutable reference to the same buffer is held at the same time,
/// (b) the buffer is not modified in MicroPython while the reference to it is
/// being held.
/// The returned value is NOT guaranteed to be a head pointer, so the
/// destination might get GC'd. Do not store the reference.
pub unsafe fn get_buffer<'a>(obj: Obj) -> Result<&'a [u8], Error> {
let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_READ)?;
if bufinfo.buf.is_null() {
// `bufinfo.buf` can be null if len == 0.
Ok(&[])
} else {
// SAFETY: We assume that `bufinfo.buf` is pointing to memory:
// - valid in `'a`
// - of at least `bufinfo.len` bytes
// The caller is responsible for ensuring that:
// - there are no mutable references
// - that the buffer is immutable in `'a`
Ok(unsafe { slice::from_raw_parts(bufinfo.buf as _, bufinfo.len) })
}
}
/// Get a mutable reference to a buffer from a MicroPython object.
///
/// SAFETY:
/// The caller is responsible for ensuring uniqueness of the mutable reference,
/// in particular that:
/// (a) no other reference to the same buffer is held at the same time,
/// (b) the buffer is not modified in MicroPython while the reference to it is
/// being held.
/// The returned value is NOT guaranteed to be a head pointer, so the
/// destination might get GC'd. Do not store the reference.
pub unsafe fn get_buffer_mut<'a>(obj: Obj) -> Result<&'a mut [u8], Error> {
let bufinfo = get_buffer_info(obj, ffi::MP_BUFFER_WRITE)?;
if bufinfo.buf.is_null() {
// `bufinfo.buf` can be null if len == 0.
Ok(&mut [])
} else {
// SAFETY: We assume that `bufinfo.buf` is pointing to memory:
// - valid and mutable in `'a`,
// - of at least `bufinfo.len` bytes.
// The caller is responsible for ensuring that:
// - there are no other references
// - the buffer is not mutated outside of Rust's control.
Ok(unsafe { slice::from_raw_parts_mut(bufinfo.buf as _, bufinfo.len) })
}
}
pub fn hexlify_bytes(obj: Obj, offset: usize, max_len: usize) -> Result<StrBuffer, Error> {
if !obj.is_bytes() {
return Err(Error::TypeError);
}
// Convert offset to byte representation, handle case where it points in the
// middle of a byte.
let bin_off = offset / 2;
let hex_off = offset % 2;
// SAFETY:
// (a) only immutable references are taken
// (b) reference is discarded before returning to micropython
let bin_slice = unsafe { get_buffer(obj)? };
let bin_slice = &bin_slice[bin_off..];
let max_len = max_len & !1;
let hex_len = (bin_slice.len() * 2).min(max_len);
let result = StrBuffer::alloc_with(hex_len, move |buffer| hexlify(bin_slice, buffer))?;
Ok(result.skip_prefix(hex_off))
}