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/translations/obj.rs

238 lines
7.4 KiB

use crate::{
error::Error,
io::InputStream,
micropython::{
buffer::{get_buffer, StrBuffer},
ffi,
map::Map,
module::Module,
obj::Obj,
qstr::Qstr,
simple_type::SimpleTypeObj,
typ::Type,
util,
},
trezorhal::translations,
};
use super::translated_string::TranslatedString;
impl TryFrom<TranslatedString> for StrBuffer {
type Error = Error;
fn try_from(value: TranslatedString) -> Result<Self, Self::Error> {
let blob = super::flash::get()?;
let translated = value.translate(blob.as_ref());
StrBuffer::alloc(translated)
// TODO fall back to English (which is static and can be converted
// infallibly) if the allocation fails?
}
}
fn translate(translation: TranslatedString) -> Result<Obj, Error> {
translation
.translate(super::flash::get()?.as_ref())
.try_into()
}
// SAFETY: Caller is supposed to be MicroPython, or copy MicroPython contracts
// about the meaning of arguments.
unsafe extern "C" fn tr_attr_fn(_self_in: Obj, attr: ffi::qstr, dest: *mut Obj) {
let block = || {
let arg = unsafe { dest.read() };
if !arg.is_null() {
// Null destination would mean a `setattr`.
return Err(Error::TypeError);
}
let attr = Qstr::from_u16(attr as u16);
let result = if let Some(translation) = TranslatedString::from_qstr(attr) {
translate(translation)?
} else {
return Err(Error::AttributeError(attr));
};
unsafe { dest.write(result) };
Ok(())
};
unsafe { util::try_or_raise(block) }
}
static TR_TYPE: Type = obj_type! {
name: Qstr::MP_QSTR_TR,
attr_fn: tr_attr_fn,
};
static TR_OBJ: SimpleTypeObj = SimpleTypeObj::new(&TR_TYPE);
fn make_translations_header(header: &super::blob::TranslationsHeader<'_>) -> Result<Obj, Error> {
let version_objs: [Obj; 4] = {
let v = header.version;
[v[0].into(), v[1].into(), v[2].into(), v[3].into()]
};
attr_tuple! {
Qstr::MP_QSTR_language => header.language.try_into()?,
Qstr::MP_QSTR_version => util::new_tuple(&version_objs)?,
Qstr::MP_QSTR_data_len => header.data_len.try_into()?,
Qstr::MP_QSTR_data_hash => header.data_hash.as_ref().try_into()?,
Qstr::MP_QSTR_total_len => header.total_len.try_into()?,
}
}
pub unsafe extern "C" fn translations_header_new(
_self_in: Obj,
n_args: usize,
n_kw: usize,
args: *const Obj,
) -> Obj {
let block = |args: &[Obj], kwargs: &Map| {
if args.len() != 1 || !kwargs.is_empty() {
return Err(Error::TypeError);
}
// SAFETY: reference is discarded at the end of this function.
let buffer = unsafe { get_buffer(args[0])? };
let (header, _) =
super::blob::TranslationsHeader::parse_from(&mut InputStream::new(buffer))?;
make_translations_header(&header)
};
unsafe { util::try_with_args_and_kwargs_inline(n_args, n_kw, args, block) }
}
pub extern "C" fn translations_header_from_flash(_cls_in: Obj) -> Obj {
let block = || match super::flash::get()?.as_ref() {
Some(translations) => make_translations_header(translations.header()),
None => Ok(Obj::const_none()),
};
unsafe { util::try_or_raise(block) }
}
static TRANSLATIONS_HEADER_TYPE: Type = obj_type! {
name: Qstr::MP_QSTR_TranslationsHeader,
locals: &obj_dict!(obj_map! {
Qstr::MP_QSTR_load_from_flash => obj_fn_1!(translations_header_from_flash).as_obj(),
}),
call_fn: translations_header_new,
};
static TRANSLATIONS_HEADER_OBJ: SimpleTypeObj = SimpleTypeObj::new(&TRANSLATIONS_HEADER_TYPE);
extern "C" fn area_bytesize() -> Obj {
let bytesize = translations::area_bytesize();
unsafe { util::try_or_raise(|| bytesize.try_into()) }
}
extern "C" fn get_language() -> Obj {
let block = || {
let blob = super::flash::get()?;
let lang_name = blob.as_ref().map(|t| t.header().language);
lang_name.unwrap_or(super::DEFAULT_LANGUAGE).try_into()
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn init() -> Obj {
let block = || {
super::flash::init();
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn deinit() -> Obj {
let block = || {
super::flash::deinit()?;
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn erase() -> Obj {
let block = || {
super::flash::erase()?;
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn write(data: Obj, offset: Obj) -> Obj {
let block = || {
// SAFETY: reference is discarded at the end of the block
let data = unsafe { get_buffer(data)? };
let offset: usize = offset.try_into()?;
super::flash::write(data, offset)?;
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
}
extern "C" fn verify(data: Obj) -> Obj {
let block = || {
// SAFETY: reference is discarded at the end of the block
let data = unsafe { get_buffer(data)? };
super::blob::Translations::new(data)?;
Ok(Obj::const_none())
};
unsafe { util::try_or_raise(block) }
}
#[no_mangle]
#[rustfmt::skip]
pub static mp_module_trezortranslate: Module = obj_module! {
/// from trezortranslate_keys import TR as TR # noqa: F401
/// """Translation object with attributes."""
Qstr::MP_QSTR_TR => TR_OBJ.as_obj(),
/// def area_bytesize() -> int:
/// """Maximum size of the translation blob that can be stored."""
Qstr::MP_QSTR_area_bytesize => obj_fn_0!(area_bytesize).as_obj(),
/// def get_language() -> str:
/// """Get the current language."""
Qstr::MP_QSTR_get_language => obj_fn_0!(get_language).as_obj(),
/// def init() -> None:
/// """Initialize the translations system.
///
/// Loads and verifies translation data from flash. If the verification passes,
/// Trezor UI is translated from that point forward.
/// """
Qstr::MP_QSTR_init => obj_fn_0!(init).as_obj(),
/// def deinit() -> None:
/// """Deinitialize the translations system.
///
/// Translations must be deinitialized before erasing or writing to flash.
/// """
Qstr::MP_QSTR_deinit => obj_fn_0!(deinit).as_obj(),
/// def erase() -> None:
/// """Erase the translations blob from flash."""
Qstr::MP_QSTR_erase => obj_fn_0!(erase).as_obj(),
/// def write(data: bytes, offset: int) -> None:
/// """Write data to the translations blob in flash."""
Qstr::MP_QSTR_write => obj_fn_2!(write).as_obj(),
/// def verify(data: bytes) -> None:
/// """Verify the translations blob."""
Qstr::MP_QSTR_verify => obj_fn_1!(verify).as_obj(),
/// class TranslationsHeader:
/// """Metadata about the translations blob."""
///
/// language: str
/// version: tuple[int, int, int, int]
/// data_len: int
/// data_hash: bytes
/// total_len: int
///
/// def __init__(self, header_bytes: bytes) -> None:
/// """Parse header from bytes.
/// The header has variable length.
/// """
///
/// @staticmethod
/// def load_from_flash() -> TranslationsHeader | None:
/// """Load translations from flash."""
Qstr::MP_QSTR_TranslationsHeader => TRANSLATIONS_HEADER_OBJ.as_obj(),
};