⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/desktop/
screen_lock.rs

1//! Screen Lock
2//!
3//! Provides a fullscreen lock screen with password authentication.
4//! Triggered via Ctrl+Alt+L or idle timeout. Renders a dark gradient
5//! background with a padlock icon, username, and dot-masked password
6//! field. Integrates with the security/auth module for credential
7//! verification.
8//!
9//! All rendering uses integer math only (no floating point). Text is
10//! drawn via `crate::graphics::font8x16::glyph()` into a `u32` pixel
11//! buffer (0xAARRGGBB native-endian).
12
13#![allow(dead_code)]
14
15use alloc::string::String;
16
17// ---------------------------------------------------------------------------
18// Constants
19// ---------------------------------------------------------------------------
20
21/// Maximum characters in the password input buffer.
22const MAX_PASSWORD_LEN: usize = 128;
23
24/// Cursor blink half-period in ticks (500 ticks = 500ms at 1000Hz).
25const CURSOR_BLINK_TICKS: u64 = 500;
26
27/// Default idle timeout before auto-lock: 5 minutes at 1000Hz.
28const DEFAULT_IDLE_TIMEOUT_TICKS: u64 = 300_000;
29
30/// Lockout duration in ticks after exceeding max failed attempts (30 seconds).
31const LOCKOUT_DURATION_TICKS: u64 = 30_000;
32
33/// Default maximum consecutive failed authentication attempts before lockout.
34const DEFAULT_MAX_ATTEMPTS: u32 = 5;
35
36/// Font glyph dimensions (VGA 8x16).
37const GLYPH_W: usize = 8;
38const GLYPH_H: usize = 16;
39
40/// Password input field dimensions.
41const INPUT_FIELD_W: usize = 320;
42const INPUT_FIELD_H: usize = 28;
43
44/// Padlock icon dimensions (drawn from rectangles).
45const PADLOCK_W: usize = 40;
46const PADLOCK_H: usize = 50;
47
48// ---------------------------------------------------------------------------
49// Colors (0xAARRGGBB)
50// ---------------------------------------------------------------------------
51
52/// Background gradient top color (dark blue-black).
53const BG_TOP: (u32, u32, u32) = (0x0C, 0x0C, 0x1E);
54/// Background gradient bottom color (darker navy).
55const BG_BOT: (u32, u32, u32) = (0x06, 0x06, 0x12);
56
57/// Lock icon color (steel blue).
58const LOCK_COLOR: u32 = 0xFF4A7A9B;
59/// Lock icon shackle color (lighter steel blue).
60const LOCK_SHACKLE_COLOR: u32 = 0xFF5A8AAB;
61
62/// Title text color (white-ish).
63const TITLE_COLOR: u32 = 0xFFDDDDDD;
64/// Username text color (light grey).
65const USERNAME_COLOR: u32 = 0xFFBBBBBB;
66/// Hint text color (dim grey).
67const HINT_COLOR: u32 = 0xFF777777;
68/// Password dot color (white).
69const DOT_COLOR: u32 = 0xFFEEEEEE;
70/// Cursor color (white).
71const CURSOR_COLOR: u32 = 0xFFFFFFFF;
72/// Error text color (red).
73const ERROR_COLOR: u32 = 0xFFDD4444;
74/// Lockout text color (orange-red).
75const LOCKOUT_COLOR: u32 = 0xFFFF8844;
76/// Input field background color (semi-transparent dark).
77const INPUT_BG_COLOR: u32 = 0xFF1A1A2E;
78/// Input field border color.
79const INPUT_BORDER_COLOR: u32 = 0xFF3A3A5E;
80/// Success flash color (green tint).
81const SUCCESS_COLOR: u32 = 0xFF44DD44;
82
83// ---------------------------------------------------------------------------
84// LockState
85// ---------------------------------------------------------------------------
86
87/// Current state of the screen locker.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum LockState {
90    /// Screen is unlocked -- normal desktop operation.
91    Unlocked,
92    /// Screen is locked -- waiting for user input.
93    Locked,
94    /// User is typing a password (active authentication attempt).
95    Authenticating,
96    /// Last authentication attempt failed.
97    AuthFailed,
98    /// Authentication succeeded (brief transition before unlock).
99    AuthSuccess,
100}
101
102// ---------------------------------------------------------------------------
103// LockAction
104// ---------------------------------------------------------------------------
105
106/// Action returned from `handle_key` to the caller.
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum LockAction {
109    /// No action needed.
110    None,
111    /// Password submitted -- caller should check result via `attempt_auth`.
112    Authenticate,
113    /// Authentication failed (password incorrect).
114    AuthFailed,
115    /// Account is locked out due to too many failures.
116    LockedOut,
117    /// Screen has been successfully unlocked.
118    Unlocked,
119}
120
121// ---------------------------------------------------------------------------
122// ScreenLocker
123// ---------------------------------------------------------------------------
124
125/// Fullscreen lock screen with password authentication.
126pub struct ScreenLocker {
127    /// Current lock state.
128    state: LockState,
129    /// Password being entered (masked as dots on screen).
130    password_buffer: String,
131    /// Whether the cursor is currently visible (toggles for blink effect).
132    cursor_visible: bool,
133    /// Tick counter at which cursor visibility last toggled.
134    cursor_blink_tick: u64,
135    /// Number of consecutive failed authentication attempts.
136    failed_attempts: u32,
137    /// Maximum failed attempts before temporary lockout.
138    max_attempts: u32,
139    /// Tick at which the lockout expires (0 = no active lockout).
140    lockout_until_tick: u64,
141    /// Message displayed at the top of the lock screen.
142    lock_message: &'static str,
143    /// Username displayed above the password field.
144    user_name: &'static str,
145    /// Ticks of inactivity before the screen auto-locks.
146    idle_timeout_ticks: u64,
147    /// Tick of most recent user activity (key press, mouse movement).
148    last_activity_tick: u64,
149    /// Framebuffer width in pixels.
150    screen_width: usize,
151    /// Framebuffer height in pixels.
152    screen_height: usize,
153    /// Tick at which the last auth failure occurred (for brief error display).
154    fail_display_tick: u64,
155    /// Tick at which auth succeeded (for brief success flash).
156    success_display_tick: u64,
157}
158
159impl ScreenLocker {
160    /// Create a new screen locker in the `Unlocked` state.
161    pub fn new(screen_width: usize, screen_height: usize) -> Self {
162        Self {
163            state: LockState::Unlocked,
164            password_buffer: String::new(),
165            cursor_visible: true,
166            cursor_blink_tick: 0,
167            failed_attempts: 0,
168            max_attempts: DEFAULT_MAX_ATTEMPTS,
169            lockout_until_tick: 0,
170            lock_message: "VeridianOS",
171            user_name: "root",
172            idle_timeout_ticks: DEFAULT_IDLE_TIMEOUT_TICKS,
173            last_activity_tick: 0,
174            screen_width,
175            screen_height,
176            fail_display_tick: 0,
177            success_display_tick: 0,
178        }
179    }
180
181    // -----------------------------------------------------------------------
182    // State transitions
183    // -----------------------------------------------------------------------
184
185    /// Lock the screen. Clears any in-progress password entry.
186    pub fn lock(&mut self) {
187        self.password_buffer.clear();
188        self.cursor_visible = true;
189        self.state = LockState::Locked;
190    }
191
192    /// Unlock the screen and reset failure counters.
193    pub fn unlock(&mut self) {
194        self.password_buffer.clear();
195        self.failed_attempts = 0;
196        self.lockout_until_tick = 0;
197        self.state = LockState::Unlocked;
198    }
199
200    /// Returns `true` if the screen is in any locked/authenticating state.
201    pub fn is_locked(&self) -> bool {
202        !matches!(self.state, LockState::Unlocked)
203    }
204
205    /// Returns the current lock state.
206    pub fn state(&self) -> LockState {
207        self.state
208    }
209
210    // -----------------------------------------------------------------------
211    // Input handling
212    // -----------------------------------------------------------------------
213
214    /// Process a key press while the lock screen is active.
215    ///
216    /// `key` is an ASCII byte (printable characters, Enter=0x0D,
217    /// Backspace=0x08, Escape=0x1B). Returns a `LockAction` indicating what
218    /// happened.
219    pub fn handle_key(&mut self, key: u8, current_tick: u64) -> LockAction {
220        self.record_activity(current_tick);
221
222        // If we are in the brief AuthSuccess flash, auto-unlock
223        if self.state == LockState::AuthSuccess {
224            if current_tick.saturating_sub(self.success_display_tick) > 500 {
225                self.unlock();
226                return LockAction::Unlocked;
227            }
228            return LockAction::None;
229        }
230
231        // Transition from Locked/AuthFailed to Authenticating on first printable key
232        if matches!(self.state, LockState::Locked | LockState::AuthFailed) && is_printable(key) {
233            self.state = LockState::Authenticating;
234            // Fall through to handle the key below
235        }
236
237        // Check lockout
238        if self.lockout_until_tick > 0 && current_tick < self.lockout_until_tick {
239            return LockAction::LockedOut;
240        }
241        // Clear expired lockout
242        if self.lockout_until_tick > 0 && current_tick >= self.lockout_until_tick {
243            self.lockout_until_tick = 0;
244            self.failed_attempts = 0;
245        }
246
247        match key {
248            // Enter: attempt authentication
249            0x0D => {
250                if self.password_buffer.is_empty() {
251                    return LockAction::None;
252                }
253                let success = self.attempt_auth(current_tick);
254                if success {
255                    self.state = LockState::AuthSuccess;
256                    self.success_display_tick = current_tick;
257                    LockAction::Authenticate
258                } else {
259                    LockAction::AuthFailed
260                }
261            }
262
263            // Escape: clear the password buffer
264            0x1B => {
265                self.password_buffer.clear();
266                self.state = LockState::Locked;
267                LockAction::None
268            }
269
270            // Backspace: delete the last character
271            0x08 | 0x7F => {
272                self.password_buffer.pop();
273                if self.password_buffer.is_empty() {
274                    self.state = LockState::Locked;
275                }
276                LockAction::None
277            }
278
279            // Printable ASCII characters: append to password buffer
280            ch if is_printable(ch) => {
281                if self.password_buffer.len() < MAX_PASSWORD_LEN {
282                    self.password_buffer.push(ch as char);
283                    self.state = LockState::Authenticating;
284                }
285                LockAction::None
286            }
287
288            _ => LockAction::None,
289        }
290    }
291
292    // -----------------------------------------------------------------------
293    // Authentication
294    // -----------------------------------------------------------------------
295
296    /// Attempt to authenticate with the current password buffer contents.
297    ///
298    /// On success: resets failure count, transitions to `AuthSuccess`.
299    /// On failure: increments `failed_attempts`; if `>= max_attempts`, sets
300    /// a lockout timer.
301    ///
302    /// Returns `true` on successful authentication.
303    pub fn attempt_auth(&mut self, current_tick: u64) -> bool {
304        let password = &self.password_buffer;
305
306        // Try the kernel auth manager first
307        let success = verify_password(self.user_name, password.as_str());
308
309        if success {
310            self.failed_attempts = 0;
311            self.lockout_until_tick = 0;
312            self.state = LockState::AuthSuccess;
313            self.success_display_tick = current_tick;
314            true
315        } else {
316            self.failed_attempts += 1;
317            self.state = LockState::AuthFailed;
318            self.fail_display_tick = current_tick;
319
320            if self.failed_attempts >= self.max_attempts {
321                self.lockout_until_tick = current_tick + LOCKOUT_DURATION_TICKS;
322            }
323
324            self.password_buffer.clear();
325            false
326        }
327    }
328
329    // -----------------------------------------------------------------------
330    // Idle timeout
331    // -----------------------------------------------------------------------
332
333    /// Check whether the idle timeout has elapsed since the last activity.
334    ///
335    /// Returns `true` if the screen should be locked due to inactivity.
336    /// Only meaningful when the screen is currently unlocked.
337    pub fn check_idle_timeout(&mut self, current_tick: u64) -> bool {
338        if self.state != LockState::Unlocked {
339            return false;
340        }
341        if self.idle_timeout_ticks == 0 {
342            return false;
343        }
344        current_tick.saturating_sub(self.last_activity_tick) >= self.idle_timeout_ticks
345    }
346
347    /// Record user activity (resets the idle timer).
348    pub fn record_activity(&mut self, current_tick: u64) {
349        self.last_activity_tick = current_tick;
350    }
351
352    // -----------------------------------------------------------------------
353    // Tick / animation
354    // -----------------------------------------------------------------------
355
356    /// Advance internal animation timers. Call once per frame or per tick.
357    ///
358    /// Toggles cursor blink state every `CURSOR_BLINK_TICKS` ticks.
359    /// Also auto-transitions from AuthSuccess to Unlocked after a brief delay.
360    pub fn tick(&mut self, current_tick: u64) {
361        // Cursor blink
362        if current_tick.saturating_sub(self.cursor_blink_tick) >= CURSOR_BLINK_TICKS {
363            self.cursor_visible = !self.cursor_visible;
364            self.cursor_blink_tick = current_tick;
365        }
366
367        // Auto-unlock after AuthSuccess flash (500ms)
368        if self.state == LockState::AuthSuccess
369            && current_tick.saturating_sub(self.success_display_tick) > 500
370        {
371            self.unlock();
372        }
373
374        // Auto-clear AuthFailed message after 3 seconds
375        if self.state == LockState::AuthFailed
376            && current_tick.saturating_sub(self.fail_display_tick) > 3000
377        {
378            self.state = LockState::Locked;
379        }
380    }
381
382    // -----------------------------------------------------------------------
383    // Configuration
384    // -----------------------------------------------------------------------
385
386    /// Set the idle timeout in ticks. Pass 0 to disable auto-lock.
387    pub fn set_idle_timeout(&mut self, ticks: u64) {
388        self.idle_timeout_ticks = ticks;
389    }
390
391    /// Set the maximum number of failed attempts before lockout.
392    pub fn set_max_attempts(&mut self, n: u32) {
393        self.max_attempts = n;
394    }
395
396    /// Set the display message shown on the lock screen.
397    pub fn set_lock_message(&mut self, msg: &'static str) {
398        self.lock_message = msg;
399    }
400
401    /// Set the username displayed on the lock screen.
402    pub fn set_user_name(&mut self, name: &'static str) {
403        self.user_name = name;
404    }
405
406    /// Update screen dimensions (e.g., on resolution change).
407    pub fn set_screen_size(&mut self, width: usize, height: usize) {
408        self.screen_width = width;
409        self.screen_height = height;
410    }
411
412    // -----------------------------------------------------------------------
413    // Rendering
414    // -----------------------------------------------------------------------
415
416    /// Render the lock screen into a `u32` pixel buffer.
417    ///
418    /// `buffer` is `buf_width * buf_height` elements in 0xAARRGGBB layout.
419    /// The caller is responsible for blitting this buffer to the framebuffer
420    /// (with any necessary RGB/BGR conversion).
421    pub fn render_to_buffer(
422        &self,
423        buffer: &mut [u32],
424        buf_width: usize,
425        buf_height: usize,
426        current_tick: u64,
427    ) {
428        // --- 1. Dark gradient background ---
429        self.render_gradient_background(buffer, buf_width, buf_height);
430
431        // Center coordinates
432        let cx = buf_width / 2;
433        let cy = buf_height / 2;
434
435        // Vertical layout (top to bottom, centered on cy - 40):
436        //   padlock icon       (cy - 120)
437        //   lock_message        (cy - 60)
438        //   username            (cy - 36)
439        //   input field         (cy - 14)
440        //   hint / error text   (cy + 24)
441
442        let base_y = if cy > 120 { cy - 40 } else { 80 };
443
444        // --- 2. Padlock icon ---
445        let padlock_x = cx.saturating_sub(PADLOCK_W / 2);
446        let padlock_y = base_y.saturating_sub(100);
447        self.render_padlock(buffer, buf_width, buf_height, padlock_x, padlock_y);
448
449        // --- 3. Lock message (title) ---
450        let title = self.lock_message.as_bytes();
451        let title_x = cx.saturating_sub(title.len() * GLYPH_W / 2);
452        let title_y = base_y.saturating_sub(44);
453        draw_string_u32(
454            buffer,
455            buf_width,
456            buf_height,
457            title,
458            title_x,
459            title_y,
460            TITLE_COLOR,
461        );
462
463        // --- 4. Username ---
464        let user_bytes = self.user_name.as_bytes();
465        let user_x = cx.saturating_sub(user_bytes.len() * GLYPH_W / 2);
466        let user_y = base_y.saturating_sub(20);
467        draw_string_u32(
468            buffer,
469            buf_width,
470            buf_height,
471            user_bytes,
472            user_x,
473            user_y,
474            USERNAME_COLOR,
475        );
476
477        // --- 5. Password input field ---
478        let field_x = cx.saturating_sub(INPUT_FIELD_W / 2);
479        let field_y = base_y + 4;
480        self.render_input_field(
481            buffer,
482            buf_width,
483            buf_height,
484            field_x,
485            field_y,
486            current_tick,
487        );
488
489        // --- 6. Status text below input field ---
490        let status_y = field_y + INPUT_FIELD_H + 12;
491        self.render_status_text(buffer, buf_width, buf_height, cx, status_y, current_tick);
492
493        // --- 7. AuthSuccess overlay flash ---
494        if self.state == LockState::AuthSuccess {
495            let elapsed = current_tick.saturating_sub(self.success_display_tick);
496            if elapsed < 500 {
497                // Brief green tint on the input field border
498                fill_rect(
499                    buffer,
500                    buf_width,
501                    buf_height,
502                    field_x,
503                    field_y,
504                    INPUT_FIELD_W,
505                    2,
506                    SUCCESS_COLOR,
507                );
508                fill_rect(
509                    buffer,
510                    buf_width,
511                    buf_height,
512                    field_x,
513                    field_y + INPUT_FIELD_H - 2,
514                    INPUT_FIELD_W,
515                    2,
516                    SUCCESS_COLOR,
517                );
518            }
519        }
520    }
521
522    /// Render the gradient background.
523    fn render_gradient_background(&self, buffer: &mut [u32], buf_width: usize, buf_height: usize) {
524        for y in 0..buf_height {
525            // Integer fixed-point 8.8 gradient interpolation
526            let t256 = if buf_height > 0 {
527                (y * 256) / buf_height
528            } else {
529                0
530            };
531            let inv_t = 256 - t256;
532            let r = (BG_TOP.0 * inv_t as u32 + BG_BOT.0 * t256 as u32) / 256;
533            let g = (BG_TOP.1 * inv_t as u32 + BG_BOT.1 * t256 as u32) / 256;
534            let b = (BG_TOP.2 * inv_t as u32 + BG_BOT.2 * t256 as u32) / 256;
535            let pixel = 0xFF00_0000 | (r << 16) | (g << 8) | b;
536
537            let row_start = y * buf_width;
538            let row_end = row_start + buf_width;
539            if row_end <= buffer.len() {
540                for px in &mut buffer[row_start..row_end] {
541                    *px = pixel;
542                }
543            }
544        }
545    }
546
547    /// Render a padlock icon composed of simple rectangles.
548    fn render_padlock(
549        &self,
550        buffer: &mut [u32],
551        buf_width: usize,
552        buf_height: usize,
553        x: usize,
554        y: usize,
555    ) {
556        // Body: filled rectangle (lower portion of the padlock)
557        let body_x = x + 4;
558        let body_y = y + 20;
559        let body_w = PADLOCK_W - 8;
560        let body_h = PADLOCK_H - 20;
561        fill_rect(
562            buffer, buf_width, buf_height, body_x, body_y, body_w, body_h, LOCK_COLOR,
563        );
564
565        // Shackle: an arch drawn as a thick rectangular outline (upper portion)
566        let shackle_x = x + 10;
567        let shackle_y = y;
568        let shackle_w = PADLOCK_W - 20;
569        let shackle_h = 24;
570        let thickness = 4;
571
572        // Left vertical bar of shackle
573        fill_rect(
574            buffer,
575            buf_width,
576            buf_height,
577            shackle_x,
578            shackle_y + thickness,
579            thickness,
580            shackle_h - thickness,
581            LOCK_SHACKLE_COLOR,
582        );
583        // Right vertical bar of shackle
584        fill_rect(
585            buffer,
586            buf_width,
587            buf_height,
588            shackle_x + shackle_w - thickness,
589            shackle_y + thickness,
590            thickness,
591            shackle_h - thickness,
592            LOCK_SHACKLE_COLOR,
593        );
594        // Top horizontal bar of shackle
595        fill_rect(
596            buffer,
597            buf_width,
598            buf_height,
599            shackle_x,
600            shackle_y,
601            shackle_w,
602            thickness,
603            LOCK_SHACKLE_COLOR,
604        );
605
606        // Keyhole: small dark circle approximation in center of body
607        let keyhole_cx = body_x + body_w / 2;
608        let keyhole_cy = body_y + body_h / 3;
609        // Small filled square as keyhole dot
610        fill_rect(
611            buffer,
612            buf_width,
613            buf_height,
614            keyhole_cx.saturating_sub(3),
615            keyhole_cy.saturating_sub(3),
616            6,
617            6,
618            0xFF111122,
619        );
620        // Keyhole slot below
621        fill_rect(
622            buffer,
623            buf_width,
624            buf_height,
625            keyhole_cx.saturating_sub(2),
626            keyhole_cy + 3,
627            4,
628            8,
629            0xFF111122,
630        );
631    }
632
633    /// Render the password input field with dot-masked characters and cursor.
634    fn render_input_field(
635        &self,
636        buffer: &mut [u32],
637        buf_width: usize,
638        buf_height: usize,
639        field_x: usize,
640        field_y: usize,
641        current_tick: u64,
642    ) {
643        // Field background
644        fill_rect(
645            buffer,
646            buf_width,
647            buf_height,
648            field_x,
649            field_y,
650            INPUT_FIELD_W,
651            INPUT_FIELD_H,
652            INPUT_BG_COLOR,
653        );
654
655        // Field border (1px)
656        let border_color = if self.state == LockState::AuthFailed {
657            ERROR_COLOR
658        } else {
659            INPUT_BORDER_COLOR
660        };
661        draw_rect_outline(
662            buffer,
663            buf_width,
664            buf_height,
665            field_x,
666            field_y,
667            INPUT_FIELD_W,
668            INPUT_FIELD_H,
669            border_color,
670        );
671
672        // Password dots: centered vertically in the field
673        let dot_y = field_y + (INPUT_FIELD_H / 2) - 3; // 6px dot, centered
674        let dot_spacing: usize = 14;
675        let total_dot_width = if self.password_buffer.is_empty() {
676            0
677        } else {
678            self.password_buffer.len() * dot_spacing
679        };
680        let dots_start_x = field_x + (INPUT_FIELD_W / 2).saturating_sub(total_dot_width / 2);
681
682        for i in 0..self.password_buffer.len() {
683            let dx = dots_start_x + i * dot_spacing;
684            render_dot(buffer, buf_width, buf_height, dx + 4, dot_y, DOT_COLOR);
685        }
686
687        // Blinking cursor
688        let _ignore = current_tick; // tick state is in self.cursor_visible
689        if self.cursor_visible
690            && matches!(
691                self.state,
692                LockState::Locked | LockState::Authenticating | LockState::AuthFailed
693            )
694        {
695            let cursor_x = if self.password_buffer.is_empty() {
696                field_x + INPUT_FIELD_W / 2
697            } else {
698                dots_start_x + self.password_buffer.len() * dot_spacing + 2
699            };
700            let cursor_y = field_y + 4;
701            let cursor_h = INPUT_FIELD_H - 8;
702            fill_rect(
703                buffer,
704                buf_width,
705                buf_height,
706                cursor_x,
707                cursor_y,
708                2,
709                cursor_h,
710                CURSOR_COLOR,
711            );
712        }
713    }
714
715    /// Render status/hint/error text below the input field.
716    fn render_status_text(
717        &self,
718        buffer: &mut [u32],
719        buf_width: usize,
720        buf_height: usize,
721        cx: usize,
722        y: usize,
723        current_tick: u64,
724    ) {
725        match self.state {
726            LockState::Locked => {
727                let hint = b"Press Enter to unlock";
728                let hx = cx.saturating_sub(hint.len() * GLYPH_W / 2);
729                draw_string_u32(buffer, buf_width, buf_height, hint, hx, y, HINT_COLOR);
730            }
731
732            LockState::Authenticating => {
733                let hint = b"Type password, then press Enter";
734                let hx = cx.saturating_sub(hint.len() * GLYPH_W / 2);
735                draw_string_u32(buffer, buf_width, buf_height, hint, hx, y, HINT_COLOR);
736            }
737
738            LockState::AuthFailed => {
739                let msg = b"Incorrect password";
740                let mx = cx.saturating_sub(msg.len() * GLYPH_W / 2);
741                draw_string_u32(buffer, buf_width, buf_height, msg, mx, y, ERROR_COLOR);
742
743                // Show remaining attempts
744                if self.failed_attempts > 0 && self.failed_attempts < self.max_attempts {
745                    let remaining = self.max_attempts - self.failed_attempts;
746                    // Format: "X attempts remaining"
747                    let mut attempt_buf = [0u8; 32];
748                    let attempt_len = format_attempts_remaining(remaining, &mut attempt_buf);
749                    let ax = cx.saturating_sub(attempt_len * GLYPH_W / 2);
750                    draw_string_u32(
751                        buffer,
752                        buf_width,
753                        buf_height,
754                        &attempt_buf[..attempt_len],
755                        ax,
756                        y + GLYPH_H + 4,
757                        HINT_COLOR,
758                    );
759                }
760
761                // Lockout message if applicable
762                if self.lockout_until_tick > 0 && current_tick < self.lockout_until_tick {
763                    let remaining_ticks = self.lockout_until_tick - current_tick;
764                    let secs = format_lockout_time(remaining_ticks);
765                    let mut lockout_buf = [0u8; 48];
766                    let lockout_len = format_lockout_message(secs, &mut lockout_buf);
767                    let lx = cx.saturating_sub(lockout_len * GLYPH_W / 2);
768                    draw_string_u32(
769                        buffer,
770                        buf_width,
771                        buf_height,
772                        &lockout_buf[..lockout_len],
773                        lx,
774                        y + (GLYPH_H + 4) * 2,
775                        LOCKOUT_COLOR,
776                    );
777                }
778            }
779
780            LockState::AuthSuccess => {
781                let msg = b"Unlocking...";
782                let mx = cx.saturating_sub(msg.len() * GLYPH_W / 2);
783                draw_string_u32(buffer, buf_width, buf_height, msg, mx, y, SUCCESS_COLOR);
784            }
785
786            LockState::Unlocked => {
787                // Nothing to render -- should not be called in unlocked state
788            }
789        }
790    }
791}
792
793// ---------------------------------------------------------------------------
794// Password verification
795// ---------------------------------------------------------------------------
796
797/// Verify a password against the kernel's auth manager.
798///
799/// Falls back to a simple DJB2 hash comparison against a known default
800/// if the auth manager is not initialized (early boot scenario).
801fn verify_password(username: &str, password: &str) -> bool {
802    // Try the kernel's auth manager (PBKDF2-HMAC-SHA256)
803    use crate::security::auth::{get_auth_manager, AuthResult};
804
805    let result = get_auth_manager().authenticate(username, password);
806    matches!(result, AuthResult::Success)
807}
808
809// ---------------------------------------------------------------------------
810// DJB2 simple hash (fallback for environments without auth manager)
811// ---------------------------------------------------------------------------
812
813/// DJB2 hash function for simple string hashing.
814///
815/// This is NOT cryptographically secure; it serves only as a basic
816/// fallback or for non-security-critical comparisons.
817pub fn djb2_hash(input: &str) -> u64 {
818    let mut hash: u64 = 5381;
819    for byte in input.bytes() {
820        // hash = hash * 33 + byte
821        hash = hash.wrapping_mul(33).wrapping_add(byte as u64);
822    }
823    hash
824}
825
826// ---------------------------------------------------------------------------
827// Drawing helpers (u32 buffer, 0xAARRGGBB)
828// ---------------------------------------------------------------------------
829
830/// Draw a string into a `u32` pixel buffer using the 8x16 VGA font.
831fn draw_string_u32(
832    buffer: &mut [u32],
833    buf_width: usize,
834    buf_height: usize,
835    text: &[u8],
836    px: usize,
837    py: usize,
838    color: u32,
839) {
840    for (i, &ch) in text.iter().enumerate() {
841        draw_char_u32(
842            buffer,
843            buf_width,
844            buf_height,
845            ch,
846            px + i * GLYPH_W,
847            py,
848            color,
849        );
850    }
851}
852
853/// Draw a single 8x16 glyph into a `u32` pixel buffer.
854fn draw_char_u32(
855    buffer: &mut [u32],
856    buf_width: usize,
857    buf_height: usize,
858    ch: u8,
859    px: usize,
860    py: usize,
861    color: u32,
862) {
863    let glyph = crate::graphics::font8x16::glyph(ch);
864
865    for (row, &bits) in glyph.iter().enumerate() {
866        let y = py + row;
867        if y >= buf_height {
868            break;
869        }
870        for col in 0..8 {
871            if (bits >> (7 - col)) & 1 != 0 {
872                let x = px + col;
873                if x >= buf_width {
874                    continue;
875                }
876                let offset = y * buf_width + x;
877                if offset < buffer.len() {
878                    buffer[offset] = color;
879                }
880            }
881        }
882    }
883}
884
885/// Fill a rectangle in a `u32` pixel buffer.
886fn fill_rect(
887    buffer: &mut [u32],
888    buf_width: usize,
889    buf_height: usize,
890    x: usize,
891    y: usize,
892    w: usize,
893    h: usize,
894    color: u32,
895) {
896    for row in y..y + h {
897        if row >= buf_height {
898            break;
899        }
900        for col in x..x + w {
901            if col >= buf_width {
902                break;
903            }
904            let offset = row * buf_width + col;
905            if offset < buffer.len() {
906                buffer[offset] = color;
907            }
908        }
909    }
910}
911
912/// Draw a 1-pixel rectangular outline in a `u32` pixel buffer.
913fn draw_rect_outline(
914    buffer: &mut [u32],
915    buf_width: usize,
916    buf_height: usize,
917    x: usize,
918    y: usize,
919    w: usize,
920    h: usize,
921    color: u32,
922) {
923    // Top edge
924    fill_rect(buffer, buf_width, buf_height, x, y, w, 1, color);
925    // Bottom edge
926    if h > 0 {
927        fill_rect(buffer, buf_width, buf_height, x, y + h - 1, w, 1, color);
928    }
929    // Left edge
930    fill_rect(buffer, buf_width, buf_height, x, y, 1, h, color);
931    // Right edge
932    if w > 0 {
933        fill_rect(buffer, buf_width, buf_height, x + w - 1, y, 1, h, color);
934    }
935}
936
937/// Render a circular dot (password mask character) at the given position.
938///
939/// Approximates a circle using a 6x6 bitmask for a clean appearance.
940fn render_dot(
941    buffer: &mut [u32],
942    buf_width: usize,
943    buf_height: usize,
944    cx: usize,
945    cy: usize,
946    color: u32,
947) {
948    // 6x6 circle bitmask (1 = filled)
949    //  .##.
950    // ####
951    // ####
952    // ####
953    // ####
954    //  .##.
955    static DOT_MASK: [[u8; 6]; 6] = [
956        [0, 1, 1, 1, 1, 0],
957        [1, 1, 1, 1, 1, 1],
958        [1, 1, 1, 1, 1, 1],
959        [1, 1, 1, 1, 1, 1],
960        [1, 1, 1, 1, 1, 1],
961        [0, 1, 1, 1, 1, 0],
962    ];
963
964    for (row, mask_row) in DOT_MASK.iter().enumerate() {
965        let y = cy + row;
966        if y >= buf_height {
967            break;
968        }
969        for (col, &set) in mask_row.iter().enumerate() {
970            if set != 0 {
971                let x = cx + col;
972                if x >= buf_width {
973                    continue;
974                }
975                let offset = y * buf_width + x;
976                if offset < buffer.len() {
977                    buffer[offset] = color;
978                }
979            }
980        }
981    }
982}
983
984// ---------------------------------------------------------------------------
985// Formatting helpers (no heap, no format!)
986// ---------------------------------------------------------------------------
987
988/// Convert remaining lockout ticks to whole seconds (rounded up).
989pub fn format_lockout_time(ticks_remaining: u64) -> u32 {
990    // 1000 ticks = 1 second at 1000Hz; round up
991    ticks_remaining.div_ceil(1000) as u32
992}
993
994/// Format "X attempts remaining" into a fixed buffer.
995/// Returns the number of bytes written.
996fn format_attempts_remaining(remaining: u32, buf: &mut [u8]) -> usize {
997    let mut pos = 0;
998
999    // Write the number
1000    pos += write_u32_to_buf(remaining, &mut buf[pos..]);
1001
1002    // Write " attempt(s) remaining"
1003    let suffix: &[u8] = if remaining == 1 {
1004        b" attempt remaining"
1005    } else {
1006        b" attempts remaining"
1007    };
1008    let copy_len = suffix.len().min(buf.len().saturating_sub(pos));
1009    buf[pos..pos + copy_len].copy_from_slice(&suffix[..copy_len]);
1010    pos += copy_len;
1011
1012    pos
1013}
1014
1015/// Format "Too many attempts. Try again in XX seconds" into a fixed buffer.
1016/// Returns the number of bytes written.
1017fn format_lockout_message(seconds: u32, buf: &mut [u8]) -> usize {
1018    let mut pos = 0;
1019    let prefix = b"Too many attempts. Try again in ";
1020    let copy_len = prefix.len().min(buf.len());
1021    buf[..copy_len].copy_from_slice(&prefix[..copy_len]);
1022    pos += copy_len;
1023
1024    pos += write_u32_to_buf(seconds, &mut buf[pos..]);
1025
1026    let suffix = b"s";
1027    let copy_len = suffix.len().min(buf.len().saturating_sub(pos));
1028    buf[pos..pos + copy_len].copy_from_slice(&suffix[..copy_len]);
1029    pos += copy_len;
1030
1031    pos
1032}
1033
1034/// Write a u32 as decimal ASCII digits into a byte buffer.
1035/// Returns the number of bytes written.
1036fn write_u32_to_buf(value: u32, buf: &mut [u8]) -> usize {
1037    if buf.is_empty() {
1038        return 0;
1039    }
1040    if value == 0 {
1041        buf[0] = b'0';
1042        return 1;
1043    }
1044
1045    // Extract digits in reverse
1046    let mut digits = [0u8; 10]; // u32 max is 4294967295 (10 digits)
1047    let mut n = value;
1048    let mut count = 0;
1049    while n > 0 && count < 10 {
1050        digits[count] = b'0' + (n % 10) as u8;
1051        n /= 10;
1052        count += 1;
1053    }
1054
1055    // Write digits in correct order
1056    let write_len = count.min(buf.len());
1057    for i in 0..write_len {
1058        buf[i] = digits[count - 1 - i];
1059    }
1060    write_len
1061}
1062
1063/// Returns `true` if the byte is a printable ASCII character (0x20..=0x7E).
1064fn is_printable(ch: u8) -> bool {
1065    (0x20..=0x7E).contains(&ch)
1066}