Skip to main content

plasma_prp/core/
location.rs

1//! plLocation — identifies a (age, chapter, page) triple via a sequence number.
2//!
3//! C++ ref: pnKeyedObject/plUoid.h, plUoid.cpp
4
5use std::fmt;
6use std::io::{Read, Write};
7
8use anyhow::Result;
9
10use crate::resource::prp::PlasmaRead;
11
12/// Flags for plLocation.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct LocFlags(u16);
15
16impl LocFlags {
17    pub const LOCAL_ONLY: u16 = 0x1;
18    pub const VOLATILE: u16 = 0x2;
19    pub const RESERVED: u16 = 0x4;
20    pub const BUILT_IN: u16 = 0x8;
21    pub const ITINERANT: u16 = 0x10;
22}
23
24/// Sequence number ranges.
25const GLOBAL_FIXED_LOC_IDX: u32 = 0;
26const LOCAL_LOC_START_IDX: u32 = 3;
27const LOCAL_LOC_END_IDX: u32 = 32;
28const NORMAL_LOC_START_IDX: u32 = LOCAL_LOC_END_IDX + 1;
29const RESERVED_LOC_START: u32 = 0xFF000000;
30const GLOBAL_SERVER_LOC_IDX: u32 = RESERVED_LOC_START;
31const RESERVED_LOC_AVAILABLE_START: u32 = GLOBAL_SERVER_LOC_IDX + 1;
32const INVALID_LOC_IDX: u32 = 0xFFFFFFFF;
33
34/// A location in the Plasma world — identifies a page within an age.
35///
36/// Stored as a sequence number (u32) + flags (u16).
37/// Equality ignores the kItinerant flag per C++ behavior.
38#[derive(Clone, Copy)]
39pub struct Location {
40    pub sequence_number: u32,
41    pub flags: u16,
42}
43
44impl Location {
45    /// Create an invalid (uninitialized) location.
46    pub fn invalid() -> Self {
47        Self {
48            sequence_number: INVALID_LOC_IDX,
49            flags: 0,
50        }
51    }
52
53    /// Create a location with the given sequence number and flags.
54    pub fn new(sequence_number: u32, flags: u16) -> Self {
55        Self {
56            sequence_number,
57            flags,
58        }
59    }
60
61    /// Create a reserved location.
62    pub fn make_reserved(number: u32) -> Self {
63        Self {
64            sequence_number: RESERVED_LOC_AVAILABLE_START + number,
65            flags: LocFlags::RESERVED,
66        }
67    }
68
69    /// Create a normal location from a page sequence offset.
70    pub fn make_normal(number: u32) -> Self {
71        Self {
72            sequence_number: NORMAL_LOC_START_IDX + number,
73            flags: 0,
74        }
75    }
76
77    pub fn is_valid(&self) -> bool {
78        self.sequence_number != INVALID_LOC_IDX
79    }
80
81    pub fn is_reserved(&self) -> bool {
82        self.flags & LocFlags::RESERVED != 0
83    }
84
85    pub fn is_itinerant(&self) -> bool {
86        self.flags & LocFlags::ITINERANT != 0
87    }
88
89    pub fn is_virtual(&self) -> bool {
90        self.sequence_number == GLOBAL_FIXED_LOC_IDX
91    }
92
93    pub fn is_local(&self) -> bool {
94        self.sequence_number >= LOCAL_LOC_START_IDX
95            && self.sequence_number <= LOCAL_LOC_END_IDX
96    }
97
98    pub fn read(reader: &mut impl Read) -> Result<Self> {
99        let sequence_number = reader.read_u32()?;
100        let flags = reader.read_u16()?;
101        Ok(Self {
102            sequence_number,
103            flags,
104        })
105    }
106
107    pub fn write(&self, writer: &mut impl Write) -> Result<()> {
108        writer.write_all(&self.sequence_number.to_le_bytes())?;
109        writer.write_all(&self.flags.to_le_bytes())?;
110        Ok(())
111    }
112
113    // Well-known locations
114    pub const GLOBAL_FIXED: Location = Location {
115        sequence_number: GLOBAL_FIXED_LOC_IDX,
116        flags: 0,
117    };
118    pub const LOCAL_START: Location = Location {
119        sequence_number: LOCAL_LOC_START_IDX,
120        flags: 0,
121    };
122    pub const LOCAL_END: Location = Location {
123        sequence_number: LOCAL_LOC_END_IDX,
124        flags: 0,
125    };
126    pub const NORMAL_START: Location = Location {
127        sequence_number: NORMAL_LOC_START_IDX,
128        flags: 0,
129    };
130    pub const GLOBAL_SERVER: Location = Location {
131        sequence_number: GLOBAL_SERVER_LOC_IDX,
132        flags: LocFlags::RESERVED,
133    };
134    pub const INVALID: Location = Location {
135        sequence_number: INVALID_LOC_IDX,
136        flags: 0,
137    };
138}
139
140impl PartialEq for Location {
141    fn eq(&self, other: &Self) -> bool {
142        // Ignore the kItinerant flag when comparing, matching C++ behavior
143        self.sequence_number == other.sequence_number
144            && (self.flags & !LocFlags::ITINERANT) == (other.flags & !LocFlags::ITINERANT)
145    }
146}
147
148impl Eq for Location {}
149
150impl std::hash::Hash for Location {
151    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
152        self.sequence_number.hash(state);
153        (self.flags & !LocFlags::ITINERANT).hash(state);
154    }
155}
156
157impl PartialOrd for Location {
158    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
159        Some(self.cmp(other))
160    }
161}
162
163impl Ord for Location {
164    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
165        self.sequence_number.cmp(&other.sequence_number)
166    }
167}
168
169impl Default for Location {
170    fn default() -> Self {
171        Self::invalid()
172    }
173}
174
175impl fmt::Debug for Location {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "Location(S:{:#x} F:{:#x})", self.sequence_number, self.flags)
178    }
179}
180
181impl fmt::Display for Location {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        write!(f, "S{:#x}F{:#x}", self.sequence_number, self.flags)
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use std::io::Cursor;
191
192    #[test]
193    fn test_invalid_location() {
194        let loc = Location::invalid();
195        assert!(!loc.is_valid());
196    }
197
198    #[test]
199    fn test_valid_location() {
200        let loc = Location::new(100, 0);
201        assert!(loc.is_valid());
202    }
203
204    #[test]
205    fn test_round_trip() {
206        let loc = Location::new(0x12345678, 0x0003);
207        let mut buf = Vec::new();
208        loc.write(&mut buf).unwrap();
209        assert_eq!(buf.len(), 6);
210
211        let mut cursor = Cursor::new(&buf);
212        let loc2 = Location::read(&mut cursor).unwrap();
213        assert_eq!(loc, loc2);
214    }
215
216    #[test]
217    fn test_itinerant_equality() {
218        let loc1 = Location::new(100, 0);
219        let loc2 = Location::new(100, LocFlags::ITINERANT);
220        assert_eq!(loc1, loc2);
221    }
222
223    #[test]
224    fn test_reserved() {
225        let loc = Location::make_reserved(5);
226        assert!(loc.is_reserved());
227        assert!(loc.is_valid());
228    }
229
230    #[test]
231    fn test_well_known() {
232        assert!(Location::GLOBAL_FIXED.is_virtual());
233        assert!(Location::GLOBAL_SERVER.is_reserved());
234        assert!(!Location::INVALID.is_valid());
235    }
236}