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 }