1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
//! A wrapper around the asynchronous NBGL [nbgl_useCaseKeypad](https://github.com/LedgerHQ/ledger-secure-sdk/blob/master/lib_nbgl/src/nbgl_use_case.c#L4565) C API binding.
//!
//! Draws a keypad for user input, allowing for PIN entry and other numeric input.
use super::*;
use core::sync::atomic::{AtomicPtr, Ordering};
/// A builder to create and show a keypad for user input.
pub struct NbglKeypad {
title: CString,
min_digits: u8,
max_digits: u8,
shuffled: bool,
hide: bool,
}
impl SyncNBGL for NbglKeypad {}
impl Default for NbglKeypad {
fn default() -> Self {
Self::new()
}
}
const PIN_BUFFER_SIZE: usize = 16;
static PIN_BUFFER_PTR: AtomicPtr<u8> = AtomicPtr::new(core::ptr::null_mut());
// RAII guard: clears PIN_BUFFER_PTR when dropped, even on early return or panic.
struct PinBufferGuard;
impl Drop for PinBufferGuard {
fn drop(&mut self) {
PIN_BUFFER_PTR.store(core::ptr::null_mut(), Ordering::Release);
}
}
unsafe extern "C" fn pin_callback(pin: *const u8, pin_len: u8) {
unsafe {
let len = (pin_len as usize).min(PIN_BUFFER_SIZE);
let buf = PIN_BUFFER_PTR.load(Ordering::Acquire);
if !buf.is_null() {
core::ptr::copy_nonoverlapping(pin, buf, len);
}
G_ENDED = true;
}
}
unsafe extern "C" fn action_callback() {
unsafe {
G_RET = SyncNbgl::UxSyncRetPinRejected.into();
G_ENDED = true;
}
}
impl NbglKeypad {
/// Creates a new keypad builder with default settings.
/// By default, the title is "Enter PIN", minimum and maximum digits are set to 4,
/// the keypad is shuffled, and input is hidden.
/// # Returns
/// Returns a new instance of `NbglKeypad`.
pub fn new() -> NbglKeypad {
NbglKeypad {
title: CString::new("Enter PIN").unwrap(),
min_digits: 4,
max_digits: 4,
shuffled: true,
hide: true,
}
}
/// Sets the title to display at the top of the keypad.
/// # Arguments
/// * `title` - The title to display at the top of the keypad.
/// # Returns
/// Returns the builder itself to allow method chaining.
pub fn title(self, title: &str) -> NbglKeypad {
NbglKeypad {
title: CString::new(title).unwrap(),
..self
}
}
/// Sets the minimum number of digits required for input.
/// # Arguments
/// * `min` - The minimum number of digits required for input.
/// # Returns
/// Returns the builder itself to allow method chaining.
pub fn min_digits(self, min: u8) -> NbglKeypad {
NbglKeypad {
min_digits: min,
..self
}
}
/// Sets the maximum number of digits allowed for input.
/// # Arguments
/// * `max` - The maximum number of digits allowed for input.
/// # Returns
/// Returns the builder itself to allow method chaining.
pub fn max_digits(self, max: u8) -> NbglKeypad {
NbglKeypad {
max_digits: max.min(PIN_BUFFER_SIZE as u8),
..self
}
}
/// Sets whether the keypad should be shuffled.
/// # Arguments
/// * `shuffle` - If `true`, the keypad will be shuffled; otherwise, it will be in a fixed order.
/// # Returns
/// Returns the builder itself to allow method chaining.
pub fn shuffled(self, shuffle: bool) -> NbglKeypad {
NbglKeypad {
shuffled: shuffle,
..self
}
}
/// Sets whether the input should be hidden (e.g., for PIN entry).
/// # Arguments
/// * `hide` - If `true`, the input will be hidden; otherwise, it will be visible.
/// # Returns
/// Returns the builder itself to allow method chaining.
pub fn hide(self, hide: bool) -> NbglKeypad {
NbglKeypad { hide, ..self }
}
fn ask_internal(self, pin: &[u8]) -> SyncNbgl {
unsafe {
let mut buffer = [0u8; PIN_BUFFER_SIZE];
PIN_BUFFER_PTR.store(buffer.as_mut_ptr(), Ordering::Release);
let _guard = PinBufferGuard;
self.ux_sync_init();
nbgl_useCaseKeypad(
self.title.as_ptr() as *const c_char,
self.min_digits,
self.max_digits,
self.shuffled,
self.hide,
Some(pin_callback),
Some(action_callback),
);
self.ux_sync_wait(false);
// Compare with set pin code (_guard clears PIN_BUFFER_PTR on drop below)
if pin == &buffer[..pin.len()] {
SyncNbgl::UxSyncRetPinValidated
} else {
SyncNbgl::UxSyncRetPinRejected
}
}
}
/// Shows the keypad and waits for user input.
/// # Arguments
/// * `pin` - A slice containing the expected PIN for validation.
/// # Returns
/// Returns `true` if the entered PIN matches the expected PIN,
/// otherwise returns `false`.
#[cfg(feature = "io_new")]
pub fn ask<const N: usize>(self, _comm: &mut crate::io::Comm<N>, pin: &[u8]) -> bool {
self.ask_internal(pin) == SyncNbgl::UxSyncRetPinValidated
}
/// Shows the keypad and waits for user input.
/// # Arguments
/// * `pin` - A slice containing the expected PIN for validation.
/// # Returns
/// Returns `SyncNbgl::UxSyncRetPinValidated` if the entered PIN matches the expected PIN,
/// otherwise returns `SyncNbgl::UxSyncRetPinRejected`.
#[cfg(not(feature = "io_new"))]
pub fn ask(self, pin: &[u8]) -> SyncNbgl {
self.ask_internal(pin)
}
}