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 }