diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d37d43..da3d285 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: AWEOS CI Pipeline on: push: - branches: [ main, master ] + branches: [ main, master, 'feat/**' ] pull_request: branches: [ main, master ] @@ -16,31 +16,58 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Rust Toolchain run: | rustup show rustup target add x86_64-unknown-none rustup target add x86_64-unknown-uefi - - name: Check Workspace Compilation run: cargo check --workspace --all-targets - - name: Run Workspace Tests - run: cargo test --workspace - + run: cargo test --workspace --all-targets - name: Validate Formatting run: cargo fmt --all --check - + - name: Validate Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + - name: Install QEMU and Xvfb + run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 xvfb - name: Build Boot Images run: ./scripts/build-images.sh - - name: Verify Image Artifacts run: | test -f dist/aweos-x86_64.iso test -f dist/aweos-x86_64.img test -f dist/aweos-uefi.img test -f dist/aweos-bios.img - test -f build/aweos-x86_64.iso - test -f build/aweos-x86_64.img - echo "All boot artifacts successfully generated and verified." + - name: Headless QEMU Smoke Test + timeout-minutes: 3 + run: | + qemu-system-x86_64 --version + set +e + timeout 30s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 + status=$? + set -e + cat qemu-serial.log + test $status -eq 124 || test $status -eq 0 + grep -q "AWEOS: kernel state = RUNNING" qemu-serial.log + grep -q "AWEOS: entering Ring 3" qemu-serial.log + grep -q "AWEOS: persistent initd/appd/storage/UI runtime control plane started" qemu-serial.log + - name: Graphical QEMU Startup Smoke Test + timeout-minutes: 3 + run: | + set +e + timeout 30s xvfb-run -a qemu-system-x86_64 -display gtk -serial file:qemu-graphical.log -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos + status=$? + set -e + cat qemu-graphical.log || true + test $status -eq 124 || test $status -eq 0 + grep -q "AWEOS: kernel state = RUNNING" qemu-graphical.log + - name: Upload QEMU Evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: aweos-qemu-evidence + path: | + qemu-serial.log + qemu-graphical.log + if-no-files-found: warn diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1afa61b..1d568bb 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -10,3 +10,9 @@ path = "src/lib.rs" [dependencies] awe-boot-protocol = { workspace = true } awe-ayui = { workspace = true } +awe-initd = { workspace = true } +awe-appd = { workspace = true } +awe-driverd = { workspace = true } +awe-netd = { workspace = true } +awe-storaged = { workspace = true } +awe-securityd = { workspace = true } diff --git a/kernel/src/arch/x86_64/input.rs b/kernel/src/arch/x86_64/input.rs new file mode 100644 index 0000000..2056df6 --- /dev/null +++ b/kernel/src/arch/x86_64/input.rs @@ -0,0 +1,104 @@ +#![no_std] + +use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, AtomicU64, Ordering}; +use crate::drivers::{KeyCode, Ps2Event}; + +pub const CAPACITY: usize = 128; +static HEAD: AtomicUsize = AtomicUsize::new(0); +static TAIL: AtomicUsize = AtomicUsize::new(0); +static QUEUE: [AtomicU64; CAPACITY] = [const { AtomicU64::new(0) }; CAPACITY]; +static KB_EXTENDED: AtomicBool = AtomicBool::new(false); +static KB_BREAK: AtomicBool = AtomicBool::new(false); +static MOUSE_INDEX: AtomicU8 = AtomicU8::new(0); +static MOUSE_0: AtomicU8 = AtomicU8::new(0); +static MOUSE_1: AtomicU8 = AtomicU8::new(0); +static MOUSE_2: AtomicU8 = AtomicU8::new(0); + +fn enqueue(encoded: u64) -> bool { + let tail = TAIL.load(Ordering::Relaxed); + let next = (tail + 1) % CAPACITY; + if next == HEAD.load(Ordering::Acquire) { return false; } + QUEUE[tail].store(encoded, Ordering::Release); + TAIL.store(next, Ordering::Release); + true +} + +pub fn dequeue() -> Option { + let head = HEAD.load(Ordering::Relaxed); + if head == TAIL.load(Ordering::Acquire) { return None; } + let encoded = QUEUE[head].load(Ordering::Acquire); + HEAD.store((head + 1) % CAPACITY, Ordering::Release); + if encoded >> 63 == 0 { + let code = (encoded & 0xffff) as u8; + let pressed = ((encoded >> 16) & 1) != 0; + Some(Ps2Event::Key { code: map_key(code), pressed }) + } else { + let dx = (encoded as u16) as i16; + let dy = ((encoded >> 16) as u16) as i16; + let buttons = ((encoded >> 32) & 0xff) as u8; + Some(Ps2Event::Pointer { dx, dy, buttons }) + } +} + +fn map_key(code: u8) -> KeyCode { + match code { + 0x01 => KeyCode::Escape, 0x0D => KeyCode::Tab, 0x1C => KeyCode::Enter, + 0x0E => KeyCode::Backspace, 0x39 => KeyCode::Space, 0x4B => KeyCode::Left, + 0x4D => KeyCode::Right, 0x48 => KeyCode::Up, 0x50 => KeyCode::Down, + other => KeyCode::Unknown(other), + } +} + +pub fn irq_keyboard_byte(byte: u8) -> bool { + match byte { + 0xE0 => { KB_EXTENDED.store(true, Ordering::Relaxed); false } + 0xF0 => { KB_BREAK.store(true, Ordering::Relaxed); false } + code => { + let extended = KB_EXTENDED.swap(false, Ordering::Relaxed); + let pressed = !KB_BREAK.swap(false, Ordering::Relaxed); + let code = if extended { code } else { code }; + let mut encoded = code as u64; + if pressed { encoded |= 1 << 16; } + enqueue(encoded) + } + } +} + +pub fn irq_mouse_byte(byte: u8) -> bool { + let index = MOUSE_INDEX.load(Ordering::Relaxed) as usize; + if index == 0 && byte & 0x08 == 0 { return false; } + match index { + 0 => MOUSE_0.store(byte, Ordering::Relaxed), + 1 => MOUSE_1.store(byte, Ordering::Relaxed), + 2 => MOUSE_2.store(byte, Ordering::Relaxed), + _ => return false, + } + if index < 2 { MOUSE_INDEX.store((index + 1) as u8, Ordering::Relaxed); return false; } + MOUSE_INDEX.store(0, Ordering::Relaxed); + let flags = MOUSE_0.load(Ordering::Relaxed); + if flags & 0xC0 != 0 { return false; } + let raw_x = MOUSE_1.load(Ordering::Relaxed) as i16; + let raw_y = MOUSE_2.load(Ordering::Relaxed) as i16; + let dx = if flags & 0x10 != 0 { raw_x - 256 } else { raw_x }; + let dy_raw = if flags & 0x20 != 0 { raw_y - 256 } else { raw_y }; + let dy = -dy_raw; + let encoded = (dx as u16 as u64) | ((dy as u16 as u64) << 16) | (((flags & 7) as u64) << 32) | (1u64 << 63); + enqueue(encoded) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn keyboard_irq_generates_decoded_event() { + assert!(irq_keyboard_byte(0x1C)); + assert_eq!(dequeue(), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: true })); + irq_keyboard_byte(0xF0); irq_keyboard_byte(0x1C); + assert_eq!(dequeue(), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: false })); + } + #[test] + fn mouse_irq_generates_signed_event() { + assert!(!irq_mouse_byte(0x09)); assert!(!irq_mouse_byte(0xFE)); assert!(irq_mouse_byte(0x02)); + assert_eq!(dequeue(), Some(Ps2Event::Pointer { dx: -2, dy: -2, buttons: 1 })); + } +} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index a112dad..583acc1 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -3,51 +3,36 @@ use super::idt::Idt; pub const TIMER_VECTOR: u8 = 32; +pub const KEYBOARD_VECTOR: u8 = 33; +pub const MOUSE_VECTOR: u8 = 44; -/// Remaps the 8259 PIC so IRQs 0..15 land on IDT vectors 32..47. pub unsafe fn init_pic() { unsafe { use super::{io_in8, io_out8}; - - // Save masks let mask1 = io_in8(0x21); let mask2 = io_in8(0xA1); - - // ICW1: Start initialization in cascade mode io_out8(0x20, 0x11); io_out8(0xA0, 0x11); - - // ICW2: Vector offsets (32 for master, 40 for slave) io_out8(0x21, 0x20); io_out8(0xA1, 0x28); - - // ICW3: Cascade setup io_out8(0x21, 0x04); io_out8(0xA1, 0x02); - - // ICW4: 8086 mode io_out8(0x21, 0x01); io_out8(0xA1, 0x01); - - // Restore masks (or unmask IRQ0 timer & IRQ1 keyboard) - io_out8(0x21, mask1 & !0x01); // unmask IRQ0 (timer) - io_out8(0xA1, mask2); + // Enable IRQ0 timer, IRQ1 keyboard, and IRQ2 cascade for slave IRQ12 mouse. + io_out8(0x21, mask1 & !0x07); + io_out8(0xA1, mask2 & !0x10); } } pub unsafe fn pic_send_eoi(irq: u8) { unsafe { use super::io_out8; - if irq >= 8 { - io_out8(0xA0, 0x20); - } + if irq >= 8 { io_out8(0xA0, 0x20); } io_out8(0x20, 0x20); } } -/// Installs the minimal early interrupt surface. The timer handler remains an -/// explicit ABI boundary so scheduler code can be attached without changing -/// IDT construction. pub fn install_early_interrupts(idt: &mut Idt, code_selector: u16, timer_handler: u64) { idt.set_handler(TIMER_VECTOR, timer_handler, code_selector); } @@ -56,12 +41,17 @@ pub fn install_early_interrupts(idt: &mut Idt, code_selector: u16, timer_handler mod tests { use super::*; extern "C" fn timer() {} - + #[test] + fn vectors_match_pic_remap() { + assert_eq!(TIMER_VECTOR, 32); + assert_eq!(KEYBOARD_VECTOR, 33); + assert_eq!(MOUSE_VECTOR, 44); + } #[test] fn timer_vector_is_installed() { let mut idt = Idt::new(); install_early_interrupts(&mut idt, 0x08, timer as *const () as usize as u64); assert!(idt.is_present(TIMER_VECTOR)); - assert!(!idt.is_present(33)); + assert!(!idt.is_present(KEYBOARD_VECTOR)); } -} +} \ No newline at end of file diff --git a/kernel/src/arch/x86_64/isr_stubs.rs b/kernel/src/arch/x86_64/isr_stubs.rs index c69567a..abded38 100644 --- a/kernel/src/arch/x86_64/isr_stubs.rs +++ b/kernel/src/arch/x86_64/isr_stubs.rs @@ -8,6 +8,8 @@ use core::arch::global_asm; use core::sync::atomic::{AtomicU64, Ordering}; static TIMER_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); +static KEYBOARD_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); +static MOUSE_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); global_asm!( r#".intel_syntax noprefix @@ -30,10 +32,8 @@ awe_isr_common: push r13 push r14 push r15 - mov rdi, rsp call awe_interrupt_handler - pop r15 pop r14 pop r13 @@ -49,7 +49,6 @@ awe_isr_common: pop rcx pop rbx pop rax - add rsp, 16 iretq @@ -91,35 +90,19 @@ ISR_NOERR 19 ISR_NOERR 20 ISR_NOERR 32 ISR_NOERR 33 +ISR_NOERR 44 .att_syntax prefix "# ); unsafe extern "C" { - pub fn awe_isr_0(); - pub fn awe_isr_1(); - pub fn awe_isr_2(); - pub fn awe_isr_3(); - pub fn awe_isr_4(); - pub fn awe_isr_5(); - pub fn awe_isr_6(); - pub fn awe_isr_7(); - pub fn awe_isr_8(); - pub fn awe_isr_9(); - pub fn awe_isr_10(); - pub fn awe_isr_11(); - pub fn awe_isr_12(); - pub fn awe_isr_13(); - pub fn awe_isr_14(); - pub fn awe_isr_15(); - pub fn awe_isr_16(); - pub fn awe_isr_17(); - pub fn awe_isr_18(); - pub fn awe_isr_19(); - pub fn awe_isr_20(); - pub fn awe_isr_32(); - pub fn awe_isr_33(); + pub fn awe_isr_0(); pub fn awe_isr_1(); pub fn awe_isr_2(); pub fn awe_isr_3(); + pub fn awe_isr_4(); pub fn awe_isr_5(); pub fn awe_isr_6(); pub fn awe_isr_7(); + pub fn awe_isr_8(); pub fn awe_isr_9(); pub fn awe_isr_10(); pub fn awe_isr_11(); + pub fn awe_isr_12(); pub fn awe_isr_13(); pub fn awe_isr_14(); pub fn awe_isr_15(); + pub fn awe_isr_16(); pub fn awe_isr_17(); pub fn awe_isr_18(); pub fn awe_isr_19(); + pub fn awe_isr_20(); pub fn awe_isr_32(); pub fn awe_isr_33(); pub fn awe_isr_44(); } pub fn init_idt_stubs(idt: &mut super::idt::Idt, cs: u16) { @@ -146,53 +129,44 @@ pub fn init_idt_stubs(idt: &mut super::idt::Idt, cs: u16) { idt.set_handler(20, awe_isr_20 as *const () as usize as u64, cs); idt.set_handler(32, awe_isr_32 as *const () as usize as u64, cs); idt.set_handler(33, awe_isr_33 as *const () as usize as u64, cs); + idt.set_handler(44, awe_isr_44 as *const () as usize as u64, cs); } fn print_u64_hex(mut val: u64) { let hex = b"0123456789ABCDEF"; - let mut buf = [b'0'; 18]; - buf[0] = b'0'; - buf[1] = b'x'; - for i in (2..18).rev() { - buf[i] = hex[(val & 0xF) as usize]; - val >>= 4; - } - for b in buf { - super::serial_write_byte(b); - } + let mut buf = [b'0'; 18]; buf[0] = b'0'; buf[1] = b'x'; + for i in (2..18).rev() { buf[i] = hex[(val & 0xF) as usize]; val >>= 4; } + for b in buf { super::serial_write_byte(b); } } #[unsafe(no_mangle)] pub extern "C" fn awe_interrupt_handler(frame: &mut InterruptFrame) { let vector = frame.vector as u8; if vector == 32 { - unsafe { - pic_send_eoi(0); - } + unsafe { pic_send_eoi(0); } TIMER_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); super::timer::interrupt_tick(); } else if vector == 33 { - unsafe { - let _scancode = super::io_in8(0x60); - pic_send_eoi(1); - } + let scancode = unsafe { super::io_in8(0x60) }; + super::input::irq_keyboard_byte(scancode); + KEYBOARD_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); + unsafe { pic_send_eoi(1); } + } else if vector == 44 { + let byte = unsafe { super::io_in8(0x60) }; + super::input::irq_mouse_byte(byte); + MOUSE_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); + unsafe { pic_send_eoi(12); } } else { - serial_write_str("AWEOS EXCEPTION vector="); - print_u64_hex(frame.vector); - serial_write_str(" err="); - print_u64_hex(frame.error_code); - serial_write_str(" rip="); - print_u64_hex(frame.rip); - serial_write_str(" cs="); - print_u64_hex(frame.cs); - serial_write_str(" rsp="); - print_u64_hex(frame.rsp); - serial_write_str(" ss="); - print_u64_hex(frame.ss); + serial_write_str("AWEOS EXCEPTION vector="); print_u64_hex(frame.vector); + serial_write_str(" err="); print_u64_hex(frame.error_code); + serial_write_str(" rip="); print_u64_hex(frame.rip); + serial_write_str(" cs="); print_u64_hex(frame.cs); + serial_write_str(" rsp="); print_u64_hex(frame.rsp); + serial_write_str(" ss="); print_u64_hex(frame.ss); serial_write_str("\r\n"); } } -pub fn timer_irq_count() -> u64 { - TIMER_IRQ_COUNT.load(Ordering::Acquire) -} +pub fn timer_irq_count() -> u64 { TIMER_IRQ_COUNT.load(Ordering::Acquire) } +pub fn keyboard_irq_count() -> u64 { KEYBOARD_IRQ_COUNT.load(Ordering::Acquire) } +pub fn mouse_irq_count() -> u64 { MOUSE_IRQ_COUNT.load(Ordering::Acquire) } diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 78869a8..c283ab8 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -4,6 +4,7 @@ pub mod boot; pub mod entry; pub mod gdt; pub mod idt; +pub mod input; pub mod interrupts; pub mod isr; pub mod isr_stubs; @@ -14,89 +15,33 @@ pub const PAGE_SIZE: u64 = 4096; #[inline(always)] pub unsafe fn read_cr3() -> u64 { - unsafe { - let value: u64; - core::arch::asm!("mov {}, cr3", out(reg) value, options(nomem, nostack, preserves_flags)); - value - } + unsafe { let value: u64; core::arch::asm!("mov {}, cr3", out(reg) value, options(nomem, nostack, preserves_flags)); value } } - #[inline(always)] pub unsafe fn io_out32(port: u16, value: u32) { - unsafe { - core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); - } + unsafe { core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); } } - #[inline(always)] pub unsafe fn io_in32(port: u16) -> u32 { - unsafe { - let value: u32; - core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack, preserves_flags)); - value - } + unsafe { let value: u32; core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack, preserves_flags)); value } } - pub fn serial_write_byte(byte: u8) { - unsafe { - while (io_in8(0x3FD) & 0x20) == 0 {} - io_out8(0x3F8, byte); - } -} - -pub fn serial_write_str(s: &str) { - for b in s.bytes() { - serial_write_byte(b); - } + unsafe { while (io_in8(0x3FD) & 0x20) == 0 {} io_out8(0x3F8, byte); } } - +pub fn serial_write_str(s: &str) { for b in s.bytes() { serial_write_byte(b); } } #[inline(always)] -pub unsafe fn write_cr3(value: u64) { - unsafe { - core::arch::asm!("mov cr3, {}", in(reg) value, options(nostack, preserves_flags)); - } -} - +pub unsafe fn write_cr3(value: u64) { unsafe { core::arch::asm!("mov cr3, {}", in(reg) value, options(nostack, preserves_flags)); } } #[inline(always)] pub unsafe fn read_msr(msr: u32) -> u64 { - unsafe { - let low: u32; - let high: u32; - core::arch::asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high, options(nomem, nostack, preserves_flags)); - ((high as u64) << 32) | (low as u64) - } + unsafe { let low: u32; let high: u32; core::arch::asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high, options(nomem, nostack, preserves_flags)); ((high as u64) << 32) | low as u64 } } - #[inline(always)] pub unsafe fn write_msr(msr: u32, value: u64) { - unsafe { - let low = value as u32; - let high = (value >> 32) as u32; - core::arch::asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high, options(nomem, nostack, preserves_flags)); - } + unsafe { let low = value as u32; let high = (value >> 32) as u32; core::arch::asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high, options(nomem, nostack, preserves_flags)); } } - #[inline(always)] -pub unsafe fn read_rflags() -> u64 { - unsafe { - let value: u64; - core::arch::asm!("pushfq; pop {}", out(reg) value, options(nomem, preserves_flags)); - value - } -} - +pub unsafe fn read_rflags() -> u64 { unsafe { let value: u64; core::arch::asm!("pushfq; pop {}", out(reg) value, options(nomem, preserves_flags)); value } } #[inline(always)] -pub unsafe fn io_out8(port: u16, value: u8) { - unsafe { - core::arch::asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); - } -} - +pub unsafe fn io_out8(port: u16, value: u8) { unsafe { core::arch::asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); } } #[inline(always)] -pub unsafe fn io_in8(port: u16) -> u8 { - unsafe { - let value: u8; - core::arch::asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack, preserves_flags)); - value - } -} +pub unsafe fn io_in8(port: u16) -> u8 { unsafe { let value: u8; core::arch::asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack, preserves_flags)); value } } diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs index 36f5820..970367f 100644 --- a/kernel/src/drivers/mod.rs +++ b/kernel/src/drivers/mod.rs @@ -38,6 +38,7 @@ pub mod linux_transaction_orchestrator; pub mod pci; pub mod pci_virtio_probe; pub mod product_gate; +pub mod ps2; pub mod universal; pub mod virtio; pub mod virtio_block; @@ -45,35 +46,19 @@ pub mod virtio_pci; pub mod windows; pub use android::AndroidLayer; pub use bus::{DeviceId, DeviceKind, DriverBus}; -pub use compat::{ - CompatibilityRegistry, DriverManifest, DriverSource, bind_compatible_driver, validate_contract, -}; +pub use compat::{CompatibilityRegistry, DriverManifest, DriverSource, bind_compatible_driver, validate_contract}; pub use contract::{DeviceContract, DmaPolicy, InterruptMode, MmioRegion}; -pub use core::{ - AdapterState, AndroidDriverAdapter, CoreError, DriverAdapter, DriverIdentity, DriverSlot, - HardwareAbstraction, HardwareInfo, LinuxDriverAdapter, WindowsDriverAdapter, -}; +pub use core::{AdapterState, AndroidDriverAdapter, CoreError, DriverAdapter, DriverIdentity, DriverSlot, HardwareAbstraction, HardwareInfo, LinuxDriverAdapter, WindowsDriverAdapter}; pub use hal_registers::RegisterBank; pub use installer::{InstallError, InstallPlan, InstallerPackage, PackageFormat, plan_install}; pub use learning::{DriverExperience, ExperienceDb, ProbeOutcome}; pub use linux::LinuxLayer; -pub use linux_activation::{ - ActivationError as LinuxActivationError, build_activation_order, validate_activation_order, -}; -pub use linux_activation_rollback::{ - RollbackError as LinuxRollbackError, activation_failed, build_rollback_order, -}; -pub use linux_dependency::{ - Dependency as LinuxDependency, DependencyError as LinuxDependencyError, - validate as validate_linux_dependencies, -}; -pub use linux_dependency_graph::{ - GraphError as LinuxGraphError, validate_graph as validate_linux_dependency_graph, -}; +pub use linux_activation::{ActivationError as LinuxActivationError, build_activation_order, validate_activation_order}; +pub use linux_activation_rollback::{RollbackError as LinuxRollbackError, activation_failed, build_rollback_order}; +pub use linux_dependency::{Dependency as LinuxDependency, DependencyError as LinuxDependencyError, validate as validate_linux_dependencies}; +pub use linux_dependency_graph::{GraphError as LinuxGraphError, validate_graph as validate_linux_dependency_graph}; pub use linux_dependency_multi_instance::{DependencyMultiError, DependencyMultiInstanceManager}; -pub use linux_dependency_order::{ - OrderError as LinuxDependencyOrderError, topological_order as linux_dependency_order, -}; +pub use linux_dependency_order::{OrderError as LinuxDependencyOrderError, topological_order as linux_dependency_order}; pub use linux_driver_execution_guard::{ExecutionGuard, ExecutionGuardError}; pub use linux_driver_health::{DriverHealth, DriverHealthMonitor, HealthError, HealthState}; pub use linux_driver_ops::{DriverLifecycle, DriverOp, DriverOpError, DriverState}; @@ -84,10 +69,7 @@ pub use linux_fault_impact::{FaultImpact, FaultImpactError}; pub use linux_fault_recovery::{FaultRecovery, RecoveryError}; pub use linux_install::{InstallError as LinuxInstallError, InstallPlan as LinuxInstallPlan, plan}; pub use linux_multi_instance::{DriverInstance, MultiInstanceError, MultiInstanceManager}; -pub use linux_package::{ - LDRIVER_MAGIC, LinuxPackageError, LinuxPackageHeader, MAX_PAYLOAD, prepare_probe, - validate_package, -}; +pub use linux_package::{LDRIVER_MAGIC, LinuxPackageError, LinuxPackageHeader, MAX_PAYLOAD, prepare_probe, validate_package}; pub use linux_recovery_pipeline::{RecoveryPipeline, RecoveryPipelineError, RecoveryReport}; pub use linux_resolver::{LinuxCandidate, ResolveError, resolve}; pub use linux_resource_manager::{Resource, ResourceError, ResourceKind, ResourceManager}; @@ -95,34 +77,15 @@ pub use linux_resource_transaction::{ResourceTransaction, ResourceTransactionErr pub use linux_runtime::{LinuxDriverDescriptor, LinuxRuntime, LinuxRuntimeError}; pub use linux_transaction::{DriverTransaction, TransactionError, TransactionState}; pub use linux_transaction_bridge::{BridgeError as LinuxBridgeError, TransactionBridge}; -pub use linux_transaction_graph::{ - GraphTransactionError as LinuxGraphTransactionError, prepare_graph_guarded, -}; -pub use linux_transaction_guard::{ - GuardError as LinuxGuardError, install_plan_guarded, prepare_guarded, -}; -pub use linux_transaction_orchestrator::{ - ActivationOrchestrator, OrchestratorError as LinuxOrchestratorError, - OrchestratorState as LinuxOrchestratorState, -}; -pub use pci::{ - ConfigSpace, Enumerator as PciEnumerator, MAX_PCI_FUNCTIONS, PciError as PciEnumerationError, - PciFunction, -}; -pub use pci_virtio_probe::{ - MAX_VIRTIO_PROBES, ProbeError as VirtioProbeError, VirtioDeviceKind, VirtioPciProbe, -}; +pub use linux_transaction_graph::{GraphTransactionError as LinuxGraphTransactionError, prepare_graph_guarded}; +pub use linux_transaction_guard::{GuardError as LinuxGuardError, install_plan_guarded, prepare_guarded}; +pub use linux_transaction_orchestrator::{ActivationOrchestrator, OrchestratorError as LinuxOrchestratorError, OrchestratorState as LinuxOrchestratorState}; +pub use pci::{ConfigSpace, Enumerator as PciEnumerator, MAX_PCI_FUNCTIONS, PciError as PciEnumerationError, PciFunction}; +pub use pci_virtio_probe::{MAX_VIRTIO_PROBES, ProbeError as VirtioProbeError, VirtioDeviceKind, VirtioPciProbe}; pub use product_gate::{ProductGate, ProductGateError}; -pub use universal::{ - DriverAbi, DriverAction, DriverError, DriverOs, DriverRequest, DriverResult, validate_request, -}; -pub use virtio::{ - DESC_INDIRECT, DESC_NEXT, DESC_WRITE, VirtioDescriptor, VirtioDevice, VirtioError, - VirtioFeatures, VirtioQueueConfig, VirtioSplitQueue, validate_chain, -}; -pub use virtio_block::{ - BlockCompletion, BlockError, BlockOp, BlockRequest, MAX_REQUEST_SECTORS, SECTOR_SIZE, - VirtioBlockConfig, VirtioBlockQueue, -}; +pub use ps2::{Controller as Ps2Controller, EventQueue as Ps2EventQueue, KeyCode, KeyCode as Ps2KeyCode, MouseDecoder as Ps2MouseDecoder, KeyboardDecoder as Ps2KeyboardDecoder, Ps2Error, Ps2Event}; +pub use universal::{DriverAbi, DriverAction, DriverError, DriverOs, DriverRequest, DriverResult, validate_request}; +pub use virtio::{DESC_INDIRECT, DESC_NEXT, DESC_WRITE, VirtioDescriptor, VirtioDevice, VirtioError, VirtioFeatures, VirtioQueueConfig, VirtioSplitQueue, validate_chain}; +pub use virtio_block::{BlockCompletion, BlockError, BlockOp, BlockRequest, MAX_REQUEST_SECTORS, SECTOR_SIZE, VirtioBlockConfig, VirtioBlockQueue}; pub use virtio_pci::{Bar, PciError, VIRTIO_VENDOR_ID, VirtioPciCapabilities, VirtioPciTransport}; pub use windows::WindowsLayer; diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs new file mode 100644 index 0000000..6a249b0 --- /dev/null +++ b/kernel/src/drivers/ps2.rs @@ -0,0 +1,152 @@ +#![no_std] + +#[cfg(target_arch = "x86_64")] +use crate::arch::x86_64::{io_in8, io_out8}; + +pub const DATA_PORT: u16 = 0x60; +pub const STATUS_PORT: u16 = 0x64; +pub const COMMAND_PORT: u16 = 0x64; +pub const MAX_EVENTS: usize = 128; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Ps2Error { Timeout, Controller, QueueFull, InvalidPacket, Unsupported } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum KeyCode { Escape, Enter, Backspace, Tab, Space, Left, Right, Up, Down, Character(u8), Unknown(u8) } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Ps2Event { Key { code: KeyCode, pressed: bool }, Pointer { dx: i16, dy: i16, buttons: u8 } } + +pub struct EventQueue { slots: [Option; MAX_EVENTS], head: usize, len: usize } +impl EventQueue { + pub const fn new() -> Self { Self { slots: [None; MAX_EVENTS], head: 0, len: 0 } } + pub const fn len(&self) -> usize { self.len } + pub const fn is_empty(&self) -> bool { self.len == 0 } + pub fn push(&mut self, event: Ps2Event) -> Result<(), Ps2Error> { + if self.len == MAX_EVENTS { return Err(Ps2Error::QueueFull); } + let index = (self.head + self.len) % MAX_EVENTS; + self.slots[index] = Some(event); self.len += 1; Ok(()) + } + pub fn pop(&mut self) -> Option { + if self.len == 0 { return None; } + let event = self.slots[self.head].take(); + self.head = (self.head + 1) % MAX_EVENTS; self.len -= 1; event + } +} +impl Default for EventQueue { fn default() -> Self { Self::new() } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct KeyboardDecoder { extended: bool, break_code: bool } +impl KeyboardDecoder { + pub const fn new() -> Self { Self { extended: false, break_code: false } } + pub fn feed(&mut self, byte: u8) -> Option { + match byte { + 0xE0 => { self.extended = true; None } + 0xF0 => { self.break_code = true; None } + code => { + let pressed = !self.break_code; + let mapped = if self.extended { + match code { 0x4B => KeyCode::Left, 0x4D => KeyCode::Right, 0x48 => KeyCode::Up, 0x50 => KeyCode::Down, other => KeyCode::Unknown(other) } + } else { + match code { 0x01 => KeyCode::Escape, 0x0D => KeyCode::Tab, 0x1C => KeyCode::Enter, 0x0E => KeyCode::Backspace, 0x39 => KeyCode::Space, 0x10..=0x35 => KeyCode::Character(code), other => KeyCode::Unknown(other) } + }; + self.extended = false; self.break_code = false; + Some(Ps2Event::Key { code: mapped, pressed }) + } + } + } +} +impl Default for KeyboardDecoder { fn default() -> Self { Self::new() } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MouseDecoder { packet: [u8; 3], index: usize } +impl MouseDecoder { + pub const fn new() -> Self { Self { packet: [0; 3], index: 0 } } + pub fn feed(&mut self, byte: u8) -> Result, Ps2Error> { + if self.index == 0 && byte & 0x08 == 0 { return Ok(None); } + self.packet[self.index] = byte; self.index += 1; + if self.index < 3 { return Ok(None); } + self.index = 0; + let flags = self.packet[0]; + if flags & 0xC0 != 0 { return Err(Ps2Error::InvalidPacket); } + let x = self.packet[1] as i16; let y = self.packet[2] as i16; + let dx = if flags & 0x10 != 0 { x - 256 } else { x }; + let dy_raw = if flags & 0x20 != 0 { y - 256 } else { y }; + Ok(Some(Ps2Event::Pointer { dx, dy: -dy_raw, buttons: flags & 0x07 })) + } +} +impl Default for MouseDecoder { fn default() -> Self { Self::new() } } + +pub struct Controller { pub events: EventQueue, pub keyboard: KeyboardDecoder, pub mouse: MouseDecoder, pub initialized: bool } +impl Controller { + pub const fn new() -> Self { Self { events: EventQueue::new(), keyboard: KeyboardDecoder::new(), mouse: MouseDecoder::new(), initialized: false } } + + #[cfg(target_arch = "x86_64")] + pub unsafe fn init(&mut self) -> Result<(), Ps2Error> { + const LIMIT: usize = 100_000; + let mut spins = 0; + while unsafe { io_in8(STATUS_PORT) } & 0x01 != 0 { + let _ = unsafe { io_in8(DATA_PORT) }; + spins += 1; + if spins == LIMIT { return Err(Ps2Error::Timeout); } + } + unsafe { io_out8(COMMAND_PORT, 0xAE); } + self.initialized = true; Ok(()) + } + + #[cfg(not(target_arch = "x86_64"))] + pub unsafe fn init(&mut self) -> Result<(), Ps2Error> { Err(Ps2Error::Unsupported) } + + #[cfg(target_arch = "x86_64")] + pub unsafe fn poll(&mut self, max_bytes: usize) -> Result { + if !self.initialized { return Err(Ps2Error::Controller); } + let mut read = 0; + while read < max_bytes { + let status = unsafe { io_in8(STATUS_PORT) }; + if status & 0x01 == 0 { break; } + let byte = unsafe { io_in8(DATA_PORT) }; + if status & 0x20 != 0 { + if let Some(event) = self.mouse.feed(byte)? { self.events.push(event)?; } + } else if let Some(event) = self.keyboard.feed(byte) { self.events.push(event)?; } + read += 1; + } + Ok(read) + } + + #[cfg(not(target_arch = "x86_64"))] + pub unsafe fn poll(&mut self, _max_bytes: usize) -> Result { Err(Ps2Error::Unsupported) } +} +impl Default for Controller { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn keyboard_decodes_press_release_and_extended() { + let mut d = KeyboardDecoder::new(); + assert_eq!(d.feed(0x1C), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: true })); + assert_eq!(d.feed(0xF0), None); + assert_eq!(d.feed(0x1C), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: false })); + assert_eq!(d.feed(0xE0), None); + assert_eq!(d.feed(0x4B), Some(Ps2Event::Key { code: KeyCode::Left, pressed: true })); + } + #[test] + fn mouse_decodes_signed_motion() { + let mut d = MouseDecoder::new(); + assert_eq!(d.feed(0x09).unwrap(), None); + assert_eq!(d.feed(0xFE).unwrap(), None); + assert_eq!(d.feed(0x02).unwrap(), Some(Ps2Event::Pointer { dx: -2, dy: -2, buttons: 1 })); + } + #[test] + fn malformed_mouse_packet_is_rejected() { + let mut d = MouseDecoder::new(); + assert_eq!(d.feed(0xC8).unwrap_err(), Ps2Error::InvalidPacket); + } + #[test] + fn queue_is_fifo_and_bounded() { + let mut q = EventQueue::new(); + q.push(Ps2Event::Key { code: KeyCode::Escape, pressed: true }).unwrap(); + assert_eq!(q.pop(), Some(Ps2Event::Key { code: KeyCode::Escape, pressed: true })); + assert!(q.pop().is_none()); + } +} \ No newline at end of file diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index 29dcd8b..43daba1 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -1,295 +1,103 @@ #![no_std] use awe_boot_protocol::{validate, BootInfo}; - use crate::boot_phase::{BootPhase, BootProgress}; use crate::memory::PhysicalFrameAllocator; +use crate::runtime::SystemRuntime; +#[cfg(all(target_arch = "x86_64", target_os = "none"))] +use crate::runtime::FramebufferInfo; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum KernelBootStatus { - Ready = 0, - InvalidBootInfo = 1, - UnsupportedArchitecture = 2, - NoCpu = 3, - NoUsableMemory = 4, -} - -pub struct KernelContext { - progress: BootProgress, -} - -impl Default for KernelContext { - fn default() -> Self { - Self::new() - } -} +pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4 } +pub struct KernelContext { progress: BootProgress } +impl Default for KernelContext { fn default() -> Self { Self::new() } } impl KernelContext { - pub const fn new() -> Self { - Self { - progress: BootProgress::new(), - } - } - pub const fn phase(&self) -> BootPhase { - self.progress.phase() - } - pub fn advance(&mut self) -> bool { - self.progress.advance() - } + pub const fn new() -> Self { Self { progress: BootProgress::new() } } + pub const fn phase(&self) -> BootPhase { self.progress.phase() } + pub fn advance(&mut self) -> bool { self.progress.advance() } } -/// Stable entry contract between AWE Loader and CellKernel. -pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { - if !validate(info) { - return KernelBootStatus::InvalidBootInfo; - } - if !info.architecture.is_supported() { - return KernelBootStatus::UnsupportedArchitecture; - } - if info.cpu_count == 0 { - return KernelBootStatus::NoCpu; - } - if info.memory_region_count == 0 || info.memory_regions.is_null() { - return KernelBootStatus::NoUsableMemory; - } +static mut SYSTEM_RUNTIME: SystemRuntime = SystemRuntime::new(); +pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { + if !validate(info) { return KernelBootStatus::InvalidBootInfo; } + if !info.architecture.is_supported() { return KernelBootStatus::UnsupportedArchitecture; } + if info.cpu_count == 0 { return KernelBootStatus::NoCpu; } + if info.memory_region_count == 0 || info.memory_regions.is_null() { return KernelBootStatus::NoUsableMemory; } let mut frames = unsafe { PhysicalFrameAllocator::from_boot_info(info) }; - if frames.allocate().is_none() { - return KernelBootStatus::NoUsableMemory; - } + if frames.allocate().is_none() { return KernelBootStatus::NoUsableMemory; } #[cfg(all(target_arch = "x86_64", target_os = "none"))] { - use crate::arch::x86_64::gdt::init_gdt; + use crate::arch::x86_64::gdt::{init_gdt, KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR}; + use crate::arch::x86_64::idt::IDT; use crate::arch::x86_64::interrupts::init_pic; + use crate::arch::x86_64::isr_stubs::init_idt_stubs; use crate::arch::x86_64::serial_write_str; use crate::memory::allocator::init_kernel_heap; use crate::platform::pit::Pit; + use crate::syscall::init_msr_syscall; static mut KERNEL_STACK: [u8; 65536] = [0; 65536]; static mut USER_STACK: [u8; 16384] = [0; 16384]; - - let stack_top = core::ptr::addr_of_mut!(KERNEL_STACK) as u64 + 65536; + let kernel_stack_top = core::ptr::addr_of_mut!(KERNEL_STACK) as u64 + 65536; let user_stack_top = core::ptr::addr_of_mut!(USER_STACK) as u64 + 16384; - use crate::arch::x86_64::gdt::{KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR}; - use crate::arch::x86_64::idt::IDT; - use crate::arch::x86_64::isr_stubs::init_idt_stubs; - use crate::syscall::init_msr_syscall; - - init_gdt(stack_top); - serial_write_str("AWEOS: GDT & TSS initialized\r\n"); - - unsafe { - init_msr_syscall( - userspace_entry as *const () as usize as u64, - KERNEL_CODE_SELECTOR, - USER_CODE_SELECTOR, - ); - } - serial_write_str("AWEOS: SYSCALL/SYSRET MSRs initialized\r\n"); - + init_gdt(kernel_stack_top); + unsafe { init_msr_syscall(userspace_entry as *const () as usize as u64, KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR); } let idt_ptr = core::ptr::addr_of_mut!(IDT); - unsafe { - init_idt_stubs(&mut *idt_ptr, KERNEL_CODE_SELECTOR); - (*idt_ptr).load(); - } - serial_write_str("AWEOS: IDT initialized\r\n"); - + unsafe { init_idt_stubs(&mut *idt_ptr, KERNEL_CODE_SELECTOR); (*idt_ptr).load(); } init_kernel_heap(); - serial_write_str("AWEOS: Kernel Heap initialized\r\n"); - - use crate::drivers::pci; - let mut pci_out = [None; 16]; - let mut enumerator = pci::Enumerator::new(pci::PortConfigSpace); - if let Ok(_count) = enumerator.scan_bus(0, &mut pci_out) { - serial_write_str("AWEOS: PCI Bus 0 enumerated\r\n"); - } - - unsafe { - init_pic(); - } - if let Some(pit) = Pit::new(1000) { - unsafe { - pit.program(); - } - } - serial_write_str("AWEOS: Interrupts & PIC/PIT initialized\r\n"); - - serial_write_str("AWEOS: Preemptive Scheduler initialized\r\n"); - serial_write_str("AWEOS: boot protocol validated\r\n"); - serial_write_str("AWEOS: kernel state = RUNNING\r\n"); - serial_write_str("AWEOS: kernel is alive\r\n"); - serial_write_str("AWEOS: Entering Ring 3 Userspace...\r\n"); - unsafe { - enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); + unsafe { init_pic(); } + if let Some(pit) = Pit::new(1000) { unsafe { pit.program(); } } + serial_write_str("AWEOS: hardware/interrupt foundation initialized\r\n"); + + let runtime = unsafe { core::ptr::addr_of_mut!(SYSTEM_RUNTIME).as_mut().unwrap() }; + if info.framebuffer_address != 0 && info.framebuffer_size != 0 { + let fb = FramebufferInfo { address: info.framebuffer_address, size: info.framebuffer_size, width: info.framebuffer_width, height: info.framebuffer_height, pitch: info.framebuffer_pitch, bytes_per_pixel: 4 }; + if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidBootInfo; } + serial_write_str("AWEOS: BootInfo framebuffer accepted by runtime\r\n"); } + if runtime.register_core_services().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.start_core_services().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.mount_core_namespaces(32).is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.admit_core_apps().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.start_core_apps().is_err() { return KernelBootStatus::NoUsableMemory; } + serial_write_str("AWEOS: end-user runtime control plane started\r\n"); + serial_write_str("AWEOS: entering Ring 3 with IOPL=0\r\n"); + unsafe { enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); } } - KernelBootStatus::Ready } #[cfg(all(target_arch = "x86_64", target_os = "none"))] #[unsafe(no_mangle)] pub extern "C" fn userspace_entry() -> ! { - use crate::arch::x86_64::serial_write_str; - use awe_ayui::{AppType, Compositor, Framebuffer, Rect}; - - let msg = b"AWEOS: Ring 3 userspace reached and active!\r\n"; - serial_write_str("AWEOS: Ring 3 userspace reached and active!\r\n"); - - let mut process = crate::process::ProcessDescriptor { - id: crate::process::ProcessId(1), - state: crate::process::ProcessState::Running, - budget: crate::process::ResourceBudget { - cpu_ticks: 1000, - memory_bytes: 1048576, - ipc_messages: 1000, - }, - }; - let mut context = crate::syscall::SyscallContext { - process: &mut process, - }; - - context.dispatch(8, [msg.as_ptr() as u64, msg.len() as u64, 0, 0, 0, 0]); - serial_write_str("AWEOS: Initializing AWE-Compositor & Graphical Desktop Shell...\r\n"); - - let mut compositor = Compositor::new(); - let term_win = compositor - .create_app_window( - Rect { x: 40, y: 40, width: 500, height: 340 }, - AppType::Terminal, - b"AWETerminal v1.0", - ) - .unwrap_or(awe_ayui::WindowId(1)); - let sysinfo_win = compositor - .create_app_window( - Rect { x: 300, y: 100, width: 440, height: 320 }, - AppType::SystemMonitor, - b"System Information", - ) - .unwrap_or(awe_ayui::WindowId(2)); - let about_win = compositor - .create_app_window( - Rect { x: 180, y: 160, width: 360, height: 220 }, - AppType::Generic, - b"About AWEOS", - ) - .unwrap_or(awe_ayui::WindowId(3)); - - compositor.focus(about_win).ok(); - compositor.focus(sysinfo_win).ok(); - compositor.focus(term_win).ok(); - - serial_write_str("AWEOS: Desktop GUI initialized automatically with 3 active windows\r\n"); - - static mut BACK_BUFFER: [u8; 800 * 600 * 4] = [0; 800 * 600 * 4]; - let back_buf = unsafe { &mut *core::ptr::addr_of_mut!(BACK_BUFFER) }; - let mut fb = Framebuffer { - width: 800, - height: 600, - stride: 800, - buffer: back_buf, - gpu_accel: true, - }; - - serial_write_str("AWEOS: Entering interactive AWE-Compositor Main Event Loop...\r\n"); - context.dispatch( - 8, - [ - b"AWEOS: userspace execution completed cleanly!\r\n".as_ptr() as u64, - 48, - 0, - 0, - 0, - 0, - ], - ); - - let mut tick = 0u64; - loop { - tick = tick.wrapping_add(1); - if tick % 50 == 0 { - compositor.render_to_framebuffer(&mut fb); - } - unsafe { - core::arch::asm!("pause"); - } - } + let message = b"AWEOS: Ring 3 active; privileged device access is mediated by syscall/IPC.\r\n"; + let mut process = crate::process::ProcessDescriptor { id: crate::process::ProcessId(1), state: crate::process::ProcessState::Running, budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 } }; + let mut syscall = crate::syscall::SyscallContext { process: &mut process }; + let _ = syscall.dispatch(8, [message.as_ptr() as u64, message.len() as u64, 0, 0, 0, 0]); + loop { let _ = syscall.dispatch(0, [0; 6]); unsafe { core::arch::asm!("pause"); } } } #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - - let user_cs = USER_CODE_SELECTOR as u64; - let user_ss = USER_DATA_SELECTOR as u64; - // IF=1, IOPL=0. Ring 3 must use syscalls/IPC for privileged operations. - let rflags = 0x0202u64; - - unsafe { - core::arch::asm!( - "push {0}", - "push {1}", - "push {2}", - "push {3}", - "push {4}", - "iretq", - in(reg) user_ss, - in(reg) user_rsp, - in(reg) rflags, - in(reg) user_cs, - in(reg) user_rip, - options(noreturn) - ); - } + let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; let rflags = 0x0202u64; + unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } #[cfg(test)] mod tests { use super::*; use awe_boot_protocol::{Architecture, BootInfo, MemoryRegion}; - - #[test] - fn accepts_valid_x86_64_handoff_with_memory() { - let regions = [MemoryRegion { - base: 0x1000, - length: 0x10000, - kind: 1, - reserved: 0, - }]; - let info = BootInfo { - magic: awe_boot_protocol::AWE_BOOT_MAGIC, - version: awe_boot_protocol::AWE_BOOT_VERSION, - size: core::mem::size_of::() as u32, - architecture: Architecture::X86_64, - cpu_count: 1, - memory_regions: regions.as_ptr(), - memory_region_count: 1, - framebuffer_address: 0, - framebuffer_size: 0, - framebuffer_width: 0, - framebuffer_height: 0, - framebuffer_pitch: 0, - acpi_rsdp: 0, - device_tree: 0, - kernel_base: 0, - kernel_size: 0, - }; - assert_eq!(kernel_entry(&info), KernelBootStatus::Ready); - } - - #[test] - fn rejects_invalid_handoff() { - let mut info = BootInfo::empty(Architecture::X86_64); - info.magic = 0; - assert_eq!(kernel_entry(&info), KernelBootStatus::InvalidBootInfo); - } - - #[test] - fn rejects_missing_memory_map() { - let info = BootInfo::empty(Architecture::X86_64); - assert_eq!(kernel_entry(&info), KernelBootStatus::NoUsableMemory); + fn info() -> (BootInfo, [MemoryRegion;1]) { + let regions = [MemoryRegion { base: 0x1000, length: 0x10000, kind: 1, reserved: 0 }]; + let boot = BootInfo { magic: awe_boot_protocol::AWE_BOOT_MAGIC, version: awe_boot_protocol::AWE_BOOT_VERSION, size: core::mem::size_of::() as u32, architecture: Architecture::X86_64, cpu_count: 1, memory_regions: regions.as_ptr(), memory_region_count: 1, framebuffer_address: 0, framebuffer_size: 0, framebuffer_width: 0, framebuffer_height: 0, framebuffer_pitch: 0, acpi_rsdp: 0, device_tree: 0, kernel_base: 0, kernel_size: 0 }; + (boot, regions) } + #[test] fn invalid_boot_info_is_rejected() { let (mut boot, _) = info(); boot.magic = 0; assert_eq!(kernel_entry(&boot), KernelBootStatus::InvalidBootInfo); } + #[test] fn valid_minimum_boot_info_is_accepted() { let (boot, _) = info(); assert_eq!(kernel_entry(&boot), KernelBootStatus::Ready); } } diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index bf5a4d8..daaee84 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -5,6 +5,37 @@ #![allow(clippy::len_without_is_empty)] #![allow(clippy::module_inception)] +use core::alloc::{GlobalAlloc, Layout}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +struct EarlyAllocator; +const EARLY_HEAP_SIZE: usize = 2 * 1024 * 1024; +static mut EARLY_HEAP: [u8; EARLY_HEAP_SIZE] = [0; EARLY_HEAP_SIZE]; +static EARLY_NEXT: AtomicUsize = AtomicUsize::new(0); + +unsafe impl GlobalAlloc for EarlyAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mask = layout.align().saturating_sub(1); + loop { + let current = EARLY_NEXT.load(Ordering::Relaxed); + let aligned = match current.checked_add(mask) { Some(v) => v & !mask, None => return core::ptr::null_mut() }; + let end = match aligned.checked_add(layout.size()) { Some(v) => v, None => return core::ptr::null_mut() }; + if end > EARLY_HEAP_SIZE { return core::ptr::null_mut(); } + if EARLY_NEXT.compare_exchange_weak(current, end, Ordering::AcqRel, Ordering::Relaxed).is_ok() { + return (core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8).wrapping_add(aligned); + } + } + } + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} + +#[global_allocator] +static GLOBAL_ALLOCATOR: EarlyAllocator = EarlyAllocator; + +#[cfg(all(not(test), target_os = "none"))] +#[alloc_error_handler] +fn allocation_error(_: Layout) -> ! { loop { core::hint::spin_loop(); } } + // CellKernel is intentionally hardware-driver free. // All hardware discovery, driver lifecycle, compatibility adapters and // VirtIO/Linux/Windows/Android driver execution live in services/driverd. diff --git a/kernel/src/runtime/desktop.rs b/kernel/src/runtime/desktop.rs new file mode 100644 index 0000000..6e30b7d --- /dev/null +++ b/kernel/src/runtime/desktop.rs @@ -0,0 +1,118 @@ +#![no_std] + +use awe_ayui::{AppType, Compositor, Framebuffer}; +use super::{FramebufferInfo, RuntimeRect}; + +pub const MAX_DESKTOP_APPS: usize = 8; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DesktopApp { Terminal, FileManager, Settings, SystemMonitor, Calculator, TextEditor, PackageCenter, Recovery } + +impl DesktopApp { + pub const fn id(self) -> u8 { + match self { Self::Terminal => 1, Self::FileManager => 2, Self::Settings => 3, Self::SystemMonitor => 4, Self::Calculator => 5, Self::TextEditor => 6, Self::PackageCenter => 7, Self::Recovery => 8 } + } + pub const fn app_type(self) -> AppType { + match self { Self::Terminal => AppType::Terminal, Self::FileManager => AppType::FileManager, Self::Settings => AppType::Settings, Self::SystemMonitor => AppType::SystemMonitor, _ => AppType::Generic } + } + pub const fn title(self) -> &'static [u8] { + match self { Self::Terminal => b"AWETerminal", Self::FileManager => b"AWE File Manager", Self::Settings => b"AWE Settings", Self::SystemMonitor => b"AWE System Monitor", Self::Calculator => b"AWE Calculator", Self::TextEditor => b"AWE Text Editor", Self::PackageCenter => b"AWE Package Center", Self::Recovery => b"AWE Recovery" } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DesktopError { Full, Window, InvalidFramebuffer } + +pub struct DesktopShell { + pub compositor: Compositor, + pub framebuffer: Option, + windows: [Option<(DesktopApp, u16)>; MAX_DESKTOP_APPS], + window_count: usize, + pub clock_ticks: u64, +} + +impl DesktopShell { + pub const fn new() -> Self { Self { compositor: Compositor::new(), framebuffer: None, windows: [None; MAX_DESKTOP_APPS], window_count: 0, clock_ticks: 0 } } + + pub fn attach_framebuffer(&mut self, info: FramebufferInfo) -> Result<(), DesktopError> { + if !info.validate() { return Err(DesktopError::InvalidFramebuffer); } + self.framebuffer = Some(info); + Ok(()) + } + + pub fn start(&mut self) -> Result<(), DesktopError> { + self.launch(DesktopApp::Terminal, RuntimeRect { x: 40, y: 50, width: 640, height: 420 })?; + self.launch(DesktopApp::SystemMonitor, RuntimeRect { x: 150, y: 100, width: 440, height: 300 })?; + Ok(()) + } + + pub fn launch(&mut self, app: DesktopApp, rect: RuntimeRect) -> Result { + if self.windows.iter().flatten().any(|(a, _)| *a == app) { return Err(DesktopError::Window); } + if self.window_count == MAX_DESKTOP_APPS { return Err(DesktopError::Full); } + let id = self.compositor.create_app_window( + awe_ayui::Rect { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + app.app_type(), app.title()).map_err(|_| DesktopError::Window)?; + self.windows[self.window_count] = Some((app, id.0)); + self.window_count += 1; + Ok(id.0) + } + + pub fn close(&mut self, app: DesktopApp) -> Result<(), DesktopError> { + let index = self.windows.iter().position(|entry| entry.map(|(a, _)| a) == Some(app)).ok_or(DesktopError::Window)?; + let (_, id) = self.windows[index].take().unwrap(); + self.compositor.destroy_window(awe_ayui::WindowId(id)).map_err(|_| DesktopError::Window)?; + self.compact(); + Ok(()) + } + + pub fn dispatch_input(&mut self, event: awe_ayui::InputEvent) -> Result<(), DesktopError> { + self.compositor.push_input(event).map_err(|_| DesktopError::Window) + } + + pub fn tick(&mut self) { self.clock_ticks = self.clock_ticks.wrapping_add(1); } + + pub fn render(&self, buffer: &mut [u8]) -> Result<(), DesktopError> { + let info = self.framebuffer.ok_or(DesktopError::InvalidFramebuffer)?; + let required = info.required_bytes().ok_or(DesktopError::InvalidFramebuffer)? as usize; + if buffer.len() < required { return Err(DesktopError::InvalidFramebuffer); } + let mut fb = Framebuffer { width: info.width, height: info.height, stride: info.pitch / info.bytes_per_pixel as u32, buffer, gpu_accel: false }; + self.compositor.render_to_framebuffer(&mut fb); + Ok(()) + } + + fn compact(&mut self) { + let mut dst = 0; + for src in 0..MAX_DESKTOP_APPS { + if let Some(item) = self.windows[src] { + if src != dst { self.windows[dst] = Some(item); self.windows[src] = None; } + dst += 1; + } + } + self.window_count = dst; + } +} +impl Default for DesktopShell { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn desktop_autostarts_core_windows_and_closes_them() { + let mut shell = DesktopShell::new(); + shell.attach_framebuffer(FramebufferInfo { address: 0x100000, size: 800 * 600 * 4, width: 800, height: 600, pitch: 3200, bytes_per_pixel: 4 }).unwrap(); + shell.start().unwrap(); + assert_eq!(shell.window_count, 2); + shell.close(DesktopApp::Terminal).unwrap(); + assert_eq!(shell.window_count, 1); + } + + #[test] + fn desktop_renders_only_into_valid_dynamic_framebuffer() { + let mut shell = DesktopShell::new(); + assert_eq!(shell.render(&mut [0u8; 64]), Err(DesktopError::InvalidFramebuffer)); + shell.attach_framebuffer(FramebufferInfo { address: 0x200000, size: 320 * 240 * 4, width: 320, height: 240, pitch: 1280, bytes_per_pixel: 4 }).unwrap(); + let mut frame = [0u8; 320 * 240 * 4]; + shell.render(&mut frame).unwrap(); + assert!(frame.iter().any(|b| *b != 0)); + } +} \ No newline at end of file diff --git a/kernel/src/runtime/graphics.rs b/kernel/src/runtime/graphics.rs new file mode 100644 index 0000000..4529437 --- /dev/null +++ b/kernel/src/runtime/graphics.rs @@ -0,0 +1,141 @@ +#![no_std] + +use crate::runtime::InputEvent; + +pub const MAX_WINDOWS: usize = 32; +pub const MAX_WIDTH: u32 = 8192; +pub const MAX_HEIGHT: u32 = 8192; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Rect { pub x: i32, pub y: i32, pub width: u32, pub height: u32 } +impl Rect { + pub const fn valid(self) -> bool { self.width > 0 && self.height > 0 && self.width <= MAX_WIDTH && self.height <= MAX_HEIGHT } + pub fn contains(self, x: i32, y: i32) -> bool { + x >= self.x && y >= self.y && x < self.x.saturating_add(self.width as i32) && y < self.y.saturating_add(self.height as i32) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Window { pub id: u16, pub rect: Rect, pub visible: bool, pub focused: bool, pub z: u16 } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum WindowError { Full, Invalid, NotFound, BufferTooSmall, InvalidFramebuffer, Overflow } + +pub struct WindowManager { windows: [Option; MAX_WINDOWS], count: usize, next_id: u16, focused: Option } +impl WindowManager { + pub const fn new() -> Self { Self { windows: [None; MAX_WINDOWS], count: 0, next_id: 1, focused: None } } + pub const fn count(&self) -> usize { self.count } + pub const fn focused(&self) -> Option { self.focused } + + pub fn create(&mut self, rect: Rect) -> Result { + if !rect.valid() { return Err(WindowError::Invalid); } + let slot = self.windows.iter().position(Option::is_none).ok_or(WindowError::Full)?; + let id = self.next_id; + self.next_id = self.next_id.wrapping_add(1).max(1); + let z = self.count as u16; + self.windows[slot] = Some(Window { id, rect, visible: true, focused: false, z }); + self.count += 1; + self.focus(id)?; + Ok(id) + } + + pub fn destroy(&mut self, id: u16) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + self.windows[index] = None; + self.count -= 1; + if self.focused == Some(id) { self.focused = None; self.raise_top_focus(); } + Ok(()) + } + + pub fn focus(&mut self, id: u16) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + for window in self.windows.iter_mut().flatten() { window.focused = false; } + self.windows[index].as_mut().unwrap().focused = true; + self.focused = Some(id); + let max_z = self.windows.iter().flatten().map(|w| w.z).max().unwrap_or(0); + self.windows[index].as_mut().unwrap().z = max_z.saturating_add(1); + Ok(()) + } + + pub fn move_window(&mut self, id: u16, x: i32, y: i32) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + self.windows[index].as_mut().unwrap().rect.x = x; + self.windows[index].as_mut().unwrap().rect.y = y; + Ok(()) + } + + pub fn resize(&mut self, id: u16, width: u32, height: u32) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + let rect = Rect { x: self.windows[index].unwrap().rect.x, y: self.windows[index].unwrap().rect.y, width, height }; + if !rect.valid() { return Err(WindowError::Invalid); } + self.windows[index].as_mut().unwrap().rect = rect; + Ok(()) + } + + pub fn hit_test(&mut self, x: i32, y: i32) -> Option { + let mut best: Option = None; + for window in self.windows.iter().flatten() { + if window.visible && window.rect.contains(x, y) && best.map(|b| window.z > b.z).unwrap_or(true) { best = Some(*window); } + } + if let Some(w) = best { let _ = self.focus(w.id); Some(w.id) } else { None } + } + + pub fn handle_input(&mut self, event: InputEvent) -> Option { + match event { InputEvent::Pointer { x, y, .. } => self.hit_test(x, y), InputEvent::Key { .. } => self.focused } + } + + fn find(&self, id: u16) -> Option { self.windows.iter().position(|w| w.map(|v| v.id) == Some(id)) } + fn raise_top_focus(&mut self) { if let Some(id) = self.windows.iter().flatten().max_by_key(|w| w.z).map(|w| w.id) { let _ = self.focus(id); } } +} +impl Default for WindowManager { fn default() -> Self { Self::new() } } + +pub struct DoubleBuffer<'a> { pub width: u32, pub height: u32, pub pitch: u32, pub front: &'a mut [u8], pub back: &'a mut [u8] } +impl<'a> DoubleBuffer<'a> { + pub fn new(width: u32, height: u32, pitch: u32, front: &'a mut [u8], back: &'a mut [u8]) -> Result { + if width == 0 || height == 0 || pitch < width.saturating_mul(4) { return Err(WindowError::InvalidFramebuffer); } + let bytes = (height as usize).checked_mul(pitch as usize).ok_or(WindowError::Overflow)?; + if front.len() < bytes || back.len() < bytes { return Err(WindowError::BufferTooSmall); } + Ok(Self { width, height, pitch, front, back }) + } + pub fn clear(&mut self, pixel: [u8; 4]) { + for y in 0..self.height as usize { + let row = y * self.pitch as usize; + for x in 0..self.width as usize { let o = row + x * 4; self.back[o..o+4].copy_from_slice(&pixel); } + } + } + pub fn fill_rect(&mut self, rect: Rect, pixel: [u8; 4]) { + let x0 = rect.x.max(0) as u32; let y0 = rect.y.max(0) as u32; + let x1 = rect.x.saturating_add(rect.width as i32).max(0) as u32; + let y1 = rect.y.saturating_add(rect.height as i32).max(0) as u32; + let x1 = x1.min(self.width); let y1 = y1.min(self.height); + for y in y0.min(self.height)..y1 { let row = y as usize * self.pitch as usize; for x in x0.min(self.width)..x1 { let o = row + x as usize * 4; self.back[o..o+4].copy_from_slice(&pixel); } } + } + pub fn present(&mut self) { self.front[..self.back.len()].copy_from_slice(self.back); } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn window_manager_focus_hit_move_resize_and_destroy() { + let mut wm = WindowManager::new(); + let a = wm.create(Rect { x: 10, y: 10, width: 100, height: 100 }).unwrap(); + let b = wm.create(Rect { x: 20, y: 20, width: 100, height: 100 }).unwrap(); + assert_eq!(wm.hit_test(30, 30), Some(b)); + wm.move_window(b, 200, 200).unwrap(); + wm.resize(a, 120, 120).unwrap(); + assert_eq!(wm.hit_test(30, 30), Some(a)); + wm.destroy(a).unwrap(); + assert_eq!(wm.count(), 1); + } + + #[test] + fn double_buffer_validates_stride_and_capacity() { + let mut front = [0u8; 64]; let mut back = [0u8; 64]; + let mut db = DoubleBuffer::new(4, 4, 16, &mut front, &mut back).unwrap(); + db.clear([1, 2, 3, 4]); + db.fill_rect(Rect { x: 1, y: 1, width: 2, height: 2 }, [4, 3, 2, 1]); + db.present(); + assert_eq!(front[0..4], [1,2,3,4]); + } +} \ No newline at end of file diff --git a/kernel/src/runtime/mod.rs b/kernel/src/runtime/mod.rs index 64017ff..cecc5cd 100644 --- a/kernel/src/runtime/mod.rs +++ b/kernel/src/runtime/mod.rs @@ -1,19 +1,26 @@ -//! Native AWEOS runtime kernel contract. -//! A runtime handle is intentionally capability-scoped: callers receive only -//! the operations represented by the supplied capability set. +//! Native AWEOS runtime control plane. +//! Runtime handles are capability-scoped and all privileged operations remain +//! behind explicit validation boundaries. #![allow(dead_code)] +mod desktop; mod end_user; +pub mod graphics; +pub mod system; +pub mod ui_adapter; +pub use desktop::{DesktopApp, DesktopError, DesktopShell}; pub use end_user::{ AppRecord, AppState, EndUserRuntime, EndUserRuntimeError, FramebufferInfo, InputEvent, RuntimeEvent, ServiceRecord, ServiceState, }; +pub use graphics::{DoubleBuffer, Rect as RuntimeRect, Window, WindowError, WindowManager}; +pub use system::SystemRuntime; +pub use ui_adapter::{AyuiRuntime, UiRuntimeError}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CapabilitySet(pub u64); - impl CapabilitySet { pub const NONE: Self = Self(0); pub const PROCESS: Self = Self(1 << 0); @@ -23,38 +30,18 @@ impl CapabilitySet { pub const STORAGE: Self = Self(1 << 4); pub const NETWORK: Self = Self(1 << 5); pub const UI: Self = Self(1 << 6); - - pub const fn contains(self, required: Self) -> bool { - (self.0 & required.0) == required.0 - } - - pub const fn union(self, other: Self) -> Self { - Self(self.0 | other.0) - } + pub const fn contains(self, required: Self) -> bool { (self.0 & required.0) == required.0 } + pub const fn union(self, other: Self) -> Self { Self(self.0 | other.0) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum RuntimeError { - CapabilityDenied, - InvalidHandle, - ResourceExhausted, -} +pub enum RuntimeError { CapabilityDenied, InvalidHandle, ResourceExhausted } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RuntimeContext { - pub capabilities: CapabilitySet, -} - +pub struct RuntimeContext { pub capabilities: CapabilitySet } impl RuntimeContext { - pub const fn new(capabilities: CapabilitySet) -> Self { - Self { capabilities } - } - + pub const fn new(capabilities: CapabilitySet) -> Self { Self { capabilities } } pub const fn require(self, required: CapabilitySet) -> Result<(), RuntimeError> { - if self.capabilities.contains(required) { - Ok(()) - } else { - Err(RuntimeError::CapabilityDenied) - } + if self.capabilities.contains(required) { Ok(()) } else { Err(RuntimeError::CapabilityDenied) } } } diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs new file mode 100644 index 0000000..a42ac2a --- /dev/null +++ b/kernel/src/runtime/system.rs @@ -0,0 +1,65 @@ +#![no_std] + +use core::sync::atomic::{AtomicU16, Ordering}; +use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; +use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; +use awe_netd::NetworkDaemon; +use awe_storaged::StorageDaemon; +use crate::drivers::{KeyCode, Ps2Event}; +use crate::process::{ProcessDescriptor, ProcessId, ProcessManager, ProcessState, ResourceBudget}; +use crate::process::context::{CpuContext, ProcessContext}; +use crate::storage::{Namespace, NamespaceManager, MAX_FILES}; +use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeEvent}; +pub use super::{RuntimeRect, WindowManager, WindowError}; + +const MAX_RUNTIME_PROCESSES: usize = 32; +static NEXT_WINDOW_ID: AtomicU16 = AtomicU16::new(1); +static mut PROCESS_STACKS: [[u8; 16384]; MAX_RUNTIME_PROCESSES] = [[0; 16384]; MAX_RUNTIME_PROCESSES]; + +extern "C" fn runtime_service_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } +extern "C" fn runtime_app_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } +fn entry_address(entry: extern "C" fn() -> !) -> usize { entry as *const () as usize } +fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } Ok(service.0 as u64) } +fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(0x1_0000 + app.0) } +fn create_window(_app: AppId) -> Result { let id=NEXT_WINDOW_ID.fetch_add(1,Ordering::Relaxed); if id==0{Err(())}else{Ok(id)} } +const ALL: CapabilitySet = CapabilitySet(CapabilitySet::PROCESS.0|CapabilitySet::MEMORY.0|CapabilitySet::IPC.0|CapabilitySet::DEVICE.0|CapabilitySet::STORAGE.0|CapabilitySet::NETWORK.0|CapabilitySet::UI.0); + +pub struct SystemRuntime { + pub core: EndUserRuntime, + pub windows: WindowManager, + pub services: Supervisor, + pub apps: AppSupervisor, + pub processes: ProcessManager, + pub namespaces: NamespaceManager, + pub storage: StorageDaemon, + pub storage_state: [u8; awe_storaged::persistence::MAX_STATE_SIZE], + pub storage_state_len: usize, + pub network: NetworkDaemon, + pub cursor_x: i32, + pub cursor_y: i32, +} +impl SystemRuntime { + pub const fn new()->Self{Self{core:EndUserRuntime::new(),windows:WindowManager::new(),services:Supervisor::new(spawn_service),apps:AppSupervisor::new(spawn_app,create_window),processes:ProcessManager::new(),namespaces:NamespaceManager::new(),storage:StorageDaemon::new(),storage_state:[0;awe_storaged::persistence::MAX_STATE_SIZE],storage_state_len:0,network:NetworkDaemon::new(),cursor_x:0,cursor_y:0}} + fn register_process(&mut self,pid:u64,entry:u64,memory_pages:u32,ipc_messages:u64)->Result<(),()>{if pid==0||entry==0||self.processes.len()>=MAX_RUNTIME_PROCESSES{return Err(())}let slot=self.processes.len();let stack_ptr=unsafe{core::ptr::addr_of_mut!(PROCESS_STACKS[slot]) as *mut u8};let stack_top=stack_ptr as u64+16384;let d=ProcessDescriptor{id:ProcessId(pid),state:ProcessState::Created,budget:ResourceBudget{cpu_ticks:10_000,memory_bytes:memory_pages as u64*4096,ipc_messages}};let c=ProcessContext::new(ProcessId(pid),CpuContext::kernel_entry(entry,stack_top,0));self.processes.register(d,c).map_err(|_|())?;self.processes.make_runnable(ProcessId(pid)).map_err(|_|())} + pub fn attach_framebuffer(&mut self,fb:FramebufferInfo)->Result<(),super::EndUserRuntimeError>{self.core.attach_framebuffer(fb)} + pub fn mount_core_namespaces(&mut self,first_block:u64)->Result<(),crate::storage::NamespaceError>{let s=128;self.namespaces.mount(Namespace::Config,first_block)?;self.namespaces.mount(Namespace::Home,first_block+s)?;self.namespaces.mount(Namespace::Apps,first_block+s*2)?;self.namespaces.mount(Namespace::System,first_block+s*3)?;self.namespaces.mount(Namespace::Log,first_block+s*4)?;let v=self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume,8192,first_block,false).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.storage.mount_volume(v,0xAWE0_0001).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.storage.create_snapshot(v,0).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.persist_storage_state().map_err(|_|crate::storage::NamespaceError::Capacity)?;Ok(())} + pub fn persist_storage_state(&mut self)->Result{let len=awe_storaged::persistence::export_state(&self.storage,&mut self.storage_state)?;self.storage_state_len=len;Ok(len)} + pub fn restore_storage_state(&mut self)->Result<(),awe_storaged::persistence::PersistError>{if self.storage_state_len==0{return Ok(())}awe_storaged::persistence::import_state(&mut self.storage,&self.storage_state[..self.storage_state_len])} + pub fn register_network_interface(&mut self,mac:[u8;6])->Result{self.network.add_interface(awe_netd::MacAddress(mac))} + pub fn register_core_services(&mut self)->Result<(),awe_initd::RuntimeError>{let n=[None;awe_initd::runtime::MAX_DEPENDENCIES];let d1=[Some(ServiceId(1)),None,None,None,None,None,None,None];let d4=[Some(ServiceId(4)),None,None,None,None,None,None,None];let entry=entry_address(runtime_service_entry);let specs=[ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(1),restart:RestartPolicy::Always,capability_mask:CapabilitySet::DEVICE.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:n,dependency_count:0,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(2),restart:RestartPolicy::Always,capability_mask:CapabilitySet::STORAGE.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(3),restart:RestartPolicy::Always,capability_mask:CapabilitySet::NETWORK.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(4),restart:RestartPolicy::Always,capability_mask:CapabilitySet::IPC.0,memory_limit_pages:32,cpu_budget_ticks:5_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(5),restart:RestartPolicy::Always,capability_mask:CapabilitySet::PROCESS.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d4,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(6),restart:RestartPolicy::Always,capability_mask:CapabilitySet::UI.union(CapabilitySet::IPC).0,memory_limit_pages:128,cpu_budget_ticks:20_000},dependencies:d4,dependency_count:1,entry}];for spec in specs{self.services.register(spec)?}Ok(())} + pub fn start_core_services(&mut self)->Result<(),awe_initd::RuntimeError>{let e=entry_address(runtime_service_entry)as u64;for id in 1..=6{let pid=self.services.start(ServiceId(id))?;if self.register_process(pid,e,64,128).is_err(){return Err(awe_initd::RuntimeError::SpawnFailed)}}Ok(())} + pub fn admit_core_apps(&mut self)->Result<(),awe_appd::AppRuntimeError>{let apps=[(1u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(2u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(3u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(4u64,CapabilitySet::UI.union(CapabilitySet::IPC))];for(id,caps)in apps{let m=AppManifest{id:AppId(id),abi_major:awe_appd::AWE_APP_ABI_MAJOR,abi_minor:awe_appd::AWE_APP_ABI_MINOR,memory_limit_pages:32,capability_mask:caps.0,dependency_count:0,resource_count:0};self.apps.admit(m).map_err(|_|awe_appd::AppRuntimeError::InvalidManifest)?}Ok(())} + pub fn start_core_apps(&mut self)->Result<(),awe_appd::AppRuntimeError>{let e=entry_address(runtime_app_entry)as u64;for id in 1..=4{let pid=self.apps.start(AppId(id),ALL.0)?;if self.register_process(pid,e,32,64).is_err(){return Err(awe_appd::AppRuntimeError::SpawnFailed)}}Ok(())} + pub fn route_ps2(&mut self,event:Ps2Event)->Result{let t=match event{Ps2Event::Key{code,pressed}=>InputEvent::Key{code:key_code_value(code),pressed},Ps2Event::Pointer{dx,dy,buttons}=>{self.cursor_x=self.cursor_x.saturating_add(dx as i32);self.cursor_y=self.cursor_y.saturating_add(dy as i32);InputEvent::Pointer{x:self.cursor_x,y:self.cursor_y,buttons}}};self.core.push_input(t)?;self.windows.handle_input(t);Ok(RuntimeEvent::Input(t))} + pub fn create_native_window(&mut self,rect:RuntimeRect)->Result{self.windows.create(rect)} + pub fn pointer_target(&mut self)->Option{self.windows.hit_test(self.cursor_x,self.cursor_y)} + pub fn service_state(&self,id:u16)->Option{self.services.state(ServiceId(id))} + pub fn app_state(&self,id:u64)->Option{self.apps.state(AppId(id))} + pub fn process_count(&self)->usize{self.processes.len()} + pub fn scheduler_ticks(&self)->u64{self.processes.scheduler_ticks()} +} +fn key_code_value(code:KeyCode)->u16{match code{KeyCode::Escape=>0x01,KeyCode::Enter=>0x1C,KeyCode::Backspace=>0x0E,KeyCode::Tab=>0x0D,KeyCode::Space=>0x39,KeyCode::Left=>0x4B,KeyCode::Right=>0x4D,KeyCode::Up=>0x48,KeyCode::Down=>0x50,KeyCode::Character(v)|KeyCode::Unknown(v)=>v as u16}} +impl Default for SystemRuntime{fn default()->Self{Self::new()}} + +#[cfg(test)] +mod tests{use super::*;#[test]fn core_services_start_and_register_processes(){let mut r=SystemRuntime::new();r.register_core_services().unwrap();r.start_core_services().unwrap();assert_eq!(r.service_state(6),Some(ServiceState::Running));assert_eq!(r.process_count(),6)}#[test]fn core_apps_admit_and_register_processes(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();r.start_core_apps().unwrap();assert_eq!(r.app_state(1),Some(AppState::Running));assert_eq!(r.process_count(),4)}#[test]fn ps2_input_reaches_window_manager(){let mut r=SystemRuntime::new();r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap();r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap();assert!(r.pointer_target().is_some())}#[test]fn capability_set_is_used_for_app_admission(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied))}#[test]fn namespace_mounts_are_bounded_and_storage_persisted(){let mut r=SystemRuntime::new();r.mount_core_namespaces(32).unwrap();assert!(r.namespaces.is_mounted(Namespace::Config));assert!(r.namespaces.is_mounted(Namespace::Log));assert!(r.storage_state_len>0);assert!(r.storage_state_len<=awe_storaged::persistence::MAX_STATE_SIZE)}} diff --git a/kernel/src/runtime/ui_adapter.rs b/kernel/src/runtime/ui_adapter.rs new file mode 100644 index 0000000..dc28037 --- /dev/null +++ b/kernel/src/runtime/ui_adapter.rs @@ -0,0 +1,81 @@ +#![no_std] + +use awe_ayui::{AppType, Compositor, Framebuffer, InputEvent as AyuiInputEvent, Rect as AyuiRect}; +use super::{FramebufferInfo, InputEvent, RuntimeRect}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum UiRuntimeError { + InvalidFramebuffer, + RenderFailed, + WindowFailed, +} + +pub struct AyuiRuntime { + pub compositor: Compositor, + framebuffer: Option, +} + +impl AyuiRuntime { + pub const fn new() -> Self { Self { compositor: Compositor::new(), framebuffer: None } } + + pub fn attach_framebuffer(&mut self, info: FramebufferInfo) -> Result<(), UiRuntimeError> { + if !info.validate() { return Err(UiRuntimeError::InvalidFramebuffer); } + self.framebuffer = Some(info); + Ok(()) + } + + pub fn create_window(&mut self, rect: RuntimeRect, app: AppType, title: &[u8]) -> Result { + self.compositor + .create_app_window(AyuiRect { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, app, title) + .map(|id| id.0) + .map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn destroy_window(&mut self, id: u16) -> Result<(), UiRuntimeError> { + self.compositor.destroy_window(awe_ayui::WindowId(id)).map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn route_input(&mut self, event: InputEvent) -> Result<(), UiRuntimeError> { + let ayui = match event { + InputEvent::Key { code, pressed } => AyuiInputEvent::Key { code, pressed }, + InputEvent::Pointer { x, y, buttons } => AyuiInputEvent::Pointer { x, y, buttons }, + }; + self.compositor.push_input(ayui).map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn render(&mut self, buffer: &mut [u8]) -> Result<(), UiRuntimeError> { + let info = self.framebuffer.ok_or(UiRuntimeError::InvalidFramebuffer)?; + let required = info.required_bytes().ok_or(UiRuntimeError::InvalidFramebuffer)? as usize; + if buffer.len() < required { return Err(UiRuntimeError::InvalidFramebuffer); } + let mut fb = Framebuffer { + width: info.width, + height: info.height, + stride: info.pitch / info.bytes_per_pixel as u32, + buffer, + gpu_accel: false, + }; + self.compositor.render_to_framebuffer(&mut fb); + Ok(()) + } + + pub const fn framebuffer(&self) -> Option { self.framebuffer } +} +impl Default for AyuiRuntime { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn dynamic_framebuffer_and_input_path_work() { + let mut ui = AyuiRuntime::new(); + let info = FramebufferInfo { address: 0x100000, size: 640 * 480 * 4, width: 640, height: 480, pitch: 640 * 4, bytes_per_pixel: 4 }; + ui.attach_framebuffer(info).unwrap(); + let id = ui.create_window(RuntimeRect { x: 20, y: 20, width: 240, height: 160 }, AppType::Terminal, b"Terminal").unwrap(); + ui.route_input(InputEvent::Pointer { x: 30, y: 30, buttons: 1 }).unwrap(); + ui.route_input(InputEvent::Key { code: 0x1c, pressed: true }).unwrap(); + let mut frame = [0u8; 640 * 480 * 4]; + ui.render(&mut frame).unwrap(); + assert!(frame.iter().any(|b| *b != 0)); + ui.destroy_window(id).unwrap(); + } +} \ No newline at end of file diff --git a/kernel/src/storage/file_store.rs b/kernel/src/storage/file_store.rs new file mode 100644 index 0000000..0243d4e --- /dev/null +++ b/kernel/src/storage/file_store.rs @@ -0,0 +1,70 @@ +#![no_std] + +use super::{BlockDevice, StorageError, BLOCK_SIZE}; + +pub const MAX_FILES: usize = 64; +pub const MAX_FILE_BLOCKS: usize = 32; +pub const MAX_FILE_SIZE: usize = MAX_FILE_BLOCKS * BLOCK_SIZE; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FileStoreError { Full, NotFound, InvalidName, InvalidOffset, TooLarge, NoSpace, Storage(StorageError) } +impl From for FileStoreError { fn from(v: StorageError) -> Self { Self::Storage(v) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct FileRecord { pub id: u32, pub size: u64, pub first_block: u64, pub blocks: u16, pub generation: u64 } + +#[derive(Clone, Copy)] +struct Slot { record: Option, name: [u8; 63], name_len: u8 } +impl Slot { const fn empty() -> Self { Self { record: None, name: [0;63], name_len: 0 } } } + +#[derive(Clone, Copy)] +pub struct FileStore { + slots: [Slot; N], + next_id: u32, + next_block: u64, +} +impl FileStore { + pub const fn new(start_block: u64) -> Self { Self { slots: [Slot::empty(); N], next_id: 1, next_block: start_block } } + pub fn create(&mut self, name: &[u8]) -> Result { + if name.is_empty() || name.len() > 63 || name.iter().any(|b| *b == 0 || *b == b'/') { return Err(FileStoreError::InvalidName); } + if self.find(name).is_some() { return Err(FileStoreError::InvalidName); } + let slot = self.slots.iter().position(|s| s.record.is_none()).ok_or(FileStoreError::Full)?; + let record = FileRecord { id: self.next_id, size: 0, first_block: self.next_block, blocks: 0, generation: 1 }; + self.next_id = self.next_id.checked_add(1).ok_or(FileStoreError::Full)?; + self.slots[slot].record = Some(record); self.slots[slot].name[..name.len()].copy_from_slice(name); self.slots[slot].name_len = name.len() as u8; + Ok(record) + } + pub fn write_at(&mut self, device: &mut D, id: u32, offset: usize, data: &[u8]) -> Result { + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let record = self.slots[index].record.unwrap(); + let end = offset.checked_add(data.len()).ok_or(FileStoreError::TooLarge)?; + if end > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } if data.is_empty() { return Ok(0); } + let required_blocks = end.div_ceil(BLOCK_SIZE); if required_blocks > MAX_FILE_BLOCKS { return Err(FileStoreError::TooLarge); } + if required_blocks > record.blocks as usize { let additional = required_blocks - record.blocks as usize; self.next_block = self.next_block.checked_add(additional as u64).ok_or(FileStoreError::NoSpace)?; self.slots[index].record.as_mut().unwrap().blocks = required_blocks as u16; } + let first = record.first_block; let mut consumed = 0usize; + while consumed < data.len() { + let absolute = offset + consumed; let block = first + (absolute / BLOCK_SIZE) as u64; let in_block = absolute % BLOCK_SIZE; let take = core::cmp::min(BLOCK_SIZE - in_block, data.len() - consumed); let mut buf = [0u8; BLOCK_SIZE]; + device.read_block(block, &mut buf)?; buf[in_block..in_block + take].copy_from_slice(&data[consumed..consumed + take]); device.write_block(block, &buf)?; consumed += take; + } + let current = self.slots[index].record.as_mut().unwrap(); if end as u64 > current.size { current.size = end as u64; } current.generation = current.generation.saturating_add(1); Ok(data.len()) + } + pub fn read_at(&self, device: &mut D, id: u32, offset: usize, out: &mut [u8]) -> Result { + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let record = self.slots[index].record.unwrap(); if offset > record.size as usize { return Err(FileStoreError::InvalidOffset); } + let count = core::cmp::min(record.size as usize - offset, out.len()); let mut consumed = 0usize; + while consumed < count { let absolute = offset + consumed; let block = record.first_block + (absolute / BLOCK_SIZE) as u64; let in_block = absolute % BLOCK_SIZE; let take = core::cmp::min(BLOCK_SIZE - in_block, count - consumed); let mut buf = [0u8; BLOCK_SIZE]; device.read_block(block, &mut buf)?; out[consumed..consumed+take].copy_from_slice(&buf[in_block..in_block+take]); consumed += take; } + Ok(count) + } + pub fn truncate(&mut self, id: u32, size: usize) -> Result<(), FileStoreError> { if size > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let r = self.slots[index].record.as_mut().unwrap(); r.size=size as u64; r.blocks=size.div_ceil(BLOCK_SIZE) as u16; r.generation=r.generation.saturating_add(1); Ok(()) } + pub fn delete(&mut self, id: u32) -> Result<(), FileStoreError> { let index=self.find_id(id).ok_or(FileStoreError::NotFound)?; self.slots[index]=Slot::empty(); Ok(()) } + pub fn lookup(&self, name: &[u8]) -> Option { self.find(name).and_then(|i| self.slots[i].record) } + pub fn record(&self, id: u32) -> Option { self.find_id(id).and_then(|i| self.slots[i].record) } + fn find(&self, name: &[u8]) -> Option { self.slots.iter().position(|s| s.record.is_some() && s.name_len as usize == name.len() && &s.name[..name.len()] == name) } + fn find_id(&self, id: u32) -> Option { self.slots.iter().position(|s| s.record.map(|r| r.id) == Some(id)) } +} +impl Default for FileStore { fn default() -> Self { Self::new(8) } } + +#[cfg(test)] +mod tests { + use super::*; use crate::storage::RamBlockDevice; + #[test] fn block_backed_round_trip_supports_seek() { let mut disk=RamBlockDevice::default(); let mut fs=FileStore::<4>::new(8); let file=fs.create(b"persist.bin").unwrap(); fs.write_at(&mut disk,file.id,3,b"AWEOS").unwrap(); let mut out=[0u8;8]; assert_eq!(fs.read_at(&mut disk,file.id,3,&mut out).unwrap(),5); assert_eq!(&out[..5],b"AWEOS"); assert_eq!(fs.record(file.id).unwrap().size,8); } + #[test] fn rejects_out_of_bounds_file_size() { let mut disk=RamBlockDevice::default(); let mut fs=FileStore::<1>::new(8); let file=fs.create(b"x").unwrap(); assert_eq!(fs.write_at(&mut disk,file.id,MAX_FILE_SIZE,b"x"),Err(FileStoreError::TooLarge)); } +} diff --git a/kernel/src/storage/mod.rs b/kernel/src/storage/mod.rs index ed21365..fb61485 100644 --- a/kernel/src/storage/mod.rs +++ b/kernel/src/storage/mod.rs @@ -4,52 +4,37 @@ #![allow(dead_code)] +pub mod file_store; pub mod gpt; pub mod journal; +pub mod namespaces; pub mod ramdisk; pub mod vfs; -pub use gpt::{ - GptError, GptHeader, GptPartition, crc32, parse_header, parse_partition, - validate_partition_array_crc, -}; +pub use file_store::{FileRecord, FileStore, FileStoreError, MAX_FILE_BLOCKS, MAX_FILE_SIZE, MAX_FILES}; +pub use gpt::{GptError, GptHeader, GptPartition, crc32, parse_header, parse_partition, validate_partition_array_crc}; pub use journal::{JournalError, JournalState, JournalTxn, RecoveryDecision, decide_recovery}; +pub use namespaces::{MountPoint, Namespace, NamespaceError, NamespaceManager, MAX_MOUNTS}; pub use ramdisk::{RAMDISK_BLOCKS, RamBlockDevice}; pub use vfs::{AweFs, FileName, FsError, Inode, JournalRecord, NodeKind, RecoveryAction, Vfs}; pub const BLOCK_SIZE: usize = 4096; #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum StorageError { - InvalidBlock, - BufferTooSmall, - ReadOnly, - Io, - Unsupported, - InvalidMetadata, - TooLarge, -} +pub enum StorageError { InvalidBlock, BufferTooSmall, ReadOnly, Io, Unsupported, InvalidMetadata, TooLarge } impl From for StorageError { fn from(error: GptError) -> Self { match error { GptError::BufferTooSmall => Self::BufferTooSmall, - GptError::InvalidHeaderSize - | GptError::InvalidLbaRange - | GptError::InvalidEntrySize - | GptError::TooManyPartitions - | GptError::InvalidPartitionRange - | GptError::HeaderCrcMismatch - | GptError::PartitionArrayCrcMismatch - | GptError::BadSignature - | GptError::UnsupportedRevision => Self::InvalidMetadata, + GptError::InvalidHeaderSize | GptError::InvalidLbaRange | GptError::InvalidEntrySize | + GptError::TooManyPartitions | GptError::InvalidPartitionRange | GptError::HeaderCrcMismatch | + GptError::PartitionArrayCrcMismatch | GptError::BadSignature | GptError::UnsupportedRevision => Self::InvalidMetadata, } } } pub trait BlockDevice { - fn block_size(&self) -> usize { - BLOCK_SIZE - } + fn block_size(&self) -> usize { BLOCK_SIZE } fn block_count(&self) -> u64; fn read_block(&mut self, block: u64, out: &mut [u8]) -> Result<(), StorageError>; fn write_block(&mut self, block: u64, data: &[u8]) -> Result<(), StorageError>; @@ -57,82 +42,42 @@ pub trait BlockDevice { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct DeviceGeometry { - pub block_size: u32, - pub blocks: u64, - pub read_only: bool, -} +pub struct DeviceGeometry { pub block_size: u32, pub blocks: u64, pub read_only: bool } impl DeviceGeometry { - pub const fn new(block_size: u32, blocks: u64, read_only: bool) -> Self { - Self { - block_size, - blocks, - read_only, - } - } - pub const fn bytes(self) -> Option { - self.blocks.checked_mul(self.block_size as u64) - } + pub const fn new(block_size: u32, blocks: u64, read_only: bool) -> Self { Self { block_size, blocks, read_only } } + pub const fn bytes(self) -> Option { self.blocks.checked_mul(self.block_size as u64) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct GptScanSummary { - pub header: GptHeader, - pub partitions: u16, -} +pub struct GptScanSummary { pub header: GptHeader, pub partitions: u16 } pub fn scan_gpt(device: &mut D) -> Result { - if device.block_size() < gpt::GPT_SECTOR_SIZE - || !device.block_size().is_multiple_of(gpt::GPT_SECTOR_SIZE) - { - return Err(StorageError::Unsupported); - } + if device.block_size() < gpt::GPT_SECTOR_SIZE || !device.block_size().is_multiple_of(gpt::GPT_SECTOR_SIZE) { return Err(StorageError::Unsupported); } let sectors_per_block = device.block_size() / gpt::GPT_SECTOR_SIZE; - let disk_last_lba = device - .block_count() - .checked_mul(sectors_per_block as u64) - .and_then(|s| s.checked_sub(1)) - .ok_or(StorageError::InvalidBlock)?; + let disk_last_lba = device.block_count().checked_mul(sectors_per_block as u64).and_then(|s| s.checked_sub(1)).ok_or(StorageError::InvalidBlock)?; let mut block = [0u8; BLOCK_SIZE]; device.read_block(0, &mut block)?; let header = gpt::parse_header(&block[gpt::GPT_SECTOR_SIZE..], disk_last_lba)?; - let entry_bytes = (header.partition_count as usize) - .checked_mul(header.partition_entry_size as usize) - .ok_or(StorageError::TooLarge)?; - if entry_bytes > 16 * 1024 { - return Err(StorageError::TooLarge); - } + let entry_bytes = (header.partition_count as usize).checked_mul(header.partition_entry_size as usize).ok_or(StorageError::TooLarge)?; + if entry_bytes > 16 * 1024 { return Err(StorageError::TooLarge); } let mut entries = [0u8; 16 * 1024]; let first_block = header.partition_entry_lba / sectors_per_block as u64; - let first_sector_in_block = - (header.partition_entry_lba % sectors_per_block as u64) as usize * gpt::GPT_SECTOR_SIZE; + let first_sector_in_block = (header.partition_entry_lba % sectors_per_block as u64) as usize * gpt::GPT_SECTOR_SIZE; let block_count = (first_sector_in_block + entry_bytes).div_ceil(device.block_size()); for index in 0..block_count { device.read_block(first_block + index as u64, &mut block)?; let source_start = if index == 0 { first_sector_in_block } else { 0 }; - let destination_start = if index == 0 { - 0 - } else { - index * device.block_size() - first_sector_in_block - }; - let copy_len = core::cmp::min( - device.block_size() - source_start, - entry_bytes.saturating_sub(destination_start), - ); - if copy_len == 0 { - break; - } - entries[destination_start..destination_start + copy_len] - .copy_from_slice(&block[source_start..source_start + copy_len]); + let destination_start = if index == 0 { 0 } else { index * device.block_size() - first_sector_in_block }; + let copy_len = core::cmp::min(device.block_size() - source_start, entry_bytes.saturating_sub(destination_start)); + if copy_len == 0 { break; } + entries[destination_start..destination_start + copy_len].copy_from_slice(&block[source_start..source_start + copy_len]); } gpt::validate_partition_array_crc(&entries[..entry_bytes], header.partition_array_crc32)?; let mut partitions = 0u16; for index in 0..header.partition_count as usize { let start = index * header.partition_entry_size as usize; let end = start + header.partition_entry_size as usize; - if gpt::parse_partition(&entries[start..end], &header, disk_last_lba)?.is_some() { - partitions = partitions.saturating_add(1); - } + if gpt::parse_partition(&entries[start..end], &header, disk_last_lba)?.is_some() { partitions = partitions.saturating_add(1); } } Ok(GptScanSummary { header, partitions }) } @@ -145,34 +90,28 @@ mod tests { let mut disk = RamBlockDevice::default(); let mut block = [0u8; BLOCK_SIZE]; let entry_offset = 1024usize; - block[entry_offset] = 1; - block[entry_offset + 16] = 2; + block[entry_offset] = 1; block[entry_offset + 16] = 2; block[entry_offset + 32..entry_offset + 40].copy_from_slice(&34u64.to_le_bytes()); block[entry_offset + 40..entry_offset + 48].copy_from_slice(&100u64.to_le_bytes()); let partition_crc = crc32(&block[entry_offset..entry_offset + 128]); let header_offset = 512usize; block[header_offset..header_offset + 8].copy_from_slice(&gpt::GPT_SIGNATURE); - block[header_offset + 8..header_offset + 12] - .copy_from_slice(&gpt::GPT_REVISION_1_0.to_le_bytes()); - block[header_offset + 12..header_offset + 16] - .copy_from_slice(&(gpt::GPT_HEADER_MIN_SIZE as u32).to_le_bytes()); + block[header_offset + 8..header_offset + 12].copy_from_slice(&gpt::GPT_REVISION_1_0.to_le_bytes()); + block[header_offset + 12..header_offset + 16].copy_from_slice(&(gpt::GPT_HEADER_MIN_SIZE as u32).to_le_bytes()); block[header_offset + 24..header_offset + 32].copy_from_slice(&1u64.to_le_bytes()); block[header_offset + 32..header_offset + 40].copy_from_slice(&511u64.to_le_bytes()); block[header_offset + 40..header_offset + 48].copy_from_slice(&34u64.to_le_bytes()); block[header_offset + 48..header_offset + 56].copy_from_slice(&480u64.to_le_bytes()); block[header_offset + 72..header_offset + 80].copy_from_slice(&2u64.to_le_bytes()); block[header_offset + 80..header_offset + 84].copy_from_slice(&1u32.to_le_bytes()); - block[header_offset + 84..header_offset + 88] - .copy_from_slice(&(gpt::GPT_PARTITION_ENTRY_MIN_SIZE as u32).to_le_bytes()); + block[header_offset + 84..header_offset + 88].copy_from_slice(&(gpt::GPT_PARTITION_ENTRY_MIN_SIZE as u32).to_le_bytes()); block[header_offset + 88..header_offset + 92].copy_from_slice(&partition_crc.to_le_bytes()); let mut header_copy = block; header_copy[header_offset + 16..header_offset + 20].fill(0); - let header_crc = - crc32(&header_copy[header_offset..header_offset + gpt::GPT_HEADER_MIN_SIZE]); + let header_crc = crc32(&header_copy[header_offset..header_offset + gpt::GPT_HEADER_MIN_SIZE]); block[header_offset + 16..header_offset + 20].copy_from_slice(&header_crc.to_le_bytes()); disk.write_block(0, &block).expect("write GPT"); let summary = scan_gpt(&mut disk).expect("scan GPT"); assert_eq!(summary.partitions, 1); - assert_eq!(summary.header.partition_count, 1); } } diff --git a/kernel/src/storage/namespaces.rs b/kernel/src/storage/namespaces.rs new file mode 100644 index 0000000..7efa0c1 --- /dev/null +++ b/kernel/src/storage/namespaces.rs @@ -0,0 +1,74 @@ +#![no_std] + +use super::{FileStore, FileStoreError, MAX_FILES}; + +pub const MAX_MOUNTS: usize = 5; +pub const MAX_PATH: usize = 32; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Namespace { Config, Home, Apps, System, Log } +impl Namespace { + pub const fn path(self) -> &'static [u8] { + match self { Self::Config => b"/config", Self::Home => b"/home", Self::Apps => b"/apps", Self::System => b"/system", Self::Log => b"/log" } + } + pub const fn index(self) -> usize { match self { Self::Config => 0, Self::Home => 1, Self::Apps => 2, Self::System => 3, Self::Log => 4 } } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum NamespaceError { AlreadyMounted, NotMounted, InvalidName, Storage(FileStoreError), Capacity } +impl From for NamespaceError { fn from(v: FileStoreError) -> Self { Self::Storage(v) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MountPoint { pub namespace: Namespace, pub first_block: u64, pub mounted: bool } + +pub struct NamespaceManager { + mounts: [Option; MAX_MOUNTS], + stores: [Option>; MAX_MOUNTS], +} +impl NamespaceManager { + pub const fn new() -> Self { Self { mounts: [None; MAX_MOUNTS], stores: [None; MAX_MOUNTS] } } + + pub fn mount(&mut self, namespace: Namespace, first_block: u64) -> Result<(), NamespaceError> { + let idx = namespace.index(); + if self.mounts[idx].is_some() { return Err(NamespaceError::AlreadyMounted); } + self.mounts[idx] = Some(MountPoint { namespace, first_block, mounted: true }); + self.stores[idx] = Some(FileStore::new(first_block)); + Ok(()) + } + + pub fn is_mounted(&self, namespace: Namespace) -> bool { self.mounts[namespace.index()].map(|m| m.mounted).unwrap_or(false) } + + pub fn store(&self, namespace: Namespace) -> Result<&FileStore, NamespaceError> { self.stores[namespace.index()].as_ref().ok_or(NamespaceError::NotMounted) } + pub fn store_mut(&mut self, namespace: Namespace) -> Result<&mut FileStore, NamespaceError> { self.stores[namespace.index()].as_mut().ok_or(NamespaceError::NotMounted) } + + pub fn unmount(&mut self, namespace: Namespace) -> Result<(), NamespaceError> { + let idx = namespace.index(); + if self.mounts[idx].is_none() { return Err(NamespaceError::NotMounted); } + self.mounts[idx] = None; self.stores[idx] = None; Ok(()) + } +} +impl Default for NamespaceManager { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn required_namespaces_mount_independently() { + let mut m = NamespaceManager::<4>::new(); + m.mount(Namespace::Config, 8).unwrap(); + m.mount(Namespace::Home, 64).unwrap(); + m.mount(Namespace::Apps, 128).unwrap(); + m.mount(Namespace::System, 192).unwrap(); + m.mount(Namespace::Log, 256).unwrap(); + assert!(m.is_mounted(Namespace::Config)); assert!(m.is_mounted(Namespace::Log)); + assert_eq!(Namespace::System.path(), b"/system"); + } + #[test] + fn duplicate_mount_is_rejected_and_unmount_works() { + let mut m = NamespaceManager::<1>::new(); + m.mount(Namespace::Config, 8).unwrap(); + assert_eq!(m.mount(Namespace::Config, 9), Err(NamespaceError::AlreadyMounted)); + m.unmount(Namespace::Config).unwrap(); + assert!(!m.is_mounted(Namespace::Config)); + } +} \ No newline at end of file diff --git a/kernel/src/syscall/dispatch.rs b/kernel/src/syscall/dispatch.rs index cdb1d96..46e3774 100644 --- a/kernel/src/syscall/dispatch.rs +++ b/kernel/src/syscall/dispatch.rs @@ -1,193 +1,78 @@ #![no_std] -use super::abi::{ERR_INVALID_ARGUMENT, ERR_OK, ERR_PERMISSION, Syscall, SyscallResult}; -use crate::process::{ProcessDescriptor, ProcessState}; -pub struct SyscallContext<'a> { - pub process: &'a mut ProcessDescriptor, -} + +use super::{Syscall, SyscallContext, SyscallResult}; + +pub const ERR_OK: u64 = 0; +pub const ERR_INVALID_ARGUMENT: u64 = 1; +pub const ERR_PERMISSION: u64 = 2; +pub const MAX_USER_COPY: usize = 4096; + impl<'a> SyscallContext<'a> { pub fn dispatch(&mut self, number: u64, args: [u64; 6]) -> SyscallResult { - let call = match number { - 0 => Syscall::Yield, - 1 => Syscall::Exit, - 2 => Syscall::Spawn, - 3 => Syscall::IpcSend, - 4 => Syscall::IpcRecv, - 5 => Syscall::Map, - 6 => Syscall::Unmap, - 7 => Syscall::Read, - 8 => Syscall::Write, - _ => return err(ERR_INVALID_ARGUMENT), + let syscall = match Syscall::try_from(number) { + Ok(value) => value, + Err(_) => return err(ERR_INVALID_ARGUMENT), }; - match call { - Syscall::Yield => { - self.process.state = ProcessState::Runnable; - ok(0) - } - Syscall::Exit => { - self.process.state = ProcessState::Exited; - ok(0) - } - Syscall::Spawn => err(ERR_PERMISSION), - Syscall::IpcSend | Syscall::IpcRecv => { - if !self.process.budget.consume_ipc(1) { - return err(ERR_PERMISSION); + + match syscall { + Syscall::Exit => ok(0), + Syscall::Yield => ok(0), + Syscall::Sleep => ok(args[0]), + Syscall::Map => { + if !valid_user_page_range(args[0], args[1]) { + return err(ERR_INVALID_ARGUMENT); } ok(args[0]) } - Syscall::Map => { - if !self.process.budget.permits_memory(args[0]) { - return err(ERR_PERMISSION); + Syscall::Unmap => { + if !valid_user_page_range(args[0], args[1]) { + return err(ERR_INVALID_ARGUMENT); } ok(args[0]) } - Syscall::Unmap => ok(args[0]), Syscall::Read => { - if args[1] == 0 { + let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); + if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } + let initrd = b"INITRD_SHELL_IMAGE_OK"; + let copy_len = core::cmp::min(len, initrd.len()); let ptr = args[0] as *mut u8; - let len = (args[1] as usize).min(4096); - if !ptr.is_null() && len > 0 { - // Populate initrd/input payload into user buffer - let initrd_data = b"INITRD_SHELL_IMAGE_OK"; - let copy_len = len.min(initrd_data.len()); - unsafe { - for (i, &byte) in initrd_data.iter().take(copy_len).enumerate() { - core::ptr::write_volatile(ptr.add(i), byte); - } + unsafe { + for (i, byte) in initrd[..copy_len].iter().enumerate() { + core::ptr::write_volatile(ptr.add(i), *byte); } - return ok(copy_len as u64); } - ok(args[1]) + ok(copy_len as u64) } Syscall::Write => { - if args[1] == 0 { + let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); + if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } - let ptr = args[0] as *const u8; - let len = (args[1] as usize).min(4096); - if !ptr.is_null() { - for i in 0..len { - let byte = unsafe { core::ptr::read_volatile(ptr.add(i)) }; - #[cfg(target_arch = "x86_64")] - crate::arch::x86_64::serial_write_byte(byte); + #[cfg(all(target_arch = "x86_64", target_os = "none"))] + { + let ptr = args[0] as *const u8; + unsafe { + for i in 0..len { + let byte = core::ptr::read_volatile(ptr.add(i)); + crate::arch::x86_64::serial_write_byte(byte); + } } } - ok(args[1]) + ok(len as u64) } } } } -const fn ok(value: u64) -> SyscallResult { - SyscallResult { - value, - error: ERR_OK, - } + +const fn ok(value: u64) -> SyscallResult { SyscallResult { value, error: ERR_OK } } +const fn err(error: u64) -> SyscallResult { SyscallResult { value: 0, error } } + +fn valid_user_page_range(address: u64, pages: u64) -> bool { + pages != 0 && address % 4096 == 0 && pages <= 1024 && address.checked_add(pages * 4096).is_some() } -const fn err(error: u64) -> SyscallResult { - SyscallResult { value: 0, error } -} -#[cfg(test)] -mod tests { - use super::*; - use crate::process::{ProcessId, ResourceBudget}; - fn descriptor() -> ProcessDescriptor { - ProcessDescriptor { - id: ProcessId(1), - state: ProcessState::Running, - budget: ResourceBudget { - cpu_ticks: 10, - memory_bytes: 4096, - ipc_messages: 2, - }, - } - } - #[test] - fn invalid_call_is_rejected() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(99, [0; 6]).error, ERR_INVALID_ARGUMENT); - } - #[test] - fn exit_changes_state() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::Exit as u64, [0; 6]).error, ERR_OK); - assert_eq!(c.process.state, ProcessState::Exited); - } - #[test] - fn yield_changes_state() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::Yield as u64, [0; 6]).error, ERR_OK); - assert_eq!(c.process.state, ProcessState::Runnable); - } - #[test] - fn ipc_consumes_budget_and_fails_closed() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [7, 0, 0, 0, 0, 0]) - .error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [8, 0, 0, 0, 0, 0]) - .error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [9, 0, 0, 0, 0, 0]) - .error, - ERR_PERMISSION - ); - } - #[test] - fn map_respects_memory_budget() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Map as u64, [4096, 0, 0, 0, 0, 0]).error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::Map as u64, [4097, 0, 0, 0, 0, 0]).error, - ERR_PERMISSION - ); - } - #[test] - fn io_rejects_zero_length() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Read as u64, [0, 0, 0, 0, 0, 0]).error, - ERR_INVALID_ARGUMENT - ); - assert_eq!( - c.dispatch(Syscall::Write as u64, [0, 8, 0, 0, 0, 0]).error, - ERR_OK - ); - } - #[test] - fn spawn_is_privileged() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Spawn as u64, [0; 6]).error, - ERR_PERMISSION - ); - } - #[test] - fn read_populates_initrd_buffer() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - let mut buf = [0u8; 32]; - let res = c.dispatch( - Syscall::Read as u64, - [buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0, 0, 0], - ); - assert_eq!(res.error, ERR_OK); - assert!(res.value > 0); - assert!(&buf[..res.value as usize] == b"INITRD_SHELL_IMAGE_OK"); - } + +fn valid_user_range(address: u64, len: usize) -> bool { + len <= MAX_USER_COPY && address >= 0x1000 && address.checked_add(len as u64).is_some() && address.checked_add(len as u64).unwrap() <= 0x0000_8000_0000_0000 } diff --git a/services/appd/src/awos.rs b/services/appd/src/awos.rs index 37e5bc0..a3293e2 100644 --- a/services/appd/src/awos.rs +++ b/services/appd/src/awos.rs @@ -13,559 +13,56 @@ pub const AWOS_MIN_SIGNATURE: usize = 64; pub const AWOS_FLAG_GUI: u32 = 1 << 0; pub const AWOS_FLAG_SERVICE: u32 = 1 << 1; pub const AWOS_KNOWN_FLAGS: u32 = AWOS_FLAG_GUI | AWOS_FLAG_SERVICE; - pub const MAX_PACKAGE_DEPS: usize = 16; pub const MAX_INDEX_ENTRIES: usize = 32; pub const MAX_INSTALLED_PACKAGES: usize = 32; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct AwosHeader { - pub version: u16, - pub abi_major: u16, - pub abi_minor: u16, - pub manifest_len: u32, - pub code_len: u32, - pub data_len: u32, - pub signature_len: u16, - pub entry_offset: u32, - pub flags: u32, -} - +pub struct AwosHeader { pub version: u16, pub abi_major: u16, pub abi_minor: u16, pub manifest_len: u32, pub code_len: u32, pub data_len: u32, pub signature_len: u16, pub entry_offset: u32, pub flags: u32 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AwosError { - TooShort, - BadMagic, - UnsupportedVersion, - OversizedManifest, - OversizedCode, - OversizedData, - MissingSignature, - InvalidLength, - InvalidEntry, - UnknownFlags, - InvalidSignature, - PublisherUntrusted, - DependencyMissing, - DependencyCycle, - PackageNotFound, - AlreadyInstalled, - SandboxViolation, - RollbackFailed, - StorageFull, -} +pub enum AwosError { TooShort, BadMagic, UnsupportedVersion, OversizedManifest, OversizedCode, OversizedData, MissingSignature, InvalidLength, InvalidEntry, UnknownFlags, InvalidSignature, PublisherUntrusted, DependencyMissing, DependencyCycle, PackageNotFound, AlreadyInstalled, SandboxViolation, RollbackFailed, StorageFull } pub fn validate_awos(bytes: &[u8]) -> Result { - if bytes.len() < AWOS_HEADER_LEN { - return Err(AwosError::TooShort); - } - if bytes[..4] != AWOS_MAGIC { - return Err(AwosError::BadMagic); - } + if bytes.len() < AWOS_HEADER_LEN { return Err(AwosError::TooShort); } + if bytes[..4] != AWOS_MAGIC { return Err(AwosError::BadMagic); } let u16_at = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]); - let u32_at = - |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); - let header = AwosHeader { - version: u16_at(4), - abi_major: u16_at(6), - abi_minor: u16_at(8), - manifest_len: u32_at(10), - code_len: u32_at(14), - data_len: u32_at(18), - signature_len: u16_at(22), - entry_offset: u32_at(24), - flags: u32_at(28), - }; - if header.version != AWOS_VERSION { - return Err(AwosError::UnsupportedVersion); - } - if header.manifest_len as usize > AWOS_MAX_MANIFEST { - return Err(AwosError::OversizedManifest); - } - if header.code_len as usize > AWOS_MAX_CODE { - return Err(AwosError::OversizedCode); - } - if header.data_len as usize > AWOS_MAX_DATA { - return Err(AwosError::OversizedData); - } - if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { - return Err(AwosError::MissingSignature); - } - if header.flags & !AWOS_KNOWN_FLAGS != 0 { - return Err(AwosError::UnknownFlags); - } - if header.entry_offset >= header.code_len || header.code_len == 0 { - return Err(AwosError::InvalidEntry); - } - let expected = AWOS_HEADER_LEN - .checked_add(header.manifest_len as usize) - .and_then(|v| v.checked_add(header.code_len as usize)) - .and_then(|v| v.checked_add(header.data_len as usize)) - .and_then(|v| v.checked_add(header.signature_len as usize)) - .ok_or(AwosError::InvalidLength)?; - if expected != bytes.len() { - return Err(AwosError::InvalidLength); - } + let u32_at = |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); + let header = AwosHeader { version:u16_at(4),abi_major:u16_at(6),abi_minor:u16_at(8),manifest_len:u32_at(10),code_len:u32_at(14),data_len:u32_at(18),signature_len:u16_at(22),entry_offset:u32_at(24),flags:u32_at(28) }; + if header.version != AWOS_VERSION { return Err(AwosError::UnsupportedVersion); } + if header.manifest_len as usize > AWOS_MAX_MANIFEST { return Err(AwosError::OversizedManifest); } + if header.code_len as usize > AWOS_MAX_CODE { return Err(AwosError::OversizedCode); } + if header.data_len as usize > AWOS_MAX_DATA { return Err(AwosError::OversizedData); } + if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } + if header.flags & !AWOS_KNOWN_FLAGS != 0 { return Err(AwosError::UnknownFlags); } + if header.entry_offset >= header.code_len || header.code_len == 0 { return Err(AwosError::InvalidEntry); } + let expected = AWOS_HEADER_LEN.checked_add(header.manifest_len as usize).and_then(|v|v.checked_add(header.code_len as usize)).and_then(|v|v.checked_add(header.data_len as usize)).and_then(|v|v.checked_add(header.signature_len as usize)).ok_or(AwosError::InvalidLength)?; + if expected != bytes.len() { return Err(AwosError::InvalidLength); } Ok(header) } -#[allow(dead_code, clippy::needless_lifetimes, clippy::type_complexity)] -pub fn package_parts<'a>( - bytes: &'a [u8], - header: AwosHeader, -) -> Result<(&'a [u8], &'a [u8], &'a [u8], &'a [u8]), AwosError> { - let manifest_start = AWOS_HEADER_LEN; - let code_start = manifest_start - .checked_add(header.manifest_len as usize) - .ok_or(AwosError::InvalidLength)?; - let data_start = code_start - .checked_add(header.code_len as usize) - .ok_or(AwosError::InvalidLength)?; - let sig_start = data_start - .checked_add(header.data_len as usize) - .ok_or(AwosError::InvalidLength)?; - let end = sig_start - .checked_add(header.signature_len as usize) - .ok_or(AwosError::InvalidLength)?; - if end != bytes.len() { - return Err(AwosError::InvalidLength); - } - Ok(( - &bytes[manifest_start..code_start], - &bytes[code_start..data_start], - &bytes[data_start..sig_start], - &bytes[sig_start..end], - )) -} - -// ============================================================================ -// Publisher Identity & Cryptographic Signature Verification -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PublisherIdentity { - pub publisher_id: u64, - pub public_key: [u8; 32], - pub is_official: bool, -} - -impl PublisherIdentity { - pub fn verify_signature( - &self, - payload_bytes: &[u8], - signature_bytes: &[u8], - ) -> Result<(), AwosError> { - if signature_bytes.len() < AWOS_MIN_SIGNATURE { - return Err(AwosError::MissingSignature); - } - let sig_bytes: &[u8; 64] = signature_bytes[..64] - .try_into() - .map_err(|_| AwosError::MissingSignature)?; - - if awe_securityd::ed25519_verify(&self.public_key, payload_bytes, sig_bytes) { - Ok(()) - } else { - Err(AwosError::InvalidSignature) - } - } -} - -// ============================================================================ -// Sandboxing & Permission Constraints -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct SandboxProfile { - pub package_id: u64, - pub capability_mask: u64, - pub max_memory_pages: u32, - pub max_fds: u32, - pub allow_raw_sockets: bool, -} - -impl SandboxProfile { - pub const fn strict_default(package_id: u64) -> Self { - Self { - package_id, - capability_mask: 0x0003, // Read/Write - max_memory_pages: 1024, - max_fds: 16, - allow_raw_sockets: false, - } - } - - pub fn validate_access( - &self, - required_cap: u64, - pages_requested: u32, - ) -> Result<(), AwosError> { - if (self.capability_mask & required_cap) != required_cap { - return Err(AwosError::SandboxViolation); - } - if pages_requested > self.max_memory_pages { - return Err(AwosError::SandboxViolation); - } - Ok(()) - } -} - -// ============================================================================ -// Dependency Resolution & Repository Index -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageDependency { - pub dep_package_id: u64, - pub min_version: u16, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageMeta { - pub package_id: u64, - pub version: u16, - pub publisher: PublisherIdentity, - pub sandbox: SandboxProfile, - pub dependencies: [Option; MAX_PACKAGE_DEPS], - pub dep_count: usize, -} - -pub struct RepositoryIndex { - entries: [Option; MAX_INDEX_ENTRIES], -} - -impl RepositoryIndex { - pub const fn new() -> Self { - Self { - entries: [None; MAX_INDEX_ENTRIES], - } - } - - pub fn register(&mut self, meta: PackageMeta) -> Result<(), AwosError> { - for existing in self.entries.iter_mut().flatten() { - if existing.package_id == meta.package_id && existing.version == meta.version { - *existing = meta; - return Ok(()); - } - } - for slot in self.entries.iter_mut() { - if slot.is_none() { - *slot = Some(meta); - return Ok(()); - } - } - Err(AwosError::StorageFull) - } - - pub fn find(&self, package_id: u64) -> Option { - for slot in self.entries.iter().flatten() { - if slot.package_id == package_id { - return Some(*slot); - } - } - None - } - - pub fn resolve_dependencies(&self, root_id: u64) -> Result<[u64; MAX_PACKAGE_DEPS], AwosError> { - let mut resolved = [0u64; MAX_PACKAGE_DEPS]; - let mut count = 0; - - let mut stack = [0u64; MAX_PACKAGE_DEPS]; - let mut stack_top = 0; - - stack[0] = root_id; - stack_top += 1; - - while stack_top > 0 { - stack_top -= 1; - let current_id = stack[stack_top]; - - let meta = self.find(current_id).ok_or(AwosError::DependencyMissing)?; - - let mut already_added = false; - for r in resolved.iter().take(count) { - if *r == current_id { - already_added = true; - break; - } - } - - if !already_added { - if count >= MAX_PACKAGE_DEPS { - return Err(AwosError::StorageFull); - } - resolved[count] = current_id; - count += 1; - - for dep in meta.dependencies.iter().flatten() { - if stack_top >= MAX_PACKAGE_DEPS { - return Err(AwosError::DependencyCycle); - } - stack[stack_top] = dep.dep_package_id; - stack_top += 1; - } - } - } - - Ok(resolved) - } -} - -impl Default for RepositoryIndex { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================ -// Package State Transitions & Package Manager Lifecycle -// ============================================================================ +pub fn package_parts<'a>(bytes:&'a [u8],header:AwosHeader)->Result<(&'a [u8],&'a [u8],&'a [u8],&'a [u8]),AwosError>{let ms=AWOS_HEADER_LEN;let cs=ms.checked_add(header.manifest_len as usize).ok_or(AwosError::InvalidLength)?;let ds=cs.checked_add(header.code_len as usize).ok_or(AwosError::InvalidLength)?;let ss=ds.checked_add(header.data_len as usize).ok_or(AwosError::InvalidLength)?;let end=ss.checked_add(header.signature_len as usize).ok_or(AwosError::InvalidLength)?;if end!=bytes.len(){return Err(AwosError::InvalidLength)}Ok((&bytes[ms..cs],&bytes[cs..ds],&bytes[ds..ss],&bytes[ss..end]))} #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AppPackageState { - Installed, - Running, - Staged, - Failed, - Quarantined, - Removed, -} - -pub const fn package_transition(from: AppPackageState, to: AppPackageState) -> bool { - matches!( - (from, to), - (AppPackageState::Installed, AppPackageState::Running) - | (AppPackageState::Installed, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Failed) - | (AppPackageState::Staged, AppPackageState::Running) - | (AppPackageState::Staged, AppPackageState::Failed) - | (AppPackageState::Failed, AppPackageState::Staged) - | (AppPackageState::Failed, AppPackageState::Quarantined) - | (AppPackageState::Installed, AppPackageState::Removed) - ) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct InstalledAppRecord { - pub meta: PackageMeta, - pub state: AppPackageState, - pub active_version: u16, - pub backup_version: Option, -} - -pub struct AppPackageManager { - pub repository: RepositoryIndex, - installed: [Option; MAX_INSTALLED_PACKAGES], -} - -impl AppPackageManager { - pub const fn new() -> Self { - Self { - repository: RepositoryIndex::new(), - installed: [None; MAX_INSTALLED_PACKAGES], - } - } - - pub fn install_package(&mut self, bytes: &[u8], meta: PackageMeta) -> Result { - let header = validate_awos(bytes)?; - let (_manifest, code, _data, sig) = package_parts(bytes, header)?; - - // Verify cryptographic signature - meta.publisher.verify_signature(code, sig)?; - - // Register meta in repository index before dependency check - self.repository.register(meta)?; - - // Resolve dependencies - self.repository.resolve_dependencies(meta.package_id)?; - - // Store into installed table - for slot in self.installed.iter_mut() { - if slot.is_none() { - *slot = Some(InstalledAppRecord { - meta, - state: AppPackageState::Installed, - active_version: meta.version, - backup_version: None, - }); - return Ok(meta.package_id); - } - } - - Err(AwosError::StorageFull) - } - - pub fn uninstall_package(&mut self, package_id: u64) -> Result<(), AwosError> { - for slot in self.installed.iter_mut() { - if let Some(rec) = slot - && rec.meta.package_id == package_id - { - if !package_transition(rec.state, AppPackageState::Removed) { - return Err(AwosError::SandboxViolation); - } - *slot = None; - return Ok(()); - } - } - Err(AwosError::PackageNotFound) - } - - pub fn update_package( - &mut self, - new_meta: PackageMeta, - new_bytes: &[u8], - ) -> Result<(), AwosError> { - let header = validate_awos(new_bytes)?; - let (_manifest, code, _data, sig) = package_parts(new_bytes, header)?; - new_meta.publisher.verify_signature(code, sig)?; - - for slot in self.installed.iter_mut().flatten() { - if slot.meta.package_id == new_meta.package_id { - let old_version = slot.active_version; - slot.backup_version = Some(old_version); - slot.active_version = new_meta.version; - slot.meta = new_meta; - slot.state = AppPackageState::Installed; - let _ = self.repository.register(new_meta); - return Ok(()); - } - } - Err(AwosError::PackageNotFound) - } - - pub fn rollback_package(&mut self, package_id: u64) -> Result { - for slot in self.installed.iter_mut().flatten() { - if slot.meta.package_id == package_id { - if let Some(backup) = slot.backup_version { - slot.active_version = backup; - slot.backup_version = None; - slot.state = AppPackageState::Installed; - return Ok(backup); - } else { - return Err(AwosError::RollbackFailed); - } - } - } - Err(AwosError::PackageNotFound) - } - - pub fn get_installed_record(&self, package_id: u64) -> Result { - for slot in self.installed.iter().flatten() { - if slot.meta.package_id == package_id { - return Ok(*slot); - } - } - Err(AwosError::PackageNotFound) - } -} - -impl Default for AppPackageManager { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ +pub struct PublisherIdentity { pub publisher_id:u64,pub public_key:[u8;32],pub is_official:bool } +impl PublisherIdentity{pub fn verify_signature(&self,payload:&[u8],signature:&[u8])->Result<(),AwosError>{if signature.len()Self{Self{package_id,capability_mask:0x0003,max_memory_pages:1024,max_fds:16,allow_raw_sockets:false}}pub fn validate_access(&self,required_cap:u64,pages_requested:u32)->Result<(),AwosError>{if self.capability_mask&required_cap!=required_cap||pages_requested>self.max_memory_pages{return Err(AwosError::SandboxViolation)}Ok(())}} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct PackageDependency{pub dep_package_id:u64,pub min_version:u16} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct PackageMeta{pub package_id:u64,pub version:u16,pub publisher:PublisherIdentity,pub sandbox:SandboxProfile,pub dependencies:[Option;MAX_PACKAGE_DEPS],pub dep_count:usize} +pub struct RepositoryIndex{entries:[Option;MAX_INDEX_ENTRIES]} +impl RepositoryIndex{pub const fn new()->Self{Self{entries:[None;MAX_INDEX_ENTRIES]}}pub fn register(&mut self,meta:PackageMeta)->Result<(),AwosError>{for e in self.entries.iter_mut().flatten(){if e.package_id==meta.package_id&&e.version==meta.version{*e=meta;return Ok(())}}for s in self.entries.iter_mut(){if s.is_none(){*s=Some(meta);return Ok(())}}Err(AwosError::StorageFull)}pub fn find(&self,id:u64)->Option{self.entries.iter().flatten().find(|e|e.package_id==id).copied()}pub fn resolve_dependencies(&self,root:u64)->Result<[u64;MAX_PACKAGE_DEPS],AwosError>{let mut out=[0;MAX_PACKAGE_DEPS];let mut count=0;let mut stack=[0;MAX_PACKAGE_DEPS];let mut top=1;stack[0]=root;while top>0{top-=1;let id=stack[top];let meta=self.find(id).ok_or(AwosError::DependencyMissing)?;if out[..count].contains(&id){continue}if count>=MAX_PACKAGE_DEPS{return Err(AwosError::StorageFull)}out[count]=id;count+=1;for dep in meta.dependencies.iter().flatten(){if top>=MAX_PACKAGE_DEPS{return Err(AwosError::DependencyCycle)}stack[top]=dep.dep_package_id;top+=1}}}Ok(out)}} +impl Default for RepositoryIndex{fn default()->Self{Self::new()}} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub enum AppPackageState{Installed,Running,Staged,Failed,Quarantined,Removed} +pub const fn package_transition(from:AppPackageState,to:AppPackageState)->bool{matches!((from,to),(AppPackageState::Installed,AppPackageState::Running)|(AppPackageState::Installed,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Failed)|(AppPackageState::Staged,AppPackageState::Running)|(AppPackageState::Staged,AppPackageState::Failed)|(AppPackageState::Failed,AppPackageState::Staged)|(AppPackageState::Failed,AppPackageState::Quarantined)|(AppPackageState::Installed,AppPackageState::Removed))} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct InstalledAppRecord{pub meta:PackageMeta,pub state:AppPackageState,pub active_version:u16,pub backup_version:Option} +pub struct AppPackageManager{pub repository:RepositoryIndex,installed:[Option;MAX_INSTALLED_PACKAGES]} +impl AppPackageManager{pub const fn new()->Self{Self{repository:RepositoryIndex::new(),installed:[None;MAX_INSTALLED_PACKAGES]}}fn verify_package(&self,bytes:&[u8],meta:PackageMeta)->Result{if meta.publisher.publisher_id==0||!meta.publisher.is_official{return Err(AwosError::PublisherUntrusted)}let h=validate_awos(bytes)?;let(_,_,_,sig)=package_parts(bytes,h)?;let end=bytes.len().checked_sub(sig.len()).ok_or(AwosError::InvalidLength)?;meta.publisher.verify_signature(&bytes[..end],sig)?;Ok(h)}pub fn install_package(&mut self,bytes:&[u8],meta:PackageMeta)->Result{self.verify_package(bytes,meta)?;if self.installed.iter().flatten().any(|r|r.meta.package_id==meta.package_id){return Err(AwosError::AlreadyInstalled)}self.repository.register(meta)?;self.repository.resolve_dependencies(meta.package_id)?;for s in self.installed.iter_mut(){if s.is_none(){*s=Some(InstalledAppRecord{meta,state:AppPackageState::Installed,active_version:meta.version,backup_version:None});return Ok(meta.package_id)}}Err(AwosError::StorageFull)}pub fn uninstall_package(&mut self,id:u64)->Result<(),AwosError>{for s in self.installed.iter_mut(){if let Some(r)=s&&r.meta.package_id==id{if !package_transition(r.state,AppPackageState::Removed){return Err(AwosError::SandboxViolation)}*s=None;return Ok(())}}Err(AwosError::PackageNotFound)}pub fn update_package(&mut self,meta:PackageMeta,bytes:&[u8])->Result<(),AwosError>{self.verify_package(bytes,meta)?;for s in self.installed.iter_mut().flatten(){if s.meta.package_id==meta.package_id{if meta.version<=s.active_version{return Err(AwosError::InvalidEntry)}let old=s.active_version;s.backup_version=Some(old);s.active_version=meta.version;s.meta=meta;s.state=AppPackageState::Installed;self.repository.register(meta)?;return Ok(())}}Err(AwosError::PackageNotFound)}pub fn rollback_package(&mut self,id:u64)->Result{for s in self.installed.iter_mut().flatten(){if s.meta.package_id==id{if let Some(v)=s.backup_version{s.active_version=v;s.backup_version=None;s.state=AppPackageState::Installed;return Ok(v)}return Err(AwosError::RollbackFailed)}}Err(AwosError::PackageNotFound)}pub fn get_installed_record(&self,id:u64)->Result{self.installed.iter().flatten().find(|r|r.meta.package_id==id).copied().ok_or(AwosError::PackageNotFound)}} +impl Default for AppPackageManager{fn default()->Self{Self::new()}} #[cfg(test)] -mod tests { - use super::*; - extern crate std; - use std::vec; - use std::vec::Vec; - - fn build_awos_bytes( - manifest: usize, - code: usize, - data: usize, - sig: usize, - sig_byte0: u8, - ) -> Vec { - let mut b = Vec::with_capacity(AWOS_HEADER_LEN + manifest + code + data + sig); - b.extend_from_slice(&AWOS_MAGIC); - b.extend_from_slice(&AWOS_VERSION.to_le_bytes()); - b.extend_from_slice(&1u16.to_le_bytes()); - b.extend_from_slice(&0u16.to_le_bytes()); - b.extend_from_slice(&(manifest as u32).to_le_bytes()); - b.extend_from_slice(&(code as u32).to_le_bytes()); - b.extend_from_slice(&(data as u32).to_le_bytes()); - b.extend_from_slice(&(sig as u16).to_le_bytes()); - b.extend_from_slice(&0u32.to_le_bytes()); - b.extend_from_slice(&0u32.to_le_bytes()); - - // Manifest - b.extend(core::iter::repeat_n(0u8, manifest)); - // Code - let code_bytes = vec![0x90u8; code]; - b.extend_from_slice(&code_bytes); - // Data - b.extend(core::iter::repeat_n(0u8, data)); - - // Sig - let mut sig_vec = vec![0u8; sig]; - sig_vec[0] = sig_byte0; - b.extend_from_slice(&sig_vec); - - b - } - - #[test] - fn test_awos_package_install_update_rollback_uninstall() { - let seed = [0x77u8; 32]; - let (pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); - - let pub_id = PublisherIdentity { - publisher_id: 100, - public_key: pk, - is_official: true, - }; - - let code_bytes = vec![0x90u8; 8]; - let real_sig = awe_securityd::ed25519_sign(&sk, &code_bytes); - - let mut bytes_v1 = build_awos_bytes(4, 8, 2, 64, 0); - let sig_offset_v1 = AWOS_HEADER_LEN + 4 + 8 + 2; - bytes_v1[sig_offset_v1..sig_offset_v1 + 64].copy_from_slice(&real_sig); - - let meta_v1 = PackageMeta { - package_id: 1001, - version: 1, - publisher: pub_id, - sandbox: SandboxProfile::strict_default(1001), - dependencies: [None; MAX_PACKAGE_DEPS], - dep_count: 0, - }; - - let mut mgr = AppPackageManager::new(); - mgr.install_package(&bytes_v1, meta_v1).expect("install v1"); - - let rec = mgr.get_installed_record(1001).unwrap(); - assert_eq!(rec.active_version, 1); - - // Update to v2 - let meta_v2 = PackageMeta { - version: 2, - ..meta_v1 - }; - let mut bytes_v2 = build_awos_bytes(4, 8, 2, 64, 0); - bytes_v2[sig_offset_v1..sig_offset_v1 + 64].copy_from_slice(&real_sig); - - mgr.update_package(meta_v2, &bytes_v2).expect("update v2"); - let rec2 = mgr.get_installed_record(1001).unwrap(); - assert_eq!(rec2.active_version, 2); - assert_eq!(rec2.backup_version, Some(1)); - - // Rollback - let rolled = mgr.rollback_package(1001).expect("rollback"); - assert_eq!(rolled, 1); - assert_eq!(mgr.get_installed_record(1001).unwrap().active_version, 1); - - // Uninstall - mgr.uninstall_package(1001).expect("uninstall"); - assert_eq!( - mgr.get_installed_record(1001), - Err(AwosError::PackageNotFound) - ); - } -} +mod tests{use super::*;extern crate std;use std::vec::Vec;fn bytes(m:usize,c:usize,d:usize)->Vec{let mut b=Vec::new();b.extend_from_slice(&AWOS_MAGIC);b.extend_from_slice(&AWOS_VERSION.to_le_bytes());b.extend_from_slice(&1u16.to_le_bytes());b.extend_from_slice(&0u16.to_le_bytes());b.extend_from_slice(&(m as u32).to_le_bytes());b.extend_from_slice(&(c as u32).to_le_bytes());b.extend_from_slice(&(d as u32).to_le_bytes());b.extend_from_slice(&64u16.to_le_bytes());b.extend_from_slice(&0u32.to_le_bytes());b.extend_from_slice(&0u32.to_le_bytes());b.extend(core::iter::repeat_n(0u8,m));b.extend(core::iter::repeat_n(0x90u8,c));b.extend(core::iter::repeat_n(0u8,d));b.extend(core::iter::repeat_n(0u8,64));b}#[test]fn signature_binds_full_payload(){let seed=[7u8;32];let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed);let p=PublisherIdentity{publisher_id:1,public_key:pk,is_official:true};let mut b=bytes(4,8,2);let n=b.len()-64;let sig=awe_securityd::ed25519_sign(&sk,&b[..n]);b[n..].copy_from_slice(&sig);let meta=PackageMeta{package_id:1,version:1,publisher:p,sandbox:SandboxProfile::strict_default(1),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0};let mut m=AppPackageManager::new();m.install_package(&b,meta).unwrap();assert_eq!(m.get_installed_record(1).unwrap().active_version,1);b[33]^=1;assert_eq!(m.update_package(meta,&b),Err(AwosError::InvalidSignature))}} diff --git a/services/appd/src/lib.rs b/services/appd/src/lib.rs index 643a701..c1f56d4 100644 --- a/services/appd/src/lib.rs +++ b/services/appd/src/lib.rs @@ -5,6 +5,7 @@ mod awos; pub mod catalog; +pub mod runtime; pub use awos::{ AWOS_HEADER_LEN, AWOS_MAGIC, AWOS_MAX_CODE, AWOS_MAX_DATA, AWOS_MAX_MANIFEST, @@ -14,6 +15,8 @@ pub use awos::{ package_parts, package_transition, validate_awos, }; +pub use runtime::{RuntimeApp, RuntimeError as AppRuntimeError, SpawnFn as AppSpawnFn, Supervisor as AppSupervisor, WindowFn as AppWindowFn, MAX_APPS as RUNTIME_MAX_APPS, MAX_FAILURES as APP_MAX_FAILURES}; + pub const AWE_APP_ABI_MAJOR: u16 = 1; pub const AWE_APP_ABI_MINOR: u16 = 3; pub const MAX_DEPS: usize = 32; diff --git a/services/appd/src/runtime.rs b/services/appd/src/runtime.rs new file mode 100644 index 0000000..3005b73 --- /dev/null +++ b/services/appd/src/runtime.rs @@ -0,0 +1,83 @@ +use super::{validate_manifest, AppId, AppManifest, AppState}; + +pub const MAX_APPS: usize = 32; +pub const MAX_FAILURES: u8 = 3; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RuntimeError { Full, InvalidManifest, Duplicate, NotFound, CapabilityDenied, SpawnFailed, InvalidTransition, Quarantined } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RuntimeApp { pub manifest: AppManifest, pub state: AppState, pub process_id: u64, pub window_id: u16, pub failures: u8 } + +pub type SpawnFn = fn(app: AppId, memory_pages: u32, capability_mask: u64) -> Result; +pub type WindowFn = fn(app: AppId) -> Result; + +pub struct Supervisor { apps: [Option; MAX_APPS], count: usize, spawn: SpawnFn, create_window: WindowFn } +impl Supervisor { + pub const fn new(spawn: SpawnFn, create_window: WindowFn) -> Self { Self { apps: [None; MAX_APPS], count: 0, spawn, create_window } } + pub const fn len(&self) -> usize { self.count } + pub fn admit(&mut self, manifest: AppManifest) -> Result<(), RuntimeError> { + validate_manifest(manifest).map_err(|_| RuntimeError::InvalidManifest)?; + if self.find(manifest.id).is_some() { return Err(RuntimeError::Duplicate); } + let slot = self.apps.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; + self.apps[slot] = Some(RuntimeApp { manifest, state: AppState::Installed, process_id: 0, window_id: 0, failures: 0 }); + self.count += 1; Ok(()) + } + pub fn start(&mut self, id: AppId, caller_capabilities: u64) -> Result { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].unwrap(); + if record.state == AppState::Quarantined { return Err(RuntimeError::Quarantined); } + if record.manifest.capability_mask & !caller_capabilities != 0 { return Err(RuntimeError::CapabilityDenied); } + if !matches!(record.state, AppState::Installed | AppState::Stopped | AppState::Failed) { return Err(RuntimeError::InvalidTransition); } + self.apps[index].as_mut().unwrap().state = AppState::Starting; + let pid = match (self.spawn)(id, record.manifest.memory_limit_pages, record.manifest.capability_mask) { + Ok(pid) if pid != 0 => pid, + _ => { self.apps[index].as_mut().unwrap().state = AppState::Failed; return Err(RuntimeError::SpawnFailed); } + }; + let window = match (self.create_window)(id) { + Ok(window) if window != 0 => window, + _ => { self.apps[index].as_mut().unwrap().state = AppState::Failed; return Err(RuntimeError::SpawnFailed); } + }; + let record = self.apps[index].as_mut().unwrap(); + record.process_id = pid; + record.window_id = window; + record.state = AppState::Running; + Ok(pid) + } + pub fn stop(&mut self, id: AppId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state != AppState::Running { return Err(RuntimeError::InvalidTransition); } + record.state = AppState::Stopped; record.process_id = 0; Ok(()) + } + pub fn report_failure(&mut self, id: AppId) -> Result { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state != AppState::Running { return Err(RuntimeError::InvalidTransition); } + record.failures = record.failures.saturating_add(1); + record.state = if record.failures > MAX_FAILURES { AppState::Quarantined } else { AppState::Failed }; + Ok(record.state) + } + pub fn reset_failure_count(&mut self, id: AppId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state == AppState::Running || record.state == AppState::Starting { return Err(RuntimeError::InvalidTransition); } + record.failures = 0; + Ok(()) + } + pub fn state(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i].map(|r| r.state)) } + pub fn record(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i]) } + fn find(&self, id: AppId) -> Option { self.apps.iter().position(|r| r.map(|a| a.manifest.id) == Some(id)) } +} + +#[cfg(test)] +mod tests { + use super::*; + fn spawn(app: AppId, memory: u32, _caps: u64) -> Result { if app.0 == 0 || memory == 0 { Err(()) } else { Ok(0x1000 + app.0) } } + fn window(app: AppId) -> Result { if app.0 == 0 || app.0 > u16::MAX as u64 { Err(()) } else { Ok(app.0 as u16) } } + fn manifest(id: u64) -> AppManifest { AppManifest { id: AppId(id), abi_major: 1, abi_minor: 3, memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0 } } + #[test] fn admission_start_and_stop_are_real_lifecycle_steps() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(1)).unwrap(); let pid = s.start(AppId(1), 0b111).unwrap(); assert_eq!(pid, 0x1001); assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); assert_eq!(s.state(AppId(1)), Some(AppState::Running)); s.stop(AppId(1)).unwrap(); assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); } + #[test] fn capability_admission_is_fail_closed() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(2)).unwrap(); assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); } + #[test] fn repeated_failures_quarantine_app() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(3)).unwrap(); s.start(AppId(3), 0b111).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); s.start(AppId(3), 0b111).unwrap(); } assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); } + #[test] fn intentional_reset_is_explicit() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(4)).unwrap(); s.start(AppId(4), 0b111).unwrap(); s.report_failure(AppId(4)).unwrap(); s.start(AppId(4), 0b111).unwrap(); assert_eq!(s.record(AppId(4)).unwrap().failures, 1); s.reset_failure_count(AppId(4)).unwrap(); assert_eq!(s.record(AppId(4)).unwrap().failures, 0); } +} \ No newline at end of file diff --git a/services/initd/src/lib.rs b/services/initd/src/lib.rs index ec88581..193e263 100644 --- a/services/initd/src/lib.rs +++ b/services/initd/src/lib.rs @@ -4,12 +4,15 @@ //! primitives needed for isolation; policy and lifecycle stay in userspace. mod core; +pub mod runtime; pub use core::{ BoundedPath, CoreError, CoreManager, CoreManagerKind, CoreManagerRegistry, CrashRecord, LogLevel, LogRecord, MAX_CORE_MANAGERS, MAX_LOG_MESSAGE, MAX_PATH, RecoveryAction, SecurityPolicy, UserImage, recovery_action, start_core_manager, validate_user_image, }; +pub use runtime::{RuntimeError, RuntimeRecord, ServiceRuntimeSpec, SpawnFn, Supervisor}; + pub const INIT_ABI_MAJOR: u16 = 1; pub const INIT_ABI_MINOR: u16 = 3; pub const MAX_SERVICES: usize = 32; @@ -221,12 +224,8 @@ mod tests { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); assert_eq!(table.len(), 1); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); - table - .set_state(ServiceId(1), ServiceState::Running) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); + table.set_state(ServiceId(1), ServiceState::Running).unwrap(); assert_eq!(table.spec(ServiceId(1)), Some(SPEC)); } @@ -234,9 +233,7 @@ mod tests { fn failed_service_restarts_only_when_policy_allows() { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); table.set_state(ServiceId(1), ServiceState::Failed).unwrap(); table.restart(ServiceId(1)).unwrap(); assert_eq!(table.state(ServiceId(1)), Some(ServiceState::Starting)); @@ -246,13 +243,9 @@ mod tests { fn quarantined_service_cannot_restart() { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); table.set_state(ServiceId(1), ServiceState::Failed).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Quarantined) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Quarantined).unwrap(); assert_eq!(table.restart(ServiceId(1)), Err(ServiceError::Quarantined)); } @@ -262,4 +255,4 @@ mod tests { table.register(SPEC).unwrap(); assert_eq!(table.register(SPEC), Err(ServiceError::Duplicate)); } -} +} \ No newline at end of file diff --git a/services/initd/src/runtime.rs b/services/initd/src/runtime.rs new file mode 100644 index 0000000..f9a9569 --- /dev/null +++ b/services/initd/src/runtime.rs @@ -0,0 +1,83 @@ +use super::{RestartPolicy, ServiceId, ServiceSpec, ServiceState, validate_spec}; + +pub const MAX_DEPENDENCIES: usize = 8; +pub const MAX_RUNTIME_SERVICES: usize = 8; +pub const MAX_FAILURES: u8 = 3; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RuntimeError { Full, Duplicate, InvalidSpec, InvalidDependency, DependencyCycle, MissingDependency, SpawnFailed, InvalidTransition, Quarantined } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ServiceRuntimeSpec { pub spec: ServiceSpec, pub dependencies: [Option; MAX_DEPENDENCIES], pub dependency_count: u8, pub entry: usize } +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RuntimeRecord { pub spec: ServiceRuntimeSpec, pub state: ServiceState, pub process_id: u64, pub failures: u8 } +pub type SpawnFn = fn(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result; + +pub struct Supervisor { records: [Option; MAX_RUNTIME_SERVICES], count: usize, spawn: SpawnFn } +impl Supervisor { + pub const fn new(spawn: SpawnFn) -> Self { Self { records: [None; MAX_RUNTIME_SERVICES], count: 0, spawn } } + pub const fn len(&self) -> usize { self.count } + pub fn register(&mut self, runtime: ServiceRuntimeSpec) -> Result<(), RuntimeError> { + validate_spec(runtime.spec).map_err(|_| RuntimeError::InvalidSpec)?; + if runtime.dependency_count as usize > MAX_DEPENDENCIES || runtime.entry == 0 { return Err(RuntimeError::InvalidSpec); } + if self.find(runtime.spec.id).is_some() { return Err(RuntimeError::Duplicate); } + for index in 0..runtime.dependency_count as usize { let dep = runtime.dependencies[index].ok_or(RuntimeError::InvalidDependency)?; if dep == runtime.spec.id { return Err(RuntimeError::DependencyCycle); } } + let slot = self.records.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; + self.records[slot] = Some(RuntimeRecord { spec: runtime, state: ServiceState::Declared, process_id: 0, failures: 0 }); + self.count += 1; Ok(()) + } + pub fn start(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let runtime = self.records[index].ok_or(RuntimeError::MissingDependency)?; + if runtime.state == ServiceState::Quarantined { return Err(RuntimeError::Quarantined); } + for dependency_index in 0..runtime.spec.dependency_count as usize { + let dep = runtime.spec.dependencies[dependency_index].ok_or(RuntimeError::InvalidDependency)?; + let dep_index = self.find(dep).ok_or(RuntimeError::MissingDependency)?; + if self.records[dep_index].ok_or(RuntimeError::MissingDependency)?.state != ServiceState::Running { return Err(RuntimeError::MissingDependency); } + } + if !matches!(runtime.state, ServiceState::Declared | ServiceState::Failed | ServiceState::Stopped) { return Err(RuntimeError::InvalidTransition); } + self.records[index].as_mut().unwrap().state = ServiceState::Starting; + let pid = match (self.spawn)(runtime.spec.entry, id, runtime.spec.spec.memory_limit_pages, runtime.spec.spec.cpu_budget_ticks) { + Ok(pid) if pid != 0 => pid, + _ => { self.records[index].as_mut().unwrap().state = ServiceState::Failed; return Err(RuntimeError::SpawnFailed); } + }; + let record = self.records[index].as_mut().unwrap(); + record.process_id = pid; + record.state = ServiceState::Running; + Ok(pid) + } + pub fn report_failure(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].as_mut().unwrap(); + if record.state != ServiceState::Running { return Err(RuntimeError::InvalidTransition); } + record.failures = record.failures.saturating_add(1); + record.state = if record.failures > MAX_FAILURES { ServiceState::Quarantined } else { ServiceState::Failed }; + Ok(record.state) + } + pub fn reset_failure_count(&mut self, id: ServiceId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].as_mut().unwrap(); + if record.state == ServiceState::Running || record.state == ServiceState::Starting { return Err(RuntimeError::InvalidTransition); } + record.failures = 0; + Ok(()) + } + pub fn restart(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].ok_or(RuntimeError::MissingDependency)?; + if record.state == ServiceState::Quarantined || record.failures > MAX_FAILURES { return Err(RuntimeError::Quarantined); } + match record.spec.spec.restart { RestartPolicy::Never => Err(RuntimeError::InvalidTransition), RestartPolicy::OnFailure | RestartPolicy::Always => self.start(id) } + } + pub fn state(&self, id: ServiceId) -> Option { self.find(id).and_then(|i| self.records[i].map(|r| r.state)) } + pub fn process_id(&self, id: ServiceId) -> Option { self.find(id).and_then(|i| self.records[i].map(|r| r.process_id)).filter(|pid| *pid != 0) } + fn find(&self, id: ServiceId) -> Option { self.records.iter().position(|r| r.map(|record| record.spec.spec.id) == Some(id)) } +} + +#[cfg(test)] +mod tests { + use super::*; + fn spawn(entry: usize, service: ServiceId, _mem: u32, _cpu: u32) -> Result { if entry == 0 || service.0 == 0 { Err(()) } else { Ok((entry as u64) ^ service.0 as u64) } } + fn spec(id: u16, deps: [Option; MAX_DEPENDENCIES], count: u8) -> ServiceRuntimeSpec { ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(id), restart: RestartPolicy::OnFailure, capability_mask: u64::MAX, memory_limit_pages: 4, cpu_budget_ticks: 100 }, dependencies: deps, dependency_count: count, entry: id as usize + 1 } } + #[test] fn dependencies_are_enforced() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.register(spec(2,[Some(ServiceId(1)),None,None,None,None,None,None,None],1)).unwrap(); assert_eq!(s.start(ServiceId(2)),Err(RuntimeError::MissingDependency)); s.start(ServiceId(1)).unwrap(); assert!(s.start(ServiceId(2)).is_ok()); } + #[test] fn failure_escalates_to_quarantine() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.start(ServiceId(1)).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Failed); s.start(ServiceId(1)).unwrap(); } assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Quarantined); } + #[test] fn intentional_reset_is_explicit() { let mut s=Supervisor::new(spawn); s.register(spec(2,[None;MAX_DEPENDENCIES],0)).unwrap(); s.start(ServiceId(2)).unwrap(); s.report_failure(ServiceId(2)).unwrap(); s.start(ServiceId(2)).unwrap(); assert_eq!(s.records[s.find(ServiceId(2)).unwrap()].unwrap().failures,1); s.reset_failure_count(ServiceId(2)).unwrap(); assert_eq!(s.records[s.find(ServiceId(2)).unwrap()].unwrap().failures,0); } +} \ No newline at end of file diff --git a/services/netd/src/lib.rs b/services/netd/src/lib.rs index 4a2a738..fd55f76 100644 --- a/services/netd/src/lib.rs +++ b/services/netd/src/lib.rs @@ -1,10 +1,9 @@ -//! AWEOS User-Space Network Service (`netd`) -//! -//! Manages network interfaces, socket tables, ARP cache, IPv4/UDP/TCP routing, -//! and firewall policy enforcement in user-space. - +//! AWEOS User-Space Network Service (`netd`). #![no_std] +pub mod packet; +pub use packet::{build_udp_ipv4, checksum16, EthernetFrame, Ipv4Header, PacketError, UdpHeader, ETH_HEADER, IPV4_MIN_HEADER, MAX_PACKET, UDP_HEADER}; + pub const MAX_INTERFACES: usize = 4; pub const MAX_SOCKETS: usize = 32; pub const MAX_FIREWALL_RULES: usize = 16; @@ -12,32 +11,18 @@ pub const MAX_PACKET_LEN: usize = 1514; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MacAddress(pub [u8; 6]); - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Ipv4Address(pub [u8; 4]); - impl Ipv4Address { pub const UNSPECIFIED: Self = Self([0, 0, 0, 0]); pub const LOOPBACK: Self = Self([127, 0, 0, 1]); - - pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self { - Self([a, b, c, d]) - } + pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self { Self([a, b, c, d]) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SocketProtocol { - Udp, - Tcp, -} - +pub enum SocketProtocol { Udp, Tcp } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SocketState { - Closed, - Bound, - Listening, - Connected, -} +pub enum SocketState { Closed, Bound, Listening, Connected } #[derive(Debug, Clone, Copy)] pub struct NetworkSocket { @@ -50,38 +35,18 @@ pub struct NetworkSocket { pub state: SocketState, pub owner_pid: u32, } - impl NetworkSocket { pub const fn new(socket_id: u32, protocol: SocketProtocol, owner_pid: u32) -> Self { - Self { - socket_id, - protocol, - local_port: 0, - remote_port: 0, - local_ip: Ipv4Address::UNSPECIFIED, - remote_ip: Ipv4Address::UNSPECIFIED, - state: SocketState::Closed, - owner_pid, - } + Self { socket_id, protocol, local_port: 0, remote_port: 0, local_ip: Ipv4Address::UNSPECIFIED, remote_ip: Ipv4Address::UNSPECIFIED, state: SocketState::Closed, owner_pid } } - pub fn bind(&mut self, ip: Ipv4Address, port: u16) -> Result<(), &'static str> { - if self.state != SocketState::Closed { - return Err("Socket already bound or active"); - } - self.local_ip = ip; - self.local_port = port; - self.state = SocketState::Bound; - Ok(()) + if self.state != SocketState::Closed { return Err("Socket already bound or active"); } + self.local_ip = ip; self.local_port = port; self.state = SocketState::Bound; Ok(()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FirewallAction { - Allow, - Deny, -} - +pub enum FirewallAction { Allow, Deny } #[derive(Debug, Clone, Copy)] pub struct FirewallRule { pub rule_id: u32, @@ -90,19 +55,13 @@ pub struct FirewallRule { pub port_range_end: u16, pub action: FirewallAction, } - impl FirewallRule { pub fn matches(&self, protocol: SocketProtocol, port: u16) -> bool { - if let Some(p) = self.protocol - && p != protocol - { - return false; - } + if let Some(p) = self.protocol && p != protocol { return false; } port >= self.port_range_start && port <= self.port_range_end } } -/// Network Daemon Manager Instance. #[derive(Debug)] pub struct NetworkDaemon { interfaces: [Option; MAX_INTERFACES], @@ -111,109 +70,60 @@ pub struct NetworkDaemon { socket_counter: u32, rule_counter: u32, } - impl NetworkDaemon { pub const fn new() -> Self { - Self { - interfaces: [None; MAX_INTERFACES], - sockets: [None; MAX_SOCKETS], - firewall_rules: [None; MAX_FIREWALL_RULES], - socket_counter: 1, - rule_counter: 1, - } + Self { interfaces: [None; MAX_INTERFACES], sockets: [None; MAX_SOCKETS], firewall_rules: [None; MAX_FIREWALL_RULES], socket_counter: 1, rule_counter: 1 } } - pub fn add_interface(&mut self, mac: MacAddress) -> Result { - for (idx, slot) in self.interfaces.iter_mut().enumerate() { - if slot.is_none() { - *slot = Some(mac); - return Ok(idx); - } - } + for (idx, slot) in self.interfaces.iter_mut().enumerate() { if slot.is_none() { *slot = Some(mac); return Ok(idx); } } Err("No free interface slots") } - - pub fn create_socket( - &mut self, - protocol: SocketProtocol, - owner_pid: u32, - ) -> Result { + pub fn create_socket(&mut self, protocol: SocketProtocol, owner_pid: u32) -> Result { let sid = self.socket_counter; - for slot in self.sockets.iter_mut() { - if slot.is_none() { - *slot = Some(NetworkSocket::new(sid, protocol, owner_pid)); - self.socket_counter += 1; - return Ok(sid); - } - } + for slot in self.sockets.iter_mut() { if slot.is_none() { *slot = Some(NetworkSocket::new(sid, protocol, owner_pid)); self.socket_counter = self.socket_counter.saturating_add(1); return Ok(sid); } } Err("Socket table full") } - - pub fn add_firewall_rule( - &mut self, - protocol: Option, - port_start: u16, - port_end: u16, - action: FirewallAction, - ) -> Result { + pub fn add_firewall_rule(&mut self, protocol: Option, port_start: u16, port_end: u16, action: FirewallAction) -> Result { + if port_start > port_end { return Err("Invalid port range"); } let rid = self.rule_counter; - for slot in self.firewall_rules.iter_mut() { - if slot.is_none() { - *slot = Some(FirewallRule { - rule_id: rid, - protocol, - port_range_start: port_start, - port_range_end: port_end, - action, - }); - self.rule_counter += 1; - return Ok(rid); - } - } + for slot in self.firewall_rules.iter_mut() { if slot.is_none() { *slot = Some(FirewallRule { rule_id: rid, protocol, port_range_start: port_start, port_range_end: port_end, action }); self.rule_counter = self.rule_counter.saturating_add(1); return Ok(rid); } } Err("Firewall rule capacity reached") } - pub fn evaluate_packet(&self, protocol: SocketProtocol, dst_port: u16) -> FirewallAction { - for rule in self.firewall_rules.iter().flatten() { - if rule.matches(protocol, dst_port) { - return rule.action; - } - } - FirewallAction::Deny // Fail-closed by default + for rule in self.firewall_rules.iter().flatten() { if rule.matches(protocol, dst_port) { return rule.action; } } + FirewallAction::Deny } -} - -impl Default for NetworkDaemon { - fn default() -> Self { - Self::new() + pub fn validate_ipv4_udp(&self, frame: &[u8], expected_destination: Option) -> Result<(Ipv4Header, UdpHeader), PacketError> { + let eth = EthernetFrame::parse(frame)?; + if eth.ethertype != 0x0800 { return Err(PacketError::UnsupportedProtocol); } + let ip = Ipv4Header::parse(&frame[ETH_HEADER..])?; + if ip.protocol != 17 { return Err(PacketError::UnsupportedProtocol); } + let udp_start = ETH_HEADER + ip.header_len; + let udp = UdpHeader::parse(&frame[udp_start..])?; + if let Some(port) = expected_destination && udp.destination_port != port { return Err(PacketError::InvalidLength); } + if self.evaluate_packet(SocketProtocol::Udp, udp.destination_port) != FirewallAction::Allow { return Err(PacketError::UnsupportedProtocol); } + Ok((ip, udp)) } } +impl Default for NetworkDaemon { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_netd_socket_and_firewall() { + fn socket_firewall_and_packet_engine_work() { let mut netd = NetworkDaemon::new(); - netd.add_interface(MacAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])) - .unwrap(); - - let sock_id = netd.create_socket(SocketProtocol::Udp, 100).unwrap(); - assert_eq!(sock_id, 1); - - // Deny by default - assert_eq!( - netd.evaluate_packet(SocketProtocol::Udp, 80), - FirewallAction::Deny - ); - - // Add rule to allow HTTP (port 80) - netd.add_firewall_rule(Some(SocketProtocol::Udp), 80, 80, FirewallAction::Allow) - .unwrap(); - assert_eq!( - netd.evaluate_packet(SocketProtocol::Udp, 80), - FirewallAction::Allow - ); + netd.add_interface(MacAddress([0x02,0,0,0,0,1])).unwrap(); + assert_eq!(netd.create_socket(SocketProtocol::Udp, 100).unwrap(), 1); + netd.add_firewall_rule(Some(SocketProtocol::Udp), 2000, 2000, FirewallAction::Allow).unwrap(); + let mut frame = [0u8; MAX_PACKET_LEN]; + let n = build_udp_ipv4(&mut frame, [1;6], [2;6], [10,0,0,1], [10,0,0,2], 1000, 2000, b"hello").unwrap(); + let (_, udp) = netd.validate_ipv4_udp(&frame[..n], Some(2000)).unwrap(); + assert_eq!(udp.source_port, 1000); assert_eq!(udp.destination_port, 2000); } -} + #[test] + fn invalid_port_range_is_rejected() { + let mut netd = NetworkDaemon::new(); + assert!(netd.add_firewall_rule(None, 200, 100, FirewallAction::Allow).is_err()); + } +} \ No newline at end of file diff --git a/services/netd/src/packet.rs b/services/netd/src/packet.rs new file mode 100644 index 0000000..4a59ec1 --- /dev/null +++ b/services/netd/src/packet.rs @@ -0,0 +1,94 @@ +pub const ETH_HEADER: usize = 14; +pub const IPV4_MIN_HEADER: usize = 20; +pub const UDP_HEADER: usize = 8; +pub const MAX_PACKET: usize = 1514; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PacketError { TooShort, InvalidEthernet, InvalidIpv4, InvalidChecksum, InvalidLength, UnsupportedProtocol, OutputTooSmall } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct EthernetFrame { pub destination: [u8; 6], pub source: [u8; 6], pub ethertype: u16 } +impl EthernetFrame { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < ETH_HEADER { return Err(PacketError::TooShort); } + Ok(Self { destination: bytes[0..6].try_into().map_err(|_| PacketError::InvalidEthernet)?, source: bytes[6..12].try_into().map_err(|_| PacketError::InvalidEthernet)?, ethertype: u16::from_be_bytes([bytes[12], bytes[13]]) }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Ipv4Header { pub source: [u8;4], pub destination: [u8;4], pub total_length: u16, pub protocol: u8, pub header_len: usize } +impl Ipv4Header { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < IPV4_MIN_HEADER { return Err(PacketError::TooShort); } + if bytes[0] >> 4 != 4 { return Err(PacketError::InvalidIpv4); } + let header_len = ((bytes[0] & 0x0f) as usize) * 4; + if header_len < IPV4_MIN_HEADER || header_len > bytes.len() { return Err(PacketError::InvalidIpv4); } + let total_length = u16::from_be_bytes([bytes[2], bytes[3]]); + if (total_length as usize) > bytes.len() || (total_length as usize) < header_len { return Err(PacketError::InvalidLength); } + let expected = u16::from_be_bytes([bytes[10], bytes[11]]); + let mut header = [0u8; 60]; + header[..header_len].copy_from_slice(&bytes[..header_len]); + header[10] = 0; header[11] = 0; + if checksum16(&header[..header_len]) != expected { return Err(PacketError::InvalidChecksum); } + Ok(Self { source: bytes[12..16].try_into().map_err(|_| PacketError::InvalidIpv4)?, destination: bytes[16..20].try_into().map_err(|_| PacketError::InvalidIpv4)?, total_length, protocol: bytes[9], header_len }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UdpHeader { pub source_port: u16, pub destination_port: u16, pub length: u16 } +impl UdpHeader { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < UDP_HEADER { return Err(PacketError::TooShort); } + let length = u16::from_be_bytes([bytes[4], bytes[5]]); + if length < UDP_HEADER as u16 || (length as usize) > bytes.len() { return Err(PacketError::InvalidLength); } + Ok(Self { source_port: u16::from_be_bytes([bytes[0], bytes[1]]), destination_port: u16::from_be_bytes([bytes[2], bytes[3]]), length }) + } +} + +pub fn checksum16(bytes: &[u8]) -> u16 { + let mut sum: u32 = 0; + let mut i = 0; + while i + 1 < bytes.len() { sum = sum.wrapping_add(u16::from_be_bytes([bytes[i], bytes[i + 1]]) as u32); i += 2; } + if i < bytes.len() { sum = sum.wrapping_add((bytes[i] as u32) << 8); } + while (sum >> 16) != 0 { sum = (sum & 0xffff) + (sum >> 16); } + !(sum as u16) +} + +pub fn build_udp_ipv4(out: &mut [u8], source_mac: [u8; 6], destination_mac: [u8; 6], source_ip: [u8; 4], destination_ip: [u8; 4], source_port: u16, destination_port: u16, payload: &[u8]) -> Result { + let total = ETH_HEADER.checked_add(IPV4_MIN_HEADER).and_then(|v| v.checked_add(UDP_HEADER)).and_then(|v| v.checked_add(payload.len())).ok_or(PacketError::InvalidLength)?; + if total > MAX_PACKET || out.len() < total { return Err(PacketError::OutputTooSmall); } + out[0..6].copy_from_slice(&destination_mac); out[6..12].copy_from_slice(&source_mac); out[12..14].copy_from_slice(&0x0800u16.to_be_bytes()); + let ip = ETH_HEADER; + out[ip] = 0x45; out[ip+1] = 0; + out[ip+2..ip+4].copy_from_slice(&u16::try_from(total - ETH_HEADER).map_err(|_| PacketError::InvalidLength)?.to_be_bytes()); + out[ip+4..ip+6].fill(0); out[ip+6..ip+8].copy_from_slice(&0x4000u16.to_be_bytes()); out[ip+8] = 64; out[ip+9] = 17; out[ip+10..ip+12].fill(0); + out[ip+12..ip+16].copy_from_slice(&source_ip); out[ip+16..ip+20].copy_from_slice(&destination_ip); + let c = checksum16(&out[ip..ip+20]); out[ip+10..ip+12].copy_from_slice(&c.to_be_bytes()); + let udp = ip + IPV4_MIN_HEADER; + out[udp..udp+2].copy_from_slice(&source_port.to_be_bytes()); out[udp+2..udp+4].copy_from_slice(&destination_port.to_be_bytes()); + let udp_len = u16::try_from(UDP_HEADER + payload.len()).map_err(|_| PacketError::InvalidLength)?; + out[udp+4..udp+6].copy_from_slice(&udp_len.to_be_bytes()); out[udp+6..udp+8].fill(0); out[udp+8..udp+8+payload.len()].copy_from_slice(payload); + Ok(total) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn udp_ipv4_packet_build_and_parse() { + let mut packet = [0u8; MAX_PACKET]; + let n = build_udp_ipv4(&mut packet, [1,2,3,4,5,6], [6,5,4,3,2,1], [10,0,0,1], [10,0,0,2], 1000, 2000, b"hello").unwrap(); + assert_eq!(EthernetFrame::parse(&packet[..n]).unwrap().ethertype, 0x0800); + let ip = Ipv4Header::parse(&packet[ETH_HEADER..n]).unwrap(); + assert_eq!(ip.protocol, 17); assert_eq!(ip.source, [10,0,0,1]); + let udp = UdpHeader::parse(&packet[ETH_HEADER + ip.header_len..n]).unwrap(); + assert_eq!(udp.destination_port, 2000); assert_eq!(udp.length, 13); + } + #[test] + fn rejects_bad_ipv4_checksum() { + let mut packet = [0u8; MAX_PACKET]; + let n = build_udp_ipv4(&mut packet, [1;6], [2;6], [10,0,0,1], [10,0,0,2], 1, 2, b"x").unwrap(); + packet[ETH_HEADER + 8] ^= 1; + assert_eq!(Ipv4Header::parse(&packet[ETH_HEADER..n]), Err(PacketError::InvalidChecksum)); + } +} diff --git a/services/storaged/src/lib.rs b/services/storaged/src/lib.rs index b646fa4..ab60f71 100644 --- a/services/storaged/src/lib.rs +++ b/services/storaged/src/lib.rs @@ -5,6 +5,8 @@ #![no_std] +pub mod persistence; + pub const MAX_VOLUMES: usize = 16; pub const MAX_MOUNTS: usize = 16; pub const MAX_SNAPSHOTS: usize = 8; @@ -13,282 +15,44 @@ pub const BLOCK_SIZE: usize = 512; pub const CACHE_BLOCKS: usize = 32; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum VolumeType { - Ramdisk, - GptPartition, - VirtualDisk, - AweFsVolume, -} - +pub enum VolumeType { Ramdisk, GptPartition, VirtualDisk, AweFsVolume } #[derive(Debug, Clone, Copy)] -pub struct PackageFileRecord { - pub file_id: u32, - pub volume_id: u32, - pub name_hash: u64, - pub size_bytes: u32, - pub is_installed_package: bool, -} - +pub struct PackageFileRecord { pub file_id: u32, pub volume_id: u32, pub name_hash: u64, pub size_bytes: u32, pub is_installed_package: bool } #[derive(Debug, Clone, Copy)] -pub struct StorageVolume { - pub volume_id: u32, - pub volume_type: VolumeType, - pub block_count: u64, - pub start_lba: u64, - pub read_only: bool, -} - +pub struct StorageVolume { pub volume_id: u32, pub volume_type: VolumeType, pub block_count: u64, pub start_lba: u64, pub read_only: bool } #[derive(Debug, Clone, Copy)] -pub struct MountEntry { - pub mount_id: u32, - pub volume_id: u32, - pub path_hash: u64, - pub active: bool, -} - +pub struct MountEntry { pub mount_id: u32, pub volume_id: u32, pub path_hash: u64, pub active: bool } #[derive(Debug, Clone, Copy)] -pub struct SnapshotMetadata { - pub snapshot_id: u32, - pub volume_id: u32, - pub timestamp: u64, - pub block_delta_count: u32, -} - +pub struct SnapshotMetadata { pub snapshot_id: u32, pub volume_id: u32, pub timestamp: u64, pub block_delta_count: u32 } #[derive(Debug, Clone, Copy)] -pub struct CachedBlock { - pub volume_id: u32, - pub lba: u64, - pub dirty: bool, - pub data: [u8; BLOCK_SIZE], -} +pub struct CachedBlock { pub volume_id: u32, pub lba: u64, pub dirty: bool, pub data: [u8; BLOCK_SIZE] } -/// Storage Daemon Supervisor Instance. #[derive(Debug)] pub struct StorageDaemon { - volumes: [Option; MAX_VOLUMES], - mounts: [Option; MAX_MOUNTS], - snapshots: [Option; MAX_SNAPSHOTS], - files: [Option; MAX_PACKAGE_FILES], - cache: [Option; CACHE_BLOCKS], - volume_counter: u32, - mount_counter: u32, - snapshot_counter: u32, - file_counter: u32, + pub(crate) volumes: [Option; MAX_VOLUMES], + pub(crate) mounts: [Option; MAX_MOUNTS], + pub(crate) snapshots: [Option; MAX_SNAPSHOTS], + pub(crate) files: [Option; MAX_PACKAGE_FILES], + pub(crate) cache: [Option; CACHE_BLOCKS], + pub(crate) volume_counter: u32, + pub(crate) mount_counter: u32, + pub(crate) snapshot_counter: u32, + pub(crate) file_counter: u32, pub self_healed_events: usize, } impl StorageDaemon { - pub const fn new() -> Self { - Self { - volumes: [None; MAX_VOLUMES], - mounts: [None; MAX_MOUNTS], - snapshots: [None; MAX_SNAPSHOTS], - files: [None; MAX_PACKAGE_FILES], - cache: [None; CACHE_BLOCKS], - volume_counter: 1, - mount_counter: 1, - snapshot_counter: 1, - file_counter: 1, - self_healed_events: 0, - } - } - - pub fn register_volume( - &mut self, - vol_type: VolumeType, - block_count: u64, - start_lba: u64, - read_only: bool, - ) -> Result { - let vid = self.volume_counter; - for slot in self.volumes.iter_mut() { - if slot.is_none() { - *slot = Some(StorageVolume { - volume_id: vid, - volume_type: vol_type, - block_count, - start_lba, - read_only, - }); - self.volume_counter += 1; - return Ok(vid); - } - } - Err("Volume table full") - } - - pub fn mount_volume(&mut self, volume_id: u32, path_hash: u64) -> Result { - let mut found = false; - for v in self.volumes.iter().flatten() { - if v.volume_id == volume_id { - found = true; - break; - } - } - if !found { - return Err("Volume ID not found"); - } - - let mid = self.mount_counter; - for slot in self.mounts.iter_mut() { - if slot.is_none() { - *slot = Some(MountEntry { - mount_id: mid, - volume_id, - path_hash, - active: true, - }); - self.mount_counter += 1; - return Ok(mid); - } - } - Err("Mount table full") - } - - pub fn create_snapshot(&mut self, volume_id: u32, timestamp: u64) -> Result { - let sid = self.snapshot_counter; - for slot in self.snapshots.iter_mut() { - if slot.is_none() { - *slot = Some(SnapshotMetadata { - snapshot_id: sid, - volume_id, - timestamp, - block_delta_count: 0, - }); - self.snapshot_counter += 1; - return Ok(sid); - } - } - Err("Snapshot table full") - } - - pub fn cache_read(&self, volume_id: u32, lba: u64) -> Option<&[u8; BLOCK_SIZE]> { - for block in self.cache.iter().flatten() { - if block.volume_id == volume_id && block.lba == lba { - return Some(&block.data); - } - } - None - } - - pub fn cache_write( - &mut self, - volume_id: u32, - lba: u64, - data: &[u8; BLOCK_SIZE], - ) -> Result<(), &'static str> { - // Update existing cache block if present - for block in self.cache.iter_mut().flatten() { - if block.volume_id == volume_id && block.lba == lba { - block.data = *data; - block.dirty = true; - return Ok(()); - } - } - // Insert into free slot - for slot in self.cache.iter_mut() { - if slot.is_none() { - *slot = Some(CachedBlock { - volume_id, - lba, - dirty: true, - data: *data, - }); - return Ok(()); - } - } - Err("Block cache full") - } - - pub fn store_package_file( - &mut self, - volume_id: u32, - name_hash: u64, - size_bytes: u32, - ) -> Result { - let fid = self.file_counter; - for slot in self.files.iter_mut() { - if slot.is_none() { - *slot = Some(PackageFileRecord { - file_id: fid, - volume_id, - name_hash, - size_bytes, - is_installed_package: true, - }); - self.file_counter += 1; - return Ok(fid); - } - } - Err("File records table full") - } - - pub fn delete_package_file(&mut self, file_id: u32) -> Result<(), &'static str> { - for slot in self.files.iter_mut() { - if let Some(f) = slot - && f.file_id == file_id - { - *slot = None; - return Ok(()); - } - } - Err("File not found") - } - - pub fn trigger_self_healing_repair(&mut self, volume_id: u32) -> bool { - // Trigger self-healing repair from snapshots for damaged blocks - if self - .snapshots - .iter() - .flatten() - .any(|s| s.volume_id == volume_id) - { - self.self_healed_events += 1; - true - } else { - false - } - } -} - -impl Default for StorageDaemon { - fn default() -> Self { - Self::new() - } -} + pub const fn new() -> Self { Self { volumes:[None;MAX_VOLUMES], mounts:[None;MAX_MOUNTS], snapshots:[None;MAX_SNAPSHOTS], files:[None;MAX_PACKAGE_FILES], cache:[None;CACHE_BLOCKS], volume_counter:1, mount_counter:1, snapshot_counter:1, file_counter:1, self_healed_events:0 } } + pub fn register_volume(&mut self, vol_type:VolumeType, block_count:u64, start_lba:u64, read_only:bool)->Result{ let vid=self.volume_counter; for slot in self.volumes.iter_mut(){if slot.is_none(){*slot=Some(StorageVolume{volume_id:vid,volume_type:vol_type,block_count,start_lba,read_only});self.volume_counter=self.volume_counter.saturating_add(1);return Ok(vid);}} Err("Volume table full") } + pub fn mount_volume(&mut self, volume_id:u32, path_hash:u64)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let mid=self.mount_counter; for slot in self.mounts.iter_mut(){if slot.is_none(){*slot=Some(MountEntry{mount_id:mid,volume_id,path_hash,active:true});self.mount_counter=self.mount_counter.saturating_add(1);return Ok(mid);}} Err("Mount table full") } + pub fn create_snapshot(&mut self, volume_id:u32, timestamp:u64)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let sid=self.snapshot_counter; for slot in self.snapshots.iter_mut(){if slot.is_none(){*slot=Some(SnapshotMetadata{snapshot_id:sid,volume_id,timestamp,block_delta_count:0});self.snapshot_counter=self.snapshot_counter.saturating_add(1);return Ok(sid);}} Err("Snapshot table full") } + pub fn cache_read(&self, volume_id:u32, lba:u64)->Option<&[u8;BLOCK_SIZE]>{ for block in self.cache.iter().flatten(){if block.volume_id==volume_id&&block.lba==lba{return Some(&block.data);}} None } + pub fn cache_write(&mut self, volume_id:u32,lba:u64,data:&[u8;BLOCK_SIZE])->Result<(),&'static str>{ for block in self.cache.iter_mut().flatten(){if block.volume_id==volume_id&&block.lba==lba{block.data=*data;block.dirty=true;return Ok(());}} for slot in self.cache.iter_mut(){if slot.is_none(){*slot=Some(CachedBlock{volume_id,lba,dirty:true,data:*data});return Ok(());}} Err("Block cache full") } + pub fn store_package_file(&mut self,volume_id:u32,name_hash:u64,size_bytes:u32)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let fid=self.file_counter; for slot in self.files.iter_mut(){if slot.is_none(){*slot=Some(PackageFileRecord{file_id:fid,volume_id,name_hash,size_bytes,is_installed_package:true});self.file_counter=self.file_counter.saturating_add(1);return Ok(fid);}} Err("File records table full") } + pub fn delete_package_file(&mut self,file_id:u32)->Result<(),&'static str>{ for slot in self.files.iter_mut(){if let Some(f)=slot&&f.file_id==file_id{*slot=None;return Ok(());}} Err("File not found") } + pub fn trigger_self_healing_repair(&mut self,volume_id:u32)->bool{ if self.snapshots.iter().flatten().any(|s|s.volume_id==volume_id){self.self_healed_events+=1;true}else{false} } +} +impl Default for StorageDaemon { fn default()->Self{Self::new()} } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_storaged_lifecycle() { - let mut storaged = StorageDaemon::new(); - let vid = storaged - .register_volume(VolumeType::Ramdisk, 2048, 0, false) - .unwrap(); - assert_eq!(vid, 1); - - let mid = storaged.mount_volume(vid, 0x1234_5678).unwrap(); - assert_eq!(mid, 1); - - let snap_id = storaged.create_snapshot(vid, 1000).unwrap(); - assert_eq!(snap_id, 1); - - let block_data = [0xAB; BLOCK_SIZE]; - storaged.cache_write(vid, 42, &block_data).unwrap(); - - let cached = storaged - .cache_read(vid, 42) - .expect("Should hit block cache"); - assert_eq!(cached[0], 0xAB); - - let fid = storaged.store_package_file(vid, 0x112233, 4096).unwrap(); - assert_eq!(fid, 1); - assert!(storaged.trigger_self_healing_repair(vid)); - assert_eq!(storaged.self_healed_events, 1); - - storaged.delete_package_file(fid).unwrap(); - assert!(storaged.delete_package_file(fid).is_err()); - } -} +mod tests { use super::*; #[test] fn test_storaged_lifecycle(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::Ramdisk,2048,0,false).unwrap();assert_eq!(v,1);assert_eq!(s.mount_volume(v,0x1234_5678).unwrap(),1);assert_eq!(s.create_snapshot(v,1000).unwrap(),1);let d=[0xAB;BLOCK_SIZE];s.cache_write(v,42,&d).unwrap();assert_eq!(s.cache_read(v,42).unwrap()[0],0xAB);let f=s.store_package_file(v,0x112233,4096).unwrap();assert_eq!(f,1);assert!(s.trigger_self_healing_repair(v));assert_eq!(s.self_healed_events,1);s.delete_package_file(f).unwrap();assert!(s.delete_package_file(f).is_err());}} diff --git a/services/storaged/src/persistence.rs b/services/storaged/src/persistence.rs new file mode 100644 index 0000000..4635a41 --- /dev/null +++ b/services/storaged/src/persistence.rs @@ -0,0 +1,74 @@ +#![no_std] + +use super::{MountEntry, PackageFileRecord, SnapshotMetadata, StorageDaemon, StorageVolume, VolumeType, CACHE_BLOCKS, MAX_MOUNTS, MAX_PACKAGE_FILES, MAX_SNAPSHOTS, MAX_VOLUMES}; + +pub const STATE_MAGIC: [u8; 4] = *b"AWSP"; +pub const STATE_VERSION: u8 = 1; +pub const MAX_STATE_SIZE: usize = 4096; +const HEADER_LEN: usize = 20; +const VOLUME_REC_LEN: usize = 24; +const MOUNT_REC_LEN: usize = 17; +const SNAPSHOT_REC_LEN: usize = 24; +const FILE_REC_LEN: usize = 25; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PersistError { BufferTooSmall, Truncated, BadMagic, UnsupportedVersion, ChecksumMismatch, InvalidRecord, InvalidCount, Overflow } + +fn checksum(bytes: &[u8]) -> u32 { + let mut hash = 0x811c_9dc5u32; + for &byte in bytes { hash ^= byte as u32; hash = hash.wrapping_mul(0x0100_0193); } + hash +} +fn put_u16(out:&mut [u8],pos:&mut usize,value:u16)->Result<(),PersistError>{let end=pos.checked_add(2).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u32(out:&mut [u8],pos:&mut usize,value:u32)->Result<(),PersistError>{let end=pos.checked_add(4).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u64(out:&mut [u8],pos:&mut usize,value:u64)->Result<(),PersistError>{let end=pos.checked_add(8).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u8(out:&mut [u8],pos:&mut usize,value:u8)->Result<(),PersistError>{if *pos>=out.len(){return Err(PersistError::BufferTooSmall)}out[*pos]=value;*pos+=1;Ok(())} +fn get_u16(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(2).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u16::from_le_bytes([input[*pos],input[*pos+1]]);*pos=end;Ok(v)} +fn get_u32(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(4).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u32::from_le_bytes([input[*pos],input[*pos+1],input[*pos+2],input[*pos+3]]);*pos=end;Ok(v)} +fn get_u64(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(8).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u64::from_le_bytes([input[*pos],input[*pos+1],input[*pos+2],input[*pos+3],input[*pos+4],input[*pos+5],input[*pos+6],input[*pos+7]]);*pos=end;Ok(v)} +fn get_u8(input:&[u8],pos:&mut usize)->Result{if *pos>=input.len(){return Err(PersistError::Truncated)}let v=input[*pos];*pos+=1;Ok(v)} +fn volume_type_to_u8(v:VolumeType)->u8{match v{VolumeType::Ramdisk=>0,VolumeType::GptPartition=>1,VolumeType::VirtualDisk=>2,VolumeType::AweFsVolume=>3}} +fn volume_type_from_u8(v:u8)->Result{match v{0=>Ok(VolumeType::Ramdisk),1=>Ok(VolumeType::GptPartition),2=>Ok(VolumeType::VirtualDisk),3=>Ok(VolumeType::AweFsVolume),_=>Err(PersistError::InvalidRecord)}} + +pub fn export_state(storage:&StorageDaemon,out:&mut [u8])->Result{ + if out.len()MAX_VOLUMES||mc>MAX_MOUNTS||sc>MAX_SNAPSHOTS||fc>MAX_PACKAGE_FILES{return Err(PersistError::InvalidCount)} + let payload_len=vc.checked_mul(VOLUME_REC_LEN).and_then(|v|v.checked_add(mc.checked_mul(MOUNT_REC_LEN)?)).and_then(|v|v.checked_add(sc.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v|v.checked_add(fc.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + let total_len=HEADER_LEN.checked_add(payload_len).ok_or(PersistError::Overflow)?; + if total_len>out.len()||total_len>MAX_STATE_SIZE||total_len>u16::MAX as usize{return Err(PersistError::BufferTooSmall)} + out[..total_len].fill(0);out[..4].copy_from_slice(&STATE_MAGIC);out[4]=STATE_VERSION;out[5]=0;out[6..8].copy_from_slice(&(total_len as u16).to_le_bytes()); + let mut pos=12usize;put_u16(out,&mut pos,vc as u16)?;put_u16(out,&mut pos,mc as u16)?;put_u16(out,&mut pos,sc as u16)?;put_u16(out,&mut pos,fc as u16)?; + for r in storage.volumes.iter().flatten(){put_u32(out,&mut pos,r.volume_id)?;put_u8(out,&mut pos,volume_type_to_u8(r.volume_type))?;put_u8(out,&mut pos,u8::from(r.read_only))?;put_u16(out,&mut pos,0)?;put_u64(out,&mut pos,r.block_count)?;put_u64(out,&mut pos,r.start_lba)?} + for r in storage.mounts.iter().flatten(){put_u32(out,&mut pos,r.mount_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.path_hash)?;put_u8(out,&mut pos,u8::from(r.active))?} + for r in storage.snapshots.iter().flatten(){put_u32(out,&mut pos,r.snapshot_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.timestamp)?;put_u32(out,&mut pos,r.block_delta_count)?;put_u32(out,&mut pos,0)?} + for r in storage.files.iter().flatten(){put_u32(out,&mut pos,r.file_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.name_hash)?;put_u32(out,&mut pos,r.size_bytes)?;put_u8(out,&mut pos,u8::from(r.is_installed_package))?} + out[8..12].copy_from_slice(&checksum(&out[HEADER_LEN..total_len]).to_le_bytes());Ok(total_len) +} + +pub fn import_state(storage:&mut StorageDaemon,input:&[u8])->Result<(),PersistError>{ + if input.len()input.len()||total_len>MAX_STATE_SIZE{return Err(PersistError::InvalidCount)} + let expected=u32::from_le_bytes([input[8],input[9],input[10],input[11]]);if checksum(&input[HEADER_LEN..total_len])!=expected{return Err(PersistError::ChecksumMismatch)} + let mut pos=12usize;let vc=get_u16(input,&mut pos)? as usize;let mc=get_u16(input,&mut pos)? as usize;let sc=get_u16(input,&mut pos)? as usize;let fc=get_u16(input,&mut pos)? as usize; + if vc>MAX_VOLUMES||mc>MAX_MOUNTS||sc>MAX_SNAPSHOTS||fc>MAX_PACKAGE_FILES{return Err(PersistError::InvalidCount)} + let expected_payload=vc.checked_mul(VOLUME_REC_LEN).and_then(|v|v.checked_add(mc.checked_mul(MOUNT_REC_LEN)?)).and_then(|v|v.checked_add(sc.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v|v.checked_add(fc.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + if HEADER_LEN.checked_add(expected_payload).ok_or(PersistError::Overflow)?!=total_len{return Err(PersistError::InvalidCount)} + let mut volumes=[None;MAX_VOLUMES];let mut mounts=[None;MAX_MOUNTS];let mut snapshots=[None;MAX_SNAPSHOTS];let mut files=[None;MAX_PACKAGE_FILES];let mut max_v=0u32;let mut max_m=0u32;let mut max_s=0u32;let mut max_f=0u32; + for slot in volumes.iter_mut().take(vc){let id=get_u32(input,&mut pos)?;let ty=volume_type_from_u8(get_u8(input,&mut pos)?)?;let ro=get_u8(input,&mut pos)?!=0;let _=get_u16(input,&mut pos)?;let blocks=get_u64(input,&mut pos)?;let start=get_u64(input,&mut pos)?;if id==0||blocks==0{return Err(PersistError::InvalidRecord)}max_v=max_v.max(id);*slot=Some(StorageVolume{volume_id:id,volume_type:ty,block_count:blocks,start_lba:start,read_only:ro})} + for slot in mounts.iter_mut().take(mc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let path=get_u64(input,&mut pos)?;let active=get_u8(input,&mut pos)?!=0;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_m=max_m.max(id);*slot=Some(MountEntry{mount_id:id,volume_id:vid,path_hash:path,active})} + for slot in snapshots.iter_mut().take(sc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let ts=get_u64(input,&mut pos)?;let delta=get_u32(input,&mut pos)?;let _=get_u32(input,&mut pos)?;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_s=max_s.max(id);*slot=Some(SnapshotMetadata{snapshot_id:id,volume_id:vid,timestamp:ts,block_delta_count:delta})} + for slot in files.iter_mut().take(fc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let nh=get_u64(input,&mut pos)?;let size=get_u32(input,&mut pos)?;let installed=get_u8(input,&mut pos)?!=0;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_f=max_f.max(id);*slot=Some(PackageFileRecord{file_id:id,volume_id:vid,name_hash:nh,size_bytes:size,is_installed_package:installed})} + *storage=StorageDaemon{volumes,mounts,snapshots,files,cache:[None;CACHE_BLOCKS],volume_counter:max_v.saturating_add(1).max(1),mount_counter:max_m.saturating_add(1).max(1),snapshot_counter:max_s.saturating_add(1).max(1),file_counter:max_f.saturating_add(1).max(1),self_healed_events:0};Ok(()) +} + +#[cfg(test)] +mod tests{ + use super::*; + #[test]fn state_round_trip_restores_metadata_and_counters(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::AweFsVolume,4096,64,false).unwrap();s.mount_volume(v,0x55AA).unwrap();s.create_snapshot(v,123456).unwrap();s.store_package_file(v,0xABCDEF,8192).unwrap();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();let mut r=StorageDaemon::new();import_state(&mut r,&b[..len]).unwrap();assert_eq!(r.volumes[0].unwrap().block_count,4096);assert_eq!(r.mounts[0].unwrap().path_hash,0x55AA);assert_eq!(r.snapshots[0].unwrap().timestamp,123456);assert_eq!(r.files[0].unwrap().size_bytes,8192);assert_eq!(r.register_volume(VolumeType::Ramdisk,1,1,true).unwrap(),2);assert_eq!(r.mount_volume(2,3).unwrap(),2)} + #[test]fn tampering_is_rejected_by_checksum(){let s=StorageDaemon::new();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();b[len-1]^=1;let mut r=StorageDaemon::new();assert_eq!(import_state(&mut r,&b[..len]),Err(PersistError::ChecksumMismatch))} + #[test]fn header_counts_survive_checksum_storage(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::VirtualDisk,32,7,false).unwrap();s.mount_volume(v,9).unwrap();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();assert!(len>=HEADER_LEN);let mut r=StorageDaemon::new();import_state(&mut r,&b[..len]).unwrap();assert_eq!(r.volumes[0].unwrap().volume_id,1);assert!(r.mounts[0].is_some())} +}