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 }