Skip to main content

softgpu_core/
handle.rs

1//! Generation-safe packed handles.
2//!
3//! SoftGPU never exposes raw table indices alone. A handle packs
4//! `kind | generation | index` so stale, forged, and cross-kind values fail
5//! deterministically after recycle.
6
7use std::fmt;
8
9/// Discriminator stored in the high bits of a packed handle.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[repr(u8)]
12pub enum HandleKind {
13    Agent = 1,
14    Region = 2,
15    MemoryPool = 3,
16    Signal = 4,
17    Queue = 5,
18}
19
20impl HandleKind {
21    pub fn from_u8(value: u8) -> Option<Self> {
22        match value {
23            1 => Some(Self::Agent),
24            2 => Some(Self::Region),
25            3 => Some(Self::MemoryPool),
26            4 => Some(Self::Signal),
27            5 => Some(Self::Queue),
28            _ => None,
29        }
30    }
31
32    pub fn as_str(self) -> &'static str {
33        match self {
34            Self::Agent => "agent",
35            Self::Region => "region",
36            Self::MemoryPool => "memory_pool",
37            Self::Signal => "signal",
38            Self::Queue => "queue",
39        }
40    }
41}
42
43/// Opaque 64-bit handle: `[kind:8][generation:24][index:32]`.
44#[derive(Clone, Copy, PartialEq, Eq, Hash)]
45pub struct PackedHandle(u64);
46
47impl PackedHandle {
48    pub const INVALID: Self = Self(0);
49
50    pub fn pack(kind: HandleKind, generation: u32, index: u32) -> Self {
51        let generation = generation & 0x00FF_FFFF;
52        let value =
53            (u64::from(kind as u8) << 56) | (u64::from(generation) << 32) | u64::from(index);
54        Self(value)
55    }
56
57    pub fn from_raw(raw: u64) -> Self {
58        Self(raw)
59    }
60
61    pub fn raw(self) -> u64 {
62        self.0
63    }
64
65    pub fn is_invalid(self) -> bool {
66        self.0 == 0
67    }
68
69    pub fn kind(self) -> Option<HandleKind> {
70        HandleKind::from_u8((self.0 >> 56) as u8)
71    }
72
73    pub fn generation(self) -> u32 {
74        ((self.0 >> 32) & 0x00FF_FFFF) as u32
75    }
76
77    pub fn index(self) -> u32 {
78        self.0 as u32
79    }
80}
81
82impl fmt::Debug for PackedHandle {
83    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84        match self.kind() {
85            Some(kind) => write!(
86                f,
87                "PackedHandle {{ kind: {}, gen: {}, index: {}, raw: {:#x} }}",
88                kind.as_str(),
89                self.generation(),
90                self.index(),
91                self.0
92            ),
93            None => write!(f, "PackedHandle {{ invalid_or_unknown: {:#x} }}", self.0),
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn pack_round_trip() {
104        let h = PackedHandle::pack(HandleKind::Agent, 7, 3);
105        assert_eq!(h.kind(), Some(HandleKind::Agent));
106        assert_eq!(h.generation(), 7);
107        assert_eq!(h.index(), 3);
108        assert_eq!(PackedHandle::from_raw(h.raw()), h);
109    }
110
111    #[test]
112    fn all_kinds_round_trip() {
113        for kind in [
114            HandleKind::Agent,
115            HandleKind::Region,
116            HandleKind::MemoryPool,
117            HandleKind::Signal,
118            HandleKind::Queue,
119        ] {
120            let h = PackedHandle::pack(kind, 1, 2);
121            assert_eq!(h.kind(), Some(kind));
122        }
123    }
124
125    #[test]
126    fn generation_is_masked_to_24_bits() {
127        let h = PackedHandle::pack(HandleKind::Agent, 0x01FF_FFFF, 1);
128        assert_eq!(h.generation(), 0x00FF_FFFF);
129    }
130
131    #[test]
132    fn zero_is_invalid() {
133        assert!(PackedHandle::INVALID.is_invalid());
134        assert!(PackedHandle::from_raw(0).kind().is_none());
135    }
136}