lib/closure/src/schema/value.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const name_bytes_max: usize = 64;
4 pub const descriptor_bytes_max: usize = 192;
5 pub const digest_bytes: usize = 32;
6 pub const digest_hex_bytes: usize = digest_bytes * 2;
7
8 pub const TextError = error{
9 EmptyText,
10 TextTooLong,
11 InvalidText,
12 };
13
14 pub const DigestParseError = error{
15 InvalidDigestLength,
16 InvalidDigestByte,
17 };
18
19 pub const Name = FixedText(name_bytes_max);
20 pub const Descriptor = FixedText(descriptor_bytes_max);
21
22 pub fn FixedText(comptime bytes_max: usize) type {
23 return extern struct {
24 len: u16,
25 bytes: [bytes_max]u8,
26
27 const Self = @This();
28
29 pub const Error: type = TextError;
30
31 pub fn init(input: []const u8) Error!Self {
32 if (input.len == 0) return error.EmptyText;
33 if (input.len > bytes_max) return error.TextTooLong;
34 var value = Self{
35 .len = @intCast(input.len),
36 .bytes = @splat(0),
37 };
38 for (input, 0..) |byte, index| {
39 if (byte < 0x20 or byte == 0x7f) {
40 return error.InvalidText;
41 }
42 value.bytes[index] = byte;
43 }
44 return value;
45 }
46
47 pub fn empty() Self {
48 return .{
49 .len = 0,
50 .bytes = @splat(0),
51 };
52 }
53
54 pub fn slice(self: *const Self) []const u8 {
55 return self.bytes[0..self.len];
56 }
57
58 pub fn isEmpty(self: *const Self) bool {
59 return self.len == 0;
60 }
61
62 pub fn eql(self: *const Self, other: *const Self) bool {
63 return std.mem.eql(u8, self.slice(), other.slice());
64 }
65 };
66 }
67
68 pub const Digest = extern struct {
69 bytes: [digest_bytes]u8,
70
71 pub const ParseError: type = DigestParseError;
72
73 pub fn zero() Digest {
74 return .{ .bytes = @splat(0) };
75 }
76
77 pub fn fromHex(input: []const u8) ParseError!Digest {
78 if (input.len != digest_hex_bytes) {
79 return error.InvalidDigestLength;
80 }
81 var output = Digest.zero();
82 for (0..digest_bytes) |index| {
83 const high = try nibble(input[index * 2]);
84 const low = try nibble(input[index * 2 + 1]);
85 output.bytes[index] = (high << 4) | low;
86 }
87 return output;
88 }
89
90 pub fn isKnown(self: *const Digest) bool {
91 for (self.bytes) |byte| {
92 if (byte != 0) return true;
93 }
94 return false;
95 }
96
97 pub fn eql(self: *const Digest, other: *const Digest) bool {
98 return std.mem.eql(u8, &self.bytes, &other.bytes);
99 }
100
101 pub fn hex(self: *const Digest) [digest_hex_bytes]u8 {
102 return std.fmt.bytesToHex(self.bytes, .lower);
103 }
104 };
105
106 pub const ProfileRef = extern struct {
107 required: bool,
108 id: Name,
109 source: Name,
110 body_sha256: Digest,
111
112 pub fn known(self: *const ProfileRef) bool {
113 if (!self.required) return true;
114 return !self.id.isEmpty() and
115 !self.source.isEmpty() and
116 self.body_sha256.isKnown();
117 }
118
119 pub fn absent() ProfileRef {
120 return .{
121 .required = false,
122 .id = Name.empty(),
123 .source = Name.empty(),
124 .body_sha256 = Digest.zero(),
125 };
126 }
127 };
128
129 pub const ProfileSet = extern struct {
130 executable: ProfileRef,
131 service_trust: ProfileRef,
132 model_origin: ProfileRef,
133 bootstrap: ProfileRef,
134
135 pub fn known(self: *const ProfileSet) bool {
136 return self.executable.known() and
137 self.service_trust.known() and
138 self.model_origin.known() and
139 self.bootstrap.known();
140 }
141 };
142
143 fn nibble(byte: u8) Digest.ParseError!u8 {
144 if (byte >= '0' and byte <= '9') return byte - '0';
145 if (byte >= 'a' and byte <= 'f') return byte - 'a' + 10;
146 return error.InvalidDigestByte;
147 }
148
149 test "fixed text rejects empty oversized and control input" {
150 try std.testing.expectError(error.EmptyText, Name.init(""));
151 var oversized: [name_bytes_max + 1]u8 = @splat('a');
152 try std.testing.expectError(
153 error.TextTooLong,
154 Name.init(&oversized),
155 );
156 try std.testing.expectError(
157 error.InvalidText,
158 Name.init("bad\nname"),
159 );
160 }
161
162 test "digest round trips canonical lowercase hexadecimal" {
163 const source =
164 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
165 const digest = try Digest.fromHex(source);
166 try std.testing.expect(digest.isKnown());
167 try std.testing.expectEqualStrings(source, &digest.hex());
168 try std.testing.expectError(
169 error.InvalidDigestByte,
170 Digest.fromHex(
171 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg",
172 ),
173 );
174 }