CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PinEntryView.cpp
Go to the documentation of this file.
1
7
12#include "cdc_ui/ViewStack.h"
13#include "cdc_core/PinManager.h"
14#include "cdc_ui/I18n.h"
15#include "cdc_hal/IDisplay.h"
16#include "cdc_log.h"
17#include <goodisplay/gdey029T94.h>
18#include <cstring>
19
20static const char* TAG = "PinEntryView";
21
25static constexpr int TITLE_Y = 15;
26static constexpr int PIN_Y = 50;
27static constexpr int PIN_DOT_SIZE = 16;
28static constexpr int PIN_DOT_SPACING = 24;
29static constexpr int RETRIES_Y = 80;
30
32static constexpr uint32_t TOAST_DURATION_SHORT_MS = 1500;
33static constexpr uint32_t TOAST_DURATION_LONG_MS = 3000;
34
36static constexpr uint32_t LOCKOUT_REFRESH_MS = 1000;
37
38namespace cdc::ui {
39
47void PinEntryView::init(const char* title, uint8_t maxPinLength, uint8_t maxAttempts) {
48 title_ = title;
49 maxLength_ = maxPinLength > MAX_PIN_LENGTH ? MAX_PIN_LENGTH : maxPinLength;
50 maxAttempts_ = maxAttempts;
51 minLength_ = core::PinManager::BADGE_PIN_MIN; // Default minimum from PinManager
52 clear();
53 dirty_ = true;
54}
55
61void PinEntryView::onEnter(void* context) {
62 (void)context;
63 clear();
64 dirty_ = true;
65}
66
72 memset(buffer_, 0, sizeof(buffer_));
73 length_ = 0;
74}
75
81void PinEntryView::addDigit(char digit) {
82 if (length_ >= maxLength_ || lockedOut_) return;
83
84 buffer_[length_++] = digit;
85 buffer_[length_] = '\0';
86 dirty_ = true;
87}
88
93void PinEntryView::backspace() {
94 if (length_ > 0 && !lockedOut_) {
95 buffer_[--length_] = '\0';
96 dirty_ = true;
97 }
98}
99
105void PinEntryView::onTick(uint32_t nowMs) {
106 (void)nowMs;
108
109 // Check if blocked status changed
110 bool blocked = pm.isBadgeBlocked();
111 if (blocked != lockedOut_) {
112 lockedOut_ = blocked;
113 dirty_ = true;
114 }
115
116 // Update display every second during lockout to show countdown
117 if (lockedOut_) {
118 static uint32_t lastUpdate = 0;
119 if (nowMs - lastUpdate >= LOCKOUT_REFRESH_MS) {
120 lastUpdate = nowMs;
121 dirty_ = true;
122 }
123 }
124}
125
133
138void PinEntryView::verify() {
139 if (length_ < minLength_) {
140 if (showMessages_) {
142 }
143 return;
144 }
145
146 if (!onVerify_) {
147 // No verify callback, just accept
148 if (onSuccess_) {
149 onSuccess_();
150 }
151 return;
152 }
153
154 bool valid = onVerify_(buffer_);
155 if (valid) {
156 LOG_I(TAG, "PIN verified successfully");
157 if (onSuccess_) {
158 onSuccess_();
159 }
160 } else {
161 attempts_++;
162 LOG_W(TAG, "PIN verification failed, attempt %d", attempts_);
163
164 // Check PinManager for block status (it manages retries persistently)
165 core::PinManager& pm = core::PinManager::instance();
166 if (pm.isBadgeBlocked()) {
167 lockedOut_ = true;
168 if (showMessages_) {
170 }
171 } else {
172 if (showMessages_) {
174 }
175 }
176 clear();
177 dirty_ = true;
178 if (onFailure_) {
179 onFailure_(lockedOut_);
180 }
181 }
182}
183
190 if (lockedOut_) {
192 }
193
194 // Handle digits
195 if (key >= '0' && key <= '9') {
196 addDigit(key);
198 }
199
200 switch (key) {
201 case KEY_NO: // Backspace or cancel
202 if (length_ > 0) {
203 backspace();
204 } else {
205 if (onCancel_) {
206 onCancel_();
208 }
210 }
212
213 case KEY_YES: // Confirm
214 verify();
216
217 default:
219 }
220}
221
226const char* PinEntryView::getFooterHint() const {
227 return ui::tr("core.hint_pin_input");
228}
229
235void PinEntryView::render(bool partial) {
237 if (!display) {
238 LOG_E(TAG, "display is null!");
239 return;
240 }
241
242 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
243 if (!gfx) {
244 LOG_E(TAG, "gfx handle is null!");
245 return;
246 }
247
248 const uint16_t width = display->getWidth();
249 const uint16_t height = display->getHeight();
250
251 if (!partial) {
252 gfx->fillScreen(EPD_WHITE);
253 }
254
255 gfx->setTextColor(EPD_BLACK);
256
257 // Title (centered)
258 if (title_) {
259 gfx->setTextSize(1);
260 render::drawHeaderCentered(gfx, title_, TITLE_Y, width);
261 }
262
263 // PIN dots (centered)
264 int totalWidth = maxLength_ * PIN_DOT_SIZE + (maxLength_ - 1) * (PIN_DOT_SPACING - PIN_DOT_SIZE);
265 int startX = (width - totalWidth) / 2;
266
267 for (uint8_t i = 0; i < maxLength_; i++) {
268 int x = startX + i * PIN_DOT_SPACING;
269 int y = PIN_Y;
270
271 if (i < length_) {
272 // Filled dot for entered digits
273 gfx->fillCircle(x + PIN_DOT_SIZE / 2, y + PIN_DOT_SIZE / 2, PIN_DOT_SIZE / 2 - 1, EPD_BLACK);
274 } else {
275 // Empty dot for remaining positions
276 gfx->drawCircle(x + PIN_DOT_SIZE / 2, y + PIN_DOT_SIZE / 2, PIN_DOT_SIZE / 2 - 1, EPD_BLACK);
277 }
278 }
279
280 // Show remaining retries or blocked status with countdown
281 {
283 gfx->setTextSize(1);
284 int16_t x1, y1;
285 uint16_t w, h;
286 char statusStr[32];
287
288 if (pm.isBadgeBlocked()) {
289 uint32_t remainingMs = pm.getLockoutRemainingMs();
290 uint32_t remainingSec = (remainingMs + 999) / 1000; // Round up
291 snprintf(statusStr, sizeof(statusStr), "%s: %lus", ui::tr("core.locked_out"), remainingSec);
292 } else {
293 uint8_t retries = pm.getBadgeRetries();
294 snprintf(statusStr, sizeof(statusStr), "%s: %d", ui::tr("core.retries"), retries);
295 }
296
297 gfx->getTextBounds(statusStr, 0, 0, &x1, &y1, &w, &h);
298 gfx->setCursor((width - w) / 2, RETRIES_Y);
299 render::printText(gfx, statusStr);
300 }
301
302 // Footer hint
303 const char* hint = getFooterHint();
304 render::drawFooterBar(gfx, width, height, nullptr, hint, false);
305
306 dirty_ = false;
307}
308
312
314
325PinEntryView* showPinEntry(const char* title,
328 uint8_t maxLength,
329 uint8_t minLength,
330 uint8_t maxAttempts) {
331 s_sharedPinEntry.init(title, maxLength, maxAttempts);
332 s_sharedPinEntry.setMinLength(minLength);
333 s_sharedPinEntry.setOnVerify(onVerify);
334 s_sharedPinEntry.setOnSuccess(onSuccess);
336 return &s_sharedPinEntry;
337}
338
339} // namespace cdc::ui
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
static constexpr int PIN_DOT_SIZE
static constexpr int PIN_Y
static constexpr int PIN_DOT_SPACING
static constexpr int TITLE_Y
Display layout constants.
static constexpr uint32_t TOAST_DURATION_SHORT_MS
Toast message display durations in milliseconds.
static constexpr uint32_t LOCKOUT_REFRESH_MS
Refresh interval for the lockout countdown display.
static constexpr uint32_t TOAST_DURATION_LONG_MS
static constexpr int RETRIES_Y
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
static constexpr uint8_t BADGE_PIN_MIN
Definition PinManager.h:49
static PinManager & instance()
Returns singleton PIN manager instance.
uint32_t getLockoutRemainingMs() const
Returns remaining badge lockout duration.
void init(const char *title, uint8_t maxPinLength=6, uint8_t maxAttempts=3)
Initializes PIN entry configuration and clears current input.
static constexpr uint8_t MAX_PIN_LENGTH
InputResult onKey(char key) override
Handles key input for PIN entry and actions.
void onEnter(void *context) override
Resets entry state when the view is entered.
void render(bool partial) override
Renders PIN dots, status text, and footer hint.
void onTick(uint32_t nowMs) override
Updates lockout state and periodic countdown refresh.
const char * getFooterHint() const override
Returns localized footer hint text.
void clear()
Clears the internal PIN buffer.
uint32_t getLockoutRemaining() const
Returns remaining lockout time.
bool(*)(const char *pin) VerifyCallback
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
bool valid
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
void drawFooterBar(Gdey029T94 *gfx, uint16_t width, uint16_t height, const char *prefix, const char *hint, bool force=false)
Draws footer bar with optional prefix and hint text.
void drawHeaderCentered(Gdey029T94 *gfx, const char *title, int y, uint16_t width)
Draws a centered header title.
void printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
static PinEntryView s_sharedPinEntry
Convenience factory/helper function.
static constexpr uint32_t TOAST_DURATION_LONG_MS
Gdey029T94 * display
InputResult
Definition IView.h:10
static constexpr char KEY_NO
Cancel / Back / Backspace.
Definition KeyCodes.h:44
static constexpr int TITLE_Y
Layout constants mirror the ones used by T9InputView.
PinEntryView * showPinEntry(const char *title, PinEntryView::VerifyCallback onVerify, PinEntryView::SuccessCallback onSuccess, uint8_t maxLength=6, uint8_t minLength=4, uint8_t maxAttempts=3)
Shows a shared PIN entry view instance.
static constexpr uint32_t TOAST_DURATION_SHORT_MS
static const char * TAG
static constexpr char KEY_YES
Confirm / OK / Save.
Definition KeyCodes.h:41
void showMessage(const char *message, MessageIcon icon=MessageIcon::NONE, uint32_t timeoutMs=0, MessageBox::CloseCallback onClose=nullptr)
Shows the shared modal message box.