lib/wayland/src/connection.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 const wayland = @import("root.zig");
  4 const wayland_options = @import("wayland_options");
  5 
  6 pub const Environment = struct {
  7     wayland_socket: ?[]const u8 = null,
  8     wayland_display: ?[]const u8 = null,
  9     xdg_runtime_dir: ?[]const u8 = null,
 10 
 11     pub fn current() Environment {
 12         return .{
 13             .wayland_socket = sys.env.getConstant("WAYLAND_SOCKET"),
 14             .wayland_display = sys.env.getConstant("WAYLAND_DISPLAY"),
 15             .xdg_runtime_dir = sys.env.getConstant("XDG_RUNTIME_DIR"),
 16         };
 17     }
 18 };
 19 
 20 pub const ProcessEnvironmentLimits = struct {
 21     retained_entry_count: usize = wayland_options.process_environment_retained_entry_count,
 22 };
 23 
 24 pub const ProcessEnvironmentCapacityError = error{
 25     CapacityOverflow,
 26 };
 27 
 28 pub const ProcessEnvironmentCapacity = struct {
 29     retained_entry_count: usize,
 30     source_entry_count: usize,
 31     generation_entry_count: usize,
 32     generation_bytes: usize,
 33     generation_owner_bytes: usize,
 34     total_static_bytes: usize,
 35 
 36     pub fn derive(limits: ProcessEnvironmentLimits) ProcessEnvironmentCapacityError!ProcessEnvironmentCapacity {
 37         const source_entry_count = std.math.add(
 38             usize,
 39             limits.retained_entry_count,
 40             1,
 41         ) catch return error.CapacityOverflow;
 42         const generation_bytes = std.math.mul(
 43             usize,
 44             source_entry_count,
 45             @sizeOf(?[*:0]const u8),
 46         ) catch return error.CapacityOverflow;
 47         const generation_owner_bytes = @sizeOf(sys.env.GenerationStorage);
 48         const total_static_bytes = std.math.add(
 49             usize,
 50             generation_bytes,
 51             generation_owner_bytes,
 52         ) catch return error.CapacityOverflow;
 53         return .{
 54             .retained_entry_count = limits.retained_entry_count,
 55             .source_entry_count = source_entry_count,
 56             .generation_entry_count = source_entry_count,
 57             .generation_bytes = generation_bytes,
 58             .generation_owner_bytes = generation_owner_bytes,
 59             .total_static_bytes = total_static_bytes,
 60         };
 61     }
 62 };
 63 
 64 pub const process_environment_capacity = ProcessEnvironmentCapacity.derive(.{}) catch |err| {
 65     @compileError("invalid Wayland process environment capacity: " ++ @errorName(err));
 66 };
 67 
 68 var process_environment_generation_entries: [process_environment_capacity.generation_entry_count]?[*:0]const u8 = undefined;
 69 var process_environment_generation =
 70     sys.env.GenerationStorage.init(&process_environment_generation_entries);
 71 
 72 pub const Error = std.mem.Allocator.Error || sys.net.StreamConnectError || sys.fd.FlagError || error{
 73     InvalidInheritedDescriptor,
 74     MissingRuntimeDirectory,
 75     InvalidRuntimeDirectory,
 76     ProcessEnvironmentCapacityExceeded,
 77     ProcessEnvironmentGenerationConsumed,
 78     InvalidDisplayName,
 79     SocketPathTooLong,
 80 };
 81 
 82 const test_capacity = wayland.TransportCapacity.derive(.{}) catch unreachable;
 83 
 84 const Endpoint = union(enum) {
 85     inherited: sys.fd.Descriptor,
 86     path: []const u8,
 87 };
 88 
 89 const InheritedSocketClaim = struct {
 90     descriptor: ?sys.fd.Descriptor = null,
 91 
 92     fn validate(self: *@This(), descriptor_text: []const u8) bool {
 93         const descriptor = std.fmt.parseInt(sys.fd.Descriptor, descriptor_text, 10) catch return false;
 94         if (descriptor < 0) return false;
 95         sys.fd.setCloseOnExec(descriptor) catch return false;
 96         self.descriptor = descriptor;
 97         return true;
 98     }
 99 };
