Skip to main content

lion_core/state/
state.rs

1// Copyright (C) 2026 HaiyangLi
2// SPDX-License-Identifier: AGPL-3.0-or-later
3//! Lion State - Unified Global State
4//!
5//! Corresponds to: Lion/State/State.lean
6//!
7//! Complete system state for Lion microkernel.
8//!
9//! NOTE: Uses Vec<(K, V)> instead of BTreeMap.
10//! All operations maintain sorted order by key for deterministic behavior.
11
12use super::{ActorRuntime, KernelState, LinearMemory, MetaState, PluginState, WorkflowInstance};
13use crate::types::{
14    ActorId, CapId, Capability, MemAddr, PluginId, ResourceId, SecurityLevel, Size, Time,
15    WorkflowId,
16};
17
18/// Error type for state operations
19#[derive(Debug)]
20pub enum StateError {
21    /// Plugin not found
22    PluginNotFound(PluginId),
23    /// Actor not found
24    ActorNotFound(ActorId),
25    /// Resource not found
26    ResourceNotFound(ResourceId),
27    /// Workflow not found
28    WorkflowNotFound(WorkflowId),
29    /// Capability not found
30    CapabilityNotFound(CapId),
31    /// Plugin does not hold capability (plugin, cap)
32    CapabilityNotHeld(PluginId, CapId),
33    /// Isolation violation (active, affected)
34    IsolationViolation(PluginId, PluginId),
35    /// Dangling capability would result from freeing this address
36    DanglingCapability(MemAddr),
37    /// Plugin already exists
38    PluginExists(PluginId),
39    /// Actor already exists
40    ActorExists(ActorId),
41    /// Resource already exists
42    ResourceExists(ResourceId),
43    /// Workflow already exists
44    WorkflowExists(WorkflowId),
45    /// Counter overflow (time or epoch would exceed u64::MAX)
46    CounterOverflow(&'static str),
47}
48
49impl std::fmt::Display for StateError {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        match self {
52            StateError::PluginNotFound(id) => write!(f, "plugin {id} not found"),
53            StateError::ActorNotFound(id) => write!(f, "actor {id} not found"),
54            StateError::ResourceNotFound(id) => write!(f, "resource {id} not found"),
55            StateError::WorkflowNotFound(id) => write!(f, "workflow {id} not found"),
56            StateError::CapabilityNotFound(id) => write!(f, "capability {id} not found"),
57            StateError::CapabilityNotHeld(pid, cid) => {
58                write!(f, "plugin {pid} does not hold capability {cid}")
59            }
60            StateError::IsolationViolation(active, affected) => {
61                write!(f, "isolation violation: {active} affects {affected}")
62            }
63            StateError::DanglingCapability(addr) => {
64                write!(f, "freeing address {addr} would create dangling capability")
65            }
66            StateError::PluginExists(id) => write!(f, "plugin {id} already exists"),
67            StateError::ActorExists(id) => write!(f, "actor {id} already exists"),
68            StateError::ResourceExists(id) => write!(f, "resource {id} already exists"),
69            StateError::WorkflowExists(id) => write!(f, "workflow {id} already exists"),
70            StateError::CounterOverflow(name) => write!(f, "{name} counter overflow"),
71        }
72    }
73}
74
75impl std::error::Error for StateError {}
76
77/// Resource metadata
78///
79/// Corresponds to Lean: `structure ResourceInfo`
80#[derive(Debug, Clone, PartialEq, Eq, Default)]
81pub struct ResourceInfo {
82    /// Security level of the resource
83    ///
84    /// Corresponds to Lean: `level : SecurityLevel`
85    pub(crate) level: SecurityLevel,
86}
87
88impl ResourceInfo {
89    /// Create a new ResourceInfo with the given security level
90    pub fn new(level: SecurityLevel) -> Self {
91        ResourceInfo { level }
92    }
93
94    /// Get the security level
95    #[inline]
96    pub fn level(&self) -> SecurityLevel {
97        self.level
98    }
99}
100
101/// Complete system state
102///
103/// Corresponds to Lean: `@[ext] structure State`
104///
105/// INVARIANTS:
106/// - All state transitions preserve isolation (non-active plugins unchanged)
107/// - Temporal safety: freed resources stay freed
108/// - Capability validity is checked at use time
109/// - All collections are sorted by key for deterministic iteration
110/// - Single source of truth: logical time lives in `kernel.now`, not duplicated here
111#[derive(Debug, Clone)]
112#[must_use = "state transitions return new state that must be used"]
113pub struct State {
114    /// Kernel state (TCB) -- owns the authoritative logical clock (`now`)
115    ///
116    /// Corresponds to Lean: `kernel : KernelState`
117    pub(crate) kernel: KernelState,
118
119    /// Plugin states
120    ///
121    /// Corresponds to Lean: `plugins : PluginId -> PluginState`
122    /// INVARIANT: Sorted by PluginId, no duplicates.
123    pub(crate) plugins: Vec<(PluginId, PluginState)>,
124
125    /// Actor runtimes
126    ///
127    /// Corresponds to Lean: `actors : ActorId -> ActorRuntime`
128    /// INVARIANT: Sorted by ActorId, no duplicates.
129    pub(crate) actors: Vec<(ActorId, ActorRuntime)>,
130
131    /// Resource metadata
132    ///
133    /// Corresponds to Lean: `resources : ResourceId -> ResourceInfo`
134    /// INVARIANT: Sorted by ResourceId, no duplicates.
135    pub(crate) resources: Vec<(ResourceId, ResourceInfo)>,
136
137    /// Workflow instances
138    ///
139    /// Corresponds to Lean: `workflows : WorkflowId -> WorkflowInstance`
140    /// INVARIANT: Sorted by WorkflowId, no duplicates.
141    pub(crate) workflows: Vec<(WorkflowId, WorkflowInstance)>,
142
143    /// Ghost state for verification
144    ///
145    /// Corresponds to Lean: `ghost : MetaState`
146    pub(crate) ghost: MetaState,
147}
148
149impl State {
150    /// Create a new empty state
151    pub fn empty() -> Self {
152        State {
153            kernel: KernelState::default(),
154            plugins: Vec::new(),
155            actors: Vec::new(),
156            resources: Vec::new(),
157            workflows: Vec::new(),
158            ghost: MetaState::empty(),
159        }
160    }
161
162    // ========================================
163    // Helper methods for sorted Vec operations
164    // ========================================
165
166    /// Find index of plugin by ID (binary search on sorted vec)
167    fn find_plugin_index(&self, pid: PluginId) -> Option<usize> {
168        self.plugins
169            .binary_search_by_key(&pid, |entry| entry.0)
170            .ok()
171    }
172
173    /// Find insertion position for plugin (binary search on sorted vec)
174    fn find_plugin_insert_pos(&self, pid: PluginId) -> (usize, bool) {
175        match self.plugins.binary_search_by_key(&pid, |entry| entry.0) {
176            Ok(pos) => (pos, true),
177            Err(pos) => (pos, false),
178        }
179    }
180
181    /// Find index of actor by ID (binary search on sorted vec)
182    fn find_actor_index(&self, aid: ActorId) -> Option<usize> {
183        self.actors.binary_search_by_key(&aid, |entry| entry.0).ok()
184    }
185
186    /// Find insertion position for actor (binary search on sorted vec)
187    fn find_actor_insert_pos(&self, aid: ActorId) -> (usize, bool) {
188        match self.actors.binary_search_by_key(&aid, |entry| entry.0) {
189            Ok(pos) => (pos, true),
190            Err(pos) => (pos, false),
191        }
192    }
193
194    /// Find index of resource by ID (binary search on sorted vec)
195    fn find_resource_index(&self, rid: ResourceId) -> Option<usize> {
196        self.resources
197            .binary_search_by_key(&rid, |entry| entry.0)
198            .ok()
199    }
200
201    /// Find insertion position for resource (binary search on sorted vec)
202    fn find_resource_insert_pos(&self, rid: ResourceId) -> (usize, bool) {
203        match self.resources.binary_search_by_key(&rid, |entry| entry.0) {
204            Ok(pos) => (pos, true),
205            Err(pos) => (pos, false),
206        }
207    }
208
209    /// Find index of workflow by ID (binary search on sorted vec)
210    fn find_workflow_index(&self, wid: WorkflowId) -> Option<usize> {
211        self.workflows
212            .binary_search_by_key(&wid, |entry| entry.0)
213            .ok()
214    }
215
216    /// Find insertion position for workflow (binary search on sorted vec)
217    fn find_workflow_insert_pos(&self, wid: WorkflowId) -> (usize, bool) {
218        match self.workflows.binary_search_by_key(&wid, |entry| entry.0) {
219            Ok(pos) => (pos, true),
220            Err(pos) => (pos, false),
221        }
222    }
223
224    // ========================================
225    // Public API
226    // ========================================
227
228    /// Get plugin memory
229    ///
230    /// Corresponds to Lean: `def State.plugin_memory`
231    pub fn plugin_memory(&self, pid: PluginId) -> Option<&LinearMemory> {
232        self.find_plugin_index(pid)
233            .map(|idx| self.plugins[idx].1.memory())
234    }
235
236    /// Get plugin security level
237    ///
238    /// Corresponds to Lean: `def State.plugin_level`
239    pub fn plugin_level(&self, pid: PluginId) -> Option<SecurityLevel> {
240        self.find_plugin_index(pid)
241            .map(|idx| self.plugins[idx].1.level())
242    }
243
244    /// Get resource security level
245    ///
246    /// Corresponds to Lean: `def State.resource_level`
247    pub fn resource_level(&self, rid: ResourceId) -> Option<SecurityLevel> {
248        self.find_resource_index(rid)
249            .map(|idx| self.resources[idx].1.level())
250    }
251
252    /// Get capability from kernel
253    ///
254    /// Corresponds to Lean: `def State.get_cap`
255    pub fn get_cap(&self, cap_id: CapId) -> Option<&Capability> {
256        self.kernel.revocation().get(cap_id)
257    }
258
259    /// Check if capability is valid in current state
260    ///
261    /// Corresponds to Lean: `def State.cap_is_valid`
262    pub fn cap_is_valid(&self, cap_id: CapId) -> bool {
263        self.kernel.revocation().is_valid(cap_id)
264    }
265
266    /// Check if plugin holds capability by ID
267    ///
268    /// Corresponds to Lean: `def State.plugin_holds`
269    pub fn plugin_holds(&self, pid: PluginId, cap_id: CapId) -> bool {
270        self.find_plugin_index(pid)
271            .is_some_and(|idx| self.plugins[idx].1.holds_cap(cap_id))
272    }
273
274    /// Allocate resource, update ghost history
275    ///
276    /// Corresponds to Lean: `def State.apply_alloc`
277    pub fn apply_alloc(&self, owner: PluginId, _size: Size) -> Self {
278        let addr = self.ghost.resource_count() as MemAddr;
279        let new_ghost = self.ghost.alloc(addr, owner);
280        State {
281            kernel: self.kernel.clone(),
282            plugins: self.plugins.clone(),
283            actors: self.actors.clone(),
284            resources: self.resources.clone(),
285            workflows: self.workflows.clone(),
286            ghost: new_ghost,
287        }
288    }
289
290    /// Allocate resource (mutating version)
291    pub fn apply_alloc_mut(&mut self, owner: PluginId, _size: Size) -> MemAddr {
292        let addr = self.ghost.resource_count() as MemAddr;
293        self.ghost.alloc_mut(addr, owner);
294        addr
295    }
296
297    /// Free resource, mark as dead in ghost history
298    ///
299    /// Corresponds to Lean: `def State.apply_free`
300    ///
301    /// SECURITY: This checks for dangling capabilities before freeing.
302    /// If any valid capability targets this address, freeing it would
303    /// create a USE-AFTER-FREE vulnerability.
304    ///
305    /// # Errors
306    ///
307    /// Returns `StateError::DanglingCapability` if a valid capability still targets the address.
308    pub fn apply_free(&self, addr: MemAddr) -> Result<Self, StateError> {
309        // GUARD: Check for dangling capabilities (B.3 blocker fix)
310        // MemAddr = u64, ResourceId = u128; widen addr for comparison
311        if self
312            .kernel
313            .revocation()
314            .any_valid_targeting(u128::from(addr))
315        {
316            return Err(StateError::DanglingCapability(addr));
317        }
318
319        Ok(State {
320            kernel: self.kernel.clone(),
321            plugins: self.plugins.clone(),
322            actors: self.actors.clone(),
323            resources: self.resources.clone(),
324            workflows: self.workflows.clone(),
325            ghost: self.ghost.free(addr),
326        })
327    }
328
329    /// Free resource (mutating version)
330    ///
331    /// SECURITY: Checks for dangling capabilities before freeing.
332    /// Requires the resource to be currently allocated (not unallocated or already freed).
333    ///
334    /// # Errors
335    ///
336    /// Returns `StateError::DanglingCapability` if a valid capability still targets the address.
337    /// Returns `StateError::ResourceNotFound` if the address was already freed or never allocated.
338    pub fn apply_free_mut(&mut self, addr: MemAddr) -> Result<(), StateError> {
339        // GUARD: Check for dangling capabilities (B.3 blocker fix)
340        // MemAddr = u64, ResourceId = u128; widen addr for comparison
341        if self
342            .kernel
343            .revocation()
344            .any_valid_targeting(u128::from(addr))
345        {
346            return Err(StateError::DanglingCapability(addr));
347        }
348        match self.ghost.free_mut(addr) {
349            Ok(()) => Ok(()),
350            Err(super::MemoryError::DoubleFree(_)) => {
351                Err(StateError::ResourceNotFound(addr.into()))
352            }
353            Err(super::MemoryError::NotAllocated(_)) => {
354                Err(StateError::ResourceNotFound(addr.into()))
355            }
356            Err(super::MemoryError::UseAfterFree(_)) => {
357                Err(StateError::ResourceNotFound(addr.into()))
358            }
359            Err(super::MemoryError::OutOfBounds(_, _, _)) => {
360                Err(StateError::ResourceNotFound(addr.into()))
361            }
362        }
363    }
364
365    /// Revoke capability (single cap)
366    ///
367    /// Corresponds to Lean: `def State.apply_revoke`
368    pub fn apply_revoke(&self, cap_id: CapId) -> Self {
369        State {
370            kernel: KernelState {
371                key_state: self.kernel.key_state().clone(),
372                policy: self.kernel.policy().clone(),
373                revocation: self.kernel.revocation().revoke(cap_id),
374                now: self.kernel.now(),
375                next_cap_id: self.kernel.next_cap_id(),
376            },
377            plugins: self.plugins.clone(),
378            actors: self.actors.clone(),
379            resources: self.resources.clone(),
380            workflows: self.workflows.clone(),
381            ghost: self.ghost.clone(),
382        }
383    }
384
385    /// Revoke capability transitively
386    ///
387    /// Corresponds to Lean: `def State.apply_cap_revoke`
388    pub fn apply_cap_revoke(&self, cap_id: CapId) -> Self {
389        State {
390            kernel: KernelState {
391                key_state: self.kernel.key_state().clone(),
392                policy: self.kernel.policy().clone(),
393                revocation: self.kernel.revocation().revoke_transitive(cap_id),
394                now: self.kernel.now(),
395                next_cap_id: self.kernel.next_cap_id(),
396            },
397            plugins: self.plugins.clone(),
398            actors: self.actors.clone(),
399            resources: self.resources.clone(),
400            workflows: self.workflows.clone(),
401            ghost: self.ghost.clone(),
402        }
403    }
404
405    /// Revoke capability transitively (mutating version)
406    ///
407    /// Uses the children-index optimized O(k) fast path with
408    /// BFS traversal and visited set (no iteration cap).
409    ///
410    /// # Errors
411    ///
412    /// Returns `KernelError::CapNotFound` if the capability does not exist.
413    pub fn apply_cap_revoke_mut(&mut self, cap_id: CapId) -> Result<(), super::KernelError> {
414        self.kernel
415            .revocation_mut()
416            .revoke_transitive_fast_mut(cap_id)
417    }
418
419    /// Delegate capability: insert new cap into kernel and grant to target
420    ///
421    /// Corresponds to Lean: `def State.apply_cap_delegate`
422    ///
423    /// Returns error if capability ID already exists (collision).
424    ///
425    /// # Errors
426    ///
427    /// Returns `KernelError::CapIdCollision` if a capability with the same ID already exists.
428    pub fn apply_cap_delegate(
429        &self,
430        new_cap: Capability,
431        target: PluginId,
432    ) -> Result<Self, super::KernelError> {
433        let cap_id = new_cap.id();
434        let new_revocation = self.kernel.revocation().insert(new_cap)?;
435        let mut new_plugins = self.plugins.clone();
436        // Find and update the target plugin (binary search on sorted vec)
437        if let Ok(idx) = new_plugins.binary_search_by_key(&target, |(pid, _)| *pid) {
438            new_plugins[idx].1.grant_cap_mut(cap_id);
439        }
440
441        Ok(State {
442            kernel: KernelState {
443                key_state: self.kernel.key_state().clone(),
444                policy: self.kernel.policy().clone(),
445                revocation: new_revocation,
446                now: self.kernel.now(),
447                next_cap_id: self.kernel.next_cap_id(),
448            },
449            plugins: new_plugins,
450            actors: self.actors.clone(),
451            resources: self.resources.clone(),
452            workflows: self.workflows.clone(),
453            ghost: self.ghost.clone(),
454        })
455    }
456
457    /// Delegate capability (mutating version)
458    ///
459    /// # Errors
460    ///
461    /// Returns `KernelError::CapIdCollision` if a capability with the same ID already exists.
462    pub fn apply_cap_delegate_mut(
463        &mut self,
464        new_cap: Capability,
465        target: PluginId,
466    ) -> Result<(), super::KernelError> {
467        let cap_id = new_cap.id();
468        self.kernel.revocation_mut().insert_mut(new_cap)?;
469        if let Some(idx) = self.find_plugin_index(target) {
470            self.plugins[idx].1.grant_cap_mut(cap_id);
471        }
472        Ok(())
473    }
474
475    /// Check if memory isolation is preserved after step
476    ///
477    /// Corresponds to Lean: `def State.preserves_isolation`
478    pub fn preserves_isolation(&self, other: &State, active: PluginId) -> bool {
479        self.plugins.iter().all(|(pid, ps)| {
480            if *pid == active {
481                return true;
482            }
483            match other.plugins.binary_search_by_key(pid, |(id, _)| *id) {
484                Ok(idx) => *ps == other.plugins[idx].1,
485                Err(_) => false,
486            }
487        })
488    }
489
490    /// Check temporal safety: freed resources stay freed
491    ///
492    /// Corresponds to Lean: `def State.temporal_safety`
493    pub fn temporal_safety(&self, other: &State) -> bool {
494        self.ghost
495            .freed_set
496            .iter()
497            .all(|addr| other.ghost.is_freed(*addr))
498    }
499
500    /// Get the current logical time (delegates to kernel.now -- single source of truth)
501    ///
502    /// Corresponds to Lean: `def State.time`
503    #[inline]
504    pub fn time(&self) -> Time {
505        self.kernel.now()
506    }
507
508    /// Advance logical time via the kernel clock (single source of truth).
509    ///
510    /// Uses checked arithmetic so overflow is explicit rather than silent.
511    ///
512    /// # Errors
513    ///
514    /// Returns `StateError::CounterOverflow` if the time counter would exceed u64::MAX.
515    pub fn tick(&mut self) -> Result<(), StateError> {
516        self.kernel
517            .tick_checked()
518            .map_err(|_| StateError::CounterOverflow("time"))
519    }
520
521    /// Get a reference to the kernel state
522    #[inline]
523    pub fn kernel(&self) -> &KernelState {
524        &self.kernel
525    }
526
527    /// Get a mutable reference to the kernel state
528    #[inline]
529    #[allow(dead_code)] // Lean correspondence
530    pub(crate) fn kernel_mut(&mut self) -> &mut KernelState {
531        &mut self.kernel
532    }
533
534    /// Get a reference to the ghost state
535    #[inline]
536    pub fn ghost(&self) -> &MetaState {
537        &self.ghost
538    }
539
540    /// Get a plugin state
541    pub fn get_plugin(&self, pid: PluginId) -> Option<&PluginState> {
542        self.find_plugin_index(pid).map(|idx| &self.plugins[idx].1)
543    }
544
545    /// Get a mutable plugin state
546    pub fn get_plugin_mut(&mut self, pid: PluginId) -> Option<&mut PluginState> {
547        self.find_plugin_index(pid)
548            .map(|idx| &mut self.plugins[idx].1)
549    }
550
551    /// Insert a plugin (checks for collision)
552    ///
553    /// SECURITY: Returns error if plugin already exists to prevent silent overwrite.
554    ///
555    /// # Errors
556    ///
557    /// Returns `StateError::PluginExists` if a plugin with the given ID is already registered.
558    pub fn insert_plugin(&mut self, pid: PluginId, ps: PluginState) -> Result<(), StateError> {
559        let (pos, exists) = self.find_plugin_insert_pos(pid);
560        if exists {
561            return Err(StateError::PluginExists(pid));
562        }
563        self.plugins.insert(pos, (pid, ps));
564        Ok(())
565    }
566
567    /// Get an actor runtime
568    pub fn get_actor(&self, aid: ActorId) -> Option<&ActorRuntime> {
569        self.find_actor_index(aid).map(|idx| &self.actors[idx].1)
570    }
571
572    /// Get a mutable actor runtime
573    pub fn get_actor_mut(&mut self, aid: ActorId) -> Option<&mut ActorRuntime> {
574        self.find_actor_index(aid)
575            .map(|idx| &mut self.actors[idx].1)
576    }
577
578    /// Insert an actor (checks for collision)
579    ///
580    /// SECURITY: Returns error if actor already exists to prevent silent overwrite.
581    ///
582    /// # Errors
583    ///
584    /// Returns `StateError::ActorExists` if an actor with the given ID is already registered.
585    pub fn insert_actor(&mut self, aid: ActorId, ar: ActorRuntime) -> Result<(), StateError> {
586        let (pos, exists) = self.find_actor_insert_pos(aid);
587        if exists {
588            return Err(StateError::ActorExists(aid));
589        }
590        self.actors.insert(pos, (aid, ar));
591        Ok(())
592    }
593
594    /// Get a resource
595    pub fn get_resource(&self, rid: ResourceId) -> Option<&ResourceInfo> {
596        self.find_resource_index(rid)
597            .map(|idx| &self.resources[idx].1)
598    }
599
600    /// Insert a resource (checks for collision)
601    ///
602    /// SECURITY: Returns error if resource already exists to prevent silent overwrite.
603    ///
604    /// # Errors
605    ///
606    /// Returns `StateError::ResourceExists` if a resource with the given ID is already registered.
607    pub fn insert_resource(&mut self, rid: ResourceId, ri: ResourceInfo) -> Result<(), StateError> {
608        let (pos, exists) = self.find_resource_insert_pos(rid);
609        if exists {
610            return Err(StateError::ResourceExists(rid));
611        }
612        self.resources.insert(pos, (rid, ri));
613        Ok(())
614    }
615
616    /// Get a workflow
617    pub fn get_workflow(&self, wid: WorkflowId) -> Option<&WorkflowInstance> {
618        self.find_workflow_index(wid)
619            .map(|idx| &self.workflows[idx].1)
620    }
621
622    /// Get a mutable workflow
623    pub fn get_workflow_mut(&mut self, wid: WorkflowId) -> Option<&mut WorkflowInstance> {
624        self.find_workflow_index(wid)
625            .map(|idx| &mut self.workflows[idx].1)
626    }
627
628    /// Insert a workflow (checks for collision)
629    ///
630    /// SECURITY: Returns error if workflow already exists to prevent silent overwrite.
631    ///
632    /// # Errors
633    ///
634    /// Returns `StateError::WorkflowExists` if a workflow with the given ID is already registered.
635    pub fn insert_workflow(
636        &mut self,
637        wid: WorkflowId,
638        wi: WorkflowInstance,
639    ) -> Result<(), StateError> {
640        let (pos, exists) = self.find_workflow_insert_pos(wid);
641        if exists {
642            return Err(StateError::WorkflowExists(wid));
643        }
644        self.workflows.insert(pos, (wid, wi));
645        Ok(())
646    }
647
648    /// Get the number of plugins
649    #[inline]
650    pub fn plugin_count(&self) -> usize {
651        self.plugins.len()
652    }
653
654    /// Get the number of actors
655    #[inline]
656    pub fn actor_count(&self) -> usize {
657        self.actors.len()
658    }
659
660    /// Get the number of resources
661    #[inline]
662    pub fn resource_count(&self) -> usize {
663        self.resources.len()
664    }
665
666    /// Get the number of workflows
667    #[inline]
668    pub fn workflow_count(&self) -> usize {
669        self.workflows.len()
670    }
671
672    /// Get all plugin IDs
673    pub fn plugin_ids(&self) -> Vec<PluginId> {
674        let mut ids = Vec::with_capacity(self.plugins.len());
675        let mut i = 0;
676        while i < self.plugins.len() {
677            ids.push(self.plugins[i].0);
678            i += 1;
679        }
680        ids
681    }
682
683    /// Get all resource IDs
684    pub fn resource_ids(&self) -> Vec<ResourceId> {
685        let mut ids = Vec::with_capacity(self.resources.len());
686        let mut i = 0;
687        while i < self.resources.len() {
688            ids.push(self.resources[i].0);
689            i += 1;
690        }
691        ids
692    }
693}
694
695impl Default for State {
696    fn default() -> Self {
697        State::empty()
698    }
699}
700
701/// Check if two states are low-equivalent (one-way)
702///
703/// Corresponds to Lean: `def low_equivalent_left`
704///
705/// All low (<= L) components of s1 agree with s2.
706#[allow(dead_code)] // Lean correspondence
707pub fn low_equivalent_left(l: SecurityLevel, s1: &State, s2: &State) -> bool {
708    plugins_low_equiv(l, s1, s2) && resources_low_equiv(l, s1, s2)
709}
710
711/// Helper: check all low plugins match
712#[allow(dead_code)] // Lean correspondence
713fn plugins_low_equiv(l: SecurityLevel, s1: &State, s2: &State) -> bool {
714    s1.plugins.iter().all(|(pid, ps1)| {
715        if ps1.level() > l {
716            return true;
717        }
718        match s2.plugins.binary_search_by_key(pid, |(id, _)| *id) {
719            Ok(idx) => *ps1 == s2.plugins[idx].1,
720            Err(_) => false,
721        }
722    })
723}
724
725/// Helper: check all low resources match
726#[allow(dead_code)] // Lean correspondence
727fn resources_low_equiv(l: SecurityLevel, s1: &State, s2: &State) -> bool {
728    s1.resources.iter().all(|(rid, ri1)| {
729        if ri1.level() > l {
730            return true;
731        }
732        match s2.resources.binary_search_by_key(rid, |(id, _)| *id) {
733            Ok(idx) => *ri1 == s2.resources[idx].1,
734            Err(_) => false,
735        }
736    })
737}
738
739/// Check if two states are low-equivalent (symmetric)
740///
741/// Corresponds to Lean: `def low_equivalent`
742///
743/// States agree on all data at level <= L (for noninterference).
744#[allow(dead_code)] // Lean correspondence
745pub fn low_equivalent(l: SecurityLevel, s1: &State, s2: &State) -> bool {
746    low_equivalent_left(l, s1, s2) && low_equivalent_left(l, s2, s1)
747}
748
749#[cfg(test)]
750mod tests {
751    use super::*;
752    use crate::types::{Right, Rights, SealedTag};
753
754    fn make_test_cap(id: CapId) -> Capability {
755        Capability::new(
756            id,
757            1,
758            1,
759            Rights::singleton(Right::Read),
760            None,
761            0,
762            SealedTag::empty(),
763        )
764        .expect("valid capability")
765    }
766
767    #[test]
768    fn test_state_empty() {
769        let s = State::empty();
770        assert_eq!(s.time(), 0);
771        assert_eq!(s.plugin_count(), 0);
772        assert_eq!(s.actor_count(), 0);
773    }
774
775    #[test]
776    fn test_state_apply_alloc() {
777        let mut s = State::empty();
778        let addr = s.apply_alloc_mut(1, 1024);
779
780        assert!(s.ghost.is_live(addr));
781    }
782
783    #[test]
784    fn test_state_apply_free() {
785        let mut s = State::empty();
786        let addr = s.apply_alloc_mut(1, 1024);
787        s.apply_free_mut(addr).expect("free should succeed");
788
789        assert!(!s.ghost.is_live(addr));
790        assert!(s.ghost.is_freed(addr));
791    }
792
793    #[test]
794    fn test_state_temporal_safety() {
795        let mut s1 = State::empty();
796        let addr = s1.apply_alloc_mut(1, 1024);
797        s1.apply_free_mut(addr).expect("free should succeed");
798
799        // s2 should also have addr freed
800        let s2 = s1.clone();
801        assert!(s1.temporal_safety(&s2));
802
803        // s3 with more allocations should still preserve temporal safety
804        let mut s3 = s1.clone();
805        s3.apply_alloc_mut(2, 512);
806        assert!(s1.temporal_safety(&s3));
807    }
808
809    #[test]
810    fn test_state_plugin_holds() {
811        let mut s = State::empty();
812        let ps = PluginState::empty(SecurityLevel::Public, 0);
813        s.insert_plugin(1, ps).unwrap();
814
815        // Plugin doesn't hold any caps initially
816        assert!(!s.plugin_holds(1, 42));
817
818        // Grant cap
819        if let Some(ps) = s.get_plugin_mut(1) {
820            ps.grant_cap_mut(42);
821        }
822        assert!(s.plugin_holds(1, 42));
823    }
824
825    #[test]
826    fn test_state_cap_delegate() {
827        let mut s = State::empty();
828        let ps = PluginState::empty(SecurityLevel::Public, 0);
829        s.insert_plugin(1, ps).unwrap();
830
831        let cap = make_test_cap(100);
832        s.apply_cap_delegate_mut(cap, 1)
833            .expect("delegate should succeed");
834
835        // Plugin should hold the cap
836        assert!(s.plugin_holds(1, 100));
837        // Cap should be valid in kernel
838        assert!(s.cap_is_valid(100));
839    }
840
841    #[test]
842    fn test_state_cap_revoke() {
843        let mut s = State::empty();
844        let ps = PluginState::empty(SecurityLevel::Public, 0);
845        s.insert_plugin(1, ps).unwrap();
846
847        let cap = make_test_cap(100);
848        s.apply_cap_delegate_mut(cap, 1)
849            .expect("delegate should succeed");
850
851        assert!(s.cap_is_valid(100));
852
853        s.apply_cap_revoke_mut(100).expect("revoke should succeed");
854        assert!(!s.cap_is_valid(100));
855    }
856
857    #[test]
858    fn test_state_isolation() {
859        let mut s1 = State::empty();
860        let _ = s1.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 100));
861        let _ = s1.insert_plugin(2, PluginState::empty(SecurityLevel::Internal, 200));
862
863        // Modify only plugin 1
864        let mut s2 = s1.clone();
865        if let Some(ps) = s2.get_plugin_mut(1) {
866            ps.grant_cap_mut(42);
867        }
868
869        // Isolation should be preserved for plugin 2
870        assert!(s1.preserves_isolation(&s2, 1));
871    }
872
873    #[test]
874    fn test_low_equivalent_refl() {
875        // Corresponds to Lean theorem: low_equivalent_refl
876        let s = State::empty();
877        assert!(low_equivalent(SecurityLevel::Public, &s, &s));
878        assert!(low_equivalent(SecurityLevel::Secret, &s, &s));
879    }
880
881    #[test]
882    fn test_low_equivalent_symm() {
883        // Corresponds to Lean theorem: low_equivalent_symm
884        let mut s1 = State::empty();
885        let _ = s1.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 0));
886
887        let s2 = s1.clone();
888
889        assert!(low_equivalent(SecurityLevel::Public, &s1, &s2));
890        assert!(low_equivalent(SecurityLevel::Public, &s2, &s1));
891    }
892
893    #[test]
894    fn test_low_equivalent_high_changes_invisible() {
895        let mut s1 = State::empty();
896        let _ = s1.insert_plugin(1, PluginState::empty(SecurityLevel::Public, 0));
897        let _ = s1.insert_plugin(2, PluginState::empty(SecurityLevel::Secret, 0));
898
899        let mut s2 = s1.clone();
900        // Modify the Secret plugin
901        if let Some(ps) = s2.get_plugin_mut(2) {
902            ps.grant_cap_mut(42);
903        }
904
905        // At Public level, states should still be equivalent
906        // (Secret changes are invisible to Public observer)
907        assert!(low_equivalent(SecurityLevel::Public, &s1, &s2));
908
909        // At Secret level, states should NOT be equivalent
910        assert!(!low_equivalent(SecurityLevel::Secret, &s1, &s2));
911    }
912
913    #[test]
914    fn test_resource_info() {
915        let ri = ResourceInfo::new(SecurityLevel::Confidential);
916        assert_eq!(ri.level(), SecurityLevel::Confidential);
917
918        let ri_default = ResourceInfo::default();
919        assert_eq!(ri_default.level(), SecurityLevel::Public);
920    }
921
922    #[test]
923    fn test_state_tick() {
924        let mut s = State::empty();
925        assert_eq!(s.time(), 0);
926
927        s.tick().expect("tick should succeed");
928        assert_eq!(s.time(), 1);
929
930        s.tick().expect("tick should succeed");
931        assert_eq!(s.time(), 2);
932    }
933
934    #[test]
935    fn test_state_time_delegates_to_kernel() {
936        // Verify that State.time() reads from kernel.now()
937        let s = State::empty();
938        assert_eq!(s.time(), s.kernel().now());
939    }
940
941    // ============== BINARY SEARCH EDGE-CASE TESTS ==============
942
943    #[test]
944    fn test_state_plugin_insert_order() {
945        let mut s = State::empty();
946        let ps = || PluginState::empty(SecurityLevel::Public, 1024);
947        // Insert out of order
948        s.insert_plugin(50, ps()).unwrap();
949        s.insert_plugin(10, ps()).unwrap();
950        s.insert_plugin(90, ps()).unwrap();
951        s.insert_plugin(30, ps()).unwrap();
952
953        // Verify sorted
954        let ids: Vec<u128> = s.plugins.iter().map(|(id, _)| *id).collect();
955        assert_eq!(ids, vec![10, 30, 50, 90]);
956    }
957
958    #[test]
959    fn test_state_empty_lookup() {
960        let s = State::empty();
961        assert!(s.get_plugin(0).is_none());
962        assert!(s.get_actor(0).is_none());
963        assert!(s.get_workflow(0).is_none());
964    }
965
966    #[test]
967    fn test_state_single_element_lookup() {
968        let mut s = State::empty();
969        s.insert_plugin(42, PluginState::empty(SecurityLevel::Public, 1024))
970            .unwrap();
971
972        assert!(s.get_plugin(42).is_some());
973        assert!(s.get_plugin(41).is_none());
974        assert!(s.get_plugin(43).is_none());
975    }
976
977    #[test]
978    fn test_state_plugin_collision() {
979        let mut s = State::empty();
980        let ps = || PluginState::empty(SecurityLevel::Public, 1024);
981        s.insert_plugin(1, ps()).unwrap();
982        let result = s.insert_plugin(1, ps());
983        assert!(matches!(result, Err(StateError::PluginExists(1))));
984    }
985}