Pausa: 11 crates del fractal Ente #0 con cerebro completo
1//! Content-addressable store. Resuelve `Payload::Wasm.module_sha256` (y en
2//! el futuro otros payloads firmados) desde el sistema de archivos con
3//! verificación de hash. Path por defecto: `$XDG_DATA_HOME/ente/cas/<hex>`.
4//!
5//! Override por env: `ENTE_CAS_ROOT`.
6
7use sha2::{Digest, Sha256};
8use std::path::PathBuf;
9use tracing::debug;
10
11pub fn cas_root() -> PathBuf {
12 if let Ok(p) = std::env::var("ENTE_CAS_ROOT") {
13 return p.into();
14 }
15 let base = if let Ok(d) = std::env::var("XDG_DATA_HOME") {
16 d
17 } else if let Ok(h) = std::env::var("HOME") {
18 format!("{h}/.local/share")
19 } else {
20 "/var/lib".into()
21 };
22 PathBuf::from(base).join("ente").join("cas")
23}
24
25pub fn sha256_of(bytes: &[u8]) -> [u8; 32] {
26 let mut hasher = Sha256::new();
27 hasher.update(bytes);
28 hasher.finalize().into()
29}
30
31pub fn hex(sha: &[u8; 32]) -> String {
32 let mut s = String::with_capacity(64);
33 for b in sha {
34 s.push_str(&format!("{:02x}", b));
35 }
36 s
37}
38
39pub fn resolve(sha: &[u8; 32]) -> anyhow::Result<Vec<u8>> {
40 let path = cas_root().join(hex(sha));
41 let bytes = std::fs::read(&path)
42 .map_err(|e| anyhow::anyhow!("CAS read {}: {e}", path.display()))?;
43 let actual = sha256_of(&bytes);
44 if &actual != sha {
45 anyhow::bail!(
46 "CAS hash mismatch en {}: declarado={} real={}",
47 path.display(), hex(sha), hex(&actual)
48 );
49 }
50 Ok(bytes)
51}
52
53/// Almacena bytes en el CAS, devuelve su SHA. Idempotente: si el archivo ya
54/// existe con el mismo hash, no reescribe.
55pub fn store(bytes: &[u8]) -> anyhow::Result<[u8; 32]> {
56 let sha = sha256_of(bytes);
57 let root = cas_root();
58 std::fs::create_dir_all(&root)
59 .map_err(|e| anyhow::anyhow!("CAS mkdir {}: {e}", root.display()))?;
60 let path = root.join(hex(&sha));
61 if !path.exists() {
62 // Escritura atómica: crear .tmp y rename.
63 let tmp = path.with_extension("tmp");
64 std::fs::write(&tmp, bytes)
65 .map_err(|e| anyhow::anyhow!("CAS write {}: {e}", tmp.display()))?;
66 std::fs::rename(&tmp, &path)
67 .map_err(|e| anyhow::anyhow!("CAS rename {}: {e}", path.display()))?;
68 debug!(hex = %hex(&sha), len = bytes.len(), path = %path.display(), "CAS store");
69 }
70 Ok(sha)
71}