100 
101 pub fn connect(
102     session_allocator: std.mem.Allocator,
103     capacity: wayland.transport.Capacity,
104 ) Error!wayland.Transport {
105     return connectProcessEnvironmentStorage(
106         session_allocator,
107         null,
108         capacity,
109         &process_environment_generation,
110     );
111 }
112 
113 pub fn connectNamed(
114     session_allocator: std.mem.Allocator,
115     display_name: []const u8,
116     capacity: wayland.transport.Capacity,
117 ) Error!wayland.Transport {
118     return connectProcessEnvironmentStorage(
119         session_allocator,
120         display_name,
121         capacity,
122         &process_environment_generation,
123     );
124 }
125 
126 fn connectProcessEnvironmentStorage(
127     session_allocator: std.mem.Allocator,
128     display_name: ?[]const u8,
129     capacity: wayland.transport.Capacity,
130     generation: *sys.env.GenerationStorage,
131 ) Error!wayland.Transport {
132     var storage = try wayland.transport.Storage.init(session_allocator, capacity);
133     errdefer storage.deinit();
134     var claim: InheritedSocketClaim = .{};
135     const inherited = sys.env.claimIf(
136         generation,
137         "WAYLAND_SOCKET",
138         &claim,
139         InheritedSocketClaim.validate,
140     ) catch |err| switch (err) {
141         error.ConditionRejected => return error.InvalidInheritedDescriptor,
142         error.GenerationStorageAlreadyUsed => return error.ProcessEnvironmentGenerationConsumed,
143         error.GenerationStorageTooSmall => return error.ProcessEnvironmentCapacityExceeded,
144     };
145     if (inherited) return storage.attach(claim.descriptor.?);
146     var environment = Environment.current();
147     environment.wayland_socket = null;
148     return connectNamedWithEnvironmentStorage(&storage, display_name, environment);
149 }
150 
151 pub fn connectNamedWithEnvironment(
152     session_allocator: std.mem.Allocator,
153     display_name: ?[]const u8,
154     environment: Environment,
155     capacity: wayland.transport.Capacity,
156 ) Error!wayland.Transport {
157     var storage = try wayland.transport.Storage.init(session_allocator, capacity);
158     errdefer storage.deinit();
159     return connectNamedWithEnvironmentStorage(&storage, display_name, environment);
160 }
161 
162 fn connectNamedWithEnvironmentStorage(
163     storage: *wayland.transport.Storage,
164     display_name: ?[]const u8,
165     environment: Environment,
166 ) Error!wayland.Transport {
167     var path_buffer: [sys.net.unix_path_capacity - 1]u8 = undefined;
168     return switch (try resolve(display_name, environment, &path_buffer)) {
169         .inherited => |descriptor| storage.attach(descriptor),
170         .path => |path| connectPath(storage, path),
171     };
172 }
173 
174 fn connectPath(
175     storage: *wayland.transport.Storage,
176     path: []const u8,
177 ) Error!wayland.Transport {
178     const address = sys.net.Address.initUnix(path) catch return error.SocketPathTooLong;
179     const stream = try sys.net.connectStream(address);
180     return storage.attach(stream.handle);
181 }
182 
183 fn resolve(
184     display_name: ?[]const u8,
185     environment: Environment,
186     path_buffer: *[sys.net.unix_path_capacity - 1]u8,
187 ) Error!Endpoint {
188     if (environment.wayland_socket) |descriptor_text| {
189         const descriptor = std.fmt.parseInt(sys.fd.Descriptor, descriptor_text, 10) catch {
190             return error.InvalidInheritedDescriptor;
191         };
192         if (descriptor < 0) return error.InvalidInheritedDescriptor;
193         return .{ .inherited = descriptor };
194     }
195 
196     const name = display_name orelse environment.wayland_display orelse "wayland-0";
197     if (std.mem.indexOfScalar(u8, name, 0) != null) return error.InvalidDisplayName;
198     if (name.len != 0 and name[0] == '/') {
199         if (name.len >= sys.net.unix_path_capacity) return error.SocketPathTooLong;
200         return .{ .path = name };
201     }
202 
203     const runtime_dir = environment.xdg_runtime_dir orelse return error.MissingRuntimeDirectory;
204     if (runtime_dir.len == 0 or runtime_dir[0] != '/' or std.mem.indexOfScalar(u8, runtime_dir, 0) != null) {
205         return error.InvalidRuntimeDirectory;
206     }
207     const separator_and_name_len = std.math.add(usize, name.len, 1) catch {
208         return error.SocketPathTooLong;
209     };
210     const path_len = std.math.add(usize, runtime_dir.len, separator_and_name_len) catch {
211         return error.SocketPathTooLong;
212     };
213     if (path_len >= sys.net.unix_path_capacity) return error.SocketPathTooLong;
214     @memcpy(path_buffer[0..runtime_dir.len], runtime_dir);
215     path_buffer[runtime_dir.len] = '/';
216     @memcpy(path_buffer[runtime_dir.len + 1 .. path_len], name);
217     return .{ .path = path_buffer[0..path_len] };
218 }
219 
220 test "display endpoint resolution follows the Wayland environment contract" {
221     var buffer: [sys.net.unix_path_capacity - 1]u8 = undefined;
222 
223     const fallback = try resolve(null, .{ .xdg_runtime_dir = "/run/user/1000" }, &buffer);
224     try std.testing.expectEqualStrings("/run/user/1000/wayland-0", fallback.path);
225 
226     const selected = try resolve(null, .{
227         .wayland_display = "wayland-7",
228         .xdg_runtime_dir = "/runtime",
229     }, &buffer);
230     try std.testing.expectEqualStrings("/runtime/wayland-7", selected.path);
231 
232     const absolute = try resolve("/tmp/custom-wayland", .{}, &buffer);
233     try std.testing.expectEqualStrings("/tmp/custom-wayland", absolute.path);
234 
235     const inherited = try resolve("ignored", .{
236         .wayland_socket = "42",
237         .wayland_display = "also-ignored",
238     }, &buffer);
239     try std.testing.expectEqual(@as(sys.fd.Descriptor, 42), inherited.inherited);
240 }
241 
242 test "invalid display endpoints fail before opening a socket" {
243     var buffer: [sys.net.unix_path_capacity - 1]u8 = undefined;
244     try std.testing.expectError(error.MissingRuntimeDirectory, resolve(null, .{}, &buffer));
245     try std.testing.expectError(
246         error.InvalidRuntimeDirectory,
247         resolve(null, .{ .xdg_runtime_dir = "relative" }, &buffer),
248     );
249     try std.testing.expectError(
250         error.InvalidInheritedDescriptor,
251         resolve(null, .{ .wayland_socket = "4x" }, &buffer),
252     );
253     try std.testing.expectError(
254         error.InvalidInheritedDescriptor,
255         resolve(null, .{ .wayland_socket = "-1" }, &buffer),
256     );
257 
258     var long_name: [sys.net.unix_path_capacity]u8 = @splat('w');
259     long_name[0] = '/';
260     try std.testing.expectError(error.SocketPathTooLong, resolve(&long_name, .{}, &buffer));
261 }
262 
263 test "connection acquires transport storage before endpoint effects" {
264     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
265         .fail_index = 0,
266     });
267     try std.testing.expectError(
268         error.OutOfMemory,
269         connectNamedWithEnvironment(failing.allocator(), null, .{}, test_capacity),
270     );
271 }
272 
273 test "process environment capacity derives exact literal-static generation storage" {
274     const capacity = try ProcessEnvironmentCapacity.derive(.{
275         .retained_entry_count = 3,
276     });
277     try std.testing.expectEqual(@as(usize, 3), capacity.retained_entry_count);
278     try std.testing.expectEqual(@as(usize, 4), capacity.source_entry_count);
279     try std.testing.expectEqual(@as(usize, 4), capacity.generation_entry_count);
280     try std.testing.expectEqual(
281         4 * @sizeOf(?[*:0]const u8),
282         capacity.generation_bytes,
283     );
284     try std.testing.expectEqual(
285         @sizeOf(sys.env.GenerationStorage),
286         capacity.generation_owner_bytes,
287     );
288     try std.testing.expectEqual(
289         capacity.generation_bytes + capacity.generation_owner_bytes,
290         capacity.total_static_bytes,
291     );
292     try std.testing.expectEqual(
293         process_environment_capacity.generation_entry_count,
294         process_environment_generation_entries.len,
295     );
296     try std.testing.expectEqual(
297         process_environment_capacity.total_static_bytes,
298         @sizeOf(@TypeOf(process_environment_generation_entries)) +
299             @sizeOf(@TypeOf(process_environment_generation)),
300     );
301 }
302 
303 test "process environment capacity rejects arithmetic overflow" {
304     try std.testing.expectError(
305         error.CapacityOverflow,
306         ProcessEnvironmentCapacity.derive(.{
307             .retained_entry_count = std.math.maxInt(usize),
308         }),
309     );
310 }
311 
312 test "inherited Wayland socket ownership transfers to the transport" {
313     const sockets = sys.fd.socketPairUnixStream(.{ .close_on_exec = true }) catch |err| switch (err) {
314         error.UnsupportedPlatform => return error.SkipZigTest,
315         else => return err,
316     };
317     var inherited_owned = true;
318     defer if (inherited_owned) sys.fd.close(sockets[0]);
319     defer sys.fd.close(sockets[1]);
320 
321     var descriptor_text: [32]u8 = undefined;
322     const value = try std.fmt.bufPrint(&descriptor_text, "{d}", .{sockets[0]});
323     var transport = try connectNamedWithEnvironment(std.testing.allocator, "ignored", .{
324         .wayland_socket = value,
325     }, test_capacity);
326     inherited_owned = false;
327     defer transport.deinit();
328 
329     try transport.queueOwned(2, 3, &.{}, &.{});
330     try std.testing.expectEqual(wayland.stream.FlushStatus.drained, try transport.flush());
331     var message: [wayland.wire.header_size]u8 = undefined;
332     try std.testing.expectEqual(message.len, try sys.fd.read(sockets[1], &message));
333     try std.testing.expectEqual(@as(u32, 2), (try wayland.wire.decode(&message)).object_id);
334 }
335 
336 test "process inherited socket claim allocates only transport storage" {
337     if (comptime !@hasDecl(std.process.Environ.Block, "view")) return error.SkipZigTest;
338     if (comptime @import("builtin").os.tag != .linux) return error.SkipZigTest;
339 
340     const sockets = try sys.fd.socketPairUnixStream(.{ .close_on_exec = false });
341     var inherited_owned = true;
342     defer if (inherited_owned) sys.fd.close(sockets[0]);
343     defer sys.fd.close(sockets[1]);
344 
345     var descriptor_text: [32]u8 = undefined;
346     const value = try std.fmt.bufPrint(&descriptor_text, "{d}", .{sockets[0]});
347     var map = sys.env.Map.init(std.testing.allocator);
348     defer map.deinit();
349     try map.put("WAYLAND_SOCKET", value);
350     const block = try map.createPosixBlock(std.testing.allocator, .{});
351     defer block.deinit(std.testing.allocator);
352     const previous = sys.env.current();
353     sys.env.installProcessEnvironment(.{ .block = block });
354     defer sys.env.installProcessEnvironment(previous);
355 
356     var generation_entries: [1]?[*:0]const u8 = undefined;
357     var generation = sys.env.GenerationStorage.init(&generation_entries);
358     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
359         .fail_index = 4,
360     });
361     var transport = try connectProcessEnvironmentStorage(
362         failing.allocator(),
363         null,
364         test_capacity,
365         &generation,
366     );
367     inherited_owned = false;
368     defer transport.deinit();
369     try std.testing.expectEqual(@as(usize, 4), failing.allocations);
370     try std.testing.expect(generation.used);
371     try std.testing.expect(sys.env.getConstant("WAYLAND_SOCKET") == null);
372 
373     try std.testing.expect(try sys.fd.closeOnExec(transport.descriptor));
374     try std.testing.expect(try sys.fd.nonBlocking(transport.descriptor));
375 }
376 
377 test "process environment max plus one rejects before descriptor effects" {
378     if (comptime !@hasDecl(std.process.Environ.Block, "view")) return error.SkipZigTest;
379     if (comptime @import("builtin").os.tag != .linux) return error.SkipZigTest;
380 
381     const sockets = try sys.fd.socketPairUnixStream(.{ .close_on_exec = false });
382     defer sys.fd.close(sockets[0]);
383     defer sys.fd.close(sockets[1]);
384 
385     var descriptor_text: [32]u8 = undefined;
386     const value = try std.fmt.bufPrint(&descriptor_text, "{d}", .{sockets[0]});
387     var map = sys.env.Map.init(std.testing.allocator);
388     defer map.deinit();
389     try map.put("WAYLAND_SOCKET", value);
390     try map.put("TINY_WAYLAND_RETAIN", "visible");
391     const block = try map.createPosixBlock(std.testing.allocator, .{});
392     defer block.deinit(std.testing.allocator);
393     const previous = sys.env.current();
394     sys.env.installProcessEnvironment(.{ .block = block });
395     defer sys.env.installProcessEnvironment(previous);
396 
397     var generation_entries: [1]?[*:0]const u8 = undefined;
398     var generation = sys.env.GenerationStorage.init(&generation_entries);
399     try std.testing.expectError(
400         error.ProcessEnvironmentCapacityExceeded,
401         connectProcessEnvironmentStorage(
402             std.testing.allocator,
403             null,
404             test_capacity,
405             &generation,
406         ),
407     );
408     try std.testing.expect(!generation.used);
409     try std.testing.expectEqualStrings(value, sys.env.getConstant("WAYLAND_SOCKET").?);
410     try std.testing.expectEqualStrings(
411         "visible",
412         sys.env.getConstant("TINY_WAYLAND_RETAIN").?,
413     );
414 
415     try std.testing.expect(!try sys.fd.closeOnExec(sockets[0]));
416 }
417 
418 test "invalid process inherited socket text remains installed" {
419     if (comptime !@hasDecl(std.process.Environ.Block, "view")) return error.SkipZigTest;
420 
421     var map = sys.env.Map.init(std.testing.allocator);
422     defer map.deinit();
423     try map.put("WAYLAND_SOCKET", "4x");
424     const block = try map.createPosixBlock(std.testing.allocator, .{});
425     defer block.deinit(std.testing.allocator);
426     const previous = sys.env.current();
427     sys.env.installProcessEnvironment(.{ .block = block });
428     defer sys.env.installProcessEnvironment(previous);
429 
430     var generation_entries: [1]?[*:0]const u8 = undefined;
431     var generation = sys.env.GenerationStorage.init(&generation_entries);
432     try std.testing.expectError(
433         error.InvalidInheritedDescriptor,
434         connectProcessEnvironmentStorage(
435             std.testing.allocator,
436             null,
437             test_capacity,
438             &generation,
439         ),
440     );
441     try std.testing.expectEqualStrings("4x", sys.env.getConstant("WAYLAND_SOCKET").?);
442 
443     var inherited = try sys.env.createMap(std.testing.allocator);
444     defer inherited.deinit();
445     try std.testing.expectEqualStrings("4x", inherited.get("WAYLAND_SOCKET").?);
446 }
447 
448 test "closed process inherited socket remains installed" {
449     if (comptime !@hasDecl(std.process.Environ.Block, "view")) return error.SkipZigTest;
450     if (comptime @import("builtin").os.tag != .linux) return error.SkipZigTest;
451 
452     const sockets = try sys.fd.socketPairUnixStream(.{ .close_on_exec = false });
453     sys.fd.close(sockets[0]);
454     defer sys.fd.close(sockets[1]);
455 
456     var descriptor_text: [32]u8 = undefined;
457     const value = try std.fmt.bufPrint(&descriptor_text, "{d}", .{sockets[0]});
458     var map = sys.env.Map.init(std.testing.allocator);
459     defer map.deinit();
460     try map.put("WAYLAND_SOCKET", value);
461     const block = try map.createPosixBlock(std.testing.allocator, .{});
462     defer block.deinit(std.testing.allocator);
463     const previous = sys.env.current();
464     sys.env.installProcessEnvironment(.{ .block = block });
465     defer sys.env.installProcessEnvironment(previous);
466 
467     var generation_entries: [1]?[*:0]const u8 = undefined;
468     var generation = sys.env.GenerationStorage.init(&generation_entries);
469     try std.testing.expectError(
470         error.InvalidInheritedDescriptor,
471         connectProcessEnvironmentStorage(
472             std.testing.allocator,
473             null,
474             test_capacity,
475             &generation,
476         ),
477     );
478     try std.testing.expectEqualStrings(value, sys.env.getConstant("WAYLAND_SOCKET").?);
479 
480     var inherited = try sys.env.createMap(std.testing.allocator);
481     defer inherited.deinit();
482     try std.testing.expectEqualStrings(value, inherited.get("WAYLAND_SOCKET").?);
483 }
484 
485 const InheritedSocketClaimWorkerFixture = struct {
486     generation: *sys.env.GenerationStorage,
487     connected: bool = false,
488     failed: bool = false,
489 
490     fn run(self: *@This()) void {
491         var transport = connectProcessEnvironmentStorage(
492             std.testing.allocator,
493             null,
494             test_capacity,
495             self.generation,
496         ) catch |err| switch (err) {
497             error.MissingRuntimeDirectory => return,
498             else => {
499                 self.failed = true;
500                 return;
501             },
502         };
503         self.connected = true;
504         transport.deinit();
505     }
506 };
507 
508 test "concurrent inherited socket claims transfer exactly once" {
509     if (comptime !@hasDecl(std.process.Environ.Block, "view")) return error.SkipZigTest;
510     if (comptime @import("builtin").os.tag != .linux) return error.SkipZigTest;
511 
512     const sockets = try sys.fd.socketPairUnixStream(.{ .close_on_exec = false });
513     var inherited_owned = true;
514     defer if (inherited_owned) sys.fd.close(sockets[0]);
515     defer sys.fd.close(sockets[1]);
516 
517     var descriptor_text: [32]u8 = undefined;
518     const value = try std.fmt.bufPrint(&descriptor_text, "{d}", .{sockets[0]});
519     var map = sys.env.Map.init(std.testing.allocator);
520     defer map.deinit();
521     try map.put("WAYLAND_SOCKET", value);
522     const block = try map.createPosixBlock(std.testing.allocator, .{});
523     defer block.deinit(std.testing.allocator);
524     const previous = sys.env.current();
525     sys.env.installProcessEnvironment(.{ .block = block });
526     defer sys.env.installProcessEnvironment(previous);
527 
528     var generation_entries: [1]?[*:0]const u8 = undefined;
529     var generation = sys.env.GenerationStorage.init(&generation_entries);
530     var workers: [8]InheritedSocketClaimWorkerFixture = undefined;
531     for (&workers) |*worker| worker.* = .{ .generation = &generation };
532     var threads: [workers.len]std.Thread = undefined;
533     for (&threads, &workers) |*thread, *worker| {
534         thread.* = try std.Thread.spawn(.{}, InheritedSocketClaimWorkerFixture.run, .{worker});
535     }
536     for (&threads) |*thread| thread.join();
537 
538     var connected: usize = 0;
539     for (workers) |worker| {
540         try std.testing.expect(!worker.failed);
541         if (worker.connected) connected += 1;
542     }
543     try std.testing.expectEqual(@as(usize, 1), connected);
544     inherited_owned = false;
545     try std.testing.expect(sys.env.getConstant("WAYLAND_SOCKET") == null);
546 }
547 
548 test "display name connects through the runtime directory" {
549     var tmp = std.testing.tmpDir(.{});
550     defer tmp.cleanup();
551 
552     var runtime_buffer: [std.fs.max_path_bytes]u8 = undefined;
553     const runtime_len = try tmp.dir.realPath(std.Options.debug_io, &runtime_buffer);
554     const runtime_dir = runtime_buffer[0..runtime_len];
555     var socket_path_buffer: [sys.net.unix_path_capacity - 1]u8 = undefined;
556     const socket_path = try std.fmt.bufPrint(&socket_path_buffer, "{s}/wayland-test", .{runtime_dir});
557 
558     var server = try sys.net.Address.listen(try sys.net.Address.initUnix(socket_path), .{});
559     defer server.deinit();
560 
561     var transport = try connectNamedWithEnvironment(std.testing.allocator, "wayland-test", .{
562         .xdg_runtime_dir = runtime_dir,
563     }, test_capacity);
564     defer transport.deinit();
565     const accepted = try server.accept();
566     defer accepted.stream.close();
567 
568     try transport.queueOwned(8, 5, &.{}, &.{});
569     try std.testing.expectEqual(wayland.stream.FlushStatus.drained, try transport.flush());
570     var message: [wayland.wire.header_size]u8 = undefined;
571     try std.testing.expectEqual(message.len, try accepted.stream.read(&message));
572     const header = try wayland.wire.decode(&message);
573     try std.testing.expectEqual(@as(u32, 8), header.object_id);
574     try std.testing.expectEqual(@as(u16, 5), header.opcode);
575 }