diff --git a/ledger_device_sdk/examples/nbgl_address.rs b/ledger_device_sdk/examples/nbgl_address.rs index 1aa9c2d4..cc09c99c 100644 --- a/ledger_device_sdk/examples/nbgl_address.rs +++ b/ledger_device_sdk/examples/nbgl_address.rs @@ -6,9 +6,16 @@ use ledger_device_sdk as _; use include_gif::include_gif; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{init_comm, NbglAddressReview, NbglGlyph}; +use ledger_device_sdk::nbgl::{ + init_comm, NbglAddressReview, NbglGlyph, NbglReviewStatus, StatusType, +}; use ledger_secure_sdk_sys::*; +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + exit_app(1); +} + #[no_mangle] extern "C" fn sample_main() { unsafe { @@ -26,5 +33,11 @@ extern "C" fn sample_main() { const FERRIS: NbglGlyph = NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); // Display the address confirmation screen. - NbglAddressReview::new().glyph(&FERRIS).show(addr_hex); + let success = NbglAddressReview::new() + .glyph(&FERRIS) + .verify_str("Verify Address") + .show(addr_hex); + NbglReviewStatus::new() + .status_type(StatusType::Address) + .show(success); } diff --git a/ledger_device_sdk/examples/nbgl_choice.rs b/ledger_device_sdk/examples/nbgl_choice.rs index fa33ca0e..54ec22cc 100644 --- a/ledger_device_sdk/examples/nbgl_choice.rs +++ b/ledger_device_sdk/examples/nbgl_choice.rs @@ -6,7 +6,7 @@ use ledger_device_sdk as _; use include_gif::include_gif; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{init_comm, NbglChoice, NbglGlyph}; +use ledger_device_sdk::nbgl::{init_comm, NbglChoice, NbglGlyph, NbglStatus}; use ledger_secure_sdk_sys::*; #[panic_handler] @@ -29,23 +29,30 @@ extern "C" fn sample_main() { const WARNING: NbglGlyph = NbglGlyph::from_include(include_gif!("icons/Warning_64px.gif", NBGL)); - let back_to_safety = NbglChoice::new().glyph(&WARNING) - .status_text(Some("Transaction rejected"),None) - .show( + let back_to_safety = NbglChoice::new().glyph(&WARNING).show( "Security risk detected", "It may not be safe to sign this transaction. To continue, you'll need to review the risk.", "Back to safety", "Review risk", ); - if !back_to_safety { - NbglChoice::new() - .status_text(Some("Transaction confirmed"), Some("Transaction rejected")) - .show( + if back_to_safety { + NbglStatus::new().text("Transaction rejected").show(false); + } else { + let confirmed = NbglChoice::new() + .show( "The transaction cannot be trusted", "Your Ledger cannot decode this transaction. If you sign it, you could be authorizing malicious actions that can drain your wallet.\n\nLearn more: ledger.com/e8", "I accept the risk", "Reject transaction" ); + + NbglStatus::new() + .text(if confirmed { + "Transaction confirmed" + } else { + "Transaction rejected" + }) + .show(confirmed); } } diff --git a/ledger_device_sdk/examples/nbgl_generic_review.rs b/ledger_device_sdk/examples/nbgl_generic_review.rs index 024c24c3..548f96a8 100644 --- a/ledger_device_sdk/examples/nbgl_generic_review.rs +++ b/ledger_device_sdk/examples/nbgl_generic_review.rs @@ -8,7 +8,8 @@ use include_gif::include_gif; use ledger_device_sdk::io::*; use ledger_device_sdk::nbgl::{ init_comm, CenteredInfo, CenteredInfoStyle, Field, InfoButton, InfoLongPress, InfosList, - NbglGenericReview, NbglGlyph, NbglPageContent, TagValueConfirm, TagValueList, TuneIndex, + NbglGenericReview, NbglGlyph, NbglPageContent, NbglStatus, TagValueConfirm, TagValueList, + TuneIndex, }; use ledger_secure_sdk_sys::*; @@ -86,5 +87,11 @@ extern "C" fn sample_main() { .add_content(NbglPageContent::TagValueConfirm(tag_value_confirm)) .add_content(NbglPageContent::InfosList(infos_list)); - review.show("Reject Example", "Example Confirmed", "Example Rejected"); + let success = review.show("Reject Example"); + let status_text = if success { + "Example confirmed" + } else { + "Example rejected" + }; + NbglStatus::new().text(status_text).show(success); } diff --git a/ledger_device_sdk/examples/nbgl_home.rs b/ledger_device_sdk/examples/nbgl_home.rs index b4c776ae..02a96ed1 100644 --- a/ledger_device_sdk/examples/nbgl_home.rs +++ b/ledger_device_sdk/examples/nbgl_home.rs @@ -11,6 +11,11 @@ use ledger_device_sdk::nvm::*; use ledger_device_sdk::NVMData; use ledger_secure_sdk_sys::*; +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + exit_app(1); +} + pub enum Instruction { GetVersion, GetAppName, diff --git a/ledger_device_sdk/examples/nbgl_review.rs b/ledger_device_sdk/examples/nbgl_review.rs index 85cfb618..514b8f81 100644 --- a/ledger_device_sdk/examples/nbgl_review.rs +++ b/ledger_device_sdk/examples/nbgl_review.rs @@ -6,7 +6,9 @@ use ledger_device_sdk as _; use include_gif::include_gif; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{init_comm, Field, NbglGlyph, NbglReview}; +use ledger_device_sdk::nbgl::{ + init_comm, Field, NbglGlyph, NbglReview, NbglReviewStatus, StatusType, +}; use ledger_secure_sdk_sys::*; #[panic_handler] @@ -45,14 +47,14 @@ extern "C" fn sample_main() { NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); // Create NBGL review. Maximum number of fields and string buffer length can be customised // with constant generic parameters of NbglReview. Default values are 32 and 1024 respectively. - let mut review: NbglReview = NbglReview::new() + let success = NbglReview::new() .titles( "Please review transaction", "To send CRAB", "Sign transaction\nto send CRAB", ) .glyph(&FERRIS) - .blind(); - - review.show(&my_fields); + .blind() + .show(&my_fields); + NbglReviewStatus::new().show(success); } diff --git a/ledger_device_sdk/examples/nbgl_spinner.rs b/ledger_device_sdk/examples/nbgl_spinner.rs new file mode 100644 index 00000000..d711d77d --- /dev/null +++ b/ledger_device_sdk/examples/nbgl_spinner.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +// Force boot section to be embedded in +use ledger_device_sdk as _; + +use include_gif::include_gif; +use ledger_device_sdk::io::*; +use ledger_device_sdk::nbgl::{ + init_comm, Field, NbglGlyph, NbglReview, NbglReviewStatus, NbglSpinner, StatusType, +}; +use ledger_device_sdk::testing::debug_print; +use ledger_secure_sdk_sys::*; + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + exit_app(1); +} + +// static spin_end: bool = false; + +#[no_mangle] +extern "C" fn sample_main() { + unsafe { + nbgl_refreshReset(); + } + + let mut comm = Comm::new(); + // Initialize reference to Comm instance for NBGL + // API calls. + init_comm(&mut comm); + + let my_field = [Field { + name: "Amount", + value: "111 CRAB", + }]; + + // Load glyph from 64x64 4bpp gif file with include_gif macro. Creates an NBGL compatible glyph. + const FERRIS: NbglGlyph = + NbglGlyph::from_include(include_gif!("examples/crab_64x64.gif", NBGL)); + // Create NBGL review. Maximum number of fields and string buffer length can be customised + // with constant generic parameters of NbglReview. Default values are 32 and 1024 respectively. + let success = NbglReview::new() + .titles( + "Please review transaction", + "To send CRAB", + "Sign transaction\nto send CRAB", + ) + .glyph(&FERRIS) + .show(&my_field); + + NbglSpinner::new().text("Please wait...").show(); + + // Simulate an idle state of the app where it just + // waits for some event to happen (such as APDU reception), going through + // the event loop to process TickerEvents so that the spinner can be animated + // every 800ms. + let mut loop_count = 50; + while loop_count > 0 { + comm.next_event::(); + loop_count -= 1; + } + NbglReviewStatus::new().show(success); +} diff --git a/ledger_device_sdk/examples/nbgl_streaming_review.rs b/ledger_device_sdk/examples/nbgl_streaming_review.rs index 1ccbe166..9157ed7a 100644 --- a/ledger_device_sdk/examples/nbgl_streaming_review.rs +++ b/ledger_device_sdk/examples/nbgl_streaming_review.rs @@ -6,7 +6,9 @@ use ledger_device_sdk as _; use include_gif::include_gif; use ledger_device_sdk::io::*; -use ledger_device_sdk::nbgl::{init_comm, Field, NbglGlyph, NbglStreamingReview, TransactionType}; +use ledger_device_sdk::nbgl::{ + init_comm, Field, NbglGlyph, NbglReviewStatus, NbglStreamingReview, StatusType, TransactionType, +}; use ledger_secure_sdk_sys::*; #[panic_handler] @@ -33,7 +35,10 @@ extern "C" fn sample_main() { .glyph(&FERRIS) .tx_type(TransactionType::Message); - review.start("Example Title", "Example Subtitle"); + if !review.start("Streaming example", "Example Subtitle") { + NbglReviewStatus::new().show(false); + return; + } let fields = [ Field { @@ -59,8 +64,12 @@ extern "C" fn sample_main() { ]; for i in 0..fields.len() { - review.continue_review(&fields[i..i + 1]); + if !review.continue_review(&fields[i..i + 1]) { + NbglReviewStatus::new().show(false); + return; + } } - review.finish("Sign to send token\n"); + let success = review.finish("Sign to send token\n"); + NbglReviewStatus::new().show(success); } diff --git a/ledger_device_sdk/src/nbgl.rs b/ledger_device_sdk/src/nbgl.rs index 0fb99537..d19c7a85 100644 --- a/ledger_device_sdk/src/nbgl.rs +++ b/ledger_device_sdk/src/nbgl.rs @@ -87,6 +87,28 @@ pub enum TransactionType { Operation, } +pub enum StatusType { + Transaction, + Message, + Operation, + Address, +} + +impl StatusType { + fn transaction_type(&self) -> Option { + match self { + StatusType::Transaction => Some(TransactionType::Transaction), + StatusType::Message => Some(TransactionType::Message), + StatusType::Operation => Some(TransactionType::Operation), + StatusType::Address => None, + } + } +} + +trait ToMessage { + fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t; +} + impl TransactionType { pub fn to_c_type(&self, blind: bool, skippable: bool) -> nbgl_operationType_t { let mut tx_type = match self { @@ -102,30 +124,35 @@ impl TransactionType { } tx_type } +} + +impl ToMessage for TransactionType { + fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t { + match (self, success) { + (TransactionType::Transaction, true) => STATUS_TYPE_TRANSACTION_SIGNED, + (TransactionType::Transaction, false) => STATUS_TYPE_TRANSACTION_REJECTED, + (TransactionType::Message, true) => STATUS_TYPE_MESSAGE_SIGNED, + (TransactionType::Message, false) => STATUS_TYPE_MESSAGE_REJECTED, + (TransactionType::Operation, true) => STATUS_TYPE_OPERATION_SIGNED, + (TransactionType::Operation, false) => STATUS_TYPE_OPERATION_REJECTED, + } + } +} - pub fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t { +impl ToMessage for StatusType { + fn to_message(&self, success: bool) -> nbgl_reviewStatusType_t { match self { - TransactionType::Transaction => { - if success { - STATUS_TYPE_TRANSACTION_SIGNED - } else { - STATUS_TYPE_TRANSACTION_REJECTED - } - } - TransactionType::Message => { - if success { - STATUS_TYPE_MESSAGE_SIGNED - } else { - STATUS_TYPE_MESSAGE_REJECTED - } - } - TransactionType::Operation => { + StatusType::Address => { if success { - STATUS_TYPE_OPERATION_SIGNED + STATUS_TYPE_ADDRESS_VERIFIED } else { - STATUS_TYPE_OPERATION_REJECTED + STATUS_TYPE_ADDRESS_REJECTED } } + _ => self + .transaction_type() + .expect("Should be a transaction type") + .to_message(success), } } } @@ -452,7 +479,6 @@ impl<'a> NbglReview<'a> { if self.blind { if !show_blind_warning() { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -470,11 +496,9 @@ impl<'a> NbglReview<'a> { // Return true if the user approved the transaction, false otherwise. match sync_ret { UX_SYNC_RET_APPROVED => { - ux_sync_reviewStatus(self.tx_type.to_message(true)); return true; } _ => { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -884,7 +908,7 @@ impl NbglGenericReview { .collect() } - pub fn show(&mut self, reject_button_str: &str, succeed_str: &str, rejected_str: &str) -> bool { + pub fn show(&mut self, reject_button_str: &str) -> bool { unsafe { let c_content_list: Vec = self.to_c_content_list(); @@ -897,8 +921,6 @@ impl NbglGenericReview { }; let reject_button_cstring = CString::new(reject_button_str).unwrap(); - let succeed_cstring = CString::new(succeed_str).unwrap(); - let rejected_cstring = CString::new(rejected_str).unwrap(); let sync_ret = ux_sync_genericReview( &content_struct as *const nbgl_genericContents_t, @@ -908,11 +930,9 @@ impl NbglGenericReview { // Return true if the user approved the transaction, false otherwise. match sync_ret { UX_SYNC_RET_APPROVED => { - ux_sync_status(succeed_cstring.as_ptr() as *const c_char, true); return true; } _ => { - ux_sync_status(rejected_cstring.as_ptr() as *const c_char, false); return false; } } @@ -962,7 +982,6 @@ impl NbglStreamingReview { if self.blind { if !show_blind_warning() { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -980,7 +999,6 @@ impl NbglStreamingReview { return true; } _ => { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -1025,7 +1043,6 @@ impl NbglStreamingReview { return true; } _ => { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -1040,11 +1057,9 @@ impl NbglStreamingReview { // Return true if the user approved the transaction, false otherwise. match sync_ret { UX_SYNC_RET_APPROVED => { - ux_sync_reviewStatus(self.tx_type.to_message(true)); return true; } _ => { - ux_sync_reviewStatus(self.tx_type.to_message(false)); return false; } } @@ -1102,11 +1117,9 @@ impl<'a> NbglAddressReview<'a> { // Return true if the user approved the address, false otherwise. match sync_ret { UX_SYNC_RET_APPROVED => { - ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_VERIFIED); return true; } UX_SYNC_RET_REJECTED => { - ux_sync_reviewStatus(STATUS_TYPE_ADDRESS_REJECTED); return false; } _ => { @@ -1122,17 +1135,11 @@ impl<'a> NbglAddressReview<'a> { /// thanks to a button and a footer at the bottom of the page. pub struct NbglChoice<'a> { glyph: Option<&'a NbglGlyph<'a>>, - confirmed_text: Option, - cancelled_text: Option, } impl<'a> NbglChoice<'a> { pub fn new() -> NbglChoice<'a> { - NbglChoice { - glyph: None, - confirmed_text: None, - cancelled_text: None, - } + NbglChoice { glyph: None } } pub fn glyph(self, glyph: &'a NbglGlyph) -> NbglChoice<'a> { @@ -1142,20 +1149,6 @@ impl<'a> NbglChoice<'a> { } } - pub fn status_text( - self, - confirmed: Option<&'a str>, - cancelled: Option<&'a str>, - ) -> NbglChoice<'a> { - let confirmed_text = confirmed.map(|s| CString::new(s).unwrap()); - let cancelled_text = cancelled.map(|s| CString::new(s).unwrap()); - NbglChoice { - confirmed_text, - cancelled_text, - ..self - } - } - pub fn show( self, message: &str, @@ -1184,15 +1177,9 @@ impl<'a> NbglChoice<'a> { // Return true if the user approved the transaction, false otherwise. match sync_ret { UX_SYNC_RET_APPROVED => { - if let Some(text) = self.confirmed_text { - ux_sync_status(text.as_ptr() as *const c_char, true); - } return true; } _ => { - if let Some(text) = self.cancelled_text { - ux_sync_status(text.as_ptr() as *const c_char, false); - } return false; } } @@ -1200,6 +1187,83 @@ impl<'a> NbglChoice<'a> { } } +/// A wrapper around the synchronous NBGL ux_sync_reviewStatus C API binding. +/// Draws a transient (3s) status page of the chosen type. +pub struct NbglReviewStatus { + status_type: StatusType, +} + +impl NbglReviewStatus { + pub fn new() -> NbglReviewStatus { + NbglReviewStatus { + status_type: StatusType::Transaction, + } + } + + pub fn status_type(self, status_type: StatusType) -> NbglReviewStatus { + NbglReviewStatus { status_type } + } + + pub fn show(&self, success: bool) { + unsafe { + ux_sync_reviewStatus(self.status_type.to_message(success)); + } + } +} + +/// A wrapper around the synchronous NBGL ux_sync_status C API binding. +/// Draws a transient (3s) status page, either of success or failure, with the given message +pub struct NbglStatus { + text: CString, +} + +impl NbglStatus { + pub fn new() -> NbglStatus { + NbglStatus { + text: CString::new("").unwrap(), + } + } + + pub fn text(self, text: &str) -> NbglStatus { + NbglStatus { + text: CString::new(text).unwrap(), + } + } + + pub fn show(&self, success: bool) { + unsafe { + ux_sync_status(self.text.as_ptr() as *const c_char, success); + } + } +} + +/// A wrapper around the asynchronous NBGL nbgl_useCaseSpinner C API binding. +/// Draws a spinner page with the given parameters. The spinner will "turn" automatically every +/// 800 ms, provided the IO event loop is running to process TickerEvents. +pub struct NbglSpinner { + text: CString, +} + +impl NbglSpinner { + pub fn new() -> NbglSpinner { + NbglSpinner { + text: CString::new("").unwrap(), + } + } + + pub fn text(self, text: &str) -> NbglSpinner { + NbglSpinner { + text: CString::new(text).unwrap(), + } + } + + pub fn show(&self) { + unsafe { + nbgl_useCaseSpinner(self.text.as_ptr() as *const c_char); + } + } +} + #[derive(Copy, Clone)] pub enum TuneIndex { Reserved,