⎇ 53dbdf0f1deea10d6fc567ea2b43e1e75d6d60cd
crates/modules/semantic_dht/minga-core/src/cas.rs
Historia
Quién tocó qué
crudo
chore: monorepo inicial con arje + minga + yahweh absorbidos
1use crate::ast::SemanticNode;
2use blake3::Hasher;
3
4/// Hash de 32 bytes que identifica unívocamente un `SemanticNode` por su
5/// estructura lógica. Dos nodos con misma estructura → mismo hash, sin
6/// importar formato, comentarios o posición en el archivo fuente.
7#[derive(
8 Debug,
9 Clone,
10 Copy,
11 PartialEq,
12 Eq,
13 Hash,
14 PartialOrd,
15 Ord,
16 serde::Serialize,
17 serde::Deserialize,
18)]
19#[serde(transparent)]
20pub struct ContentHash(pub [u8; 32]);
21
22impl ContentHash {
23 pub fn as_bytes(&self) -> &[u8; 32] {
24 &self.0
25 }
26}
27
28impl std::fmt::Display for ContentHash {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 for b in &self.0 {
31 write!(f, "{:02x}", b)?;
32 }
33 Ok(())
34 }
35}
36
37/// Hash Merkle de un `SemanticNode`. El hash es función pura de
38/// `(kind, field_name, leaf_text, &[child_hash])`. Esquema estricto:
39/// los hijos contribuyen como hash, no como bytestream completo. Eso
40/// permite verificar un nodo recibido por la red **sin tener** sus
41/// hijos: basta con tener los hashes de los hijos (que vienen en el
42/// `StoredNode.children`) y reproducir esta función.
43pub fn hash_node(node: &SemanticNode) -> ContentHash {
44 let child_hashes: Vec<ContentHash> = node.children.iter().map(hash_node).collect();
45 hash_components(
46 &node.kind,
47 node.field_name.as_deref(),
48 node.leaf_text.as_deref(),
49 &child_hashes,
50 )
51}
52
53/// Primitiva canónica del hash estructural. Es la única definición
54/// authoritativa: cualquier otra función que produzca un hash de
55/// contenido debe expresarse encima de ésta. Garantiza que
56/// `hash_node(&semantic)` y `hash_stored(&stored)` coincidan bit a bit
57/// para representaciones equivalentes del mismo árbol.
58pub fn hash_components(
59 kind: &str,
60 field_name: Option<&str>,
61 leaf_text: Option<&[u8]>,
62 child_hashes: &[ContentHash],
63) -> ContentHash {
64 let mut h = Hasher::new();
65 write_str(&mut h, kind);
66 match field_name {
67 Some(f) => {
68 h.update(&[1]);
69 write_str(&mut h, f);
70 }
71 None => {
72 h.update(&[0]);
73 }
74 }
75 match leaf_text {
76 Some(t) => {
77 h.update(&[1]);
78 h.update(&(t.len() as u64).to_le_bytes());
79 h.update(t);
80 }
81 None => {
82 h.update(&[0]);
83 }
84 }
85 h.update(&(child_hashes.len() as u64).to_le_bytes());
86 for ch in child_hashes {
87 h.update(&ch.0);
88 }
89 ContentHash(*h.finalize().as_bytes())
90}
91
92fn write_str(h: &mut Hasher, s: &str) {
93 h.update(&(s.len() as u64).to_le_bytes());
94 h.update(s.as_bytes());
95}