1use blake3::Hasher;
7
8use crate::ir::{Edge, Graph, Node};
9use crate::patch::{Operation, Patch};
10
11pub fn hash_bytes(bytes: &[u8]) -> [u8; 32] {
13 let mut hasher = Hasher::new();
14 hasher.update(bytes);
15 *hasher.finalize().as_bytes()
16}
17
18pub fn hash_patch(patch: &Patch) -> [u8; 32] {
22 let mut hasher = Hasher::new();
23 hasher.update(b"dirtydata:patch:v2:");
24
25 for op in &patch.operations {
27 hash_operation(&mut hasher, op);
28 }
29
30 if let Some(ref intent_id) = patch.intent_ref {
32 hasher.update(b"intent:");
33 hasher.update(intent_id.0.to_string().as_bytes());
34 }
35
36 for (id, hash) in patch.parents.iter().zip(patch.parent_hashes.iter()) {
38 hasher.update(b"parent:");
39 hasher.update(id.0.to_string().as_bytes());
40 hasher.update(b":hash:");
41 hasher.update(hash);
42 }
43
44 *hasher.finalize().as_bytes()
45}
46
47fn hash_operation(hasher: &mut Hasher, op: &Operation) {
48 match op {
49 Operation::AddNode(node) => {
50 hasher.update(b"op:add_node:");
51 hash_node(hasher, node);
52 }
53 Operation::RemoveNode(id) => {
54 hasher.update(b"op:remove_node:");
55 hasher.update(id.0.to_string().as_bytes());
56 }
57 Operation::ReplaceNode(node) => {
58 hasher.update(b"op:replace_node:");
59 hash_node(hasher, node);
60 }
61 Operation::ModifyConfig { node_id, delta } => {
62 hasher.update(b"op:modify_config:");
63 hasher.update(node_id.0.to_string().as_bytes());
64 let json = serde_json::to_string(delta).unwrap_or_default();
67 hasher.update(json.as_bytes());
68 }
69 Operation::AddEdge(edge) => {
70 hasher.update(b"op:add_edge:");
71 hash_edge(hasher, edge);
72 }
73 Operation::RemoveEdge(id) => {
74 hasher.update(b"op:remove_edge:");
75 hasher.update(id.0.to_string().as_bytes());
76 }
77 Operation::ModifyEdge { edge_id, delta } => {
78 hasher.update(b"op:modify_edge:");
79 hasher.update(edge_id.0.to_string().as_bytes());
80 let json = serde_json::to_string(delta).unwrap_or_default();
81 hasher.update(json.as_bytes());
82 }
83 Operation::AddModulation(m) => {
84 hasher.update(b"op:add_modulation:");
85 hash_modulation(hasher, m);
86 }
87 Operation::RemoveModulation(id) => {
88 hasher.update(b"op:remove_modulation:");
89 hasher.update(id.0.to_string().as_bytes());
90 }
91 }
92}
93
94fn hash_node(hasher: &mut Hasher, node: &Node) {
95 hasher.update(b"node:");
96 hasher.update(node.id.0.to_string().as_bytes());
97
98 let kind_json = serde_json::to_string(&node.kind).unwrap_or_default();
99 hasher.update(kind_json.as_bytes());
100
101 for port in &node.ports {
103 hasher.update(b"port:");
104 hasher.update(port.name.as_bytes());
105 let dir = serde_json::to_string(&port.direction).unwrap_or_default();
106 hasher.update(dir.as_bytes());
107 let domain = serde_json::to_string(&port.domain).unwrap_or_default();
108 hasher.update(domain.as_bytes());
109 let dtype = serde_json::to_string(&port.data_type).unwrap_or_default();
110 hasher.update(dtype.as_bytes());
111 }
112
113 let config_json = serde_json::to_string(&node.config).unwrap_or_default();
115 hasher.update(config_json.as_bytes());
116
117 let meta = serde_json::to_string(&node.metadata).unwrap_or_default();
119 hasher.update(meta.as_bytes());
120}
121
122fn hash_edge(hasher: &mut Hasher, edge: &Edge) {
123 hasher.update(b"edge:");
124 hasher.update(edge.id.0.to_string().as_bytes());
125 hasher.update(edge.source.node_id.0.to_string().as_bytes());
126 hasher.update(b":");
127 hasher.update(edge.source.port_name.as_bytes());
128 hasher.update(b"->");
129 hasher.update(edge.target.node_id.0.to_string().as_bytes());
130 hasher.update(b":");
131 hasher.update(edge.target.port_name.as_bytes());
132 hasher.update(&[edge.kind as u8]);
133}
134
135fn hash_modulation(hasher: &mut Hasher, m: &crate::ir::Modulation) {
136 hasher.update(b"modulation:");
137 hasher.update(m.id.0.to_string().as_bytes());
138 hasher.update(m.source.node_id.0.to_string().as_bytes());
139 hasher.update(b":");
140 hasher.update(m.source.port_name.as_bytes());
141 hasher.update(b"->param:");
142 hasher.update(m.target_node.0.to_string().as_bytes());
143 hasher.update(b":");
144 hasher.update(m.target_param.as_bytes());
145 hasher.update(&m.amount.to_le_bytes());
146}
147
148pub fn hash_graph(graph: &Graph) -> [u8; 32] {
151 let mut hasher = Hasher::new();
152 hasher.update(b"dirtydata:graph:v1:");
153
154 for node in graph.nodes.values() {
155 hash_node(&mut hasher, node);
156 }
157 for edge in graph.edges.values() {
158 hash_edge(&mut hasher, edge);
159 }
160 for (_, m) in &graph.modulations {
161 hash_modulation(&mut hasher, m);
162 }
163
164 hasher.update(&graph.revision.0.to_le_bytes());
165
166 *hasher.finalize().as_bytes()
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::ir::Node;
173 use crate::patch::Operation;
174
175 #[test]
176 fn test_graph_hash_deterministic() {
177 let mut g = Graph::new();
178 let node = Node::new_source("Sine");
179 let patch = Patch::from_operations(vec![Operation::AddNode(node)]);
180 g.apply(&patch).unwrap();
181
182 let h1 = hash_graph(&g);
183 let h2 = hash_graph(&g);
184 assert_eq!(h1, h2);
185 }
186
187 #[test]
188 fn test_different_graphs_different_hashes() {
189 let mut g1 = Graph::new();
190 g1.apply(&Patch::from_operations(vec![Operation::AddNode(
191 Node::new_source("Sine"),
192 )]))
193 .unwrap();
194
195 let mut g2 = Graph::new();
196 g2.apply(&Patch::from_operations(vec![Operation::AddNode(
197 Node::new_source("Noise"),
198 )]))
199 .unwrap();
200
201 assert_ne!(hash_graph(&g1), hash_graph(&g2));
202 }
203
204 #[test]
205 fn test_patch_hash_stable() {
206 let node = Node::new_processor("EQ");
207 let patch = Patch::from_operations(vec![Operation::AddNode(node.clone())]);
208 let h1 = hash_patch(&patch);
209 let h2 = hash_patch(&patch);
210 assert_eq!(h1, h2);
211 }
212}