From 9f60eb53520703c952de837046cef8cb7197ab84 Mon Sep 17 00:00:00 2001 From: Alexis Grojean Date: Thu, 18 Jan 2024 17:59:23 +0100 Subject: [PATCH] Refactor NBGL structures to be compatible with Nano devices rust SDK --- ledger_device_sdk/examples/stax.rs | 146 +++++------ ledger_device_sdk/src/nbgl.rs | 389 +++++++++++++++++------------ 2 files changed, 285 insertions(+), 250 deletions(-) diff --git a/ledger_device_sdk/examples/stax.rs b/ledger_device_sdk/examples/stax.rs index b42fa0ba..50587ba5 100644 --- a/ledger_device_sdk/examples/stax.rs +++ b/ledger_device_sdk/examples/stax.rs @@ -6,62 +6,9 @@ use ledger_device_sdk as _; use const_zero::const_zero; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{NbglUI, Field}; -use ledger_secure_sdk_sys::*; +use ledger_device_sdk::nbgl::{Field, NbglHome, NbglReview}; use ledger_device_sdk::testing::debug_print; - -#[no_mangle] -pub static mut G_ux_params: bolos_ux_params_t = unsafe { const_zero!(bolos_ux_params_t) }; - -#[panic_handler] -fn panic(_: &core::panic::PanicInfo) -> ! { - exit_app(1); -} - -enum TuneIndex { - Reserved, - Boot, - Charging, - LedgerMoment, - Error, - Neutral, - Lock, - Success, - LookAtMe, - TapCasual, - TapNext, -} - -impl TryFrom for TuneIndex { - type Error = (); - fn try_from(index: u8) -> Result { - Ok(match index { - TUNE_RESERVED => TuneIndex::Reserved, - TUNE_BOOT => TuneIndex::Boot, - TUNE_CHARGING => TuneIndex::Charging, - TUNE_LEDGER_MOMENT => TuneIndex::LedgerMoment, - TUNE_ERROR => TuneIndex::Error, - TUNE_NEUTRAL => TuneIndex::Neutral, - TUNE_LOCK => TuneIndex::Lock, - TUNE_SUCCESS => TuneIndex::Success, - TUNE_LOOK_AT_ME => TuneIndex::LookAtMe, - TUNE_TAP_CASUAL => TuneIndex::TapCasual, - TUNE_TAP_NEXT => TuneIndex::TapNext, - _ => return Err(()), - }) - } -} - -// this is a mock that does nothing yet, but should become a direct translation -// of the C original. This was done to avoid compiling `os_io_seproxyhal.c` which -// includes many other things -#[no_mangle] -extern "C" fn io_seproxyhal_play_tune(tune_index: u8) { - let index = TuneIndex::try_from(tune_index); - if index.is_err() { - return; - } -} +use ledger_secure_sdk_sys::*; pub enum Instruction { GetVersion, @@ -88,46 +35,65 @@ extern "C" fn sample_main() { let mut comm = Comm::new(); - let mut nbgl_ui = NbglUI::new(Some(&mut comm)) - .app_name("Stax Sample\0") - .info_contents(env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS")) - .icon(&BTC_BMP); - - // let myStaticReview = StaticReview::new(Some(&comm)); - - // myStaticReview.show_and_wait_validation(); - - // myNBGL.show_home(); - - let fields = [ - Field { - name: "Field 1\0", - value: "Value 1\0", - }, - Field { - name: "Field 2\0", - value: "Value 2\0", - }, - Field { - name: "Field 3\0", - value: "Value 3\0", - }, - ]; - - - if nbgl_ui.show_review_and_wait_validation(&fields) { - debug_print("Validation result: true\n"); - } else { - debug_print("Validation result: false\n"); - } - loop { - match nbgl_ui.get_events::() { - Event::Command(_) => (), + match NbglHome::new(&mut comm) + .app_name("Stax Sample\0") + .info_contents(env!("CARGO_PKG_VERSION"), env!("CARGO_PKG_AUTHORS")) + .icon(&BTC_BMP) + .show_home::() + { + Event::Command(_) => { + let fields = [ + Field { + name: "Field 1\0", + value: "Value 1\0", + }, + Field { + name: "Field 2\0", + value: "Value 2\0", + }, + Field { + name: "Field 3\0", + value: "Value 3\0", + }, + ]; + if NbglReview::new(&mut comm).review_transaction(&fields) { + debug_print("Validation result: true\n"); + } else { + debug_print("Validation result: false\n"); + } + } _ => (), }; } + // let fields = [ + // Field { + // name: "Field 1\0", + // value: "Value 1\0", + // }, + // Field { + // name: "Field 2\0", + // value: "Value 2\0", + // }, + // Field { + // name: "Field 3\0", + // value: "Value 3\0", + // }, + // ]; + + // if nbgl_ui.verify_address("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2\0") { + // debug_print("Address verified\n"); + // } else { + // debug_print("Address not verified\n"); + // } + + // if nbgl_ui.review_transaction(&fields) { + // debug_print("Validation result: true\n"); + // } else { + // debug_print("Validation result: false\n"); + // } + // exit_app(0); } diff --git a/ledger_device_sdk/src/nbgl.rs b/ledger_device_sdk/src/nbgl.rs index c22b377c..ddf36ca2 100644 --- a/ledger_device_sdk/src/nbgl.rs +++ b/ledger_device_sdk/src/nbgl.rs @@ -3,8 +3,6 @@ use const_zero::const_zero; use core::mem::transmute; use ledger_secure_sdk_sys::nbgl_icon_details_t; use ledger_secure_sdk_sys::*; -use crate::testing::debug_print; - struct DummyEvent; @@ -19,10 +17,13 @@ impl TryFrom for DummyEvent { } } -pub struct NbglUI<'a> { - comm: Option<&'a mut Comm>, +pub struct NbglHome<'a> { + comm: &'a mut Comm, } +pub struct NbglReview; +pub struct NbglAddressConfirm; + #[derive(PartialEq)] pub enum ReviewStatus { Pending, @@ -51,28 +52,94 @@ const INFOTYPES: [*const ::core::ffi::c_char; 2] = [ "Developer\0".as_ptr() as *const ::core::ffi::c_char, ]; +fn process_touch_events() { + if !seph::is_status_sent() { + seph::send_general_status(); + } + + let mut buf = [0u8; 8]; + while seph::is_status_sent() { + seph::seph_recv(&mut buf, 0); + + match buf[0] as u32 { + SEPROXYHAL_TAG_FINGER_EVENT => { + unsafe { + ux_process_finger_event(buf.as_mut_ptr()); + } + } + SEPROXYHAL_TAG_TICKER_EVENT => unsafe { + ux_process_ticker_event(); + } + _ => (), + } + } +} + +fn get_events>(comm: &mut Comm) -> Event +where + Reply: From<>::Error>, +{ + loop { + if let Event::Command(ins) = comm.next_event() { + return Event::Command(ins); + } + } +} + +fn wait_review_result() -> bool { + loop { + process_touch_events(); + unsafe { + if CTX.review_status == ReviewStatus::Validate { + return true; + } else if CTX.review_status == ReviewStatus::Reject { + return false; + } + } + } +} + +fn display_review_result_cbk(confirm: bool) { + let result_string: *const ::core::ffi::c_char; + unsafe { + if confirm { + CTX.review_status = ReviewStatus::Validate; + result_string = CTX.review_confirm_string.as_ptr() as *const ::core::ffi::c_char; + } else { + CTX.review_status = ReviewStatus::Reject; + result_string = CTX.review_reject_string.as_ptr() as *const ::core::ffi::c_char; + } + + // We don't need the callback here as we're going to display the home screen from the main app loop + ledger_secure_sdk_sys::nbgl_useCaseStatus(result_string, confirm, None); + } +} + +#[no_mangle] +pub static mut G_ux_params: bolos_ux_params_t = unsafe { const_zero!(bolos_ux_params_t) }; + static mut CTX: NbglContext = unsafe { const_zero!(NbglContext) }; -impl<'a> NbglUI<'a> { - pub fn new(comm: Option<&'a mut Comm>) -> NbglUI<'a> { - NbglUI { comm } +impl<'a> NbglHome<'a> { + pub fn new(comm: &'a mut Comm) -> NbglHome<'a> { + NbglHome { comm } } - pub fn app_name(self, app_name: &'static str) -> NbglUI<'a> { + pub fn app_name(self, app_name: &'static str) -> NbglHome<'a> { unsafe { CTX.name[..app_name.len()].copy_from_slice(app_name.as_bytes()); } self } - pub fn icon(self, icon: &'static [u8]) -> NbglUI<'a> { + pub fn icon(self, icon: &'static [u8]) -> NbglHome<'a> { unsafe { CTX.icon = Some(icon); } self } - pub fn info_contents(self, version: &str, author: &str) -> NbglUI<'a> { + pub fn info_contents(self, version: &str, author: &str) -> NbglHome<'a> { unsafe { CTX.info_contents[0][..version.len()].copy_from_slice(version.as_bytes()); CTX.info_contents[1][..author.len()].copy_from_slice(author.as_bytes()); @@ -80,68 +147,15 @@ impl<'a> NbglUI<'a> { self } - pub fn get_events>(&mut self) -> Event - where - Reply: From<>::Error>, - { - loop { - if let Some(event) = self.get_event::() { - return event; - } - } - } - pub fn show_home>(&mut self) -> Event where Reply: From<>::Error>, { Self::display_home_cbk(); - return self.get_events(); - } - - pub fn review_transaction(&mut self, fields: &[Field]) -> bool { - unsafe { - let icon = nbgl_icon_details_t { - width: 64, - height: 64, - bpp: 2, - isFile: true, - bitmap: CTX.icon.unwrap().as_ptr(), - }; - - for (i, field) in fields.iter().enumerate() { - if i >= CTX.review_pairs.len() { - break; - } - CTX.review_pairs[i] = nbgl_layoutTagValue_t { - item: field.name.as_ptr() as *const ::core::ffi::c_char, - value: field.value.as_ptr() as *const ::core::ffi::c_char, - valueIcon: core::ptr::null(), - }; - } - - CTX.nb_pairs = if fields.len() < CTX.review_pairs.len() { - fields.len() - } else { - CTX.review_pairs.len() - } as u8; - - CTX.review_status = ReviewStatus::Pending; - - ledger_secure_sdk_sys::nbgl_useCaseReviewStart( - &icon as *const nbgl_icon_details_t, - "Review tx\0".as_ptr() as *const ::core::ffi::c_char, - "Subtitle\0".as_ptr() as *const ::core::ffi::c_char, - "Reject transaction\0".as_ptr() as *const ::core::ffi::c_char, - transmute((|| Self::display_static_review_cbk()) as fn()), - None, - ); - - return self.wait_review_result(); - } + return get_events(self.comm); } - pub fn verify_address(&mut self, address: &str) -> bool { + fn display_home_cbk() { unsafe { let icon = nbgl_icon_details_t { width: 64, @@ -150,60 +164,18 @@ impl<'a> NbglUI<'a> { isFile: true, bitmap: CTX.icon.unwrap().as_ptr(), }; - - CTX.nb_pairs = 1; - CTX.review_pairs[0] = nbgl_layoutTagValue_t { - item: "\0".as_ptr() as *const ::core::ffi::c_char, // Unused - value: address.as_ptr() as *const ::core::ffi::c_char, - valueIcon: core::ptr::null(), - }; - CTX.review_status = ReviewStatus::Pending; - - ledger_secure_sdk_sys::nbgl_useCaseReviewStart( + + ledger_secure_sdk_sys::nbgl_useCaseHome( + CTX.name.as_ptr() as *const core::ffi::c_char, &icon as *const nbgl_icon_details_t, - "Verify address\0".as_ptr() as *const ::core::ffi::c_char, core::ptr::null(), - "Cancel\0".as_ptr() as *const ::core::ffi::c_char, - transmute((|| Self::display_address_confirmation_cbk()) as fn()), - None, + true as bool, + transmute((|| Self::display_settings_cbk()) as fn()), + transmute((|| exit_app(12)) as fn()), ); - - return self.wait_review_result(); - } - } - - fn get_event>(&mut self) -> Option> - where - Reply: From<>::Error>, - { - match &mut self.comm { - None => None, - Some(comm) => { - if let Event::Command(ins) = comm.next_event() { - return Some(Event::Command(ins)); - } - else - { - return None; - } - } - } - } - - fn wait_review_result(&mut self) -> bool { - loop { - self.get_event::(); - unsafe { - if CTX.review_status == ReviewStatus::Validate { - return true; - } - else if CTX.review_status == ReviewStatus::Reject { - return false; - } - } } } - + fn display_settings_cbk() { unsafe { let nav = |page: u8, content: *mut nbgl_pageContent_s| { @@ -215,13 +187,14 @@ impl<'a> NbglUI<'a> { CTX.info_contents[0].as_ptr() as *const ::core::ffi::c_char, CTX.info_contents[1].as_ptr() as *const ::core::ffi::c_char, ] - .as_ptr() as *const *const ::core::ffi::c_char; + .as_ptr() + as *const *const ::core::ffi::c_char; } else { return false; } true }; - + ledger_secure_sdk_sys::nbgl_useCaseSettings( CTX.name.as_ptr() as *const core::ffi::c_char, 0 as u8, @@ -233,8 +206,10 @@ impl<'a> NbglUI<'a> { ); } } +} - fn display_home_cbk() { +impl NbglReview { + pub fn review_transaction(&mut self, fields: &[Field]) -> bool { unsafe { let icon = nbgl_icon_details_t { width: 64, @@ -244,36 +219,44 @@ impl<'a> NbglUI<'a> { bitmap: CTX.icon.unwrap().as_ptr(), }; - ledger_secure_sdk_sys::nbgl_useCaseHome( - CTX.name.as_ptr() as *const core::ffi::c_char, + for (i, field) in fields.iter().enumerate() { + if i >= CTX.review_pairs.len() { + break; + } + CTX.review_pairs[i] = nbgl_layoutTagValue_t { + item: field.name.as_ptr() as *const ::core::ffi::c_char, + value: field.value.as_ptr() as *const ::core::ffi::c_char, + valueIcon: core::ptr::null(), + }; + } + + CTX.nb_pairs = if fields.len() < CTX.review_pairs.len() { + fields.len() + } else { + CTX.review_pairs.len() + } as u8; + + CTX.review_status = ReviewStatus::Pending; + let confirm_string = "TRANSACTION\nSIGNED\0"; + CTX.review_confirm_string[..confirm_string.len()] + .copy_from_slice(confirm_string.as_bytes()); + let reject_string = "Transaction\nRejected\0"; + CTX.review_reject_string[..reject_string.len()] + .copy_from_slice(reject_string.as_bytes()); + + ledger_secure_sdk_sys::nbgl_useCaseReviewStart( &icon as *const nbgl_icon_details_t, - core::ptr::null(), - true as bool, - transmute((|| Self::display_settings_cbk()) as fn()), - transmute((|| exit_app(12)) as fn()), + "Review tx\0".as_ptr() as *const ::core::ffi::c_char, + "Subtitle\0".as_ptr() as *const ::core::ffi::c_char, + "Reject transaction\0".as_ptr() as *const ::core::ffi::c_char, + transmute((|| Self::display_static_review_cbk()) as fn()), + transmute((|| display_review_result_cbk(false)) as fn()), ); - } - } - fn display_review_result_cbk(confirm: bool) { - - let result_string: *const ::core::ffi::c_char; - unsafe { - if confirm { - CTX.review_status = ReviewStatus::Validate; - result_string = CTX.review_confirm_string.as_ptr() as *const ::core::ffi::c_char; - } else { - CTX.review_status = ReviewStatus::Reject; - result_string = CTX.review_reject_string.as_ptr() as *const ::core::ffi::c_char; - } - - ledger_secure_sdk_sys::nbgl_useCaseStatus( - result_string, - confirm, - transmute((|| Self::display_home_cbk()) as fn())); + return wait_review_result(); } } - + fn display_static_review_cbk() { unsafe { let tag_value_list: nbgl_layoutTagValueList_t = nbgl_layoutTagValueList_t { @@ -288,23 +271,18 @@ impl<'a> NbglUI<'a> { }; // Using this icon causes a crash - let icon = nbgl_icon_details_t { - width: 32, - height: 32, - bpp: 2, - isFile: true, - bitmap: CTX.icon.unwrap().as_ptr(), - }; + // let icon = nbgl_icon_details_t { + // width: 64, + // height: 64, + // bpp: 2, + // isFile: true, + // bitmap: CTX.icon.unwrap().as_ptr(), + // }; - let confirm_string = "TRANSACTION\nSIGNED\0"; - CTX.review_confirm_string[..confirm_string.len()].copy_from_slice(confirm_string.as_bytes()); - let reject_string = "Transaction\nRejected\0"; - CTX.review_reject_string[..reject_string.len()].copy_from_slice(reject_string.as_bytes()); - let info_long_press: nbgl_pageInfoLongPress_t = nbgl_pageInfoLongPress_t { text: "Validate tx\0".as_ptr() as *const ::core::ffi::c_char, icon: core::ptr::null(), - longPressText: "Hold to validate\0".as_ptr() as *const ::core::ffi::c_char, + longPressText: "Hold to sign\0".as_ptr() as *const ::core::ffi::c_char, longPressToken: 0, tuneId: 0, }; @@ -313,23 +291,114 @@ impl<'a> NbglUI<'a> { &tag_value_list as *const nbgl_layoutTagValueList_t, &info_long_press as *const nbgl_pageInfoLongPress_t, "Reject tx\0".as_ptr() as *const ::core::ffi::c_char, - transmute((|confirm| Self::display_review_result_cbk(confirm)) as fn(confirm: bool)), + transmute( + (|confirm| display_review_result_cbk(confirm)) as fn(confirm: bool), + ), ); } } +} - fn display_address_confirmation_cbk() { +// ================================================================================================= + +impl NbglAddressConfirm { + + pub fn verify_address(&mut self, address: &str) -> bool { unsafe { + let icon = nbgl_icon_details_t { + width: 64, + height: 64, + bpp: 2, + isFile: true, + bitmap: CTX.icon.unwrap().as_ptr(), + }; + CTX.nb_pairs = 1; + CTX.review_pairs[0] = nbgl_layoutTagValue_t { + item: "\0".as_ptr() as *const ::core::ffi::c_char, // Unused + value: address.as_ptr() as *const ::core::ffi::c_char, + valueIcon: core::ptr::null(), + }; + CTX.review_status = ReviewStatus::Pending; + + ledger_secure_sdk_sys::nbgl_useCaseReviewStart( + &icon as *const nbgl_icon_details_t, + "Verify address\0".as_ptr() as *const ::core::ffi::c_char, + core::ptr::null(), + "Cancel\0".as_ptr() as *const ::core::ffi::c_char, + transmute((|| Self::display_address_confirmation_cbk()) as fn()), + None, + ); + + return wait_review_result(); + } + } + + fn display_address_confirmation_cbk() { + unsafe { let confirm_string = "ADDRESS\nVERIFIED\0"; - CTX.review_confirm_string[..confirm_string.len()].copy_from_slice(confirm_string.as_bytes()); + CTX.review_confirm_string[..confirm_string.len()] + .copy_from_slice(confirm_string.as_bytes()); let reject_string = "Address verification\ncancelled\0"; - CTX.review_reject_string[..reject_string.len()].copy_from_slice(reject_string.as_bytes()); + CTX.review_reject_string[..reject_string.len()] + .copy_from_slice(reject_string.as_bytes()); ledger_secure_sdk_sys::nbgl_useCaseAddressConfirmation( CTX.review_pairs[0].value as *const ::core::ffi::c_char, - transmute((|confirm| Self::display_review_result_cbk(confirm)) as fn(confirm: bool)), + transmute( + (|confirm| display_review_result_cbk(confirm)) as fn(confirm: bool), + ), ); } } } + +enum TuneIndex { + Reserved, + Boot, + Charging, + LedgerMoment, + Error, + Neutral, + Lock, + Success, + LookAtMe, + TapCasual, + TapNext, +} + +impl TryFrom for TuneIndex { + type Error = (); + fn try_from(index: u8) -> Result { + Ok(match index { + TUNE_RESERVED => TuneIndex::Reserved, + TUNE_BOOT => TuneIndex::Boot, + TUNE_CHARGING => TuneIndex::Charging, + TUNE_LEDGER_MOMENT => TuneIndex::LedgerMoment, + TUNE_ERROR => TuneIndex::Error, + TUNE_NEUTRAL => TuneIndex::Neutral, + TUNE_LOCK => TuneIndex::Lock, + TUNE_SUCCESS => TuneIndex::Success, + TUNE_LOOK_AT_ME => TuneIndex::LookAtMe, + TUNE_TAP_CASUAL => TuneIndex::TapCasual, + TUNE_TAP_NEXT => TuneIndex::TapNext, + _ => return Err(()), + }) + } +} + +// this is a mock that does nothing yet, but should become a direct translation +// of the C original. This was done to avoid compiling `os_io_seproxyhal.c` which +// includes many other things +#[no_mangle] +extern "C" fn io_seproxyhal_play_tune(tune_index: u8) { + let index = TuneIndex::try_from(tune_index); + if index.is_err() { + return; + } +} + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + exit_app(1); +} \ No newline at end of file