use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};
use std::time::{Duration, Instant};
use crate::core::archive::authority::ArchiveAuthority;
use crate::core::context_admission::stored::source_authority_required;
use crate::core::policy::{content, runtime};
#[derive(Clone)]
struct RefEntry {
content: Arc<str>,
authority: Option<ArchiveAuthority>,
created_at: Instant,
}
const MAX_ENTRIES: usize = 200;
const MAX_STORE_BYTES: usize = 64 * 1024 * 1024;
const TTL: Duration = Duration::from_mins(5);
fn store_lock() -> &'static Mutex<HashMap<String, RefEntry>> {
static STORE: OnceLock<Mutex<HashMap<String, RefEntry>>> = OnceLock::new();
STORE.get_or_init(|| Mutex::new(HashMap::new()))
}
const RESOLVE_LOCK_WAIT: Duration = Duration::from_millis(50);
fn lock_briefly() -> Option<std::sync::MutexGuard<'static, HashMap<String, RefEntry>>> {
let deadline = Instant::now() + RESOLVE_LOCK_WAIT;
loop {
match store_lock().try_lock() {
Ok(map) => return Some(map),
Err(std::sync::TryLockError::Poisoned(poisoned)) => return Some(poisoned.into_inner()),
Err(std::sync::TryLockError::WouldBlock) if Instant::now() < deadline => {
std::thread::sleep(Duration::from_micros(200));
}
Err(std::sync::TryLockError::WouldBlock) => return None,
}
}
}
pub fn store(content: &str) -> Option<String> {
store_with_authority(content, None)
}
pub fn store_with_authority(text: &str, authority: Option<&ArchiveAuthority>) -> Option<String> {
runtime::with_source_view(|| {
if text.len() > entry_budget()
|| (source_authority_required() && authority.is_none())
|| authority.is_some_and(|origin| !origin.admitted())
{
return None;
}
let text = content::protect_active(text).ok()?;
let text = crate::core::context_admission::recovery::admit_for_storage(&text)?;
if text.len() > entry_budget() {
return None;
}
let id = reference_id(&text, authority)?;
let mut map = store_lock().try_lock().ok()?;
map.retain(|_, entry| entry.created_at.elapsed() < TTL);
let mut bytes: usize = map.values().map(|entry| entry.content.len()).sum();
if let Some(previous) = map.remove(&id) {
bytes -= previous.content.len();
}
while map.len() >= MAX_ENTRIES || bytes.saturating_add(text.len()) > MAX_STORE_BYTES {
let oldest = map
.iter()
.min_by_key(|(_, entry)| entry.created_at)
.map(|(key, _)| key.clone())?;
bytes -= map.remove(&oldest)?.content.len();
}
map.insert(
id.clone(),
RefEntry {
content: Arc::from(text.as_str()),
authority: authority.cloned(),
created_at: Instant::now(),
},
);
Some(id)
})
.ok()
.flatten()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReferenceResolveError {
Malformed,
Missing,
Expired,
Refused(&'static str),
Gateway(String),
}
pub fn resolve_checked(id: &str) -> Result<String, ReferenceResolveError> {
let Some(digest) = id.strip_prefix("ref_") else {
return Err(ReferenceResolveError::Malformed);
};
if digest.is_empty() || !digest.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(ReferenceResolveError::Malformed);
}
let entry = {
let Some(mut map) = lock_briefly() else {
return Err(ReferenceResolveError::Missing);
};
let Some(entry) = map.get(id) else {
return Err(ReferenceResolveError::Missing);
};
if entry.created_at.elapsed() >= TTL {
map.remove(id);
return Err(ReferenceResolveError::Expired);
}
entry.clone()
};
if entry.authority.is_some()
&& !runtime::REQUEST_PROJECT
.try_with(|project| project.borrow().is_some())
.unwrap_or(false)
{
return Err(ReferenceResolveError::Refused(
"reference is bound to a project this request does not name",
));
}
runtime::with_source_view(|| {
if source_authority_required() && entry.authority.is_none() {
return Err(ReferenceResolveError::Refused(
"stored output has no source authority under the active policy",
));
}
if entry
.authority
.as_ref()
.is_some_and(|origin| !origin.admitted())
|| reference_id(&entry.content, entry.authority.as_ref()).as_deref() != Some(id)
{
return Err(ReferenceResolveError::Refused(
"the reference's source is no longer admitted",
));
}
let checked = content::protect_active(&entry.content).map_err(|_| {
ReferenceResolveError::Refused("stored output is withheld by current content rules")
})?;
crate::core::context_admission::recovery::admit_recovered(&checked, "reference")
.map_err(ReferenceResolveError::Gateway)
})
.unwrap_or(Err(ReferenceResolveError::Refused(
"current policy cannot be verified",
)))
}
pub fn resolve(id: &str) -> Option<String> {
resolve_checked(id).ok()
}
pub fn stats() -> (usize, usize) {
let Ok(map) = store_lock().try_lock() else {
return (0, 0);
};
let live = map
.values()
.filter(|entry| entry.created_at.elapsed() < TTL);
live.fold((0, 0), |(count, bytes), entry| {
(count + 1, bytes + entry.content.len())
})
}
fn entry_budget() -> usize {
crate::core::limits::max_read_bytes().min(content::MAX_PROTECTED_CONTENT_BYTES)
}
fn reference_id(text: &str, authority: Option<&ArchiveAuthority>) -> Option<String> {
let digest = if let Some(origin) = authority {
origin.id(text)?
} else {
let mut hash = blake3::Hasher::new();
hash.update(b"leanctx-public-reference-v1\0");
hash.update(text.as_bytes());
hash.finalize().to_hex().to_string()
};
Some(format!("ref_{digest}"))
}
#[cfg(test)]
mod tests;