lib/wayland/src/protocol/value/encode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const sys = @import("sys");
4 const value = @import("root.zig");
5
6 pub const Limits = struct {
7 payload_byte_count: usize = value.maximum_payload_size,
8 descriptor_count: usize = sys.ancillary.maximum_descriptors,
9 };
10
11 pub const CapacityError = error{
12 PayloadStorageTooLarge,
13 DescriptorStorageTooLarge,
14 CapacityOverflow,
15 };
16
17 pub const Capacity = struct {
18 payload_byte_count: usize,
19 descriptor_count: usize,
20 descriptor_bytes: usize,
21 total_requested_bytes: usize,
22
23 pub fn derive(limits: Limits) CapacityError!Capacity {
24 if (limits.payload_byte_count > value.maximum_payload_size) {
25 return error.PayloadStorageTooLarge;
26 }
27 if (limits.descriptor_count > sys.ancillary.maximum_descriptors) {
28 return error.DescriptorStorageTooLarge;
29 }
30 const descriptor_bytes = std.math.mul(
31 usize,
32 limits.descriptor_count,
33 @sizeOf(sys.fd.Descriptor),
34 ) catch return error.CapacityOverflow;
35 const total_requested_bytes = std.math.add(
36 usize,
37 limits.payload_byte_count,
38 descriptor_bytes,
39 ) catch return error.CapacityOverflow;
40 return .{
41 .payload_byte_count = limits.payload_byte_count,
42 .descriptor_count = limits.descriptor_count,
43 .descriptor_bytes = descriptor_bytes,
44 .total_requested_bytes = total_requested_bytes,
45 };
46 }
47 };
48
49 pub const StorageError = error{
50 PayloadCapacityExceeded,
51 DescriptorCapacityExceeded,
52 };
53
54 pub const Status = struct {
55 payload_capacity_rejection_count: u64 = 0,
56 descriptor_capacity_rejection_count: u64 = 0,
57 };
58
59 pub const default_capacity = Capacity.derive(.{}) catch unreachable;
60
61 const ProtocolError = error{
62 InvalidDescriptor,
63 InvalidInterface,
64 InvalidString,
65 InvalidUtf8,
66 InvalidVersion,
67 MessageTooLarge,
68 DescriptorCountMismatch,
69 PayloadTooSmall,
70 TooManyDescriptors,
71 };
72
73 pub const Error = StorageError || ProtocolError;
74
75 pub const Encoder = struct {
76 session_allocator: std.mem.Allocator,
77 bytes: []u8,
78 descriptors: []sys.fd.Descriptor,
79 byte_count: usize = 0,
80 descriptor_count: usize = 0,
81 payload_capacity_rejection_count: u64 = 0,
82 descriptor_capacity_rejection_count: u64 = 0,
83
84 pub fn init(
85 session_allocator: std.mem.Allocator,
86 capacity: Capacity,
87 ) std.mem.Allocator.Error!Encoder {
88 const bytes = try session_allocator.alloc(u8, capacity.payload_byte_count);
89 errdefer if (bytes.len != 0) session_allocator.free(bytes);
90 return .{
91 .session_allocator = session_allocator,
92 .bytes = bytes,
93 .descriptors = try session_allocator.alloc(
94 sys.fd.Descriptor,
95 capacity.descriptor_count,
96 ),
97 };
98 }
99
100 pub fn deinit(self: *Encoder) void {
101 self.assertValid();
102 if (self.bytes.len != 0) self.session_allocator.free(self.bytes);
103 if (self.descriptors.len != 0) self.session_allocator.free(self.descriptors);
104 self.* = undefined;
105 }
106
107 pub fn reset(self: *Encoder) void {
108 self.assertValid();
109 self.byte_count = 0;
110 self.descriptor_count = 0;
111 }
112
113 pub fn signed(self: *Encoder, item: i32) Error!void {
114 var bytes: [4]u8 = undefined;
115 std.mem.writeInt(i32, &bytes, item, builtin.cpu.arch.endian());
116 try self.appendBytes(&bytes);
117 }
118
119 pub fn unsigned(self: *Encoder, item: u32) Error!void {
120 var bytes: [4]u8 = undefined;
121 std.mem.writeInt(u32, &bytes, item, builtin.cpu.arch.endian());
122 try self.appendBytes(&bytes);
123 }
124
125 pub fn fixed(self: *Encoder, item: value.Fixed) Error!void {
126 try self.signed(item.raw);
127 }
128
129 pub fn string(self: *Encoder, text: []const u8) Error!void {
130 try validateString(text);
131 try self.lengthDelimited(text, true);
132 }
133
134 pub fn optionalString(self: *Encoder, text: ?[]const u8) Error!void {
135 if (text) |present| return self.string(present);
136 try self.unsigned(0);
137 }
138
139 pub fn object(self: *Encoder, id: value.ObjectId) Error!void {
140 try self.unsigned(id.raw);
141 }
142
143 pub fn optionalObject(self: *Encoder, id: ?value.ObjectId) Error!void {
144 try self.unsigned(if (id) |present| present.raw else 0);
145 }
146
147 pub fn newId(self: *Encoder, id: value.NewId) Error!void {
148 try self.unsigned(id.raw);
149 }
150
151 pub fn dynamicNewId(
152 self: *Encoder,
153 interface: []const u8,
154 version: u32,
155 id: value.NewId,
156 ) Error!void {
157 if (interface.len == 0) return error.InvalidInterface;
158 if (version == 0) return error.InvalidVersion;
159 const byte_mark = self.byte_count;
160 errdefer self.byte_count = byte_mark;
161 try self.string(interface);
162 try self.unsigned(version);
163 try self.newId(id);
164 }
165
166 pub fn array(self: *Encoder, bytes: []const u8) Error!void {
167 try self.lengthDelimited(bytes, false);
168 }
169
170 pub fn descriptorBorrowed(self: *Encoder, descriptor: sys.fd.Descriptor) Error!void {
171 self.assertValid();
172 if (descriptor < 0) return error.InvalidDescriptor;
173 if (self.descriptor_count == sys.ancillary.maximum_descriptors) {
174 return error.TooManyDescriptors;
175 }
176 if (self.descriptor_count == self.descriptors.len) {
177 self.descriptor_capacity_rejection_count +|= 1;
178 return error.DescriptorCapacityExceeded;
179 }
180 self.descriptors[self.descriptor_count] = descriptor;
181 self.descriptor_count += 1;
182 }
183
184 pub fn finish(
185 self: *const Encoder,
186 metadata: *const @import("../root.zig").schema.Message,
187 ) Error!value.Encoded {
188 self.assertValid();
189 if (self.byte_count < metadata.minimum_payload_size) return error.PayloadTooSmall;
190 if (self.byte_count % 4 != 0) unreachable;
191 if (self.descriptor_count != metadata.descriptor_count) {
192 return error.DescriptorCountMismatch;
193 }
194 return .{
195 .metadata = metadata,
196 .payload = self.bytes[0..self.byte_count],
197 .descriptors = self.descriptors[0..self.descriptor_count],
198 };
199 }
200
201 pub fn status(self: *const Encoder) Status {
202 self.assertValid();
203 return .{
204 .payload_capacity_rejection_count = self.payload_capacity_rejection_count,
205 .descriptor_capacity_rejection_count = self.descriptor_capacity_rejection_count,
206 };
207 }
208
209 fn appendBytes(self: *Encoder, bytes: []const u8) Error!void {
210 try self.ensurePayloadCapacity(bytes.len);
211 @memcpy(self.bytes[self.byte_count..][0..bytes.len], bytes);
212 self.byte_count += bytes.len;
213 }
214
215 fn lengthDelimited(self: *Encoder, bytes: []const u8, terminal_zero: bool) Error!void {
216 const logical_len = std.math.add(usize, bytes.len, @intFromBool(terminal_zero)) catch {
217 return error.MessageTooLarge;
218 };
219 if (logical_len > std.math.maxInt(u32)) return error.MessageTooLarge;
220 const rounded_len = std.math.add(usize, logical_len, 3) catch return error.MessageTooLarge;
221 const padded_len = rounded_len & ~@as(usize, 3);
222 const encoded_len = std.math.add(usize, 4, padded_len) catch return error.MessageTooLarge;
223 try self.ensurePayloadCapacity(encoded_len);
224
225 var length_bytes: [4]u8 = undefined;
226 std.mem.writeInt(u32, &length_bytes, @intCast(logical_len), builtin.cpu.arch.endian());
227 @memcpy(self.bytes[self.byte_count..][0..length_bytes.len], &length_bytes);
228 self.byte_count += length_bytes.len;
229 @memcpy(self.bytes[self.byte_count..][0..bytes.len], bytes);
230 self.byte_count += bytes.len;
231 if (terminal_zero) {
232 self.bytes[self.byte_count] = 0;
233 self.byte_count += 1;
234 }
235 const padding = padded_len - logical_len;
236 @memset(self.bytes[self.byte_count..][0..padding], 0);
237 self.byte_count += padding;
238 }
239
240 fn ensurePayloadCapacity(self: *Encoder, additional: usize) Error!void {
241 self.assertValid();
242 const total = std.math.add(usize, self.byte_count, additional) catch {
243 return error.MessageTooLarge;
244 };
245 if (total > value.maximum_payload_size) return error.MessageTooLarge;
246 if (total > self.bytes.len) {
247 self.payload_capacity_rejection_count +|= 1;
248 return error.PayloadCapacityExceeded;
249 }
250 }
251
252 fn assertValid(self: *const Encoder) void {
253 std.debug.assert(self.byte_count <= self.bytes.len);
254 std.debug.assert(self.descriptor_count <= self.descriptors.len);
255 }
256 };
257
258 fn validateString(text: []const u8) Error!void {
259 if (std.mem.indexOfScalar(u8, text, 0) != null) return error.InvalidString;
260 if (!std.unicode.utf8ValidateSlice(text)) return error.InvalidUtf8;
261 }
262
263 test "encoder distinguishes null and empty strings" {
264 const protocol = @import("../root.zig");
265 const metadata: protocol.schema.Message = .{
266 .name = "strings",
267 .opcode = 0,
268 .since = 1,
269 .deprecated_since = null,
270 .destructor = false,
271 .signature = "?ss",
272 .descriptor_count = 0,
273 .minimum_payload_size = 12,
274 .arguments = &.{},
275 };
276 var encoder = try Encoder.init(std.testing.allocator, default_capacity);
277 defer encoder.deinit();
278 try encoder.optionalString(null);
279 try encoder.string("");
280 const encoded = try encoder.finish(&metadata);
281 try std.testing.expectEqualSlices(
282 u8,
283 &.{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 },
284 encoded.payload,
285 );
286 }
287
288 test "encoder rejects invalid protocol strings and oversized payloads" {
289 var encoder = try Encoder.init(std.testing.allocator, default_capacity);
290 defer encoder.deinit();
291 try std.testing.expectError(error.InvalidString, encoder.string("a\x00b"));
292 try std.testing.expectError(error.InvalidUtf8, encoder.string("\xff"));
293 var oversized: [value.maximum_payload_size]u8 = undefined;
294 try std.testing.expectError(error.MessageTooLarge, encoder.array(&oversized));
295 }
296
297 test "encoder accounts for schema descriptors before exposing a message" {
298 const protocol = @import("../root.zig");
299 const metadata: protocol.schema.Message = .{
300 .name = "descriptor",
301 .opcode = 0,
302 .since = 1,
303 .deprecated_since = null,
304 .destructor = false,
305 .signature = "h",
306 .descriptor_count = 1,
307 .minimum_payload_size = 0,
308 .arguments = &.{},
309 };
310 var encoder = try Encoder.init(std.testing.allocator, default_capacity);
311 defer encoder.deinit();
312 try std.testing.expectError(error.DescriptorCountMismatch, encoder.finish(&metadata));
313 try std.testing.expectError(error.InvalidDescriptor, encoder.descriptorBorrowed(-1));
314 try encoder.descriptorBorrowed(23);
315 const encoded = try encoder.finish(&metadata);
316 try std.testing.expectEqual(@as(usize, 0), encoded.payload.len);
317 try std.testing.expectEqualSlices(sys.fd.Descriptor, &.{23}, encoded.descriptors);
318 }
319
320 test "encoder capacity derives exact payload and descriptor storage" {
321 const capacity = try Capacity.derive(.{
322 .payload_byte_count = 13,
323 .descriptor_count = 3,
324 });
325 try std.testing.expectEqual(@as(usize, 13), capacity.payload_byte_count);
326 try std.testing.expectEqual(@as(usize, 3), capacity.descriptor_count);
327 try std.testing.expectEqual(
328 3 * @sizeOf(sys.fd.Descriptor),
329 capacity.descriptor_bytes,
330 );
331 try std.testing.expectEqual(
332 13 + 3 * @sizeOf(sys.fd.Descriptor),
333 capacity.total_requested_bytes,
334 );
335 try std.testing.expectError(error.PayloadStorageTooLarge, Capacity.derive(.{
336 .payload_byte_count = value.maximum_payload_size + 1,
337 }));
338 try std.testing.expectError(error.DescriptorStorageTooLarge, Capacity.derive(.{
339 .descriptor_count = sys.ancillary.maximum_descriptors + 1,
340 }));
341 }
342
343 test "encoder acquires both regions before use" {
344 const capacity = try Capacity.derive(.{
345 .payload_byte_count = 8,
346 .descriptor_count = 1,
347 });
348 var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
349 .fail_index = 0,
350 });
351 try std.testing.expectError(
352 error.OutOfMemory,
353 Encoder.init(failing.allocator(), capacity),
354 );
355 failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
356 .fail_index = 1,
357 });
358 try std.testing.expectError(
359 error.OutOfMemory,
360 Encoder.init(failing.allocator(), capacity),
361 );
362 failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
363 .fail_index = 2,
364 });
365 var encoder = try Encoder.init(failing.allocator(), capacity);
366 defer encoder.deinit();
367 try std.testing.expectEqual(@as(usize, 2), failing.allocations);
368 }
369
370 test "encoder max plus one preserves payload and saturates status" {
371 const capacity = try Capacity.derive(.{
372 .payload_byte_count = 4,
373 .descriptor_count = 1,
374 });
375 var encoder = try Encoder.init(std.testing.allocator, capacity);
376 defer encoder.deinit();
377 try encoder.unsigned(7);
378 try std.testing.expectError(error.PayloadCapacityExceeded, encoder.unsigned(8));
379 try std.testing.expectEqual(@as(usize, 4), encoder.byte_count);
380 try std.testing.expectEqual(@as(u32, 7), std.mem.readInt(
381 u32,
382 encoder.bytes[0..4],
383 builtin.cpu.arch.endian(),
384 ));
385 try std.testing.expectEqual(@as(u64, 1), encoder.status().payload_capacity_rejection_count);
386
387 encoder.payload_capacity_rejection_count = std.math.maxInt(u64);
388 try std.testing.expectError(error.PayloadCapacityExceeded, encoder.unsigned(8));
389 try std.testing.expectEqual(
390 std.math.maxInt(u64),
391 encoder.status().payload_capacity_rejection_count,
392 );
393 }
394
395 test "encoder max plus one preserves borrowed descriptors" {
396 const capacity = try Capacity.derive(.{
397 .payload_byte_count = 0,
398 .descriptor_count = 1,
399 });
400 var encoder = try Encoder.init(std.testing.allocator, capacity);
401 defer encoder.deinit();
402 try encoder.descriptorBorrowed(7);
403 try std.testing.expectError(
404 error.DescriptorCapacityExceeded,
405 encoder.descriptorBorrowed(8),
406 );
407 try std.testing.expectEqualSlices(
408 sys.fd.Descriptor,
409 &.{7},
410 encoder.descriptors[0..encoder.descriptor_count],
411 );
412 try std.testing.expectEqual(
413 @as(u64, 1),
414 encoder.status().descriptor_capacity_rejection_count,
415 );
416
417 encoder.descriptor_capacity_rejection_count = std.math.maxInt(u64);
418 try std.testing.expectError(
419 error.DescriptorCapacityExceeded,
420 encoder.descriptorBorrowed(8),
421 );
422 try std.testing.expectEqual(
423 std.math.maxInt(u64),
424 encoder.status().descriptor_capacity_rejection_count,
425 );
426 }