lib/machine/src/profile/wire.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const os_abi = @import("os").abi;
3 const profile = @import("root.zig");
4
5 const Sha256 = std.crypto.hash.sha2.Sha256;
6
7 pub const bytes: u16 = 256;
8 pub const magic = [8]u8{ 'M', 'C', 'H', 'P', 'R', 'F', '1', 0 };
9 pub const flags: u16 = 0;
10 pub const contract_fingerprint_schema = "tiny.machine-contract-fingerprint/v1";
11 pub const profile_fingerprint_schema = "tiny.machine-profile-fingerprint/v1";
12 pub const Wire = [bytes]u8;
13 /// The identity of the shared execution rules. A caller compares this fingerprint
14 /// to know whether two profiles obey the same execution rules. The fingerprint is
15 /// computed over the profile's encoded form with the backend field and the four
16 /// claim fields zeroed, so the backend and the claims contribute nothing. A KVM
17 /// profile and its portable counterpart therefore hold the same value, so a test
18 /// can run both and compare what comes out. Restoring an instance from a checkpoint
19 /// requires this fingerprint to equal the one recorded in the checkpoint's receipt
20 /// basis. `hex` renders the digest as lowercase hexadecimal.
21 pub const ContractFingerprint = struct {
22 digest: [Sha256.digest_length]u8,
23
24 pub fn hex(self: ContractFingerprint) [Sha256.digest_length * 2]u8 {
25 return std.fmt.bytesToHex(self.digest, .lower);
26 }
27 };
28
29 /// The identity of one full profile. A caller compares this fingerprint to know
30 /// whether two runs used the very same profile, backend and claims included. The
31 /// contract, the backend, and the claims all feed it, so every pairing of a canonical
32 /// kind with a backend gets a distinct value. A restore proceeds only when the profile
33 /// in hand carries the value recorded with the checkpoint. `hex` renders the digest
34 /// as lowercase hexadecimal.
35 pub const ProfileFingerprint = struct {
36 digest: [Sha256.digest_length]u8,
37
38 pub fn hex(self: ProfileFingerprint) [Sha256.digest_length * 2]u8 {
39 return std.fmt.bytesToHex(self.digest, .lower);
40 }
41 };
42
43 pub fn encode(value: profile.Profile, output: *Wire) profile.Error!void {
44 try profile.validate(value);
45 const contract = value.contract;
46 var encoded: Wire = @splat(0);
47 @memcpy(encoded[0..8], &magic);
48 os_abi.wire.write16(encoded[8..10], contract.schema_major);
49 os_abi.wire.write16(encoded[10..12], contract.schema_minor);
50 os_abi.wire.write16(encoded[12..14], bytes);
51 os_abi.wire.write16(encoded[14..16], flags);
52 encoded[16] = @backingInt(contract.kind);
53 encoded[17] = @backingInt(contract.architecture);
54 os_abi.wire.write16(encoded[18..20], @backingInt(contract.cpu));
55 os_abi.wire.write16(encoded[20..22], @backingInt(contract.boot));
56 encoded[22] = @backingInt(contract.root_semantics);
57 encoded[23] = @backingInt(contract.missing_root);
58 os_abi.wire.write16(encoded[24..26], @backingInt(contract.device));
59 os_abi.wire.write16(encoded[26..28], @backingInt(contract.scheduling));
60 os_abi.wire.write16(encoded[28..30], @backingInt(contract.time));
61 os_abi.wire.write16(encoded[30..32], @backingInt(contract.entropy));
62 os_abi.wire.write16(encoded[32..34], @backingInt(contract.transport.dialect));
63 os_abi.wire.write16(encoded[34..36], @backingInt(contract.checkpoint));
64 os_abi.wire.write16(encoded[36..38], @backingInt(value.backend));
65 os_abi.wire.write16(encoded[38..40], @backingInt(contract.effects));
66 os_abi.wire.write16(encoded[40..42], contract.geometry.vcpu_count);
67 os_abi.wire.write16(encoded[42..44], contract.instance_limit);
68 os_abi.wire.write32(encoded[44..48], contract.geometry.page_bytes);
69 os_abi.wire.write64(encoded[48..56], contract.geometry.ram_base);
70 os_abi.wire.write64(encoded[56..64], contract.geometry.ram_bytes);
71 os_abi.wire.write16(encoded[64..66], contract.transport.abi_major);
72 os_abi.wire.write16(encoded[66..68], contract.transport.abi_minor);
73 os_abi.wire.write16(encoded[68..70], contract.transport.boot_frame_bytes);
74 os_abi.wire.write16(encoded[70..72], contract.transport.message_frame_bytes);
75 os_abi.wire.write16(encoded[72..74], contract.transport.ring_header_bytes);
76 os_abi.wire.write16(encoded[74..76], contract.transport.request_records);
77 os_abi.wire.write16(encoded[76..78], contract.transport.event_records);
78 os_abi.wire.write16(encoded[78..80], contract.transport.terminal_bytes);
79 os_abi.wire.write16(encoded[80..82], contract.transport.entropy_bytes);
80 os_abi.wire.write16(encoded[82..84], contract.transport.semantic_bytes);
81 os_abi.wire.write16(encoded[84..86], contract.transport.effect_request_bytes);
82 os_abi.wire.write16(encoded[86..88], contract.transport.effect_result_bytes);
83 os_abi.wire.write16(encoded[88..90], contract.checkpoint_candidate_limit);
84 os_abi.wire.write16(encoded[90..92], @backingInt(contract.instruction_admission));
85 os_abi.wire.write32(encoded[92..96], contract.admission_limit);
86 os_abi.wire.write16(encoded[96..98], @backingInt(contract.determinism.schema));
87 os_abi.wire.write16(encoded[98..100], contract.determinism.entry_count);
88 @memcpy(encoded[100..132], &contract.determinism.digest);
89 encodeClaim(value.claims.semantic, encoded[132..135]);
90 encodeClaim(value.claims.same_backend_whole_system, encoded[135..138]);
91 encodeClaim(value.claims.cross_host, encoded[138..141]);
92 encodeClaim(value.claims.instruction_exact, encoded[141..144]);
93 output.* = encoded;
94 }
95
96 pub fn decode(input: *const Wire) profile.Error!profile.Profile {
97 if (!std.mem.eql(u8, input[0..8], &magic)) return error.BadMagic;
98 if (os_abi.wire.read16(input[8..10]) != profile.schema_major or
99 os_abi.wire.read16(input[10..12]) != profile.schema_minor)
100 {
101 return error.UnsupportedVersion;
102 }
103 if (os_abi.wire.read16(input[12..14]) != bytes) {
104 return error.WireBytesMismatch;
105 }
106 if (os_abi.wire.read16(input[14..16]) != flags) {
107 return error.UnsupportedFlags;
108 }
109 if (!os_abi.wire.allZero(input[144..])) {
110 return error.ReservedNonzero;
111 }
112 const value: profile.Profile = .{
113 .contract = .{
114 .schema_major = os_abi.wire.read16(input[8..10]),
115 .schema_minor = os_abi.wire.read16(input[10..12]),
116 .kind = try enumValue(profile.ProfileKind, input[16]),
117 .architecture = try enumValue(profile.Architecture, input[17]),
118 .cpu = try enumValue(profile.CpuContract, os_abi.wire.read16(input[18..20])),
119 .instruction_admission = try enumValue(
120 profile.InstructionAdmission,
121 os_abi.wire.read16(input[90..92]),
122 ),
123 .boot = try enumValue(profile.BootDialect, os_abi.wire.read16(input[20..22])),
124 .root_semantics = try enumValue(profile.RootSemantics, input[22]),
125 .missing_root = try enumValue(profile.MissingRoot, input[23]),
126 .device = try enumValue(profile.DeviceDialect, os_abi.wire.read16(input[24..26])),
127 .scheduling = try enumValue(profile.Scheduling, os_abi.wire.read16(input[26..28])),
128 .time = try enumValue(profile.TimeSemantics, os_abi.wire.read16(input[28..30])),
129 .entropy = try enumValue(profile.EntropySemantics, os_abi.wire.read16(input[30..32])),
130 .geometry = .{
131 .vcpu_count = os_abi.wire.read16(input[40..42]),
132 .page_bytes = os_abi.wire.read32(input[44..48]),
133 .ram_base = os_abi.wire.read64(input[48..56]),
134 .ram_bytes = os_abi.wire.read64(input[56..64]),
135 },
136 .transport = .{
137 .dialect = try enumValue(profile.TransportDialect, os_abi.wire.read16(input[32..34])),
138 .abi_major = os_abi.wire.read16(input[64..66]),
139 .abi_minor = os_abi.wire.read16(input[66..68]),
140 .boot_frame_bytes = os_abi.wire.read16(input[68..70]),
141 .message_frame_bytes = os_abi.wire.read16(input[70..72]),
142 .ring_header_bytes = os_abi.wire.read16(input[72..74]),
143 .request_records = os_abi.wire.read16(input[74..76]),
144 .event_records = os_abi.wire.read16(input[76..78]),
145 .terminal_bytes = os_abi.wire.read16(input[78..80]),
146 .entropy_bytes = os_abi.wire.read16(input[80..82]),
147 .semantic_bytes = os_abi.wire.read16(input[82..84]),
148 .effect_request_bytes = os_abi.wire.read16(input[84..86]),
149 .effect_result_bytes = os_abi.wire.read16(input[86..88]),
150 },
151 .checkpoint = try enumValue(profile.CheckpointFormat, os_abi.wire.read16(input[34..36])),
152 .effects = try enumValue(profile.EffectSemantics, os_abi.wire.read16(input[38..40])),
153 .determinism = .{
154 .schema = try enumValue(
155 profile.DeterminismSchema,
156 os_abi.wire.read16(input[96..98]),
157 ),
158 .entry_count = os_abi.wire.read16(input[98..100]),
159 .digest = input[100..132].*,
160 },
161 .instance_limit = os_abi.wire.read16(input[42..44]),
162 .checkpoint_candidate_limit = os_abi.wire.read16(input[88..90]),
163 .admission_limit = os_abi.wire.read32(input[92..96]),
164 },
165 .backend = try enumValue(profile.BackendSemantics, os_abi.wire.read16(input[36..38])),
166 .claims = .{
167 .semantic = try decodeClaim(input[132..135]),
168 .same_backend_whole_system = try decodeClaim(input[135..138]),
169 .cross_host = try decodeClaim(input[138..141]),
170 .instruction_exact = try decodeClaim(input[141..144]),
171 },
172 };
173 try profile.validate(value);
174 return value;
175 }
176
177 pub fn contractFingerprint(value: profile.Profile) profile.Error!ContractFingerprint {
178 var encoded: Wire = undefined;
179 try encode(value, &encoded);
180 os_abi.wire.write16(encoded[36..38], 0);
181 @memset(encoded[132..144], 0);
182 return .{ .digest = digest(contract_fingerprint_schema, &encoded) };
183 }
184
185 pub fn profileFingerprint(value: profile.Profile) profile.Error!ProfileFingerprint {
186 var encoded: Wire = undefined;
187 try encode(value, &encoded);
188 return .{ .digest = digest(profile_fingerprint_schema, &encoded) };
189 }
190
191 fn digest(schema: []const u8, encoded: *const Wire) [Sha256.digest_length]u8 {
192 var hasher = Sha256.init(.{});
193 hasher.update(schema);
194 hasher.update(&.{0});
195 hasher.update(encoded);
196 var output: [Sha256.digest_length]u8 = undefined;
197 hasher.final(&output);
198 return output;
199 }
200
201 fn enumValue(comptime T: type, value: anytype) profile.Error!T {
202 inline for (std.meta.tags(T)) |candidate| {
203 if (@backingInt(candidate) == value) return candidate;
204 }
205 return error.UnknownProfileField;
206 }
207
208 fn encodeClaim(claim: profile.DeterminismClaim, output: *[3]u8) void {
209 const kind = std.meta.activeTag(claim);
210 output[0] = @backingInt(kind);
211 const evidence = switch (claim) {
212 .enforced => |value| @backingInt(value),
213 .assumed => |value| @backingInt(value),
214 };
215 os_abi.wire.write16(output[1..3], evidence);
216 }
217
218 fn decodeClaim(input: *const [3]u8) profile.Error!profile.DeterminismClaim {
219 const kind = try enumValue(profile.DeterminismClaimKind, input[0]);
220 const evidence = os_abi.wire.read16(input[1..3]);
221 return switch (kind) {
222 .enforced => .{ .enforced = try enumValue(
223 profile.DeterminismClaimWitness,
224 evidence,
225 ) },
226 .assumed => .{ .assumed = try enumValue(
227 profile.DeterminismClaimAssumption,
228 evidence,
229 ) },
230 };
231 }