CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
PinChangeView.cpp
Go to the documentation of this file.
1
6
10#include "cdc_core/PinManager.h"
11#include "cdc_ui/I18n.h"
12#include "cdc_hal/IDisplay.h"
13#include "cdc_log.h"
14#include "esp_timer.h"
15#include <goodisplay/gdey029T94.h>
16#include <cstring>
17
18static const char* TAG = "PinChangeView";
19
23static constexpr int TITLE_Y = 15;
24static constexpr int STEP_Y = 30;
25static constexpr int PIN_Y = 55;
26static constexpr int PIN_DOT_SIZE = 12;
27static constexpr int PIN_DOT_SPACING = 16;
28static constexpr int MESSAGE_Y = 90;
29
30namespace cdc::ui {
31
38void PinChangeView::init(uint8_t minLength, uint8_t maxLength) {
39 minLength_ = minLength < 4 ? 4 : minLength;
40 maxLength_ = maxLength > MAX_PIN_LENGTH ? MAX_PIN_LENGTH : maxLength;
41 step_ = Step::CURRENT_PIN;
42 clearBuffer();
43 memset(currentPin_, 0, sizeof(currentPin_));
44 memset(newPin_, 0, sizeof(newPin_));
45 memset(confirmPin_, 0, sizeof(confirmPin_));
46 message_ = nullptr;
47 messageShownMs_ = 0;
48 pinChanged_ = false;
49 dirty_ = true;
50}
51
57void PinChangeView::onEnter(void* context) {
58 (void)context;
59 init(minLength_, maxLength_);
60}
61
66void PinChangeView::clearBuffer() {
67 char* buf = getCurrentBuffer();
68 if (buf) {
69 memset(buf, 0, MAX_PIN_LENGTH + 1);
70 }
71 length_ = 0;
72}
73
78char* PinChangeView::getCurrentBuffer() {
79 switch (step_) {
80 case Step::CURRENT_PIN: return currentPin_;
81 case Step::NEW_PIN: return newPin_;
82 case Step::CONFIRM_PIN: return confirmPin_;
83 }
84 return currentPin_;
85}
86
91const char* PinChangeView::getStepTitle() const {
92 switch (step_) {
93 case Step::CURRENT_PIN: return ui::tr("core.current_pin");
94 case Step::NEW_PIN: return ui::tr("core.new_pin");
95 case Step::CONFIRM_PIN: return ui::tr("core.confirm_pin");
96 }
97 return "";
98}
99
105 if (onRetries_) return onRetries_();
107}
108
114void PinChangeView::addDigit(char digit) {
115 if (length_ >= maxLength_) return;
116
117 char* buf = getCurrentBuffer();
118 buf[length_++] = digit;
119 buf[length_] = '\0';
120 dirty_ = true;
121}
122
127void PinChangeView::backspace() {
128 if (length_ > 0) {
129 char* buf = getCurrentBuffer();
130 buf[--length_] = '\0';
131 dirty_ = true;
132 }
133}
134
140void PinChangeView::showMessage(const char* msg) {
141 message_ = msg;
142 messageShownMs_ = esp_timer_get_time() / 1000;
143 dirty_ = true;
144}
145
150void PinChangeView::confirmStep() {
151 switch (step_) {
152 case Step::CURRENT_PIN: {
153 // Verify current PIN
154 if (length_ < minLength_) {
155 showMessage(ui::tr("core.pin_too_short"));
156 clearBuffer();
157 return;
158 }
159
160 bool ok = onVerify_ ? onVerify_(currentPin_) : core::PinManager::instance().verifyBadgePin(currentPin_);
161 bool blocked = onBlocked_ ? onBlocked_() : core::PinManager::instance().isBadgeBlocked();
162 if (!ok) {
163 if (blocked) {
164 showMessage(ui::tr("core.locked_out"));
165 } else {
166 showMessage(ui::tr("core.wrong_pin"));
167 }
168 clearBuffer();
169 return;
170 }
171
172 // Move to new PIN step
173 step_ = Step::NEW_PIN;
174 clearBuffer();
175 message_ = nullptr;
176 LOG_I(TAG, "Current PIN verified, entering new PIN");
177 dirty_ = true;
178 break;
179 }
180
181 case Step::NEW_PIN: {
182 if (length_ < minLength_) {
183 showMessage(ui::tr("core.pin_too_short"));
184 clearBuffer();
185 return;
186 }
187
188 // Move to confirm step
189 step_ = Step::CONFIRM_PIN;
190 clearBuffer();
191 message_ = nullptr;
192 LOG_I(TAG, "New PIN entered, confirming");
193 dirty_ = true;
194 break;
195 }
196
197 case Step::CONFIRM_PIN: {
198 if (length_ < minLength_) {
199 showMessage(ui::tr("core.pin_too_short"));
200 clearBuffer();
201 return;
202 }
203
204 // Check if PINs match
205 if (strcmp(newPin_, confirmPin_) != 0) {
206 showMessage(ui::tr("core.pin_mismatch"));
207 // Go back to new PIN step
208 step_ = Step::NEW_PIN;
209 memset(newPin_, 0, sizeof(newPin_));
210 clearBuffer();
211 LOG_W(TAG, "PIN mismatch, re-enter new PIN");
212 return;
213 }
214
215 // Change the PIN
216 bool changed = onChange_
217 ? onChange_(currentPin_, newPin_)
218 : core::PinManager::instance().setBadgePin(newPin_);
219 if (changed) {
220 pinChanged_ = true;
221 showMessage(ui::tr("core.pin_changed"));
222 LOG_I(TAG, "PIN changed successfully");
223 } else {
224 showMessage(ui::tr("core.error_generic"));
225 LOG_E(TAG, "Failed to set new PIN");
226 }
227 break;
228 }
229 }
230}
231
237void PinChangeView::onTick(uint32_t nowMs) {
238 if (messageShownMs_ > 0 && message_ != nullptr) {
239 if (nowMs - messageShownMs_ >= MESSAGE_DISPLAY_MS) {
240 message_ = nullptr;
241 messageShownMs_ = 0;
242 dirty_ = true;
243
244 // If PIN was changed, pop the view
245 if (pinChanged_ && onComplete_) {
246 onComplete_(true);
247 }
248 }
249 }
250}
251
258 // Don't accept input if locked out
259 bool blocked = onBlocked_ ? onBlocked_() : core::PinManager::instance().isBadgeBlocked();
260 if (blocked) {
262 }
263
264 // Handle digits
265 if (key >= '0' && key <= '9') {
266 addDigit(key);
268 }
269
270 switch (key) {
271 case KEY_NO: // Backspace or cancel
272 if (length_ > 0) {
273 backspace();
274 } else if (step_ == Step::CURRENT_PIN) {
275 // Cancel from first step
276 if (onComplete_) {
277 onComplete_(false);
278 }
280 } else {
281 // Go back to previous step
282 if (step_ == Step::CONFIRM_PIN) {
283 step_ = Step::NEW_PIN;
284 length_ = strlen(newPin_);
285 } else if (step_ == Step::NEW_PIN) {
286 step_ = Step::CURRENT_PIN;
287 length_ = strlen(currentPin_);
288 }
289 dirty_ = true;
290 }
292
293 case KEY_YES: // Confirm
294 if (length_ >= minLength_) {
295 confirmStep();
296 }
298
299 default:
301 }
302}
303
308const char* PinChangeView::getFooterHint() const {
309 return ui::tr("core.hint_pin_input");
310}
311
317void PinChangeView::render(bool partial) {
319 if (!display) return;
320
321 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
322 if (!gfx) return;
323
324 const uint16_t width = display->getWidth();
325 const uint16_t height = display->getHeight();
326
327 if (!partial) {
328 gfx->fillScreen(EPD_WHITE);
329 }
330
331 gfx->setTextColor(EPD_BLACK);
332 int16_t x1, y1;
333 uint16_t w, h;
334
335 gfx->setTextSize(1);
336 const char* title = title_ ? title_ : ui::tr("core.change_pin");
337 render::drawHeaderCentered(gfx, title, TITLE_Y, width);
338
339 const char* stepTitle = getStepTitle();
340 char stepStr[48];
341 snprintf(stepStr, sizeof(stepStr), "%d/3: %s", static_cast<int>(step_) + 1, stepTitle);
342 gfx->getTextBounds(stepStr, 0, 0, &x1, &y1, &w, &h);
343 gfx->setCursor((width - w) / 2, STEP_Y);
344 render::printText(gfx, stepStr);
345
346 int dotsToShow = length_ > 8 ? length_ : 8;
347 int totalWidth = dotsToShow * PIN_DOT_SIZE + (dotsToShow - 1) * (PIN_DOT_SPACING - PIN_DOT_SIZE);
348 int startX = (width - totalWidth) / 2;
349
350 for (int i = 0; i < dotsToShow; i++) {
351 int x = startX + i * PIN_DOT_SPACING;
352 int y = PIN_Y;
353
354 if (i < length_) {
355 gfx->fillCircle(x + PIN_DOT_SIZE / 2, y + PIN_DOT_SIZE / 2, PIN_DOT_SIZE / 2 - 1, EPD_BLACK);
356 } else {
357 gfx->drawCircle(x + PIN_DOT_SIZE / 2, y + PIN_DOT_SIZE / 2, PIN_DOT_SIZE / 2 - 1, EPD_BLACK);
358 }
359 }
360
361 if (step_ == Step::CURRENT_PIN) {
362 uint8_t retries = getRetriesRemaining();
363 char retriesStr[24];
364 snprintf(retriesStr, sizeof(retriesStr), "%s: %d", ui::tr("core.retries"), retries);
365 gfx->setTextSize(1);
366 gfx->getTextBounds(retriesStr, 0, 0, &x1, &y1, &w, &h);
367 gfx->setCursor((width - w) / 2, PIN_Y + 25);
368 gfx->print(retriesStr);
369 }
370
371 gfx->fillRect(0, MESSAGE_Y - 2, width, 20, EPD_WHITE);
372 if (message_) {
373 gfx->setTextSize(1);
374 gfx->getTextBounds(message_, 0, 0, &x1, &y1, &w, &h);
375 gfx->setCursor((width - w) / 2, MESSAGE_Y);
376 render::printText(gfx, message_);
377 }
378
379 const char* hint = getFooterHint();
380 render::drawFooterBar(gfx, width, height, nullptr, hint, false);
381
382 dirty_ = false;
383}
384
385} // 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 MESSAGE_Y
static constexpr int STEP_Y
static constexpr int PIN_DOT_SPACING
static constexpr int TITLE_Y
Display layout constants.
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
uint8_t getBadgeRetries() const
Definition PinManager.h:87
bool isBadgeBlocked() const
Lockout timer handling.
static PinManager & instance()
Returns singleton PIN manager instance.
InputResult onKey(char key) override
Handles key input for PIN-change flow.
const char * getFooterHint() const override
Returns localized footer hint text.
void init(uint8_t minLength=4, uint8_t maxLength=16)
Initializes PIN-change wizard state.
static constexpr uint8_t MAX_PIN_LENGTH
static constexpr uint32_t MESSAGE_DISPLAY_MS
void onEnter(void *context) override
Resets wizard state when entering the view.
void render(bool partial) override
Renders PIN-change wizard UI.
uint8_t getRetriesRemaining() const
Returns remaining retry count for current PIN verification.
void onTick(uint32_t nowMs) override
Handles message timeout and completion callbacks.
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
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.
static const char * TAG
static constexpr char KEY_YES
Confirm / OK / Save.
Definition KeyCodes.h:41