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 }