lib/machine/src/explore/query/canon.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const explore = @import("../root.zig");
2 const fabric = @import("../../fabric/root.zig");
3 const fault = @import("../../fault/root.zig");
4 const os = @import("os");
5 const profile = @import("../../profile/root.zig");
6 const std = @import("std");
7 const types = @import("types.zig");
8 const world = @import("../../world/root.zig");
9
10 const Sha256 = std.crypto.hash.sha2.Sha256;
11 const magic = [8]u8{ 'M', 'C', 'H', 'Q', 'R', 'Y', '1', 0 };
12 const ref_magic = [8]u8{ 'M', 'C', 'H', 'E', 'V', 'R', '1', 0 };
13 const domain = "tiny.machine.explore.query.history/v1";
14 const ref_domain = "tiny.machine.explore.query.reference/v1";
15 const version: u16 = 1;
16 const flags: u8 = 0;
17 const header_bytes: usize = 64;
18 const moment_bytes: usize = 320;
19 const decision_bytes: usize = 128;
20 const event_bytes: usize = 128;
21 const frame_bytes: usize = decision_bytes + moment_bytes + 8;
22 const record_bytes: usize = 8 + fabric.transition.stream_bytes;
23 const digest_bytes: usize = Sha256.digest_length;
24
25 pub const RefWire = [80]u8;
26
27 const Header = struct {
28 frame_count: u16,
29 evidence_count: u16,
30 state: explore.TraceState,
31 };
32
33 pub fn Wire(comptime capacity: types.Capacity) type {
34 const bytes = wireBytes(capacity);
35 if (bytes > std.math.maxInt(u32)) {
36 @compileError("query history wire size exceeds u32");
37 }
38 return [bytes]u8;
39 }
40
41 pub fn wireBytes(capacity: types.Capacity) usize {
42 return identityOffset(capacity) + digest_bytes;
43 }
44
45 pub fn encode(
46 comptime capacity: types.Capacity,
47 value: anytype,
48 output: *Wire(capacity),
49 ) types.Error!void {
50 try value.validate();
51 if (value.state == .open) return error.HistoryOpen;
52 var encoded: Wire(capacity) = @splat(0);
53 encodeHeader(capacity, value, &encoded);
54 encodeMoment(value.start, encoded[startOffset()..framesOffset()]);
55 for (value.frames(), 0..) |frame, index| {
56 const offset = frameOffset(capacity, index);
57 encodeFrame(frame, encoded[offset..][0..frame_bytes]);
58 }
59 for (value.evidence(), 0..) |record, index| {
60 const offset = recordOffset(capacity, index);
61 try encodeEvidence(record, encoded[offset..][0..record_bytes]);
62 }
63 const digest = materialDigest(capacity, &encoded);
64 @memcpy(encoded[identityOffset(capacity)..], &digest);
65 output.* = encoded;
66 }
67
68 pub fn decode(
69 comptime capacity: types.Capacity,
70 input: *const Wire(capacity),
71 output: anytype,
72 ) types.Error!void {
73 try validateDigest(capacity, input);
74 const header = try decodeHeader(capacity, input);
75 var result: @TypeOf(output.*) = .{
76 .start = try decodeMoment(input[startOffset()..framesOffset()]),
77 .frame_count = header.frame_count,
78 .evidence_count = header.evidence_count,
79 .state = header.state,
80 .identity_value = .{
81 .digest = input[identityOffset(capacity)..][0..digest_bytes].*,
82 },
83 };
84 for (result.frames_storage[0..result.frame_count], 0..) |*frame, index| {
85 const offset = frameOffset(capacity, index);
86 frame.* = try decodeFrame(input[offset..][0..frame_bytes]);
87 }
88 for (result.evidence_storage[0..result.evidence_count], 0..) |*record, index| {
89 const offset = recordOffset(capacity, index);
90 record.* = try decodeEvidence(input[offset..][0..record_bytes]);
91 }
92 try result.validate();
93 var canonical: Wire(capacity) = undefined;
94 try encode(capacity, &result, &canonical);
95 if (!std.mem.eql(u8, input, &canonical)) {
96 return error.CanonicalIdentityMismatch;
97 }
98 output.* = result;
99 }
100
101 pub fn identity(
102 comptime capacity: types.Capacity,
103 input: *const Wire(capacity),
104 ) types.Error!types.Identity {
105 try validateDigest(capacity, input);
106 return .{ .digest = input[identityOffset(capacity)..][0..digest_bytes].* };
107 }
108
109 pub fn encodeRef(value: types.EvidenceRef, output: *RefWire) void {
110 var encoded: RefWire = @splat(0);
111 @memcpy(encoded[0..8], &ref_magic);
112 put(u16, &encoded, 8, version);
113 encoded[10] = @backingInt(value.target);
114 encoded[11] = flags;
115 put(u16, &encoded, 12, value.index);
116 @memcpy(encoded[16..48], &value.history.digest);
117 var hasher = Sha256.init(.{});
118 hasher.update(ref_domain);
119 hasher.update(&.{0});
120 hasher.update(encoded[0..48]);
121 var digest: [digest_bytes]u8 = undefined;
122 hasher.final(&digest);
123 @memcpy(encoded[48..80], &digest);
124 output.* = encoded;
125 }
126
127 pub fn decodeRef(input: *const RefWire) types.Error!types.EvidenceRef {
128 if (!std.mem.eql(u8, input[0..8], &ref_magic)) {
129 return error.EvidenceRefBadMagic;
130 }
131 if (get(u16, input, 8) != version) {
132 return error.EvidenceRefVersionUnsupported;
133 }
134 if (input[11] != flags or !os.abi.wire.allZero(input[14..16])) {
135 return error.EvidenceRefFieldInvalid;
136 }
137 var expected: [digest_bytes]u8 = undefined;
138 var hasher = Sha256.init(.{});
139 hasher.update(ref_domain);
140 hasher.update(&.{0});
141 hasher.update(input[0..48]);
142 hasher.final(&expected);
143 if (!std.mem.eql(u8, &expected, input[48..80])) {
144 return error.EvidenceRefFieldInvalid;
145 }
146 return .{
147 .history = .{ .digest = input[16..48].* },
148 .target = try enumValue(types.Target, input[10]),
149 .index = get(u16, input, 12),
150 };
151 }
152
153 fn encodeHeader(capacity: types.Capacity, value: anytype, output: []u8) void {
154 @memcpy(output[0..8], &magic);
155 put(u16, output, 8, version);
156 output[10] = flags;
157 put(u32, output, 12, @intCast(output.len));
158 put(u16, output, 16, capacity.frames);
159 put(u16, output, 18, capacity.evidence);
160 put(u16, output, 20, value.frame_count);
161 put(u16, output, 22, value.evidence_count);
162 output[24] = @backingInt(value.state);
163 put(u16, output, 26, moment_bytes);
164 put(u16, output, 28, decision_bytes);
165 put(u16, output, 30, event_bytes);
166 put(u16, output, 32, frame_bytes);
167 put(u16, output, 34, record_bytes);
168 put(u16, output, 36, fabric.transition.stream_bytes);
169 put(u16, output, 38, @sizeOf(os.abi.MessageWire));
170 }
171
172 fn decodeHeader(
173 comptime capacity: types.Capacity,
174 input: *const Wire(capacity),
175 ) types.Error!Header {
176 if (!std.mem.eql(u8, input[0..8], &magic)) return error.EvidenceWireBadMagic;
177 if (get(u16, input, 8) != version) {
178 return error.EvidenceWireVersionUnsupported;
179 }
180 if (input[10] != flags) return error.EvidenceWireFlagsUnsupported;
181 if (get(u32, input, 12) != input.len) return error.EvidenceWireSizeMismatch;
182 if (get(u16, input, 16) != capacity.frames or
183 get(u16, input, 18) != capacity.evidence)
184 {
185 return error.EvidenceWireCapacityMismatch;
186 }
187 if (get(u16, input, 26) != moment_bytes or
188 get(u16, input, 28) != decision_bytes or
189 get(u16, input, 30) != event_bytes or
190 get(u16, input, 32) != frame_bytes or
191 get(u16, input, 34) != record_bytes or
192 get(u16, input, 36) != fabric.transition.stream_bytes or
193 get(u16, input, 38) != @sizeOf(os.abi.MessageWire) or
194 !os.abi.wire.allZero(input[11..12]) or
195 !os.abi.wire.allZero(input[25..26]) or
196 !os.abi.wire.allZero(input[40..header_bytes]))
197 {
198 return error.EvidenceWireFieldInvalid;
199 }
200 const frame_count = get(u16, input, 20);
201 const evidence_count = get(u16, input, 22);
202 if (frame_count > capacity.frames or evidence_count > capacity.evidence) {
203 return error.EvidenceWireCapacityMismatch;
204 }
205 return .{
206 .frame_count = frame_count,
207 .evidence_count = evidence_count,
208 .state = try enumValue(explore.TraceState, input[24]),
209 };
210 }
211
212 fn encodeFrame(value: types.Frame, output: []u8) void {
213 encodeDecision(value.decision, output[0..decision_bytes]);
214 encodeMoment(value.expected, output[decision_bytes .. decision_bytes + moment_bytes]);
215 put(u16, output, decision_bytes + moment_bytes, value.evidence_start);
216 put(u16, output, decision_bytes + moment_bytes + 2, value.evidence_count);
217 }
218
219 fn decodeFrame(input: []const u8) types.Error!types.Frame {
220 return .{
221 .decision = try decodeDecision(input[0..decision_bytes]),
222 .expected = try decodeMoment(
223 input[decision_bytes .. decision_bytes + moment_bytes],
224 ),
225 .evidence_start = get(u16, input, decision_bytes + moment_bytes),
226 .evidence_count = get(u16, input, decision_bytes + moment_bytes + 2),
227 };
228 }
229
230 fn encodeEvidence(value: types.Evidence, output: []u8) types.Error!void {
231 switch (value) {
232 .transition => |wire| {
233 _ = try fabric.transition.inspect(&wire);
234 output[0] = 1;
235 @memcpy(output[8..][0..wire.len], &wire);
236 },
237 .machine => |wire| {
238 _ = try os.abi.decodeEvent(&wire);
239 output[0] = 2;
240 @memcpy(output[8..][0..wire.len], &wire);
241 },
242 .semantic => |event| {
243 output[0] = 3;
244 encodeEvent(event, output[8..][0..event_bytes]);
245 },
246 }
247 }
248
249 fn decodeEvidence(input: []const u8) types.Error!types.Evidence {
250 return switch (input[0]) {
251 1 => transition: {
252 const wire: fabric.transition.Wire = input[8..][0..fabric.transition.stream_bytes].*;
253 _ = try fabric.transition.inspect(&wire);
254 break :transition .{ .transition = wire };
255 },
256 2 => machine: {
257 const wire: os.abi.MessageWire = input[8..][0..@sizeOf(os.abi.MessageWire)].*;
258 _ = try os.abi.decodeEvent(&wire);
259 break :machine .{ .machine = wire };
260 },
261 3 => .{ .semantic = try decodeEvent(input[8..][0..event_bytes]) },
262 else => error.EvidenceWireFieldInvalid,
263 };
264 }
265
266 fn encodeMoment(value: world.Moment, output: []u8) void {
267 @memcpy(output[0..32], &value.digest);
268 put(u16, output, 32, @backingInt(value.dialect));
269 encodeWorldRoot(value.origin, output[40..208]);
270 encodeFabricRoot(value.fabric, output[208..304]);
271 }
272
273 fn decodeMoment(input: []const u8) types.Error!world.Moment {
274 return .{
275 .digest = input[0..32].*,
276 .dialect = try enumValue(world.MomentDialect, get(u16, input, 32)),
277 .origin = try decodeWorldRoot(input[40..208]),
278 .fabric = try decodeFabricRoot(input[208..304]),
279 };
280 }
281
282 fn encodeWorldRoot(value: world.Root, output: []u8) void {
283 @memcpy(output[0..32], &value.digest);
284 put(u16, output, 32, @backingInt(value.dialect));
285 output[34] = value.node_count;
286 @memcpy(output[40..72], &value.machine_contract.digest);
287 encodeFabricRoot(value.fabric, output[72..168]);
288 }
289
290 fn decodeWorldRoot(input: []const u8) types.Error!world.Root {
291 return .{
292 .digest = input[0..32].*,
293 .dialect = try enumValue(world.Dialect, get(u16, input, 32)),
294 .node_count = input[34],
295 .machine_contract = .{ .digest = input[40..72].* },
296 .fabric = try decodeFabricRoot(input[72..168]),
297 };
298 }
299
300 fn encodeFabricRoot(value: fabric.Root, output: []u8) void {
301 @memcpy(output[0..32], &value.digest);
302 put(u16, output, 32, @backingInt(value.dialect));
303 @memcpy(output[40..72], &value.machine_contract.digest);
304 put(u64, output, 72, value.entry_frontier);
305 put(u64, output, 80, value.admission_frontier);
306 put(u64, output, 88, value.fault_frontier);
307 }
308
309 fn decodeFabricRoot(input: []const u8) types.Error!fabric.Root {
310 return .{
311 .digest = input[0..32].*,
312 .dialect = try enumValue(fabric.Dialect, get(u16, input, 32)),
313 .machine_contract = .{ .digest = input[40..72].* },
314 .entry_frontier = get(u64, input, 72),
315 .admission_frontier = get(u64, input, 80),
316 .fault_frontier = get(u64, input, 88),
317 };
318 }
319
320 fn encodeDecision(value: explore.SearchDecision, output: []u8) void {
321 output[0] = @backingInt(value.site.stream);
322 put(u16, output, 2, value.site.sequence);
323 put(u64, output, 4, value.site.virtual_time_tick);
324 output[12] = value.alternative;
325 output[13] = value.alternative_count;
326 switch (value.choice) {
327 .input => |choice| encodeInput(choice, output[16..]),
328 .schedule => |choice| encodeSchedule(choice, output[16..]),
329 .topology => |choice| encodeTopology(choice, output[16..]),
330 .fault => |choice| encodeGeneratedFault(choice, output[16..]),
331 }
332 }
333
334 fn decodeDecision(input: []const u8) types.Error!explore.SearchDecision {
335 const stream = try enumValue(explore.Stream, input[0]);
336 const choice: explore.SearchChoice = switch (stream) {
337 .input => .{ .input = try decodeInput(input[16..]) },
338 .schedule => .{ .schedule = try decodeSchedule(input[16..]) },
339 .topology => .{ .topology = try decodeTopology(input[16..]) },
340 .fault => .{ .fault = try decodeGeneratedFault(input[16..]) },
341 };
342 return .{
343 .site = .{
344 .stream = stream,
345 .sequence = get(u16, input, 2),
346 .virtual_time_tick = get(u64, input, 4),
347 },
348 .alternative = input[12],
349 .alternative_count = input[13],
350 .choice = choice,
351 };
352 }
353
354 fn encodeOrigin(value: explore.Origin, output: []u8) void {
355 put(u16, output, 0, @backingInt(value.source));
356 put(u16, output, 2, value.version);
357 }
358
359 fn decodeOrigin(input: []const u8) types.Error!explore.Origin {
360 return .{
361 .source = try enumValue(profile.DeterminismSource, get(u16, input, 0)),
362 .version = get(u16, input, 2),
363 };
364 }
365
366 fn encodeInput(value: explore.GeneratedInput, output: []u8) void {
367 encodeOrigin(value.origin, output[0..4]);
368 output[4] = @backingInt(std.meta.activeTag(value.value));
369 put(u64, output, 8, value.virtual_time_tick);
370 switch (value.value) {
371 .wait => |payload| put(u64, output, 16, payload),
372 .terminal => |payload| put(u64, output, 16, payload),
373 .entropy => |payload| put(u64, output, 16, payload),
374 .packet => |payload| put(u64, output, 16, payload),
375 .service_result => |payload| output[16] = @backingInt(payload),
376 .effect_result => |payload| output[16] = @backingInt(payload),
377 }
378 }
379
380 fn decodeInput(input: []const u8) types.Error!explore.GeneratedInput {
381 const kind = try enumValue(explore.InputKind, input[4]);
382 const value: explore.InputValue = switch (kind) {
383 .wait => .{ .wait = get(u64, input, 16) },
384 .terminal => .{ .terminal = get(u64, input, 16) },
385 .entropy => .{ .entropy = get(u64, input, 16) },
386 .packet => .{ .packet = get(u64, input, 16) },
387 .service_result => .{ .service_result = try enumValue(
388 explore.OperationOutcome,
389 input[16],
390 ) },
391 .effect_result => .{ .effect_result = try enumValue(
392 explore.OperationOutcome,
393 input[16],
394 ) },
395 };
396 return .{
397 .origin = try decodeOrigin(input[0..4]),
398 .virtual_time_tick = get(u64, input, 8),
399 .value = value,
400 };
401 }
402
403 fn encodeSchedule(value: explore.GeneratedSchedule, output: []u8) void {
404 encodeOrigin(value.origin, output[0..4]);
405 output[4] = @backingInt(std.meta.activeTag(value.value));
406 put(u64, output, 8, value.virtual_time_tick);
407 switch (value.value) {
408 .turn => |node| output[16] = node,
409 .idle => {},
410 }
411 }
412
413 fn decodeSchedule(input: []const u8) types.Error!explore.GeneratedSchedule {
414 const kind = try enumValue(explore.ScheduleKind, input[4]);
415 const value: explore.ScheduleValue = switch (kind) {
416 .turn => .{ .turn = input[16] },
417 .idle => .idle,
418 };
419 return .{
420 .origin = try decodeOrigin(input[0..4]),
421 .virtual_time_tick = get(u64, input, 8),
422 .value = value,
423 };
424 }
425
426 fn encodeTopology(value: explore.GeneratedTopology, output: []u8) void {
427 encodeOrigin(value.origin, output[0..4]);
428 output[4] = @backingInt(std.meta.activeTag(value.value));
429 put(u64, output, 8, value.virtual_time_tick);
430 switch (value.value) {
431 .node => |node| {
432 @memcpy(output[16..32], &node.id.bytes);
433 output[32] = @intFromBool(node.included);
434 },
435 .complete => {},
436 }
437 }
438
439 fn decodeTopology(input: []const u8) types.Error!explore.GeneratedTopology {
440 const kind = try enumValue(explore.TopologyKind, input[4]);
441 const value: explore.TopologyValue = switch (kind) {
442 .node => .{ .node = .{
443 .id = .{ .bytes = input[16..32].* },
444 .included = try boolValue(input[32]),
445 } },
446 .complete => .complete,
447 };
448 return .{
449 .origin = try decodeOrigin(input[0..4]),
450 .virtual_time_tick = get(u64, input, 8),
451 .value = value,
452 };
453 }
454
455 fn encodeGeneratedFault(value: explore.GeneratedFault, output: []u8) void {
456 encodeOrigin(value.choice_origin, output[0..4]);
457 if (value.effect_origin) |origin| {
458 output[4] = 1;
459 encodeOrigin(origin, output[8..12]);
460 }
461 put(u64, output, 16, value.virtual_time_tick);
462 switch (value.action) {
463 .healthy => output[5] = 1,
464 .inject => |kind| {
465 output[5] = 2;
466 output[6] = @backingInt(kind);
467 },
468 .persist => |kind| {
469 output[5] = 3;
470 output[6] = @backingInt(kind);
471 },
472 .recover => |kind| {
473 output[5] = 4;
474 output[6] = @backingInt(kind);
475 },
476 }
477 }
478
479 fn decodeGeneratedFault(input: []const u8) types.Error!explore.GeneratedFault {
480 const effect_origin: ?explore.Origin = switch (input[4]) {
481 0 => null,
482 1 => try decodeOrigin(input[8..12]),
483 else => return error.EvidenceWireFieldInvalid,
484 };
485 const action: explore.FaultAction = switch (input[5]) {
486 1 => .healthy,
487 2 => .{ .inject = try enumValue(fault.Kind, input[6]) },
488 3 => .{ .persist = try enumValue(fault.Kind, input[6]) },
489 4 => .{ .recover = try enumValue(fault.Kind, input[6]) },
490 else => return error.EvidenceWireFieldInvalid,
491 };
492 return .{
493 .choice_origin = try decodeOrigin(input[0..4]),
494 .effect_origin = effect_origin,
495 .virtual_time_tick = get(u64, input, 16),
496 .action = action,
497 };
498 }
499
500 fn encodeEvent(value: explore.Event, output: []u8) void {
501 put(u16, output, 0, value.sequence);
502 put(u64, output, 8, value.virtual_time_tick);
503 switch (value.value) {
504 .controlled_input => |event| {
505 output[16] = 1;
506 encodeInput(event, output[24..]);
507 },
508 .schedule_choice => |event| {
509 output[16] = 2;
510 encodeSchedule(event, output[24..]);
511 },
512 .topology_choice => |event| {
513 output[16] = 3;
514 encodeTopology(event, output[24..]);
515 },
516 .observation => |event| {
517 output[16] = 4;
518 put(u64, output, 24, event.id);
519 put(u64, output, 32, event.value);
520 },
521 .property_declaration => |event| {
522 output[16] = 5;
523 encodeDeclaration(event, output[24..]);
524 },
525 .property_evaluation => |event| {
526 output[16] = 6;
527 encodeEvaluation(event, output[24..]);
528 },
529 .injected_fault => |event| {
530 output[16] = 7;
531 encodeGeneratedFault(event, output[24..]);
532 },
533 .diagnostic => |event| {
534 output[16] = 8;
535 put(u64, output, 24, event.id);
536 put(u32, output, 32, event.code);
537 },
538 .external_admission => |event| {
539 output[16] = 9;
540 put(u64, output, 24, event.id);
541 encodeOrigin(event.origin, output[32..36]);
542 },
543 .operation => |event| {
544 output[16] = 10;
545 put(u64, output, 24, event.id);
546 output[32] = @backingInt(event.outcome);
547 },
548 }
549 }
550
551 fn decodeEvent(input: []const u8) types.Error!explore.Event {
552 const value: explore.EventValue = switch (input[16]) {
553 1 => .{ .controlled_input = try decodeInput(input[24..]) },
554 2 => .{ .schedule_choice = try decodeSchedule(input[24..]) },
555 3 => .{ .topology_choice = try decodeTopology(input[24..]) },
556 4 => .{ .observation = .{
557 .id = get(u64, input, 24),
558 .value = get(u64, input, 32),
559 } },
560 5 => .{ .property_declaration = try decodeDeclaration(input[24..]) },
561 6 => .{ .property_evaluation = try decodeEvaluation(input[24..]) },
562 7 => .{ .injected_fault = try decodeGeneratedFault(input[24..]) },
563 8 => .{ .diagnostic = .{
564 .id = get(u64, input, 24),
565 .code = get(u32, input, 32),
566 } },
567 9 => .{ .external_admission = .{
568 .id = get(u64, input, 24),
569 .origin = try decodeOrigin(input[32..36]),
570 } },
571 10 => .{ .operation = .{
572 .id = get(u64, input, 24),
573 .outcome = try enumValue(explore.OperationOutcome, input[32]),
574 } },
575 else => return error.EvidenceWireFieldInvalid,
576 };
577 return .{
578 .sequence = get(u16, input, 0),
579 .virtual_time_tick = get(u64, input, 8),
580 .value = value,
581 };
582 }
583
584 fn encodeDeclaration(value: explore.PropertyDeclaration, output: []u8) void {
585 put(u64, output, 0, value.id);
586 output[8] = @backingInt(value.kind);
587 if (value.bound) |bound| {
588 output[9] = 1;
589 switch (bound) {
590 .steps => |steps| {
591 output[10] = 1;
592 put(u16, output, 16, steps);
593 },
594 .virtual_time => |ticks| {
595 output[10] = 2;
596 put(u64, output, 16, ticks);
597 },
598 }
599 }
600 }
601
602 fn decodeDeclaration(input: []const u8) types.Error!explore.PropertyDeclaration {
603 const bound: ?explore.Bound = switch (input[9]) {
604 0 => null,
605 1 => switch (input[10]) {
606 1 => .{ .steps = get(u16, input, 16) },
607 2 => .{ .virtual_time = get(u64, input, 16) },
608 else => return error.EvidenceWireFieldInvalid,
609 },
610 else => return error.EvidenceWireFieldInvalid,
611 };
612 return .{
613 .id = get(u64, input, 0),
614 .kind = try enumValue(explore.PropertyKind, input[8]),
615 .bound = bound,
616 };
617 }
618
619 fn encodeEvaluation(value: explore.PropertyEvaluation, output: []u8) void {
620 put(u64, output, 0, value.id);
621 output[8] = @backingInt(value.verdict);
622 output[9] = @backingInt(value.reason);
623 if (value.witness) |witness| {
624 output[10] = 1;
625 put(u16, output, 12, witness);
626 }
627 }
628
629 fn decodeEvaluation(input: []const u8) types.Error!explore.PropertyEvaluation {
630 const witness: ?u16 = switch (input[10]) {
631 0 => null,
632 1 => get(u16, input, 12),
633 else => return error.EvidenceWireFieldInvalid,
634 };
635 return .{
636 .id = get(u64, input, 0),
637 .verdict = try enumValue(explore.Verdict, input[8]),
638 .reason = try enumValue(explore.EvaluationReason, input[9]),
639 .witness = witness,
640 };
641 }
642
643 fn validateDigest(
644 comptime capacity: types.Capacity,
645 input: *const Wire(capacity),
646 ) types.Error!void {
647 const expected = materialDigest(capacity, input);
648 if (!std.mem.eql(u8, &expected, input[identityOffset(capacity)..])) {
649 return error.CanonicalIdentityMismatch;
650 }
651 }
652
653 fn materialDigest(
654 comptime capacity: types.Capacity,
655 input: *const Wire(capacity),
656 ) [digest_bytes]u8 {
657 var hasher = Sha256.init(.{});
658 hasher.update(domain);
659 hasher.update(&.{0});
660 hasher.update(input[0..identityOffset(capacity)]);
661 var result: [digest_bytes]u8 = undefined;
662 hasher.final(&result);
663 return result;
664 }
665
666 fn startOffset() usize {
667 return header_bytes;
668 }
669
670 fn framesOffset() usize {
671 return startOffset() + moment_bytes;
672 }
673
674 fn frameOffset(capacity: types.Capacity, index: usize) usize {
675 std.debug.assert(index < capacity.frames);
676 return framesOffset() + index * frame_bytes;
677 }
678
679 fn recordsOffset(capacity: types.Capacity) usize {
680 return framesOffset() + @as(usize, capacity.frames) * frame_bytes;
681 }
682
683 fn recordOffset(capacity: types.Capacity, index: usize) usize {
684 std.debug.assert(index < capacity.evidence);
685 return recordsOffset(capacity) + index * record_bytes;
686 }
687
688 fn identityOffset(capacity: types.Capacity) usize {
689 return recordsOffset(capacity) + @as(usize, capacity.evidence) * record_bytes;
690 }
691
692 fn enumValue(comptime T: type, value: anytype) types.Error!T {
693 inline for (std.meta.tags(T)) |candidate| {
694 if (@backingInt(candidate) == value) return candidate;
695 }
696 return error.EvidenceWireFieldInvalid;
697 }
698
699 fn boolValue(value: u8) types.Error!bool {
700 return switch (value) {
701 0 => false,
702 1 => true,
703 else => error.EvidenceWireFieldInvalid,
704 };
705 }
706
707 fn put(comptime T: type, output: []u8, offset: usize, value: T) void {
708 const end = offset + @sizeOf(T);
709 std.debug.assert(end <= output.len);
710 std.mem.writeInt(T, output[offset..][0..@sizeOf(T)], value, .little);
711 }
712
713 fn get(comptime T: type, input: []const u8, offset: usize) T {
714 const end = offset + @sizeOf(T);
715 std.debug.assert(end <= input.len);
716 return std.mem.readInt(T, input[offset..][0..@sizeOf(T)], .little);
717 }