Skip to main content

superfluid_daemon/
park.rs

1//! Park artifacts.
2
3use 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}