superfluid_daemon/
park.rs1use std::io::{Read, Write};
4use std::path::{Path, PathBuf};
5
6const MAGIC: &[u8; 8] = b"BRTPARK2";
7const MAGIC_V3: &[u8; 8] = b"BRTPARK3";
8const HEADER: usize = 32;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct ParkedSpace {
12 pub space_id: u32,
13 pub kind: u32,
14 pub encoding: u8,
15 pub sealed: Vec<u8>,
16}
17
18pub struct Parked {
19 pub covered: u64,
20 pub stream_digest: u64,
21 pub encoding: u8,
22 pub sealed: Vec<u8>,
23 pub spaces: Vec<ParkedSpace>,
24 pub multi_space: bool,
25}
26
27pub fn stream_digest(tokens: &[u32]) -> u64 {
28 let mut h: u64 = 0xCBF2_9CE4_8422_2325;
29 for t in tokens {
30 for b in t.to_le_bytes() {
31 h ^= b as u64;
32 h = h.wrapping_mul(0x0000_0100_0000_01B3);
33 }
34 }
35 h
36}
37
38fn artifact_path(dir: &Path, session: u64) -> PathBuf {
39 dir.join(format!("{session}.park"))
40}
41
42pub fn write(
43 dir: &Path,
44 session: u64,
45 covered: u64,
46 digest: u64,
47 encoding: u8,
48 sealed: &[u8],
49) -> std::io::Result<()> {
50 std::fs::create_dir_all(dir)?;
51 let tmp = dir.join(format!("{session}.park.tmp"));
52 {
53 let mut f = std::fs::File::create(&tmp)?;
54 f.write_all(MAGIC)?;
55 f.write_all(&covered.to_le_bytes())?;
56 f.write_all(&digest.to_le_bytes())?;
57 f.write_all(&(encoding as u64).to_le_bytes())?;
58 f.write_all(sealed)?;
59 f.sync_all()?;
60 }
61 std::fs::rename(&tmp, artifact_path(dir, session))
62}
63
64pub fn write_v3(
65 dir: &Path,
66 session: u64,
67 covered: u64,
68 digest: u64,
69 spaces: &[ParkedSpace],
70) -> std::io::Result<()> {
71 std::fs::create_dir_all(dir)?;
72 let tmp = dir.join(format!("{session}.park.tmp"));
73 {
74 let mut f = std::fs::File::create(&tmp)?;
75 f.write_all(MAGIC_V3)?;
76 f.write_all(&covered.to_le_bytes())?;
77 f.write_all(&digest.to_le_bytes())?;
78 f.write_all(&(spaces.len() as u32).to_le_bytes())?;
79 f.write_all(&[0u8; 4])?;
80 for sp in spaces {
81 f.write_all(&sp.space_id.to_le_bytes())?;
82 f.write_all(&sp.kind.to_le_bytes())?;
83 f.write_all(&[sp.encoding, 0, 0, 0, 0, 0, 0, 0])?;
84 f.write_all(&(sp.sealed.len() as u64).to_le_bytes())?;
85 f.write_all(&sp.sealed)?;
86 }
87 f.sync_all()?;
88 }
89 std::fs::rename(&tmp, artifact_path(dir, session))
90}
91
92pub fn read(dir: &Path, session: u64) -> Option<Parked> {
93 let mut f = std::fs::File::open(artifact_path(dir, session)).ok()?;
94 let mut head = [0u8; HEADER];
95 f.read_exact(&mut head).ok()?;
96 let covered = u64::from_le_bytes(head[8..16].try_into().expect("8 bytes"));
97 let stream_digest = u64::from_le_bytes(head[16..24].try_into().expect("8 bytes"));
98 if &head[0..8] == MAGIC {
99 let encoding = u64::from_le_bytes(head[24..32].try_into().expect("8 bytes"));
100 let mut sealed = Vec::new();
101 f.read_to_end(&mut sealed).ok()?;
102 if covered == 0 || sealed.is_empty() || encoding > u8::MAX as u64 {
103 return None;
104 }
105 return Some(Parked {
106 covered,
107 stream_digest,
108 encoding: encoding as u8,
109 sealed: sealed.clone(),
110 spaces: vec![ParkedSpace {
111 space_id: 1,
112 kind: superfluid_abi::space_kind::PAGED_TOKEN_KV,
113 encoding: encoding as u8,
114 sealed,
115 }],
116 multi_space: false,
117 });
118 }
119 if &head[0..8] != MAGIC_V3 {
120 return None;
121 }
122 let n_spaces = u32::from_le_bytes(head[24..28].try_into().expect("4 bytes"));
123 if covered == 0 || n_spaces == 0 || n_spaces > 64 {
124 return None;
125 }
126 let mut rest = Vec::new();
127 f.read_to_end(&mut rest).ok()?;
128 let mut at = 0usize;
129 let mut spaces = Vec::with_capacity(n_spaces as usize);
130 for _ in 0..n_spaces {
131 let hdr_end = at.checked_add(24)?;
132 if rest.len() < hdr_end {
133 return None;
134 }
135 let space_id = u32::from_le_bytes(rest[at..at + 4].try_into().ok()?);
136 let kind = u32::from_le_bytes(rest[at + 4..at + 8].try_into().ok()?);
137 let encoding = rest[at + 8];
138 let len = u64::from_le_bytes(rest[at + 16..at + 24].try_into().ok()?) as usize;
139 at = hdr_end;
140 let end = at.checked_add(len)?;
141 if rest.len() < end || len == 0 {
142 return None;
143 }
144 spaces.push(ParkedSpace {
145 space_id,
146 kind,
147 encoding,
148 sealed: rest[at..end].to_vec(),
149 });
150 at = end;
151 }
152 if at != rest.len() {
153 return None;
154 }
155 let kv = spaces
156 .iter()
157 .find(|s| s.kind == superfluid_abi::space_kind::PAGED_TOKEN_KV)
158 .cloned();
159 Some(Parked {
160 covered,
161 stream_digest,
162 encoding: kv.as_ref().map(|k| k.encoding).unwrap_or(0),
163 sealed: kv.map(|k| k.sealed).unwrap_or_default(),
164 spaces,
165 multi_space: true,
166 })
167}
168
169pub fn remove(dir: &Path, session: u64) {
170 let _ = std::fs::remove_file(artifact_path(dir, session));
171 let _ = std::fs::remove_file(dir.join(format!("{session}.park.tmp")));
172}
173
174pub fn sweep(dir: &Path, max_bytes: u64) -> u64 {
175 let Ok(rd) = std::fs::read_dir(dir) else {
176 return 0;
177 };
178 let is_artifact = |p: &std::path::Path| {
179 let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
180 name.ends_with(".park") || name.ends_with(".park.tmp")
181 };
182 let mut files: Vec<(std::time::SystemTime, u64, PathBuf)> = rd
183 .filter_map(|e| e.ok())
184 .filter(|e| is_artifact(&e.path()))
185 .filter_map(|e| {
186 let md = e.metadata().ok()?;
187 Some((md.modified().ok()?, md.len(), e.path()))
188 })
189 .collect();
190
191 let mut total: u64 = files.iter().map(|(_, len, _)| *len).sum();
192 if total <= max_bytes {
193 return 0;
194 }
195 files.sort_by_key(|(mtime, _, _)| *mtime);
196
197 let mut reclaimed = 0u64;
198 for (_, len, path) in files {
199 if total <= max_bytes {
200 break;
201 }
202 if std::fs::remove_file(&path).is_ok() {
203 total = total.saturating_sub(len);
204 reclaimed += len;
205 }
206 }
207 reclaimed
208}