use std::{cell::RefCell, rc::Rc}; use turkos_os_threads::threads::*; #[derive(Debug)] struct Stack { layout: StackLayout, bytes: Rc>>, guard: u64, prepare_fault: bool, release_fault: bool, } impl Stack { fn new(index: u64) -> Self { Self { layout: StackLayout { base: 0x8000_1000 + index * 0x20000, physical: 0x4100_0000 + index * 0x20000, bytes: 4 * PAGE, }, bytes: Rc::new(RefCell::new(vec![0xa5; 4 * PAGE as usize])), guard: 0, prepare_fault: false, release_fault: false, } } } #[derive(Default, Debug)] struct Hardware { sp: u64, unmasked: bool, in_irq: bool, cpu: u32, prepares: usize, releases: usize, released: Vec>, prepare_damage: u8, } #[derive(Debug)] struct Backend { hardware: Rc>, } unsafe impl turkos_os_threads::threads::Backend for Backend { type Stack = Stack; fn execution(&self) -> Execution { let h = self.hardware.borrow(); Execution { cpu: h.cpu, irq_masked: !h.unmasked, in_irq: h.in_irq, sp: h.sp, } } fn layout(&self, stack: &Stack) -> Result { Ok(stack.layout) } fn entry_executable(&self, entry: u64) -> bool { (0x40080000..0x40090000).contains(&entry) } fn prepare(&mut self, stack: &mut Stack) -> Result<(), Error> { self.hardware.borrow_mut().prepares += 1; let mut bytes = stack.bytes.borrow_mut(); bytes.fill(0); if stack.prepare_fault { return Err(Error::Backend); } bytes[..8].copy_from_slice(&LOW_CANARY.to_le_bytes()); let at = bytes.len() - 8; bytes[at..].copy_from_slice(&HIGH_CANARY.to_le_bytes()); match self.hardware.borrow().prepare_damage { 1 => bytes[0] ^= 1, 2 => bytes[at] ^= 1, 3 => bytes[8000] = 7, _ => {} } Ok(()) } fn observe(&self, stack: &Stack) -> Result { let bytes = stack.bytes.borrow(); Ok(StackObservation { layout: stack.layout, guard_descriptor: stack.guard, low_canary: u64::from_le_bytes(bytes[..8].try_into().unwrap()), high_canary: u64::from_le_bytes(bytes[bytes.len() - 8..].try_into().unwrap()), initially_zeroed: bytes[8..bytes.len() - 8].iter().all(|&v| v == 0), }) } fn release(&mut self, stack: Stack) -> Result<(), (Error, Stack)> { self.hardware.borrow_mut().releases += 1; stack.bytes.borrow_mut().fill(0); if stack.release_fault { return Err((Error::Backend, stack)); } self.hardware .borrow_mut() .released .push(stack.bytes.borrow().clone()); Ok(()) } } fn setup() -> (Table, Rc>) { let hardware = Rc::new(RefCell::new(Hardware::default())); let table = Table::new(Backend { hardware: hardware.clone(), }) .ok() .unwrap(); (table, hardware) } fn spawn(table: &mut Table, index: u64) -> ThreadId { table.create(Stack::new(index), 0x40080040, index).unwrap() } fn enter( table: &mut Table, hardware: &Rc>, id: ThreadId, ) { hardware.borrow_mut().sp = table.ready_context(id).unwrap().initial_sp; table.activate(id).unwrap(); } fn retire( table: &mut Table, hardware: &Rc>, id: ThreadId, ) { table.cancel(id).unwrap(); hardware.borrow_mut().sp = 0; table.reap(id).unwrap(); } #[test] fn os_threads_identity_positive() { let (mut table, h) = setup::<1>(); let a = spawn(&mut table, 0); retire(&mut table, &h, a); let b = spawn(&mut table, 0); retire(&mut table, &h, b); let c = spawn(&mut table, 0); assert_eq!((a.slot(), b.slot(), c.slot()), (0, 0, 0)); assert_eq!((a.generation(), b.generation(), c.generation()), (1, 2, 3)); assert_eq!(a.table(), c.table()); assert_eq!(table.snapshot().ready, 1); } #[test] fn os_threads_identity_negative() { let (mut table, h) = setup::<1>(); let a = spawn(&mut table, 0); enter(&mut table, &h, a); let old = table.sleep_current(a).unwrap(); table.cancel(a).unwrap(); h.borrow_mut().sp = 0; table.reap(a).unwrap(); let b = spawn(&mut table, 0); let before = table.snapshot(); assert_eq!(table.wake(old).unwrap_err().0, Error::Stale); assert_eq!(table.activate(a), Err(Error::Stale)); assert_eq!(table.cancel(a), Err(Error::Stale)); assert_eq!(table.record(b).unwrap().state, State::Ready); assert_eq!(table.snapshot(), before); } #[test] fn os_threads_stacks_positive() { let (mut table, _) = setup::<3>(); let ids = [ spawn(&mut table, 0), spawn(&mut table, 1), spawn(&mut table, 2), ]; for (i, id) in ids.iter().enumerate() { let record = table.record(*id).unwrap(); assert_eq!(record.layout, Stack::new(i as u64).layout); let context = table.ready_context(*id).unwrap(); assert_eq!(context.initial_sp % 16, 0); assert_eq!( context.initial_sp, record.layout.base + record.layout.bytes - 16 ); } assert_eq!(table.snapshot().pages, 12); } #[test] fn os_threads_stacks_negative() { let (mut table, h) = setup::<3>(); let id = spawn(&mut table, 0); let before = table.snapshot(); let mut physical_alias = Stack::new(1); physical_alias.layout.physical = Stack::new(0).layout.physical; let (error, retained) = table.create(physical_alias, 0x40080040, 0).unwrap_err(); assert_eq!(error, Error::SharedStack); assert!(retained.bytes.borrow().iter().all(|&v| v == 0xa5)); let mut virtual_alias = Stack::new(1); virtual_alias.layout.base = Stack::new(0).layout.base; assert_eq!( table.create(virtual_alias, 0x40080040, 0).unwrap_err().0, Error::SharedStack ); assert_eq!(h.borrow().prepares, 1); assert_eq!(table.snapshot(), before); assert_eq!(table.ready_context(id).unwrap().id, id); } #[test] fn os_threads_states_positive() { let (mut table, h) = setup::<1>(); let id = spawn(&mut table, 0); enter(&mut table, &h, id); assert_eq!(table.snapshot().running, 1); let wait = table.sleep_current(id).unwrap(); assert_eq!(table.snapshot().sleeping, 1); h.borrow_mut().sp = 0; assert_eq!(table.wake(wait).unwrap(), id); enter(&mut table, &h, id); table.yield_current(id).unwrap(); assert_eq!(table.record(id).unwrap().state, State::Ready); table.activate(id).unwrap(); table.exit_current(id).unwrap(); assert_eq!(table.snapshot().dead, 1); assert_eq!(table.snapshot().running, 0); } #[test] fn os_threads_states_negative() { let (mut table, h) = setup::<2>(); let a = spawn(&mut table, 0); let b = spawn(&mut table, 1); assert_eq!(table.sleep_current(a).unwrap_err(), Error::State); assert_eq!(table.activate(a), Err(Error::ActiveStack)); enter(&mut table, &h, a); h.borrow_mut().sp = table.record(b).unwrap().layout.usable_top(); assert_eq!(table.activate(b), Err(Error::State)); h.borrow_mut().sp = table.record(a).unwrap().layout.usable_top(); table.exit_current(a).unwrap(); let before = table.snapshot(); assert_eq!(table.activate(a), Err(Error::State)); assert_eq!(table.yield_current(a), Err(Error::State)); assert_eq!(table.cancel(a), Err(Error::State)); assert_eq!(table.ready_context(a), Err(Error::State)); assert_eq!(table.snapshot(), before); } #[test] fn os_threads_retire_positive() { let (mut table, h) = setup::<3>(); let ids = [ spawn(&mut table, 0), spawn(&mut table, 1), spawn(&mut table, 2), ]; enter(&mut table, &h, ids[0]); table.exit_current(ids[0]).unwrap(); enter(&mut table, &h, ids[1]); table.reap(ids[0]).unwrap(); table.exit_current(ids[1]).unwrap(); enter(&mut table, &h, ids[2]); table.reap(ids[1]).unwrap(); table.exit_current(ids[2]).unwrap(); h.borrow_mut().sp = 0; table.reap(ids[2]).unwrap(); assert_eq!(table.snapshot(), Snapshot::default()); assert_eq!(h.borrow().released.len(), 3); assert!( h.borrow() .released .iter() .all(|s| s.iter().all(|&v| v == 0)) ); assert!(table.into_backend().is_ok()); } #[test] fn os_threads_retire_negative() { let (mut table, h) = setup::<1>(); let id = spawn(&mut table, 0); enter(&mut table, &h, id); assert_eq!(table.reap(id), Err(Error::State)); table.exit_current(id).unwrap(); let before = table.snapshot(); assert_eq!(table.reap(id), Err(Error::ActiveStack)); assert_eq!(table.snapshot(), before); assert_eq!(h.borrow().releases, 0); h.borrow_mut().sp = 0; table.reap(id).unwrap(); assert_eq!(h.borrow().releases, 1); } #[test] fn failed_release_retains_actual_stack_and_blocks_reuse() { let (mut table, h) = setup::<2>(); let mut stack = Stack::new(0); stack.release_fault = true; let retained_memory = stack.bytes.clone(); let id = table.create(stack, 0x40080040, 0).unwrap(); table.cancel(id).unwrap(); assert_eq!(table.reap(id), Err(Error::Backend)); assert_eq!(table.snapshot().quarantined, 1); assert_eq!(table.snapshot().pages, 4); assert_eq!(Rc::strong_count(&retained_memory), 2); assert_eq!(table.reap(id), Err(Error::Quarantined)); assert_eq!(h.borrow().releases, 1); assert_eq!( table.create(Stack::new(0), 0x40080040, 0).unwrap_err().0, Error::SharedStack ); let healthy = spawn(&mut table, 1); enter(&mut table, &h, healthy); assert_eq!(table.snapshot().running, 1); assert!(matches!(table.into_backend(), Err((Error::Busy, _)))); } #[test] fn preparation_failure_returns_same_actual_backing_without_publication() { let (mut table, h) = setup::<1>(); let mut stack = Stack::new(0); stack.prepare_fault = true; let pointer = stack.bytes.borrow().as_ptr(); let (error, retained) = table.create(stack, 0x40080040, 0).unwrap_err(); assert_eq!(error, Error::Backend); assert_eq!(retained.bytes.borrow().as_ptr(), pointer); assert_eq!(h.borrow().prepares, 1); assert_eq!(table.snapshot().live, 0); assert_eq!(h.borrow().releases, 0); assert_eq!(spawn(&mut table, 1).generation(), 1); } #[test] fn guard_failure_returns_prepared_backing_without_consuming_generation() { let (mut table, h) = setup::<1>(); let mut stack = Stack::new(0); stack.guard = 0x403; let (error, retained) = table.create(stack, 0x40080040, 0).unwrap_err(); assert_eq!(error, Error::Guard); assert_eq!(retained.guard, 0x403); assert_eq!(table.snapshot().live, 0); assert_eq!(h.borrow().prepares, 1); assert_eq!(spawn(&mut table, 1).generation(), 1); } #[test] fn malformed_layout_and_non_executable_entry_refuse_before_stack_writes() { let (mut table, h) = setup::<1>(); for layout in [ StackLayout { base: 0, physical: 0x41000000, bytes: 16384, }, StackLayout { base: 0x80001001, physical: 0x41000000, bytes: 16384, }, StackLayout { base: 0x80001000, physical: 0, bytes: 16384, }, StackLayout { base: 0x80001000, physical: u64::MAX - 4095, bytes: 16384, }, StackLayout { base: 0x80001000, physical: 0x41000000, bytes: 4096, }, StackLayout { base: 0x80001000, physical: 0x41000000, bytes: 131072, }, ] { let mut stack = Stack::new(0); stack.layout = layout; assert_eq!( table.create(stack, 0x40080040, 0).unwrap_err().0, Error::Layout ); } for entry in [0, 0x40080041, 0x41000000] { assert_eq!( table.create(Stack::new(0), entry, 0).unwrap_err().0, Error::Entry ); } assert_eq!(h.borrow().prepares, 0); assert_eq!(table.snapshot().live, 0); } #[test] fn active_candidate_stack_is_never_zeroed() { let (mut table, h) = setup::<1>(); h.borrow_mut().sp = Stack::new(0).layout.usable_top(); let (error, stack) = table.create(Stack::new(0), 0x40080040, 0).unwrap_err(); assert_eq!(error, Error::ActiveStack); assert!(stack.bytes.borrow().iter().all(|&v| v == 0xa5)); assert_eq!(h.borrow().prepares, 0); } #[test] fn full_table_returns_candidate_unmodified() { let (mut table, h) = setup::<1>(); spawn(&mut table, 0); let (error, stack) = table.create(Stack::new(1), 0x40080040, 0).unwrap_err(); assert_eq!(error, Error::Capacity); assert!(stack.bytes.borrow().iter().all(|&v| v == 0xa5)); assert_eq!(h.borrow().prepares, 1); } #[test] fn foreign_table_identity_cannot_mutate_matching_slot_and_generation() { let (mut a, ha) = setup::<1>(); let (mut b, hb) = setup::<1>(); let ida = spawn(&mut a, 0); let idb = spawn(&mut b, 1); assert_eq!( (ida.slot(), ida.generation()), (idb.slot(), idb.generation()) ); assert_ne!(ida.table(), idb.table()); enter(&mut a, &ha, ida); let wait = a.sleep_current(ida).unwrap(); hb.borrow_mut().sp = b.record(idb).unwrap().layout.usable_top(); let before = b.snapshot(); assert_eq!(b.wake(wait).unwrap_err().0, Error::Stale); assert_eq!(b.activate(ida), Err(Error::Stale)); assert_eq!(b.cancel(ida), Err(Error::Stale)); assert_eq!(b.snapshot(), before); } #[test] fn wrong_cpu_irq_or_unmasked_context_never_changes_thread_state() { let (mut table, h) = setup::<1>(); let id = spawn(&mut table, 0); let before = table.snapshot(); for mode in 0..3 { { let mut hw = h.borrow_mut(); hw.unmasked = mode == 0; hw.in_irq = mode == 1; hw.cpu = if mode == 2 { 1 } else { 0 }; } assert_eq!(table.cancel(id), Err(Error::Context)); assert_eq!(table.activate(id), Err(Error::Context)); assert_eq!(table.snapshot(), before); assert_eq!( table.create(Stack::new(1), 0x40080040, 0).unwrap_err().0, Error::Context ); } assert_eq!(h.borrow().prepares, 1); } #[test] fn cancelled_wait_stays_stale_after_reap_and_new_sleep() { let (mut table, h) = setup::<1>(); let a = spawn(&mut table, 0); enter(&mut table, &h, a); let old = table.sleep_current(a).unwrap(); table.cancel(a).unwrap(); h.borrow_mut().sp = 0; table.reap(a).unwrap(); let b = spawn(&mut table, 0); enter(&mut table, &h, b); let current = table.sleep_current(b).unwrap(); h.borrow_mut().sp = 0; assert_eq!(table.wake(old).unwrap_err().0, Error::Stale); assert_eq!(table.snapshot().sleeping, 1); table.wake(current).unwrap(); assert_eq!(table.snapshot().ready, 1); } #[test] fn initial_canaries_and_whole_stack_zeroing_are_required_before_publication() { let (mut table, h) = setup::<1>(); for damage in 1..=3 { h.borrow_mut().prepare_damage = damage; let (error, retained) = table.create(Stack::new(0), 0x40080040, 0).unwrap_err(); assert_eq!( error, if damage == 3 { Error::NotZeroed } else { Error::Canary } ); assert_eq!(retained.layout, Stack::new(0).layout); assert_eq!(table.snapshot().live, 0); assert_eq!(h.borrow().releases, 0); } h.borrow_mut().prepare_damage = 0; assert_eq!(spawn(&mut table, 1).generation(), 1); } #[test] fn actual_canary_corruption_blocks_selection_exit_and_release_with_resources_retained() { let (mut table, h) = setup::<1>(); let stack = Stack::new(0); let memory = stack.bytes.clone(); let id = table.create(stack, 0x40080040, 0).unwrap(); memory.borrow_mut()[0] ^= 1; let before = table.snapshot(); assert_eq!(table.ready_context(id), Err(Error::Canary)); h.borrow_mut().sp = table.record(id).unwrap().layout.usable_top(); assert_eq!(table.activate(id), Err(Error::Canary)); assert_eq!(table.cancel(id), Err(Error::Canary)); assert_eq!(table.snapshot(), before); memory.borrow_mut()[0] ^= 1; table.activate(id).unwrap(); let last = memory.borrow().len() - 1; memory.borrow_mut()[last] ^= 1; assert_eq!(table.exit_current(id), Err(Error::Canary)); assert_eq!(table.snapshot().running, 1); memory.borrow_mut()[last] ^= 1; table.exit_current(id).unwrap(); h.borrow_mut().sp = 0; memory.borrow_mut()[last] ^= 1; assert_eq!(table.reap(id), Err(Error::Canary)); assert_eq!(h.borrow().releases, 0); assert_eq!(table.snapshot().pages, 4); memory.borrow_mut()[last] ^= 1; table.reap(id).unwrap(); assert!(memory.borrow().iter().all(|&v| v == 0)); assert_eq!(table.snapshot().live, 0); } #[test] fn failed_wake_context_returns_same_actual_wait_for_retry() { let (mut table, h) = setup::<1>(); let id = spawn(&mut table, 0); enter(&mut table, &h, id); let wait = table.sleep_current(id).unwrap(); let epoch = wait.epoch(); h.borrow_mut().unmasked = true; let (error, retained) = table.wake(wait).unwrap_err(); assert_eq!(error, Error::Context); assert_eq!(retained.thread(), id); assert_eq!(retained.epoch(), epoch); assert_eq!(table.snapshot().sleeping, 1); h.borrow_mut().unmasked = false; h.borrow_mut().sp = 0; table.wake(retained).unwrap(); assert_eq!(table.snapshot().ready, 1); } #[test] fn constructor_failure_preserves_actual_backend_and_does_not_prepare_any_stack() { let hardware = Rc::new(RefCell::new(Hardware::default())); let a = Table::::new(Backend { hardware: hardware.clone(), }) .err() .unwrap(); assert_eq!(a.0, Error::Capacity); assert!(Rc::ptr_eq(&a.1.hardware, &hardware)); let b = Table::::new(Backend { hardware: hardware.clone(), }) .err() .unwrap(); assert_eq!(b.0, Error::Capacity); assert!(Rc::ptr_eq(&b.1.hardware, &hardware)); hardware.borrow_mut().in_irq = true; let c = Table::::new(Backend { hardware: hardware.clone(), }) .err() .unwrap(); assert_eq!(c.0, Error::Context); assert!(Rc::ptr_eq(&c.1.hardware, &hardware)); assert_eq!(hardware.borrow().prepares, 0); assert_eq!(hardware.borrow().releases, 0); } #[test] fn dead_stack_guard_and_canary_boundaries_are_still_active_use() { let (mut table, h) = setup::<1>(); let id = spawn(&mut table, 0); table.cancel(id).unwrap(); let layout = table.record(id).unwrap().layout; for sp in [layout.guard(), layout.base, layout.base + layout.bytes] { h.borrow_mut().sp = sp; assert_eq!(table.reap(id), Err(Error::ActiveStack)); assert_eq!(h.borrow().releases, 0); assert_eq!(table.snapshot().dead, 1); } h.borrow_mut().sp = layout.base + layout.bytes + 16; table.reap(id).unwrap(); }