1use vyre::ir::Program;
5use vyre_primitives::graph::path_reconstruct::path_reconstruct as primitive_path_reconstruct;
6
7const OP_ID: &str = "vyre-libs::security::path_reconstruct";
8
9#[must_use]
11pub fn path_reconstruct(
12 parent: &str,
13 target: &str,
14 path_out: &str,
15 path_len: &str,
16 max_depth: u32,
17) -> Program {
18 crate::security::assert_security_inputs(
22 OP_ID,
23 max_depth,
24 &[
25 ("parent", parent),
26 ("target", target),
27 ("path_out", path_out),
28 ("path_len", path_len),
29 ],
30 );
31 crate::region::tag_program(
32 OP_ID,
33 primitive_path_reconstruct(parent, target, path_out, path_len, max_depth),
34 )
35}
36
37inventory::submit! {
38 crate::harness::OpEntry {
39 id: OP_ID,
40 build: || path_reconstruct("parent", "target", "path_out", "path_len", 4),
41 test_inputs: Some(|| {
42 let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
43 vec![vec![
44 to_bytes(&[0, 0, 1, 2]),
45 to_bytes(&[3]),
46 to_bytes(&[0, 0, 0, 0]),
47 to_bytes(&[0]),
48 ]]
49 }),
50 expected_output: Some(|| {
51 let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
52 vec![vec![to_bytes(&[3, 2, 1, 0]), to_bytes(&[4])]]
53 }),
54 category: Some("security"),
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use vyre_primitives::graph::path_reconstruct::cpu_ref;
62
63 #[test]
64 fn path_reconstruct_program_emits_buffers() {
65 let p = path_reconstruct("parent", "target", "path_out", "path_len", 4);
66 let names: Vec<&str> = p.buffers().iter().map(|b| b.name()).collect();
67 assert!(names.contains(&"parent"));
68 assert!(names.contains(&"target"));
69 assert!(names.contains(&"path_out"));
70 assert!(names.contains(&"path_len"));
71 }
72
73 #[test]
74 fn path_reconstruct_respects_max_depth() {
75 let p = path_reconstruct("parent", "target", "path_out", "path_len", 8);
76 let path_out_buf = p
77 .buffers()
78 .iter()
79 .find(|b| b.name() == "path_out")
80 .expect("Fix: path_out buffer");
81 assert_eq!(path_out_buf.count, 8);
82 }
83
84 #[test]
85 fn path_reconstruct_cpu_ref_happy_path() {
86 let parent = [0, 0, 1, 2];
87 let mut scratch = Vec::new();
88 let len = cpu_ref(&parent, 3, 4, &mut scratch);
89 assert_eq!(len, 4);
90 assert_eq!(scratch, vec![3, 2, 1, 0]);
91 }
92
93 #[test]
94 fn path_reconstruct_cpu_ref_oob_target_returns_self() {
95 let parent = [0, 0, 1, 2];
98 let mut scratch = Vec::new();
99 let len = cpu_ref(&parent, 10, 4, &mut scratch);
100 assert_eq!(len, 1);
101 assert_eq!(scratch[0], 10);
102 }
103
104 #[test]
105 fn path_reconstruct_cpu_ref_cycle_terminates_at_max_depth() {
106 let parent = [0, 2, 1, 3];
109 let mut scratch = Vec::new();
110 let len = cpu_ref(&parent, 1, 4, &mut scratch);
111 assert_eq!(len, 4);
112 assert_eq!(scratch, vec![1, 2, 1, 2]);
113 }
114
115 #[test]
116 fn path_reconstruct_gpu_matches_cpu_reference_on_cycle() {
117 let parent = [0u32, 2, 1, 3];
118 let target = 1u32;
119 let max_depth = 4u32;
120 let p = path_reconstruct("parent", "target", "path_out", "path_len", max_depth);
121 let to_bytes = |w: &[u32]| vyre_primitives::wire::pack_u32_slice(w);
122 let inputs = vec![
123 to_bytes(&parent),
124 to_bytes(&[target]),
125 to_bytes(&[0, 0, 0, 0]),
126 to_bytes(&[0]),
127 ];
128 let values: Vec<vyre_reference::value::Value> = inputs
129 .into_iter()
130 .map(vyre_reference::value::Value::from)
131 .collect();
132 let outputs = vyre_reference::reference_eval(&p, &values).unwrap();
133 let gpu_path_bytes = outputs[0].to_bytes();
134 let gpu_len = u32::from_le_bytes(outputs[1].to_bytes()[0..4].try_into().unwrap());
135
136 let mut cpu_scratch = Vec::new();
137 let cpu_len = cpu_ref(&parent, target, max_depth, &mut cpu_scratch);
138
139 assert_eq!(
140 gpu_len, cpu_len,
141 "GPU path length must match CPU reference on cycle"
142 );
143 let gpu_path: Vec<u32> = gpu_path_bytes
144 .chunks_exact(4)
145 .map(|c| u32::from_le_bytes(c.try_into().unwrap()))
146 .collect();
147 assert_eq!(
148 &gpu_path[..cpu_len as usize],
149 &cpu_scratch[..cpu_len as usize],
150 "GPU path must match CPU reference up to max_depth on cyclic parent array"
151 );
152 }
153
154 #[test]
155 #[should_panic(expected = "empty buffer name")]
156 fn path_reconstruct_empty_buffer_name_should_panic() {
157 let _ = path_reconstruct("", "target", "path_out", "path_len", 4);
158 }
159}