mirror of
https://github.com/trezor/trezor-firmware.git
synced 2026-02-20 00:33:30 +01:00
move paging to app, split cache from long screen layout
This commit is contained in:
@@ -1,18 +1,4 @@
|
||||
use crate::{
|
||||
debug,
|
||||
ipc::{IpcMessage, RemoteSysTask},
|
||||
micropython::buffer::StrBuffer,
|
||||
strutil::TString,
|
||||
ui::component::{base::AttachType, Event, EventCtx},
|
||||
};
|
||||
use core::mem::MaybeUninit;
|
||||
use rkyv::{
|
||||
api::low::to_bytes_in_with_alloc,
|
||||
rancor::Failure,
|
||||
ser::{allocator::SubAllocator, writer::Buffer},
|
||||
util::Align,
|
||||
};
|
||||
use trezor_structs::UtilEnum;
|
||||
use crate::{micropython::buffer::StrBuffer, strutil::TString};
|
||||
|
||||
/// A node in the linked list cache
|
||||
struct CacheNode<const T: usize> {
|
||||
@@ -79,77 +65,43 @@ impl<const T: usize> CacheNode<T> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CacheState {
|
||||
Uninit,
|
||||
Waiting(usize),
|
||||
Ready,
|
||||
}
|
||||
|
||||
/// LRU-style linked list cache with N slots.
|
||||
///
|
||||
/// head <-> ... <-> tail
|
||||
/// - Adding next: add after head, evict tail if full
|
||||
/// - Adding prev: add before tail, evict head if full
|
||||
pub struct Cache<const A: usize, const T: usize, const N: usize> {
|
||||
state: CacheState,
|
||||
pub struct Cache<const T: usize, const N: usize> {
|
||||
nodes: [CacheNode<T>; N],
|
||||
head: Option<usize>, // newest towards next direction
|
||||
tail: Option<usize>, // newest towards prev direction
|
||||
current_node: usize,
|
||||
current_page: u16,
|
||||
total_pages: u16,
|
||||
current: Option<usize>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<const A: usize, const T: usize, const N: usize> Cache<A, T, N> {
|
||||
impl<const T: usize, const N: usize> Cache<T, N> {
|
||||
const EMPTY_NODE: CacheNode<T> = CacheNode::empty();
|
||||
|
||||
pub const fn new(total_pages: u16) -> Self {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
state: CacheState::Uninit,
|
||||
nodes: [Self::EMPTY_NODE; N],
|
||||
head: None,
|
||||
tail: None,
|
||||
current_node: 0,
|
||||
total_pages,
|
||||
current_page: 0,
|
||||
current: None,
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, event: Event) {
|
||||
if matches!(event, Event::Attach(AttachType::Initial)) {
|
||||
// debug_assert!(self.cache.state == CacheState::Empty);
|
||||
// Load content into cache if needed
|
||||
self.request_page(ctx, 0);
|
||||
}
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if let Some(data) = IpcMessage::try_receive(RemoteSysTask::Unknown(2)) {
|
||||
self.update(&data.data(), ctx);
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
pub fn initialized(&self) -> bool {
|
||||
self.current.is_some()
|
||||
}
|
||||
|
||||
pub fn init(&mut self, data: &[u8]) {
|
||||
debug_assert!(self.state == CacheState::Uninit);
|
||||
debug_assert!(self.len == 0);
|
||||
|
||||
self.current_node = 0;
|
||||
self.nodes[self.current_node].set_content(data);
|
||||
self.insert_at_head(self.current_node);
|
||||
let current = 0;
|
||||
self.nodes[current].set_content(data);
|
||||
self.insert_at_head(current);
|
||||
self.len = 1;
|
||||
self.state = CacheState::Ready;
|
||||
}
|
||||
|
||||
pub fn has_next(&self) -> bool {
|
||||
self.current_page + 1 < self.total_pages
|
||||
}
|
||||
|
||||
pub fn has_prev(&self) -> bool {
|
||||
self.current_page > 0
|
||||
self.current = Some(current);
|
||||
}
|
||||
|
||||
/// Find a free node slot
|
||||
@@ -229,6 +181,8 @@ impl<const A: usize, const T: usize, const N: usize> Cache<A, T, N> {
|
||||
|
||||
/// Get a slot for a new node, evicting from specified end if needed
|
||||
fn get_slot_evict_tail(&mut self) -> usize {
|
||||
debug_assert!(self.len >= 1);
|
||||
|
||||
if let Some(idx) = self.find_free_slot() {
|
||||
idx
|
||||
} else {
|
||||
@@ -237,6 +191,8 @@ impl<const A: usize, const T: usize, const N: usize> Cache<A, T, N> {
|
||||
}
|
||||
|
||||
fn get_slot_evict_head(&mut self) -> usize {
|
||||
debug_assert!(self.len >= 1);
|
||||
|
||||
if let Some(idx) = self.find_free_slot() {
|
||||
idx
|
||||
} else {
|
||||
@@ -244,130 +200,67 @@ impl<const A: usize, const T: usize, const N: usize> Cache<A, T, N> {
|
||||
}
|
||||
}
|
||||
|
||||
fn request_page(&mut self, ctx: &mut EventCtx, page_idx: u16) {
|
||||
let data = UtilEnum::RequestSlice {
|
||||
offset: (page_idx as u32) * (A as u32),
|
||||
size: A as u32,
|
||||
};
|
||||
|
||||
let mut arena = [MaybeUninit::<u8>::uninit(); 200];
|
||||
let mut out = Align([MaybeUninit::<u8>::uninit(); 200]);
|
||||
|
||||
let bytes = to_bytes_in_with_alloc::<_, _, Failure>(
|
||||
&data,
|
||||
Buffer::from(&mut *out),
|
||||
SubAllocator::new(&mut arena),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = IpcMessage::new(9, &bytes);
|
||||
unwrap!(msg.send(RemoteSysTask::Unknown(2), 6));
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
|
||||
pub fn update(&mut self, data: &[u8], ctx: &mut EventCtx) -> u16 {
|
||||
debug_assert!(matches!(
|
||||
self.state,
|
||||
CacheState::Uninit | CacheState::Waiting(_)
|
||||
));
|
||||
|
||||
match self.state {
|
||||
CacheState::Uninit => {
|
||||
self.init(data);
|
||||
if self.has_next() {
|
||||
let idx = self.current_page as usize + 1;
|
||||
self.state = CacheState::Waiting(idx);
|
||||
self.request_page(ctx, idx as u16);
|
||||
} else {
|
||||
self.state = CacheState::Ready;
|
||||
}
|
||||
ctx.request_paint();
|
||||
}
|
||||
CacheState::Waiting(next_page) if self.current_page + 1 == next_page as u16 => {
|
||||
debug_assert!(self.head.is_some());
|
||||
debug_assert!(self.current_node == self.head.unwrap());
|
||||
self.set_next(data);
|
||||
}
|
||||
CacheState::Waiting(prev_page) if self.current_page == prev_page as u16 + 1 => {
|
||||
debug_assert!(self.tail.is_some());
|
||||
debug_assert!(self.current_node == self.tail.unwrap());
|
||||
self.set_prev(data);
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("Unexpected page received");
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
/// Add next page data (adds at head, evicts from tail if full)
|
||||
pub fn set_next(&mut self, data: &[u8]) {
|
||||
debug_assert!(self.has_next() && matches!(self.state, CacheState::Waiting(_)));
|
||||
debug_assert!(self.current_node == self.head.unwrap());
|
||||
pub fn push_head(&mut self, data: &[u8]) {
|
||||
debug_assert!(self.current == self.head);
|
||||
|
||||
let idx = self.get_slot_evict_tail();
|
||||
self.nodes[idx].set_content(data);
|
||||
self.insert_at_head(idx);
|
||||
self.len += 1;
|
||||
self.state = CacheState::Ready;
|
||||
}
|
||||
|
||||
/// Add prev page data (adds at tail, evicts from head if full)
|
||||
pub fn set_prev(&mut self, data: &[u8]) {
|
||||
debug_assert!(self.has_prev() && matches!(self.state, CacheState::Waiting(_)));
|
||||
debug_assert!(self.current_node == self.tail.unwrap());
|
||||
pub fn push_tail(&mut self, data: &[u8]) {
|
||||
// debug_assert!(self.has_prev() && matches!(self.state,
|
||||
// CacheState::Waiting(_)));
|
||||
debug_assert!(self.current == self.tail);
|
||||
let idx = self.get_slot_evict_head();
|
||||
self.nodes[idx].set_content(data);
|
||||
self.insert_at_tail(idx);
|
||||
self.len += 1;
|
||||
self.state = CacheState::Ready;
|
||||
}
|
||||
|
||||
/// Switch to next page in cache
|
||||
pub fn switch_next(&mut self, ctx: &mut EventCtx) {
|
||||
debug_assert!(self.has_next() && self.state == CacheState::Ready);
|
||||
pub fn go_next(&mut self) {
|
||||
debug_assert!(self.current.is_some());
|
||||
debug_assert!(self.nodes[unwrap!(self.current)].has_next());
|
||||
|
||||
let next_node = self.nodes[self.current_node].next;
|
||||
debug_assert!(next_node.is_some());
|
||||
self.current_node = next_node.unwrap();
|
||||
self.current_page += 1;
|
||||
|
||||
// Request prefetch if there's another page after
|
||||
if self.has_next() && self.nodes[self.current_node].next.is_none() {
|
||||
let next_idx = self.current_page as usize + 1;
|
||||
self.state = CacheState::Waiting(next_idx);
|
||||
self.request_page(ctx, next_idx as u16);
|
||||
}
|
||||
self.current = self.nodes[unwrap!(self.current)].next;
|
||||
}
|
||||
|
||||
/// Switch to prev page in cache
|
||||
pub fn switch_prev(&mut self, ctx: &mut EventCtx) {
|
||||
debug_assert!(self.has_prev() && self.state == CacheState::Ready);
|
||||
pub fn go_prev(&mut self) {
|
||||
debug_assert!(self.current.is_some());
|
||||
debug_assert!(self.nodes[unwrap!(self.current)].has_prev());
|
||||
|
||||
let prev_node = self.nodes[self.current_node].prev;
|
||||
debug_assert!(prev_node.is_some());
|
||||
self.current_node = prev_node.unwrap();
|
||||
self.current_page -= 1;
|
||||
|
||||
// Request prefetch if there's another page before
|
||||
if self.has_prev() && self.nodes[self.current_node].prev.is_none() {
|
||||
let prev_idx = self.current_page as usize - 1;
|
||||
self.state = CacheState::Waiting(prev_idx);
|
||||
self.request_page(ctx, prev_idx as u16);
|
||||
}
|
||||
self.current = self.nodes[unwrap!(self.current)].prev;
|
||||
}
|
||||
|
||||
pub fn current_page_data(&self) -> TString<'static> {
|
||||
if self.state == CacheState::Uninit {
|
||||
return TString::empty();
|
||||
pub fn is_at_head(&self) -> bool {
|
||||
debug_assert!(self.current.is_some());
|
||||
self.current == self.head
|
||||
}
|
||||
|
||||
pub fn is_at_tail(&self) -> bool {
|
||||
debug_assert!(self.current.is_some());
|
||||
self.current == self.tail
|
||||
}
|
||||
|
||||
pub fn current_data(&self) -> Option<TString<'static>> {
|
||||
if let Some(current) = self.current {
|
||||
unsafe {
|
||||
return Some(
|
||||
StrBuffer::from_ptr_and_len(
|
||||
self.nodes[current].content.as_ptr(),
|
||||
self.nodes[current].content_len,
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
let node = &self.nodes[self.current_node];
|
||||
debug_assert!(node.valid);
|
||||
|
||||
unsafe { StrBuffer::from_ptr_and_len(node.content.as_ptr(), node.content_len) }.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for the default cache configuration
|
||||
pub type PageCache<const A: usize, const T: usize> = Cache<A, T, 3>;
|
||||
pub type PageCache = Cache<360, 3>;
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
use crate::{
|
||||
ipc::{IpcMessage, RemoteSysTask},
|
||||
strutil::TString,
|
||||
ui::{
|
||||
cache::PageCache,
|
||||
component::{
|
||||
base::AttachType,
|
||||
text::{layout::LayoutFit, LineBreaking},
|
||||
Component, Event, EventCtx, Never, TextLayout,
|
||||
},
|
||||
@@ -12,9 +14,16 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::{constant::SCREEN, theme, ActionBar, ActionBarMsg, Header, Hint};
|
||||
use core::mem::MaybeUninit;
|
||||
use rkyv::{
|
||||
api::low::to_bytes_in_with_alloc,
|
||||
rancor::Failure,
|
||||
ser::{allocator::SubAllocator, writer::Buffer},
|
||||
util::Align,
|
||||
};
|
||||
use trezor_structs::UtilEnum;
|
||||
|
||||
pub const CHARS_PER_PAGE: usize = 10;
|
||||
use super::{constant::SCREEN, theme, ActionBar, ActionBarMsg, Header, Hint};
|
||||
|
||||
pub enum LongContentScreenMsg {
|
||||
Confirmed,
|
||||
@@ -29,8 +38,8 @@ pub struct LongContentScreen<'a> {
|
||||
}
|
||||
|
||||
impl<'a> LongContentScreen<'a> {
|
||||
pub fn new(title: TString<'static>, text_length: u32) -> Self {
|
||||
let content = LongContent::new(text_length);
|
||||
pub fn new(title: TString<'static>, pages: usize) -> Self {
|
||||
let content = LongContent::new(pages as u16);
|
||||
let mut action_bar = ActionBar::new_cancel_confirm();
|
||||
action_bar.update(content.pager);
|
||||
|
||||
@@ -127,20 +136,18 @@ impl<'a> crate::trace::Trace for LongContentScreen<'a> {
|
||||
|
||||
struct LongContent {
|
||||
pager: Pager,
|
||||
content_length: u32,
|
||||
cache: PageCache<CHARS_PER_PAGE, { 4 * CHARS_PER_PAGE }>,
|
||||
cache: PageCache,
|
||||
area: Rect,
|
||||
state: ContentState,
|
||||
}
|
||||
|
||||
impl LongContent {
|
||||
fn new(content_length: u32) -> Self {
|
||||
let total_pages =
|
||||
((content_length as u16) + CHARS_PER_PAGE as u16 - 1) / CHARS_PER_PAGE as u16;
|
||||
fn new(pages: u16) -> Self {
|
||||
Self {
|
||||
pager: Pager::new(total_pages),
|
||||
content_length,
|
||||
cache: PageCache::<CHARS_PER_PAGE, { 4 * CHARS_PER_PAGE }>::new(total_pages),
|
||||
pager: Pager::new(pages),
|
||||
cache: PageCache::new(),
|
||||
area: Rect::zero(),
|
||||
state: ContentState::Uninit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,14 +157,48 @@ impl LongContent {
|
||||
|
||||
fn switch_next(&mut self, ctx: &mut EventCtx) {
|
||||
debug_assert!(!self.pager.is_last());
|
||||
debug_assert!(matches!(self.state, ContentState::Ready));
|
||||
self.pager.goto_next();
|
||||
self.cache.switch_next(ctx);
|
||||
self.cache.go_next();
|
||||
|
||||
// Request prefetch if there's another page after
|
||||
if self.pager.has_next() && self.cache.is_at_head() {
|
||||
let next = self.pager.next() as usize;
|
||||
self.request_page(ctx, next);
|
||||
self.state = ContentState::Waiting(next);
|
||||
}
|
||||
}
|
||||
|
||||
fn switch_prev(&mut self, ctx: &mut EventCtx) {
|
||||
debug_assert!(!self.pager.is_first());
|
||||
debug_assert!(matches!(self.state, ContentState::Ready));
|
||||
self.pager.goto_prev();
|
||||
self.cache.switch_prev(ctx);
|
||||
self.cache.go_prev();
|
||||
|
||||
// Request prefetch if there's another page before
|
||||
if self.pager.has_prev() && self.cache.is_at_tail() {
|
||||
let prev = self.pager.prev() as usize;
|
||||
self.request_page(ctx, prev);
|
||||
self.state = ContentState::Waiting(prev);
|
||||
}
|
||||
}
|
||||
|
||||
fn request_page(&mut self, ctx: &mut EventCtx, idx: usize) {
|
||||
let data = UtilEnum::RequestPage { idx };
|
||||
|
||||
let mut arena = [MaybeUninit::<u8>::uninit(); 200];
|
||||
let mut out = Align([MaybeUninit::<u8>::uninit(); 200]);
|
||||
|
||||
let bytes = to_bytes_in_with_alloc::<_, _, Failure>(
|
||||
&data,
|
||||
Buffer::from(&mut *out),
|
||||
SubAllocator::new(&mut arena),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let msg = IpcMessage::new(9, &bytes);
|
||||
unwrap!(msg.send(RemoteSysTask::Unknown(2), 6));
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,13 +211,58 @@ impl Component for LongContent {
|
||||
}
|
||||
|
||||
fn event(&mut self, ctx: &mut EventCtx, event: Event) -> Option<Self::Msg> {
|
||||
self.cache.event(ctx, event);
|
||||
if matches!(event, Event::Attach(AttachType::Initial)) {
|
||||
// debug_assert!(self.cache.state == CacheState::Empty);
|
||||
// Load content into cache if needed
|
||||
self.request_page(ctx, 0);
|
||||
}
|
||||
|
||||
if let Event::Timer(EventCtx::ANIM_FRAME_TIMER) = event {
|
||||
if let Some(data) = IpcMessage::try_receive(RemoteSysTask::Unknown(2)) {
|
||||
debug_assert!(matches!(
|
||||
self.state,
|
||||
ContentState::Uninit | ContentState::Waiting(_)
|
||||
));
|
||||
self.state = match self.state {
|
||||
ContentState::Uninit => {
|
||||
self.cache.init(data.data());
|
||||
ctx.request_paint();
|
||||
if self.pager.has_next() {
|
||||
let idx = self.pager.next() as usize;
|
||||
self.request_page(ctx, idx);
|
||||
ContentState::Waiting(idx)
|
||||
} else {
|
||||
ContentState::Ready
|
||||
}
|
||||
}
|
||||
ContentState::Waiting(next_page)
|
||||
if self.pager.current() + 1 == next_page as u16 =>
|
||||
{
|
||||
debug_assert!(self.cache.is_at_head());
|
||||
self.cache.push_head(data.data());
|
||||
ContentState::Ready
|
||||
}
|
||||
ContentState::Waiting(prev_page)
|
||||
if self.pager.current() == prev_page as u16 + 1 =>
|
||||
{
|
||||
debug_assert!(self.cache.is_at_tail());
|
||||
self.cache.push_tail(data.data());
|
||||
ContentState::Ready
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("Unexpected page received");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.request_anim_frame();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn render<'s>(&'s self, target: &mut impl Renderer<'s>) {
|
||||
let current_page = self.cache.current_page_data();
|
||||
let current_page = self.cache.current_data().unwrap_or(TString::empty());
|
||||
|
||||
current_page.map(|text| {
|
||||
let layout = TextLayout::new(
|
||||
@@ -196,8 +282,14 @@ impl Component for LongContent {
|
||||
impl crate::trace::Trace for LongContent {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.component("LongContent");
|
||||
t.int("content length", self.content_length as i64);
|
||||
t.int("current_page", self.pager.current() as i64);
|
||||
t.int("total_pages", self.pager.total() as i64);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum ContentState {
|
||||
Uninit,
|
||||
Waiting(usize),
|
||||
Ready,
|
||||
}
|
||||
|
||||
@@ -124,11 +124,8 @@ impl FirmwareUI for UIEckhart {
|
||||
Ok(layout)
|
||||
}
|
||||
|
||||
fn confirm_long(
|
||||
title: TString<'static>,
|
||||
content_length: u32,
|
||||
) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let screen = LongContentScreen::new(title, content_length);
|
||||
fn confirm_long(title: TString<'static>, pages: usize) -> Result<impl LayoutMaybeTrace, Error> {
|
||||
let screen = LongContentScreen::new(title, pages);
|
||||
let layout = RootComponent::new(screen);
|
||||
Ok(layout)
|
||||
}
|
||||
@@ -1776,10 +1773,12 @@ impl FirmwareUI for UIEckhart {
|
||||
)?;
|
||||
LayoutObj::new_root(layout)
|
||||
}
|
||||
ArchivedTrezorUiEnum::ConfirmLong { title, content_len } => {
|
||||
ArchivedTrezorUiEnum::ConfirmLong { title, pages } => {
|
||||
// Use safe Deref trait instead of raw pointers
|
||||
let layout =
|
||||
Self::confirm_long(tstr_from_archived(title), u32::from(*content_len))?;
|
||||
let layout = Self::confirm_long(
|
||||
tstr_from_archived(title),
|
||||
unwrap!(usize::try_from(pages.to_native())),
|
||||
)?;
|
||||
LayoutObj::new_root(layout)
|
||||
}
|
||||
ArchivedTrezorUiEnum::ConfirmProperties { title, props } => {
|
||||
|
||||
@@ -41,7 +41,7 @@ pub trait FirmwareUI {
|
||||
|
||||
fn confirm_long(
|
||||
title: TString<'static>,
|
||||
content_length: u32,
|
||||
pages: usize,
|
||||
) -> Result<impl LayoutMaybeTrace, Error>;
|
||||
|
||||
fn confirm_address(
|
||||
|
||||
@@ -3,8 +3,17 @@ use alloc::string::ToString;
|
||||
use trezor_app_sdk::{Result, crypto, log, ui};
|
||||
|
||||
pub fn get_public_key(msg: EthereumGetPublicKey) -> Result<EthereumPublicKey> {
|
||||
let long_string: &str = "Hello, 世界! 🌍🌎🌏 Привет мир! مرحبا بالعالم 日本語テスト αβγδε ñoño café naïve 北京 Zürich™ €100 ½ ¼ ¾ → ← ↑ ↓ ♠♣♥♦ ✓✗ ∑∏∫∂ ≤≥≠≈ 🎉🔥💡🚀 ⚡️☀️🌙⭐️";
|
||||
log::info!("string chars: {}, string bytes: {}", long_string.chars().count(), long_string.len());
|
||||
let long_string: &str = "asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789\
|
||||
asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789\
|
||||
asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789\
|
||||
asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789\
|
||||
asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789\
|
||||
asdfghjklqwertyuiopzxcvbnmASDFGHJKLQWERTYUIOPZXCVBNM0123456789";
|
||||
log::info!(
|
||||
"string chars: {}, string bytes: {}",
|
||||
long_string.chars().count(),
|
||||
long_string.len()
|
||||
);
|
||||
|
||||
ui::confirm_long_value("title", long_string)?;
|
||||
|
||||
|
||||
@@ -96,20 +96,23 @@ impl<'a, T: Into<u16>> IpcRemote<'a, T> {
|
||||
|
||||
let archived = unsafe { rkyv::access_unchecked::<ArchivedUtilEnum>(data) };
|
||||
match archived {
|
||||
ArchivedUtilEnum::RequestSlice { offset, size } => {
|
||||
let offset_val = offset.to_native() as usize;
|
||||
let size_val = size.to_native() as usize;
|
||||
ArchivedUtilEnum::RequestPage { idx } => {
|
||||
let page_idx = idx.to_native() as usize;
|
||||
|
||||
// Find byte range for the requested char slice
|
||||
let mut chars = long_content.chars();
|
||||
let start_byte = chars
|
||||
.by_ref()
|
||||
.take(offset_val)
|
||||
.take(page_idx * crate::ui::CHARS_PER_PAGE)
|
||||
.map(|c| c.len_utf8())
|
||||
.sum::<usize>();
|
||||
let slice_len = chars
|
||||
.take(crate::ui::CHARS_PER_PAGE)
|
||||
.map(|c| c.len_utf8())
|
||||
.sum::<usize>();
|
||||
let slice_len = chars.take(size_val).map(|c| c.len_utf8()).sum::<usize>();
|
||||
let slice = &long_content.as_bytes()[start_byte..start_byte + slice_len];
|
||||
|
||||
// TODO implement Debuf for Api error
|
||||
// TODO implement Debug for Api error
|
||||
let _ = IpcMessage::new(10, slice).send(RemoteSysTask::CoreApp, 6);
|
||||
}
|
||||
_ => return Err(Error::UnexpectedResponse(reply)),
|
||||
|
||||
@@ -33,6 +33,8 @@ use crate::util::Timeout;
|
||||
pub type ArchivedTrezorUiResult = Archived<TrezorUiResult>;
|
||||
pub type ArchivedTrezorUiEnum = Archived<TrezorUiEnum>;
|
||||
|
||||
pub const CHARS_PER_PAGE: usize = 96;
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
@@ -52,11 +54,11 @@ fn ipc_ui_call(value: &TrezorUiEnum) -> UiResult {
|
||||
Ok(deserialized)
|
||||
}
|
||||
|
||||
|
||||
fn ipc_ui_long_call(value: &TrezorUiEnum, long_content: &str) -> UiResult {
|
||||
let bytes = to_bytes::<Failure>(value).unwrap();
|
||||
let message = IpcMessage::new(0, &bytes);
|
||||
let result = services_or_die().call_long(CoreIpcService::Ui, &message, Timeout::max(), long_content)?;
|
||||
let result =
|
||||
services_or_die().call_long(CoreIpcService::Ui, &message, Timeout::max(), long_content)?;
|
||||
|
||||
// Safe validation using bytecheck before accessing archived data
|
||||
let archived = rkyv::access::<ArchivedTrezorUiResult, Failure>(result.data()).unwrap();
|
||||
@@ -64,8 +66,6 @@ fn ipc_ui_long_call(value: &TrezorUiEnum, long_content: &str) -> UiResult {
|
||||
Ok(deserialized)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Send a UI call and expect a boolean confirmation result
|
||||
fn ipc_ui_call_confirm(value: TrezorUiEnum) -> UiResult {
|
||||
match ipc_ui_call(&value) {
|
||||
@@ -102,7 +102,7 @@ pub fn confirm_value(title: &str, content: &str) -> UiResult {
|
||||
pub fn confirm_long_value(title: &str, content: &str) -> UiResult {
|
||||
let value = TrezorUiEnum::ConfirmLong {
|
||||
title: ShortString::from_str(title).unwrap(),
|
||||
content_len: content.chars().count() as u32,
|
||||
pages: (content.chars().count() as usize + CHARS_PER_PAGE - 1) / CHARS_PER_PAGE,
|
||||
};
|
||||
|
||||
match ipc_ui_long_call(&value, content) {
|
||||
|
||||
@@ -124,7 +124,7 @@ pub enum TrezorUiEnum {
|
||||
},
|
||||
ConfirmLong {
|
||||
title: ShortString,
|
||||
content_len: u32,
|
||||
pages: usize,
|
||||
},
|
||||
Warning {
|
||||
title: ShortString,
|
||||
@@ -189,8 +189,5 @@ pub enum TrezorCryptoResult {
|
||||
/// Outgoing Crypto result message for IPC
|
||||
#[derive(Archive, Serialize, Deserialize)]
|
||||
pub enum UtilEnum {
|
||||
RequestSlice {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
},
|
||||
RequestPage { idx: usize },
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user