lib/sys/src/x11/connection.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("../root.zig");
  3 const auth = @import("auth.zig");
  4 const display = @import("display.zig");
  5 const protocol = @import("protocol.zig");
  6 
  7 const env = sys.env;
  8 const net = sys.net;
  9 
 10 pub const Message = [protocol.message_length]u8;
 11 
 12 const message_bytes = @sizeOf(Message);
 13 pub const minimum_reply_byte_count = 0;
 14 pub const default_reply_byte_count = 1 << 20;
 15 
 16 pub const Limits = struct {
 17     retained_message_count: usize = 256,
 18     request_byte_count: usize = protocol.maximum_request_byte_count,
 19     reply_byte_count: usize = default_reply_byte_count,
 20 };
 21 
 22 pub const CapacityError = error{
 23     RetainedMessagesEmpty,
 24     RequestStorageTooSmall,
 25     RequestStorageTooLarge,
 26     RequestStorageUnaligned,
 27     ReplyStorageTooSmall,
 28     ReplyStorageTooLarge,
 29     ReplyStorageUnaligned,
 30     CapacityOverflow,
 31 };
 32 
 33 pub const Capacity = struct {
 34     retained_message_count: usize,
 35     retained_message_bytes: usize,
 36     request_byte_count: usize,
 37     reply_byte_count: usize,
 38     initialization_scratch_byte_count: usize,
 39     steady_requested_bytes: usize,
 40     initialization_high_water_bytes: usize,
 41 
 42     pub fn derive(limits: Limits) CapacityError!Capacity {
 43         if (limits.retained_message_count == 0) return error.RetainedMessagesEmpty;
 44         if (limits.request_byte_count < protocol.minimum_request_byte_count) {
 45             return error.RequestStorageTooSmall;
 46         }
 47         if (limits.request_byte_count > protocol.maximum_request_byte_count) {
 48             return error.RequestStorageTooLarge;
 49         }
 50         if (limits.request_byte_count % 4 != 0) return error.RequestStorageUnaligned;
 51         if (@as(u64, @intCast(limits.reply_byte_count)) >
 52             protocol.maximum_reply_extra_byte_count)
 53         {
 54             return error.ReplyStorageTooLarge;
 55         }
 56         if (limits.reply_byte_count % 4 != 0) return error.ReplyStorageUnaligned;
 57         const retained_message_bytes = std.math.mul(
 58             usize,
 59             limits.retained_message_count,
 60             message_bytes,
 61         ) catch return error.CapacityOverflow;
 62         const initialization_scratch_byte_count = @max(
 63             auth.maximum_authority_file_byte_count,
 64             protocol.maximum_setup_response_byte_count,
 65         );
 66         const retained_and_request_bytes = std.math.add(
 67             usize,
 68             retained_message_bytes,
 69             limits.request_byte_count,
 70         ) catch return error.CapacityOverflow;
 71         const steady_requested_bytes = std.math.add(
 72             usize,
 73             retained_and_request_bytes,
 74             limits.reply_byte_count,
 75         ) catch return error.CapacityOverflow;
 76         const initialization_high_water_bytes = std.math.add(
 77             usize,
 78             steady_requested_bytes,
 79             initialization_scratch_byte_count,
 80         ) catch return error.CapacityOverflow;
 81         std.debug.assert(
 82             retained_message_bytes / message_bytes == limits.retained_message_count,
 83         );
 84         return .{
 85             .retained_message_count = limits.retained_message_count,
 86             .retained_message_bytes = retained_message_bytes,
 87             .request_byte_count = limits.request_byte_count,
 88             .reply_byte_count = limits.reply_byte_count,
 89             .initialization_scratch_byte_count = initialization_scratch_byte_count,
 90             .steady_requested_bytes = steady_requested_bytes,
 91             .initialization_high_water_bytes = initialization_high_water_bytes,
 92         };
 93     }
 94 };
 95 
 96 pub const Status = struct {
 97     retained_message_capacity_rejection_count: u64 = 0,
 98     request_capacity_rejection_count: u64 = 0,
 99     reply_capacity_rejection_count: u64 = 0,
100 };
101 
102 pub const RetainError = error{
103     RetainedMessageCapacityExceeded,
104 };
105 
106 const RetainedMessages = struct {
107     allocator: std.mem.Allocator,
108     items: []Message,
109     head: usize = 0,
110     count: usize = 0,
111     rejection_count: u64 = 0,
112 
113     fn init(
114         allocator: std.mem.Allocator,
115         capacity: Capacity,
116     ) std.mem.Allocator.Error!RetainedMessages {
117         std.debug.assert(capacity.retained_message_count > 0);
118         std.debug.assert(capacity.retained_message_bytes % message_bytes == 0);
119         std.debug.assert(
120             capacity.retained_message_bytes / message_bytes ==
121                 capacity.retained_message_count,
122         );
123         return .{
124             .allocator = allocator,
125             .items = try allocator.alloc(Message, capacity.retained_message_count),
126         };
127     }
128 
129     fn deinit(self: *RetainedMessages) void {
130         std.debug.assert(self.count <= self.items.len);
131         self.allocator.free(self.items);
132         self.* = undefined;
133     }
134 
135     fn append(self: *RetainedMessages, message: Message) RetainError!void {
136         std.debug.assert(self.count <= self.items.len);
137         if (self.count == self.items.len) {
138             self.rejection_count +|= 1;
139             return error.RetainedMessageCapacityExceeded;
140         }
141         const index = (self.head + self.count) % self.items.len;
142         self.items[index] = message;
143         self.count += 1;
144     }
145 
146     fn pop(self: *RetainedMessages) ?Message {
147         std.debug.assert(self.count <= self.items.len);
148         if (self.count == 0) return null;
149         const message = self.items[self.head];
150         self.head = (self.head + 1) % self.items.len;
151         self.count -= 1;
152         return message;
153     }
154 
155     fn status(self: *const RetainedMessages) Status {
156         std.debug.assert(self.count <= self.items.len);
157         return .{
158             .retained_message_capacity_rejection_count = self.rejection_count,
159         };
160     }
161 };
162 
163 const Storage = struct {
164     allocator: std.mem.Allocator,
165     retained_messages: RetainedMessages,
166     request: []u8,
167     reply: []u8,
168     initialization_scratch: []u8,
169 
170     fn init(
171         allocator: std.mem.Allocator,
172         capacity: Capacity,
173     ) std.mem.Allocator.Error!Storage {
174         var retained_messages = try RetainedMessages.init(allocator, capacity);
175         errdefer retained_messages.deinit();
176         const request = try allocator.alloc(u8, capacity.request_byte_count);
177         errdefer allocator.free(request);
178         const reply = try allocator.alloc(u8, capacity.reply_byte_count);
179         errdefer allocator.free(reply);
180         return .{
181             .allocator = allocator,
182             .retained_messages = retained_messages,
183             .request = request,
184             .reply = reply,
185             .initialization_scratch = try allocator.alloc(
186                 u8,
187                 capacity.initialization_scratch_byte_count,
188             ),
189         };
190     }
191 
192     fn deinit(self: *Storage) void {
193         self.retained_messages.deinit();
194         self.allocator.free(self.request);
195         self.allocator.free(self.reply);
196         if (self.initialization_scratch.len != 0) {
197             self.allocator.free(self.initialization_scratch);
198         }
199         self.* = undefined;
200     }
201 
202     fn releaseInitializationScratch(self: *Storage) void {
203         std.debug.assert(self.initialization_scratch.len > 0);
204         self.allocator.free(self.initialization_scratch);
205         self.initialization_scratch = &.{};
206     }
207 };
208 
209 pub const ConnectError = error{
210     MissingDisplay,
211     RemoteDisplayUnsupported,
212     InvalidDisplay,
213     ConnectionFailed,
214     AuthenticationFailed,
215     SetupFailed,
216     SetupTruncated,
217     NoUsableScreen,
218     NoUsableVisual,
219     OutOfMemory,
220 };
221 
222 pub const IoError = error{
223     ConnectionLost,
224 };
225 
226 pub const ReplyError = error{
227     ConnectionLost,
228     RequestFailed,
229     ReplyCapacityExceeded,
230     RetainedMessageCapacityExceeded,
231 } || protocol.RequestError;
232 
233 pub const ReplyView = struct {
234     head: [protocol.message_length]u8,
235     extra: []const u8,
236 
237     pub fn dataByte(self: *const ReplyView) u8 {
238         return self.head[1];
239     }
240 
241     pub fn u32At(self: *const ReplyView, offset: usize) u32 {
242         return std.mem.readInt(u32, self.head[offset..][0..4], .little);
243     }
244 
245     pub fn u16At(self: *const ReplyView, offset: usize) u16 {
246         return std.mem.readInt(u16, self.head[offset..][0..2], .little);
247     }
248 };
249 
250 fn connectSocket(path: []const u8) !net.Stream {
251     if (net.Address.initUnixAbstract(path)) |abstract| {
252         if (net.connectStream(abstract)) |stream| return stream else |_| {}
253     } else |_| {}
254     const filesystem = try net.Address.initUnix(path);
255     return net.connectStream(filesystem);
256 }
257 
258 pub const Connection = struct {
259     allocator: std.mem.Allocator,
260     target: display.Target,
261     stream: net.Stream,
262     setup: protocol.Setup,
263     screen: u32,
264     next_resource: u32 = 0,
265     retained_messages: RetainedMessages,
266     request_storage: []u8,
267     request_scratch: protocol.Request,
268     reply_storage: []u8,
269     receive_head: Message = undefined,
270     receive_head_count: usize = 0,
271     reply_capacity_rejection_count: u64 = 0,
272 
273     pub fn connect(
274         allocator: std.mem.Allocator,
275         capacity: Capacity,
276     ) ConnectError!Connection {
277         const target = display.parse(env.get("DISPLAY")) catch |err| switch (err) {
278             error.MissingDisplay => return error.MissingDisplay,
279             error.RemoteDisplayUnsupported => return error.RemoteDisplayUnsupported,
280             error.InvalidDisplay => return error.InvalidDisplay,
281         };
282         return connectTo(allocator, target, capacity);
283     }
284 
285     pub fn connectTo(
286         allocator: std.mem.Allocator,
287         target: display.Target,
288         capacity: Capacity,
289     ) ConnectError!Connection {
290         var storage = Storage.init(allocator, capacity) catch return error.OutOfMemory;
291         errdefer storage.deinit();
292 
293         var path_buffer: [64]u8 = undefined;
294         const path = target.socketPath(&path_buffer) catch return error.InvalidDisplay;
295 
296         var stream = connectSocket(path) catch return error.ConnectionFailed;
297         errdefer stream.close();
298 
299         var setup_request_storage: [protocol.setup_request_byte_count]u8 = undefined;
300         var setup_request = protocol.Request.init(
301             &setup_request_storage,
302             setup_request_storage.len,
303         );
304         const cookie = auth.loadCookie(storage.initialization_scratch, target.display);
305         if (cookie) |found| {
306             protocol.setupRequest(&setup_request, auth.cookie_name, &found.data) catch unreachable;
307         } else {
308             protocol.setupRequest(&setup_request, "", "") catch unreachable;
309         }
310         stream.writeAll(setup_request.bytes()) catch return error.ConnectionFailed;
311 
312         var prefix: [8]u8 = undefined;
313         readExact(stream, &prefix) catch return error.ConnectionFailed;
314         const status = prefix[0];
315         const body_units = std.mem.readInt(u16, prefix[6..8], .little);
316 
317         const body_byte_count = @as(usize, body_units) * 4;
318         std.debug.assert(body_byte_count <= storage.initialization_scratch.len);
319         const body = storage.initialization_scratch[0..body_byte_count];
320         readExact(stream, body) catch return error.ConnectionFailed;
321 
322         if (status == 0) return error.AuthenticationFailed;
323         if (status != 1) return error.SetupFailed;
324 
325         const setup = protocol.parseSetup(body, target.screen) catch |err| switch (err) {
326             error.SetupFailed => return error.SetupFailed,
327             error.SetupTruncated => return error.SetupTruncated,
328             error.NoUsableScreen => return error.NoUsableScreen,
329             error.NoUsableVisual => return error.NoUsableVisual,
330         };
331         const server_request_bytes = @as(usize, setup.max_request_units) * 4;
332         if (server_request_bytes < protocol.minimum_request_byte_count) {
333             return error.SetupFailed;
334         }
335         const request_byte_count = @min(storage.request.len, server_request_bytes);
336         storage.releaseInitializationScratch();
337 
338         return .{
339             .allocator = allocator,
340             .target = target,
341             .stream = stream,
342             .setup = setup,
343             .screen = target.screen,
344             .retained_messages = storage.retained_messages,
345             .request_storage = storage.request,
346             .request_scratch = protocol.Request.init(storage.request, request_byte_count),
347             .reply_storage = storage.reply,
348         };
349     }
350 
351     pub fn close(self: *Connection) void {
352         self.retained_messages.deinit();
353         self.allocator.free(self.request_storage);
354         self.allocator.free(self.reply_storage);
355         self.stream.close();
356     }
357 
358     pub fn generateId(self: *Connection) u32 {
359         const id = self.setup.resource_id_base | (self.next_resource & self.setup.resource_id_mask);
360         self.next_resource += 1;
361         return id;
362     }
363 
364     pub fn maxRequestBytes(self: *const Connection) usize {
365         return self.request_scratch.byteCapacity();
366     }
367 
368     pub fn beginRequest(self: *Connection) *protocol.Request {
369         self.request_scratch.reset();
370         return &self.request_scratch;
371     }
372 
373     pub fn sendRequest(self: *Connection) IoError!void {
374         const bytes = self.request_scratch.bytes();
375         std.debug.assert(bytes.len >= 4);
376         std.debug.assert(bytes.len % 4 == 0);
377         self.stream.writeAll(bytes) catch return error.ConnectionLost;
378     }
379 
380     pub fn nextMessage(self: *Connection) IoError!?Message {
381         if (self.retained_messages.pop()) |message| return message;
382         try self.fillHead(false);
383         if (self.receive_head_count < protocol.message_length) return null;
384         const head = self.receive_head;
385         self.receive_head_count = 0;
386         return head;
387     }
388 
389     pub fn retainMessage(self: *Connection, message: Message) RetainError!void {
390         try self.retained_messages.append(message);
391     }
392 
393     pub fn storageStatus(self: *const Connection) Status {
394         var status = self.retained_messages.status();
395         status.request_capacity_rejection_count =
396             self.request_scratch.capacityRejectionCount();
397         status.reply_capacity_rejection_count = self.reply_capacity_rejection_count;
398         return status;
399     }
400 
401     pub fn waitReadable(self: *Connection, timeout_ms: i32) IoError!bool {
402         return net.pollReadable(self.stream.handle, timeout_ms) catch error.ConnectionLost;
403     }
404 
405     pub fn waitReply(self: *Connection) ReplyError!ReplyView {
406         while (true) {
407             try self.fillHead(true);
408             const head = self.receive_head;
409             const code = head[0] & 0x7F;
410             if (code == 1) {
411                 const extra_units = std.mem.readInt(u32, head[4..8], .little);
412                 const extra_byte_count = @as(u64, extra_units) * 4;
413                 self.receive_head_count = 0;
414                 if (extra_byte_count > self.reply_storage.len) {
415                     self.reply_capacity_rejection_count +|= 1;
416                     discardExact(self.stream, extra_byte_count) catch
417                         return error.ConnectionLost;
418                     return error.ReplyCapacityExceeded;
419                 }
420                 const extra_len: usize = @intCast(extra_byte_count);
421                 const extra = self.reply_storage[0..extra_len];
422                 readExact(self.stream, extra) catch return error.ConnectionLost;
423                 return .{ .head = head, .extra = extra };
424             }
425             if (code == 0) {
426                 self.receive_head_count = 0;
427                 return error.RequestFailed;
428             }
429             try self.retained_messages.append(head);
430             self.receive_head_count = 0;
431         }
432     }
433 
434     fn fillHead(self: *Connection, blocking: bool) IoError!void {
435         std.debug.assert(self.receive_head_count <= self.receive_head.len);
436         while (self.receive_head_count < self.receive_head.len) {
437             if (!blocking) {
438                 const readable = net.pollReadable(self.stream.handle, 0) catch
439                     return error.ConnectionLost;
440                 if (!readable) return;
441             }
442             const count = self.stream.read(self.receive_head[self.receive_head_count..]) catch
443                 return error.ConnectionLost;
444             if (count == 0) return error.ConnectionLost;
445             self.receive_head_count += count;
446         }
447     }
448 
449     pub fn internAtom(self: *Connection, name: []const u8) ReplyError!u32 {
450         const request = self.beginRequest();
451         try protocol.internAtom(request, name);
452         try self.sendRequest();
453         const reply = try self.waitReply();
454         return reply.u32At(8);
455     }
456 };
457 
458 fn readExact(stream: net.Stream, buffer: []u8) !void {
459     var filled: usize = 0;
460     while (filled < buffer.len) {
461         const count = try stream.read(buffer[filled..]);
462         if (count == 0) return error.ConnectionLost;
463         filled += count;
464     }
465 }
466 
467 fn discardExact(stream: net.Stream, byte_count: u64) !void {
468     std.debug.assert(byte_count <= protocol.maximum_reply_extra_byte_count);
469     var scratch: [protocol.reply_discard_scratch_byte_count]u8 = undefined;
470     var remaining = byte_count;
471     while (remaining > 0) {
472         const count: usize = @intCast(@min(remaining, scratch.len));
473         try readExact(stream, scratch[0..count]);
474         remaining -= count;
475     }
476 }
477 
478 test "connection capacity derives exact steady and initialization storage" {
479     const capacity = try Capacity.derive(.{});
480     try std.testing.expectEqual(@as(usize, 256), capacity.retained_message_count);
481     try std.testing.expectEqual(256 * message_bytes, capacity.retained_message_bytes);
482     try std.testing.expectEqual(
483         protocol.maximum_request_byte_count,
484         capacity.request_byte_count,
485     );
486     try std.testing.expectEqual(default_reply_byte_count, capacity.reply_byte_count);
487     try std.testing.expectEqual(
488         capacity.retained_message_bytes + capacity.request_byte_count + capacity.reply_byte_count,
489         capacity.steady_requested_bytes,
490     );
491     try std.testing.expectEqual(
492         @max(
493             auth.maximum_authority_file_byte_count,
494             protocol.maximum_setup_response_byte_count,
495         ),
496         capacity.initialization_scratch_byte_count,
497     );
498     try std.testing.expectEqual(
499         capacity.steady_requested_bytes + capacity.initialization_scratch_byte_count,
500         capacity.initialization_high_water_bytes,
501     );
502     try std.testing.expectError(error.RetainedMessagesEmpty, Capacity.derive(.{
503         .retained_message_count = 0,
504     }));
505     try std.testing.expectError(error.RequestStorageTooSmall, Capacity.derive(.{
506         .request_byte_count = protocol.minimum_request_byte_count - 4,
507     }));
508     try std.testing.expectError(error.RequestStorageTooLarge, Capacity.derive(.{
509         .request_byte_count = protocol.maximum_request_byte_count + 4,
510     }));
511     try std.testing.expectError(error.RequestStorageUnaligned, Capacity.derive(.{
512         .request_byte_count = protocol.minimum_request_byte_count + 1,
513     }));
514     try std.testing.expectEqual(
515         minimum_reply_byte_count,
516         (try Capacity.derive(.{ .reply_byte_count = minimum_reply_byte_count })).reply_byte_count,
517     );
518     try std.testing.expectError(error.ReplyStorageUnaligned, Capacity.derive(.{
519         .reply_byte_count = 5,
520     }));
521     if (@sizeOf(usize) > @sizeOf(u32)) {
522         try std.testing.expectError(error.ReplyStorageTooLarge, Capacity.derive(.{
523             .reply_byte_count = @as(usize, @intCast(protocol.maximum_reply_extra_byte_count)) + 4,
524         }));
525     }
526     try std.testing.expectError(error.CapacityOverflow, Capacity.derive(.{
527         .retained_message_count = std.math.maxInt(usize) / message_bytes + 1,
528     }));
529 }
530 
531 test "connection storage acquires all regions before readiness" {
532     const capacity = try Capacity.derive(.{
533         .retained_message_count = 2,
534         .request_byte_count = protocol.minimum_request_byte_count,
535         .reply_byte_count = 4,
536     });
537     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
538         .fail_index = 0,
539     });
540     try std.testing.expectError(error.OutOfMemory, Storage.init(failing.allocator(), capacity));
541 
542     failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
543         .fail_index = 1,
544     });
545     try std.testing.expectError(error.OutOfMemory, Storage.init(failing.allocator(), capacity));
546 
547     failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
548         .fail_index = 2,
549     });
550     try std.testing.expectError(error.OutOfMemory, Storage.init(failing.allocator(), capacity));
551 
552     failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
553         .fail_index = 3,
554     });
555     try std.testing.expectError(error.OutOfMemory, Storage.init(failing.allocator(), capacity));
556 
557     failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
558         .fail_index = 4,
559     });
560     var storage = try Storage.init(failing.allocator(), capacity);
561     defer storage.deinit();
562     try std.testing.expectEqual(@as(usize, 2), storage.retained_messages.items.len);
563     try std.testing.expectEqual(
564         protocol.minimum_request_byte_count,
565         storage.request.len,
566     );
567     try std.testing.expectEqual(@as(usize, 4), storage.reply.len);
568     try std.testing.expectEqual(
569         capacity.initialization_scratch_byte_count,
570         storage.initialization_scratch.len,
571     );
572     try std.testing.expectEqual(@as(usize, 4), failing.allocations);
573     storage.releaseInitializationScratch();
574     try std.testing.expectEqual(@as(usize, 0), storage.initialization_scratch.len);
575     try std.testing.expectEqual(@as(usize, 1), failing.deallocations);
576 }
577 
578 test "retained messages preserve FIFO order without steady allocation" {
579     const capacity = try Capacity.derive(.{ .retained_message_count = 2 });
580     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
581         .fail_index = 0,
582     });
583     try std.testing.expectError(
584         error.OutOfMemory,
585         RetainedMessages.init(failing.allocator(), capacity),
586     );
587 
588     failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
589         .fail_index = 1,
590     });
591     var retained = try RetainedMessages.init(failing.allocator(), capacity);
592     defer retained.deinit();
593 
594     const first: Message = @splat(1);
595     const second: Message = @splat(2);
596     const third: Message = @splat(3);
597     try retained.append(first);
598     try retained.append(second);
599     try std.testing.expectError(
600         error.RetainedMessageCapacityExceeded,
601         retained.append(third),
602     );
603     try std.testing.expectEqual(first, retained.pop().?);
604     try retained.append(third);
605     try std.testing.expectEqual(second, retained.pop().?);
606     try std.testing.expectEqual(third, retained.pop().?);
607     try std.testing.expect(retained.pop() == null);
608     try std.testing.expectEqual(Status{
609         .retained_message_capacity_rejection_count = 1,
610     }, retained.status());
611 
612     retained.rejection_count = std.math.maxInt(u64);
613     try retained.append(first);
614     try retained.append(second);
615     try std.testing.expectError(
616         error.RetainedMessageCapacityExceeded,
617         retained.append(third),
618     );
619     try std.testing.expectEqual(Status{
620         .retained_message_capacity_rejection_count = std.math.maxInt(u64),
621     }, retained.status());
622     try std.testing.expectEqual(@as(usize, 1), failing.allocations);
623 }
624 
625 test "reply wait rejects before consuming a message that cannot be retained" {
626     const capacity = try Capacity.derive(.{
627         .retained_message_count = 1,
628         .reply_byte_count = 4,
629     });
630     var request_storage: [protocol.minimum_request_byte_count]u8 = undefined;
631     var reply_storage: [4]u8 = undefined;
632     var connection: Connection = .{
633         .allocator = std.testing.allocator,
634         .target = undefined,
635         .stream = undefined,
636         .setup = undefined,
637         .screen = 0,
638         .retained_messages = try RetainedMessages.init(std.testing.allocator, capacity),
639         .request_storage = &request_storage,
640         .request_scratch = protocol.Request.init(&request_storage, request_storage.len),
641         .reply_storage = &reply_storage,
642     };
643     defer connection.retained_messages.deinit();
644 
645     const retained: Message = @splat(7);
646     try connection.retainMessage(retained);
647 
648     var blocked: Message = @splat(0);
649     blocked[0] = @backingInt(protocol.EventCode.key_press);
650     var reply: Message = @splat(0);
651     reply[0] = @backingInt(protocol.EventCode.reply);
652     connection.receive_head = blocked;
653     connection.receive_head_count = protocol.message_length;
654 
655     try std.testing.expectError(
656         error.RetainedMessageCapacityExceeded,
657         connection.waitReply(),
658     );
659     try std.testing.expectEqual(protocol.message_length, connection.receive_head_count);
660     try std.testing.expectEqual(retained, (try connection.nextMessage()).?);
661     try std.testing.expectEqual(blocked, (try connection.nextMessage()).?);
662     connection.receive_head = reply;
663     connection.receive_head_count = protocol.message_length;
664     const received_reply = try connection.waitReply();
665     try std.testing.expectEqual(reply, received_reply.head);
666     const request = connection.beginRequest();
667     try protocol.sendEvent(request, 1, 0, &blocked);
668     try protocol.windowRequest(request, .map_window, 1);
669     try std.testing.expectError(
670         error.RequestCapacityExceeded,
671         protocol.windowRequest(request, .unmap_window, 1),
672     );
673     try std.testing.expectEqual(Status{
674         .retained_message_capacity_rejection_count = 1,
675         .request_capacity_rejection_count = 1,
676     }, connection.storageStatus());
677 }
678 
679 test "reply storage recovers after max plus one without steady allocation" {
680     const sockets = net.socketPairUnixStream() catch |err| switch (err) {
681         error.UnsupportedPlatform => return error.SkipZigTest,
682         else => return err,
683     };
684     defer net.close(sockets[0]);
685     defer net.close(sockets[1]);
686 
687     const capacity = try Capacity.derive(.{
688         .retained_message_count = 1,
689         .request_byte_count = protocol.minimum_request_byte_count,
690         .reply_byte_count = 4,
691     });
692     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{
693         .fail_index = 1,
694     });
695     var request_storage: [protocol.minimum_request_byte_count]u8 = undefined;
696     var reply_storage: [4]u8 = undefined;
697     var connection: Connection = .{
698         .allocator = failing.allocator(),
699         .target = undefined,
700         .stream = net.Stream.initFd(sockets[1]),
701         .setup = undefined,
702         .screen = 0,
703         .retained_messages = try RetainedMessages.init(failing.allocator(), capacity),
704         .request_storage = &request_storage,
705         .request_scratch = protocol.Request.init(&request_storage, request_storage.len),
706         .reply_storage = &reply_storage,
707     };
708     defer connection.retained_messages.deinit();
709 
710     var oversized_head: Message = @splat(0);
711     oversized_head[0] = @backingInt(protocol.EventCode.reply);
712     std.mem.writeInt(u32, oversized_head[4..8], 2, .little);
713     var exact_head: Message = @splat(0);
714     exact_head[0] = @backingInt(protocol.EventCode.reply);
715     std.mem.writeInt(u32, exact_head[4..8], 1, .little);
716 
717     const peer = net.Stream.initFd(sockets[0]);
718     try peer.writeAll(&oversized_head);
719     try peer.writeAll("eight888");
720     try peer.writeAll(&exact_head);
721     try peer.writeAll("four");
722 
723     try std.testing.expectError(error.ReplyCapacityExceeded, connection.waitReply());
724     const exact = try connection.waitReply();
725     try std.testing.expectEqual(exact_head, exact.head);
726     try std.testing.expectEqualStrings("four", exact.extra);
727     try std.testing.expectEqual(@as(usize, 1), failing.allocations);
728     try std.testing.expectEqual(@as(u64, 1), connection.storageStatus().reply_capacity_rejection_count);
729 
730     connection.reply_capacity_rejection_count = std.math.maxInt(u64);
731     try peer.writeAll(&oversized_head);
732     try peer.writeAll("eight888");
733     try std.testing.expectError(error.ReplyCapacityExceeded, connection.waitReply());
734     try std.testing.expectEqual(
735         std.math.maxInt(u64),
736         connection.storageStatus().reply_capacity_rejection_count,
737     );
738     try std.testing.expectEqual(@as(usize, 1), failing.allocations);
739 }
740 
741 test "connection performs the setup handshake against a live display" {
742     if (env.get("DISPLAY") == null) return error.SkipZigTest;
743 
744     const capacity = try Capacity.derive(.{});
745     var connection = Connection.connect(std.testing.allocator, capacity) catch |err| switch (err) {
746         error.ConnectionFailed, error.AuthenticationFailed => return error.SkipZigTest,
747         else => return err,
748     };
749     defer connection.close();
750 
751     try std.testing.expect(connection.setup.root != 0);
752     try std.testing.expect(connection.setup.visual != 0);
753     try std.testing.expect(connection.setup.max_request_units >= 4096);
754     try std.testing.expect(connection.setup.visual_depth == 24 or connection.setup.visual_depth == 32);
755 
756     const first = connection.generateId();
757     const second = connection.generateId();
758     try std.testing.expect(first != second);
759 
760     const protocols = try connection.internAtom("WM_PROTOCOLS");
761     try std.testing.expect(protocols != 0);
762     const again = try connection.internAtom("WM_PROTOCOLS");
763     try std.testing.expectEqual(protocols, again);
764 }