lib/machine/src/checkpoint/stream/decode.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const admission = @import("../../admission/root.zig");
  2 const core = @import("machine_instance_core");
  3 const instance_receipt = @import("../../instance/receipt/root.zig");
  4 const os = @import("os");
  5 const owner = @import("../owner/root.zig");
  6 const schema = @import("schema.zig");
  7 const stream_types = @import("types.zig");
  8 const std = @import("std");
  9 
 10 const wire = os.abi.wire;
 11 
 12 /// Decodes one fixed 4096-byte header under `expected_root`. The function
 13 /// validates the framing, the reserved ranges, the root fields, and the settled
 14 /// receipt. A decoded root other than `expected_root` returns
 15 /// `CheckpointRootMismatch`. The call returns the material and a memory digest,
 16 /// which the caller authenticates against the memory bytes that follow.
 17 pub fn header(
 18     input: *const [schema.header_bytes]u8,
 19     expected_root: owner.Root,
 20 ) stream_types.Error!stream_types.Decoded {
 21     try validateEnvelope(input);
 22     try validateReserved(input);
 23     const root_value = try decodeRoot(input);
 24     if (!std.meta.eql(root_value, expected_root)) {
 25         return error.CheckpointRootMismatch;
 26     }
 27     const memory: owner.MemoryDigest = .{
 28         .digest = digest(input, schema.MemoryLayout.digest_offset),
 29     };
 30     try validateDigest(memory.digest);
 31     const receipt = try decodeSemantic(input);
 32     const cpu = decodeCpu(input);
 33     return .{
 34         .material = .{
 35             .profile = root_value.profile,
 36             .receipt = receipt,
 37             .cpu = cpu,
 38             .immutable_image = decodeImmutable(input, cpu),
 39         },
 40         .memory = memory,
 41     };
 42 }
 43 
 44 fn validateEnvelope(
 45     input: *const [schema.header_bytes]u8,
 46 ) stream_types.Error!void {
 47     const layout = schema.Envelope;
 48     if (!std.mem.eql(
 49         u8,
 50         input[layout.magic_offset..layout.version_offset],
 51         &schema.magic,
 52     )) return error.BadMagic;
 53     if (get16(input, layout.version_offset) != schema.version) {
 54         return error.UnsupportedVersion;
 55     }
 56     if (get16(input, layout.header_bytes_offset) != schema.header_bytes) {
 57         return error.HeaderBytesMismatch;
 58     }
 59     if (get16(input, layout.flags_offset) != schema.flags) {
 60         return error.UnsupportedFlags;
 61     }
 62     if (get16(input, layout.format_offset) != schema.checkpoint_format) {
 63         return error.CheckpointFormatMismatch;
 64     }
 65     if (get64(input, layout.stream_bytes_offset) != schema.encoded_stream_bytes or
 66         get64(input, layout.ram_bytes_offset) != schema.ram_bytes)
 67     {
 68         return error.StreamBytesMismatch;
 69     }
 70     if (get32(input, layout.page_bytes_offset) != schema.page_bytes) {
 71         return error.PageBytesMismatch;
 72     }
 73     if (get16(input, layout.abi_major_offset) != schema.abi_major or
 74         get16(input, layout.abi_minor_offset) != schema.abi_minor)
 75     {
 76         return error.AbiVersionMismatch;
 77     }
 78     try validateShape(input);
 79 }
 80 
 81 fn validateShape(
 82     input: *const [schema.header_bytes]u8,
 83 ) stream_types.Error!void {
 84     const envelope = schema.Envelope;
 85     const immutable = schema.ImmutableLayout;
 86     if (get16(input, envelope.load_capacity_offset) != schema.load_capacity or
 87         get16(input, envelope.load_bytes_offset) != schema.load_bytes or
 88         get16(input, envelope.digest_bytes_offset) != schema.digest_bytes or
 89         get16(input, envelope.root_bytes_offset) != schema.root_bytes or
 90         get16(input, envelope.semantic_receipt_bytes_offset) !=
 91             schema.semantic_receipt_bytes or
 92         get16(input, envelope.cpu_bytes_offset) != schema.cpu_bytes or
 93         get16(input, envelope.immutable_descriptor_bytes_offset) !=
 94             schema.immutable_descriptor_bytes or
 95         get16(input, immutable.load_bytes_offset) != schema.load_bytes)
 96     {
 97         return error.MetadataShapeMismatch;
 98     }
 99 }
