lib/choir/src/composition/module/serialization/format.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const binary = @import("../../../serialization/root.zig").binary;
 3 
 4 pub const magic = "CHOIRCMP";
 5 pub const version: u32 = 6;
 6 pub const header_size = magic.len + @sizeOf(u32) + @sizeOf(u64);
 7 pub const checksum_size = @sizeOf(u32);
 8 pub const max_serialized_bytes: usize = 256 * 1024 * 1024;
 9 pub const max_string_bytes: usize = 1024 * 1024;
10 pub const max_blob_bytes: usize = 256 * 1024 * 1024;
11 pub const max_collection_entries: usize = 1 << 16;
12 pub const max_total_entries: usize = 1 << 18;
13 
14 /// Distinct compiler manifests one module may reference. Fragments are assembled
15 /// from a handful of toolchains at most, and a stream that needs more is refused
16 /// rather than served with a table the import store cannot hold.
17 pub const max_compiler_manifests: usize = 16;
18 
19 pub const limits = binary.Limits{
20     .serialized_bytes = max_serialized_bytes,
21     .string_bytes = max_string_bytes,
22     .blob_bytes = max_blob_bytes,
23     .collection_entries = max_collection_entries,
24     .total_entries = max_total_entries,
25 };
26 
27 pub const Error = binary.Error || error{
28     InvalidMagic,
29     UnsupportedVersion,
30     TrailingBytes,
31     InvalidChecksum,
32 };
33 
34 pub const WriteError = binary.WriteError;
35 pub const Writer = binary.Writer(limits);
36 pub const Reader = binary.Reader(limits);
37 
38 pub fn initWriter(allocator: std.mem.Allocator) WriteError!Writer {
39     var writer = Writer.init(allocator);
40     errdefer writer.deinit();
41     try writer.writeRaw(magic);
42     try writer.writeInt(u32, version);
43     try writer.writeInt(u64, 0);
44     return writer;
45 }
46 
47 pub fn finishWriter(writer: *Writer) WriteError![]u8 {
48     const payload = writer.bytes.items[header_size..];
49     const payload_length = std.math.cast(u64, payload.len) orelse return error.LimitExceeded;
50     const length_offset = magic.len + @sizeOf(u32);
51     std.mem.writeInt(u64, writer.bytes.items[length_offset..][0..@sizeOf(u64)], payload_length, .little);
52     try writer.writeInt(u32, checksum(payload));
53     return try writer.finish();
54 }
55 
56 pub fn checksum(payload: []const u8) u32 {
57     var crc = std.hash.Crc32.init();
58     crc.update(payload);
59     return crc.final();
60 }
61 
62 pub fn readPayload(bytes: []const u8) Error!Reader {
63     if (bytes.len > max_serialized_bytes) return error.LimitExceeded;
64     if (bytes.len < header_size + checksum_size) return error.Truncated;
65     if (!std.mem.eql(u8, bytes[0..magic.len], magic)) return error.InvalidMagic;
66     if (std.mem.readInt(u32, bytes[magic.len..][0..@sizeOf(u32)], .little) != version) return error.UnsupportedVersion;
67 
68     const length_offset = magic.len + @sizeOf(u32);
69     const payload_length_u64 = std.mem.readInt(u64, bytes[length_offset..][0..@sizeOf(u64)], .little);
70     const payload_length = std.math.cast(usize, payload_length_u64) orelse return error.LimitExceeded;
71     const expected_length = std.math.add(usize, header_size, payload_length) catch return error.LimitExceeded;
72     const framed_length = std.math.add(usize, expected_length, checksum_size) catch return error.LimitExceeded;
73     if (framed_length > max_serialized_bytes) return error.LimitExceeded;
74     if (bytes.len < framed_length) return error.Truncated;
75     if (bytes.len > framed_length) return error.TrailingBytes;
76 
77     const payload = bytes[header_size..expected_length];
78     const expected_checksum = std.mem.readInt(u32, bytes[expected_length..][0..checksum_size], .little);
79     if (checksum(payload) != expected_checksum) return error.InvalidChecksum;
80     return try Reader.init(payload);
81 }