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 }