lib/closure/src/canonical/generation.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const schema = @import("../schema/root.zig");
2 const std = @import("std");
3
4 const Sha256 = std.crypto.hash.sha2.Sha256;
5
6 const Hasher = struct {
7 state: Sha256,
8
9 fn init() Hasher {
10 var state = Sha256.init(.{});
11 state.update("tiny.closure.generation/v1\x00");
12 return .{ .state = state };
13 }
14
15 fn bytes(self: *Hasher, value: []const u8) void {
16 self.state.update(value);
17 }
18
19 fn tag(self: *Hasher, value: []const u8) void {
20 self.bytes(value);
21 self.putU8(0);
22 }
23
24 fn text(self: *Hasher, value: anytype) void {
25 self.putU16(value.len);
26 self.bytes(value.slice());
27 }
28
29 fn digest(self: *Hasher, value: *const schema.Digest) void {
30 self.bytes(&value.bytes);
31 }
32
33 fn putU8(self: *Hasher, value: u8) void {
34 self.bytes(&.{value});
35 }
36
37 fn putU16(self: *Hasher, value: u16) void {
38 var encoded: [2]u8 = undefined;
39 std.mem.writeInt(u16, &encoded, value, .little);
40 self.bytes(&encoded);
41 }
42
43 fn putU32(self: *Hasher, value: u32) void {
44 var encoded: [4]u8 = undefined;
45 std.mem.writeInt(u32, &encoded, value, .little);
46 self.bytes(&encoded);
47 }
48
49 fn putU64(self: *Hasher, value: u64) void {
50 var encoded: [8]u8 = undefined;
51 std.mem.writeInt(u64, &encoded, value, .little);
52 self.bytes(&encoded);
53 }
54
55 fn count(self: *Hasher, value: usize) void {
56 self.putU64(@intCast(value));
57 }
58
59 fn finish(self: *Hasher) schema.Digest {
60 var output: schema.Digest = undefined;
61 self.state.final(&output.bytes);
62 return output;
63 }
64 };
65
66 pub fn generation(manifest: schema.GenerationInput) schema.Digest {
67 var hasher = Hasher.init();
68 hasher.putU64(manifest.id);
69 nodes(&hasher, manifest.nodes);
70 edges(&hasher, manifest.edges);
71 artifacts(&hasher, manifest.artifacts);
72 ranges(&hasher, manifest.ranges);
73 digests(&hasher, manifest.digests);
74 provenance(&hasher, manifest.provenance_parents);
75 sources(&hasher, manifest.source_records);
76 builds(&hasher, manifest.build_records);
77 authorities(&hasher, manifest.authorities);
78 lineages(&hasher, manifest.lineage_references);
79 services(&hasher, manifest.service_descriptors);
80 roots(&hasher, manifest.residual_roots);
81 claims(&hasher, manifest.claims);
82 claimNodes(&hasher, manifest.claim_nodes);
83 return hasher.finish();
84 }
85
86 fn nodes(hasher: *Hasher, values: []const schema.Node) void {
87 hasher.tag("nodes");
88 hasher.count(values.len);
89 for (values) |*value| {
90 hasher.putU32(value.id);
91 hasher.text(&value.descriptor);
92 hasher.digest(&value.identity);
93 hasher.putU8(@backingInt(value.subject_kind));
94 hasher.putU8(@backingInt(value.material_role));
95 hasher.putU8(@backingInt(value.origin));
96 hasher.putU16(value.phases);
97 hasher.putU8(@backingInt(value.execution_locus));
98 hasher.text(&value.owner);
99 hasher.putU8(@backingInt(value.authority));
100 hasher.putU8(@backingInt(value.artifact_kind));
101 }
102 }
103
104 fn edges(hasher: *Hasher, values: []const schema.Edge) void {
105 hasher.tag("edges");
106 hasher.count(values.len);
107 for (values) |value| {
108 hasher.putU32(value.id);
109 hasher.putU32(value.source);
110 hasher.putU32(value.target);
111 hasher.putU8(@backingInt(value.kind));
112 }
113 }
114
115 fn artifacts(hasher: *Hasher, values: []const schema.Artifact) void {
116 hasher.tag("artifacts");
117 hasher.count(values.len);
118 for (values) |*value| {
119 hasher.putU32(value.id);
120 hasher.putU32(value.node);
121 hasher.putU64(value.byte_length);
122 hasher.digest(&value.digest);
123 hasher.putU8(@backingInt(value.witness));
124 }
125 }
126
127 fn ranges(hasher: *Hasher, values: []const schema.ByteRange) void {
128 hasher.tag("ranges");
129 hasher.count(values.len);
130 for (values) |*value| {
131 hasher.putU32(value.id);
132 hasher.putU32(value.artifact);
133 hasher.putU64(value.offset);
134 hasher.putU64(value.length);
135 hasher.digest(&value.digest);
136 hasher.putU8(@intFromBool(value.executable));
137 hasher.putU8(@backingInt(value.witness));
138 }
139 }
140
141 fn digests(hasher: *Hasher, values: []const schema.DigestRecord) void {
142 hasher.tag("digests");
143 hasher.count(values.len);
144 for (values) |*value| {
145 hasher.putU32(value.id);
146 hasher.putU32(value.node);
147 hasher.text(&value.purpose);
148 hasher.digest(&value.digest);
149 hasher.putU8(@backingInt(value.witness));
150 }
151 }
152
153 fn provenance(
154 hasher: *Hasher,
155 values: []const schema.ProvenanceParent,
156 ) void {
157 hasher.tag("provenance");
158 hasher.count(values.len);
159 for (values) |value| {
160 hasher.putU32(value.id);
161 hasher.putU32(value.child);
162 hasher.putU32(value.parent);
163 }
164 }
165
166 fn sources(hasher: *Hasher, values: []const schema.SourceRecord) void {
167 hasher.tag("sources");
168 hasher.count(values.len);
169 for (values) |*value| {
170 hasher.putU32(value.id);
171 hasher.putU32(value.node);
172 hasher.text(&value.path);
173 hasher.digest(&value.digest);
174 hasher.putU8(@backingInt(value.witness));
175 }
176 }
177
178 fn builds(hasher: *Hasher, values: []const schema.BuildRecord) void {
179 hasher.tag("builds");
180 hasher.count(values.len);
181 for (values) |*value| {
182 hasher.putU32(value.id);
183 hasher.putU32(value.node);
184 hasher.putU32(value.tool);
185 hasher.text(&value.option);
186 hasher.digest(&value.digest);
187 hasher.putU8(@backingInt(value.witness));
188 }
189 }
190
191 fn authorities(hasher: *Hasher, values: []const schema.Authority) void {
192 hasher.tag("authorities");
193 hasher.count(values.len);
194 for (values) |value| {
195 hasher.putU32(value.id);
196 hasher.putU32(value.source);
197 hasher.putU32(value.target);
198 hasher.putU8(@backingInt(value.granted));
199 }
200 }
201
202 fn lineages(
203 hasher: *Hasher,
204 values: []const schema.LineageReference,
205 ) void {
206 hasher.tag("lineages");
207 hasher.count(values.len);
208 for (values) |*value| {
209 hasher.putU32(value.id);
210 hasher.putU32(value.node);
211 hasher.text(&value.descriptor);
212 hasher.digest(&value.digest);
213 hasher.putU8(@backingInt(value.witness));
214 }
215 }
216
217 fn services(
218 hasher: *Hasher,
219 values: []const schema.ServiceDescriptor,
220 ) void {
221 hasher.tag("services");
222 hasher.count(values.len);
223 for (values) |*value| {
224 hasher.putU32(value.id);
225 hasher.putU32(value.node);
226 hasher.text(&value.provider);
227 hasher.text(&value.protocol);
228 hasher.text(&value.endpoint_rule);
229 hasher.text(&value.trust_anchor);
230 hasher.text(&value.failure_contract);
231 hasher.putU8(@backingInt(value.requirement));
232 }
233 }
234
235 fn roots(hasher: *Hasher, values: []const schema.ResidualRoot) void {
236 hasher.tag("roots");
237 hasher.count(values.len);
238 for (values) |value| {
239 hasher.putU32(value.id);
240 hasher.putU32(value.claim);
241 hasher.putU32(value.node);
242 hasher.putU8(@backingInt(value.treatment));
243 }
244 }
245
246 fn claims(hasher: *Hasher, values: []const schema.Claim) void {
247 hasher.tag("claims");
248 hasher.count(values.len);
249 for (values) |*value| {
250 hasher.putU32(value.id);
251 hasher.text(&value.name);
252 hasher.putU32(value.artifact);
253 hasher.digest(&value.artifact_digest);
254 profile(hasher, &value.profiles.executable);
255 profile(hasher, &value.profiles.service_trust);
256 profile(hasher, &value.profiles.model_origin);
257 profile(hasher, &value.profiles.bootstrap);
258 hasher.putU16(value.traversed_edges);
259 }
260 }
261
262 fn profile(hasher: *Hasher, value: *const schema.ProfileRef) void {
263 hasher.putU8(@intFromBool(value.required));
264 hasher.text(&value.id);
265 hasher.text(&value.source);
266 hasher.digest(&value.body_sha256);
267 }
268
269 fn claimNodes(hasher: *Hasher, values: []const schema.ClaimNode) void {
270 hasher.tag("claim-nodes");
271 hasher.count(values.len);
272 for (values) |value| {
273 hasher.putU32(value.id);
274 hasher.putU32(value.claim);
275 hasher.putU32(value.node);
276 hasher.putU8(@backingInt(value.treatment));
277 }
278 }
279
280 const Fixture = struct {
281 nodes: [1]schema.Node,
282 artifacts: [1]schema.Artifact,
283 claims: [1]schema.Claim,
284 };
285
286 fn fixture() !Fixture {
287 const artifact_digest = filled(2);
288 return .{
289 .nodes = .{.{
290 .id = 1,
291 .descriptor = try schema.Descriptor.init("artifact"),
292 .identity = artifact_digest,
293 .subject_kind = .binary,
294 .material_role = .platform,
295 .origin = .owned_derivation,
296 .phases = schema.phaseBit(.shipment),
297 .execution_locus = .normal_world_cpu,
298 .owner = try schema.Name.init("closure"),
299 .authority = .none,
300 .artifact_kind = .executable,
301 }},
302 .artifacts = .{.{
303 .id = 1,
304 .node = 1,
305 .byte_length = 1,
306 .digest = artifact_digest,
307 .witness = .owned,
308 }},
309 .claims = .{.{
310 .id = 1,
311 .name = try schema.Name.init("exact"),
312 .artifact = 1,
313 .artifact_digest = artifact_digest,
314 .profiles = .{
315 .executable = schema.ProfileRef.absent(),
316 .service_trust = schema.ProfileRef.absent(),
317 .model_origin = schema.ProfileRef.absent(),
318 .bootstrap = schema.ProfileRef.absent(),
319 },
320 .traversed_edges = 0,
321 }},
322 };
323 }
324
325 fn fixtureInput(
326 value: anytype,
327 generation_root: schema.Digest,
328 ) schema.GenerationInput {
329 return .{
330 .id = 7,
331 .root = generation_root,
332 .nodes = &value.nodes,
333 .edges = &.{},
334 .artifacts = &value.artifacts,
335 .ranges = &.{},
336 .digests = &.{},
337 .provenance_parents = &.{},
338 .source_records = &.{},
339 .build_records = &.{},
340 .authorities = &.{},
341 .lineage_references = &.{},
342 .service_descriptors = &.{},
343 .residual_roots = &.{},
344 .claims = &value.claims,
345 .claim_nodes = &.{},
346 };
347 }
348
349 fn filled(byte: u8) schema.Digest {
350 return .{ .bytes = @splat(byte) };
351 }
352
353 test "generation root is fieldwise deterministic and not self referential" {
354 var facts = try fixture();
355 const first = generation(fixtureInput(&facts, schema.Digest.zero()));
356 const second = generation(fixtureInput(&facts, filled(9)));
357 try std.testing.expect(first.eql(&second));
358 facts.nodes[0].authority = .platform_control;
359 const changed = generation(fixtureInput(&facts, filled(9)));
360 try std.testing.expect(!first.eql(&changed));
361 }