1#![allow(dead_code)]
14
15use alloc::string::String;
16
17const MAX_PASSWORD_LEN: usize = 128;
23
24const CURSOR_BLINK_TICKS: u64 = 500;
26
27const DEFAULT_IDLE_TIMEOUT_TICKS: u64 = 300_000;
29
30const LOCKOUT_DURATION_TICKS: u64 = 30_000;
32
33const DEFAULT_MAX_ATTEMPTS: u32 = 5;
35
36const GLYPH_W: usize = 8;
38const GLYPH_H: usize = 16;
39
40const INPUT_FIELD_W: usize = 320;
42const INPUT_FIELD_H: usize = 28;
43
44const PADLOCK_W: usize = 40;
46const PADLOCK_H: usize = 50;
47
48const BG_TOP: (u32, u32, u32) = (0x0C, 0x0C, 0x1E);
54const BG_BOT: (u32, u32, u32) = (0x06, 0x06, 0x12);
56
57const LOCK_COLOR: u32 = 0xFF4A7A9B;
59const LOCK_SHACKLE_COLOR: u32 = 0xFF5A8AAB;
61
62const TITLE_COLOR: u32 = 0xFFDDDDDD;
64const USERNAME_COLOR: u32 = 0xFFBBBBBB;
66const HINT_COLOR: u32 = 0xFF777777;
68const DOT_COLOR: u32 = 0xFFEEEEEE;
70const CURSOR_COLOR: u32 = 0xFFFFFFFF;
72const ERROR_COLOR: u32 = 0xFFDD4444;
74const LOCKOUT_COLOR: u32 = 0xFFFF8844;
76const INPUT_BG_COLOR: u32 = 0xFF1A1A2E;
78const INPUT_BORDER_COLOR: u32 = 0xFF3A3A5E;
80const SUCCESS_COLOR: u32 = 0xFF44DD44;
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum LockState {
90 Unlocked,
92 Locked,
94 Authenticating,
96 AuthFailed,
98 AuthSuccess,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub enum LockAction {
109 None,
111 Authenticate,
113 AuthFailed,
115 LockedOut,
117 Unlocked,
119}
120
121pub struct ScreenLocker {
127 state: LockState,
129 password_buffer: String,
131 cursor_visible: bool,
133 cursor_blink_tick: u64,
135 failed_attempts: u32,
137 max_attempts: u32,
139 lockout_until_tick: u64,
141 lock_message: &'static str,
143 user_name: &'static str,
145 idle_timeout_ticks: u64,
147 last_activity_tick: u64,
149 screen_width: usize,
151 screen_height: usize,
153 fail_display_tick: u64,
155 success_display_tick: u64,
157}
158
159impl ScreenLocker {
160 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 pub fn lock(&mut self) {
187 self.password_buffer.clear();
188 self.cursor_visible = true;
189 self.state = LockState::Locked;
190 }
191
192 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 pub fn is_locked(&self) -> bool {
202 !matches!(self.state, LockState::Unlocked)
203 }
204
205 pub fn state(&self) -> LockState {
207 self.state
208 }
209
210 pub fn handle_key(&mut self, key: u8, current_tick: u64) -> LockAction {
220 self.record_activity(current_tick);
221
222 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 if matches!(self.state, LockState::Locked | LockState::AuthFailed) && is_printable(key) {
233 self.state = LockState::Authenticating;
234 }
236
237 if self.lockout_until_tick > 0 && current_tick < self.lockout_until_tick {
239 return LockAction::LockedOut;
240 }
241 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 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 0x1B => {
265 self.password_buffer.clear();
266 self.state = LockState::Locked;
267 LockAction::None
268 }
269
270 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 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 pub fn attempt_auth(&mut self, current_tick: u64) -> bool {
304 let password = &self.password_buffer;
305
306 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 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 pub fn record_activity(&mut self, current_tick: u64) {
349 self.last_activity_tick = current_tick;
350 }
351
352 pub fn tick(&mut self, current_tick: u64) {
361 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 if self.state == LockState::AuthSuccess
369 && current_tick.saturating_sub(self.success_display_tick) > 500
370 {
371 self.unlock();
372 }
373
374 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 pub fn set_idle_timeout(&mut self, ticks: u64) {
388 self.idle_timeout_ticks = ticks;
389 }
390
391 pub fn set_max_attempts(&mut self, n: u32) {
393 self.max_attempts = n;
394 }
395
396 pub fn set_lock_message(&mut self, msg: &'static str) {
398 self.lock_message = msg;
399 }
400
401 pub fn set_user_name(&mut self, name: &'static str) {
403 self.user_name = name;
404 }
405
406 pub fn set_screen_size(&mut self, width: usize, height: usize) {
408 self.screen_width = width;
409 self.screen_height = height;
410 }
411
412 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 self.render_gradient_background(buffer, buf_width, buf_height);
430
431 let cx = buf_width / 2;
433 let cy = buf_height / 2;
434
435 let base_y = if cy > 120 { cy - 40 } else { 80 };
443
444 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 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 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 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 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 if self.state == LockState::AuthSuccess {
495 let elapsed = current_tick.saturating_sub(self.success_display_tick);
496 if elapsed < 500 {
497 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 fn render_gradient_background(&self, buffer: &mut [u32], buf_width: usize, buf_height: usize) {
524 for y in 0..buf_height {
525 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 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 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 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 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 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 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 let keyhole_cx = body_x + body_w / 2;
608 let keyhole_cy = body_y + body_h / 3;
609 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 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 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 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 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 let dot_y = field_y + (INPUT_FIELD_H / 2) - 3; 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 let _ignore = current_tick; 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 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 if self.failed_attempts > 0 && self.failed_attempts < self.max_attempts {
745 let remaining = self.max_attempts - self.failed_attempts;
746 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 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 }
789 }
790 }
791}
792
793fn verify_password(username: &str, password: &str) -> bool {
802 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
809pub fn djb2_hash(input: &str) -> u64 {
818 let mut hash: u64 = 5381;
819 for byte in input.bytes() {
820 hash = hash.wrapping_mul(33).wrapping_add(byte as u64);
822 }
823 hash
824}
825
826fn 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
853fn 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
885fn 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
912fn 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 fill_rect(buffer, buf_width, buf_height, x, y, w, 1, color);
925 if h > 0 {
927 fill_rect(buffer, buf_width, buf_height, x, y + h - 1, w, 1, color);
928 }
929 fill_rect(buffer, buf_width, buf_height, x, y, 1, h, color);
931 if w > 0 {
933 fill_rect(buffer, buf_width, buf_height, x + w - 1, y, 1, h, color);
934 }
935}
936
937fn 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 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
984pub fn format_lockout_time(ticks_remaining: u64) -> u32 {
990 ticks_remaining.div_ceil(1000) as u32
992}
993
994fn format_attempts_remaining(remaining: u32, buf: &mut [u8]) -> usize {
997 let mut pos = 0;
998
999 pos += write_u32_to_buf(remaining, &mut buf[pos..]);
1001
1002 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
1015fn 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
1034fn 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 let mut digits = [0u8; 10]; 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 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
1063fn is_printable(ch: u8) -> bool {
1065 (0x20..=0x7E).contains(&ch)
1066}