100 
101 fn validateReserved(
102     input: *const [schema.header_bytes]u8,
103 ) stream_types.Error!void {
104     if (!wire.allZero(
105         input[schema.Envelope.reserved_offset..schema.Envelope.limit],
106     ) or !wire.allZero(
107         input[schema.SemanticLayout.reserved_offset..schema.SemanticLayout.transcript_offset],
108     ) or !wire.allZero(
109         input[schema.CpuLayout.reserved_offset..schema.CpuLayout.limit],
110     ) or !wire.allZero(input[schema.ImmutableLayout.limit..])) {
111         return error.ReservedNonzero;
112     }
113     for (0..schema.load_capacity) |index| {
114         const start = schema.ImmutableLayout.loads_offset +
115             index * schema.load_bytes;
116         if (!wire.allZero(input[start + schema.LoadLayout.reserved_offset .. start + schema.LoadLayout.limit])) {
117             return error.ReservedNonzero;
118         }
119     }
120 }
121 
122 fn decodeRoot(
123     input: *const [schema.header_bytes]u8,
124 ) stream_types.Error!owner.Root {
125     const value: owner.Root = .{
126         .digest = digest(input, schema.RootLayout.digest_offset),
127         .profile = .{
128             .digest = digest(input, schema.RootLayout.profile_offset),
129         },
130         .state = .{
131             .digest = digest(input, schema.RootLayout.state_offset),
132         },
133     };
134     try validateDigest(value.digest);
135     try validateDigest(value.profile.digest);
136     try validateDigest(value.state.digest);
137     return value;
138 }
139 
140 fn decodeSemantic(
141     input: *const [schema.header_bytes]u8,
142 ) stream_types.Error!instance_receipt.SemanticReceipt {
143     const layout = schema.SemanticLayout;
144     const frontiers = decodeFrontiers(input);
145     const semantic_frontier = get64(input, layout.semantic_frontier_offset);
146     const block_generation = std.math.add(
147         u64,
148         semantic_frontier,
149         1,
150     ) catch return error.CheckpointCorrupt;
151     const block_digest = digest(input, layout.block_root_offset);
152     const settled_request = get64(input, layout.settled_request_offset);
153     const event_produced = get64(input, layout.event_produced_offset);
154     const value: instance_receipt.SemanticReceipt = .{
155         .admission_receipt = .{ .digest = digest(input, layout.admission_offset) },
156         .basis = .{
157             .contract = .{ .digest = digest(input, layout.contract_offset) },
158             .source_root = digest(input, layout.source_root_offset),
159             .frontiers = frontiers,
160             .outstanding_effect = null,
161         },
162         .image_digest = digest(input, layout.image_offset),
163         .execution_fingerprint = .{
164             .digest = digest(input, layout.execution_offset),
165         },
166         .block_root = .{
167             .generation = block_generation,
168             .digest = block_digest,
169         },
170         .boundary = decodeBoundary(
171             input,
172             frontiers,
173             semantic_frontier,
174             block_digest,
175             settled_request,
176             event_produced,
177         ),
178         .settled = .{
179             .request_cursor = settled_request,
180             .event_cursor = event_produced,
181         },
182         .k0 = .{ .counter = input[layout.k0_offset] },
183         .semantic_transcript_digest = digest(input, layout.transcript_offset),
184     };
185     try instance_receipt.verifySemantic(value);
186     return value;
187 }
188 
189 fn decodeFrontiers(
190     input: *const [schema.header_bytes]u8,
191 ) admission.Frontiers {
192     const layout = schema.SemanticLayout;
193     return .{
194         .input = get64(input, layout.input_frontier_offset),
195         .terminal_input_offset = get64(input, layout.terminal_input_offset),
196         .virtual_time_tick = get64(input, layout.virtual_time_offset),
197         .entropy_generation = get64(input, layout.entropy_offset),
198         .effect = get64(input, layout.effect_offset),
199     };
200 }
201 
202 fn decodeBoundary(
203     input: *const [schema.header_bytes]u8,
204     frontiers: admission.Frontiers,
205     semantic_frontier: u64,
206     block_digest: os.abi.Digest,
207     settled_request: u64,
208     event_produced: u64,
209 ) instance_receipt.SemanticBoundary {
210     const layout = schema.SemanticLayout;
211     return .{
212         .request_sequence = settled_request,
213         .semantic_frontier = semantic_frontier,
214         .effect_frontier = frontiers.effect,
215         .terminal_offset = get64(input, layout.terminal_offset),
216         .virtual_time_tick = frontiers.virtual_time_tick,
217         .entropy_generation = frontiers.entropy_generation,
218         .request_consumed = settled_request,
219         .request_produced = settled_request,
220         .event_consumed = get64(input, layout.event_consumed_offset),
221         .event_produced = event_produced,
222         .unresolved_effects = 0,
223         .scheduler = .idle,
224         .block_root = block_digest,
225         .input_frontier = frontiers.input,
226         .terminal_input_offset = frontiers.terminal_input_offset,
227     };
228 }
229 
230 fn decodeCpu(input: *const [schema.header_bytes]u8) owner.CpuState {
231     const layout = schema.CpuLayout;
232     return .{
233         .rip = get64(input, layout.rip_offset),
234         .rsp = get64(input, layout.rsp_offset),
235         .rdi = get64(input, layout.rdi_offset),
236         .rflags = get64(input, layout.rflags_offset),
237         .cr0 = get64(input, layout.cr0_offset),
238         .cr3 = get64(input, layout.cr3_offset),
239         .cr4 = get64(input, layout.cr4_offset),
240         .efer = get64(input, layout.efer_offset),
241         .code_selector = get16(input, layout.code_selector_offset),
242         .data_selector = get16(input, layout.data_selector_offset),
243     };
244 }
245 
246 fn decodeImmutable(
247     input: *const [schema.header_bytes]u8,
248     cpu: owner.CpuState,
249 ) core.provenance.ImmutableImage {
250     const layout = schema.ImmutableLayout;
251     var loads: [schema.load_capacity]core.provenance.ImmutableLoad = @splat(.{});
252     for (&loads, 0..) |*load, index| {
253         load.* = decodeLoad(input, layout.loads_offset + index * schema.load_bytes);
254     }
255     return .{
256         .digest = digest(input, layout.digest_offset),
257         .initial = cpu,
258         .entry_offset = get32(input, layout.entry_offset),
259         .load_count = get16(input, layout.load_count_offset),
260         .loads = loads,
261     };
262 }
263 
264 fn decodeLoad(
265     input: *const [schema.header_bytes]u8,
266     start: usize,
267 ) core.provenance.ImmutableLoad {
268     const layout = schema.LoadLayout;
269     return .{
270         .source_offset = get32(input, start + layout.source_offset),
271         .file_bytes = get32(input, start + layout.file_offset),
272         .memory_bytes = get32(input, start + layout.memory_offset),
273         .physical_offset = get32(input, start + layout.physical_offset),
274         .virtual_offset = get64(input, start + layout.virtual_offset),
275         .flags = get32(input, start + layout.flags_offset),
276     };
277 }
278 
279 fn digest(
280     input: *const [schema.header_bytes]u8,
281     offset: usize,
282 ) os.abi.Digest {
283     return input[offset..][0..schema.digest_bytes].*;
284 }
285 
286 fn validateDigest(value: os.abi.Digest) owner.Error!void {
287     wire.validateDigest(value) catch return error.CheckpointCorrupt;
288 }
289 
290 fn get16(input: *const [schema.header_bytes]u8, offset: usize) u16 {
291     return wire.read16(input[offset..][0..2]);
292 }
293 
294 fn get32(input: *const [schema.header_bytes]u8, offset: usize) u32 {
295     return wire.read32(input[offset..][0..4]);
296 }
297 
298 fn get64(input: *const [schema.header_bytes]u8, offset: usize) u64 {
299     return wire.read64(input[offset..][0..8]);
300 }