lib/wayland/src/runtime/request.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 const wayland = @import("../root.zig");
  4 const catalog_model = @import("catalog.zig");
  5 const object = @import("object.zig");
  6 
  7 pub const NewId = union(enum) {
  8     fixed,
  9     dynamic: Dynamic,
 10 
 11     pub const Dynamic = struct {
 12         interface: []const u8,
 13         version: u32,
 14     };
 15 };
 16 
 17 pub const Value = union(enum) {
 18     int: i32,
 19     uint: u32,
 20     fixed: wayland.protocol.value.Fixed,
 21     string: ?[]const u8,
 22     object: ?u32,
 23     new_id: NewId,
 24     array: []const u8,
 25     descriptor_owned: sys.fd.Descriptor,
 26 };
 27 
 28 pub const Prepared = struct {
 29     encoded: wayland.protocol.value.Encoded,
 30     created_ids: []u32,
 31 };
 32 
 33 pub fn prepare(
 34     ids: *wayland.ids.Pool,
 35     objects: *object.Table,
 36     catalog: catalog_model.Catalog,
 37     encoder: *wayland.protocol.value.Encoder,
 38     parent: object.Entry,
 39     metadata: *const wayland.protocol.schema.Message,
 40     values: []const Value,
 41     created_ids: []u32,
 42 ) !Prepared {
 43     if (values.len != metadata.arguments.len) return error.ArgumentCountMismatch;
 44     if (created_ids.len != metadata.newIdCount()) return error.CreatedIdCountMismatch;
 45     encoder.reset();
 46     errdefer encoder.reset();
 47     var created_count: usize = 0;
 48     errdefer rollback(ids, objects, created_ids[0..created_count]);
 49 
 50     for (metadata.arguments, values) |argument, value| switch (argument.kind) {
 51         .int => try encoder.signed(switch (value) {
 52             .int => |item| item,
 53             else => return error.ArgumentTypeMismatch,
 54         }),
 55         .uint => try encoder.unsigned(switch (value) {
 56             .uint => |item| item,
 57             else => return error.ArgumentTypeMismatch,
 58         }),
 59         .fixed => try encoder.fixed(switch (value) {
 60             .fixed => |item| item,
 61             else => return error.ArgumentTypeMismatch,
 62         }),
 63         .string => {
 64             const item = switch (value) {
 65                 .string => |item| item,
 66                 else => return error.ArgumentTypeMismatch,
 67             };
 68             if (argument.nullable) {
 69                 try encoder.optionalString(item);
 70             } else {
 71                 try encoder.string(item orelse return error.NullArgument);
 72             }
 73         },
 74         .object => {
 75             const id = switch (value) {
 76                 .object => |item| item,
 77                 else => return error.ArgumentTypeMismatch,
 78             };
 79             if (id) |present| {
 80                 const referenced = try objects.requireLive(present);
 81                 if (argument.interface) |name| {
 82                     if (referenced.interface != try catalog.require(name)) {
 83                         return error.ObjectInterfaceMismatch;
 84                     }
 85                 }
 86                 try encoder.object(try .init(present));
 87             } else {
 88                 if (!argument.nullable) return error.NullArgument;
 89                 try encoder.optionalObject(null);
 90             }
 91         },
 92         .new_id => {
 93             const specification = switch (value) {
 94                 .new_id => |item| item,
 95                 else => return error.ArgumentTypeMismatch,
 96             };
 97             const interface, const version = if (argument.interface) |name| fixed: {
 98                 switch (specification) {
 99                     .fixed => {},
100                     else => return error.ArgumentTypeMismatch,
101                 }
102                 const interface = try catalog.require(name);
103                 break :fixed .{ interface, @min(parent.version, interface.version) };
104             } else dynamic: {
105                 const dynamic = switch (specification) {
106                     .dynamic => |item| item,
107                     else => return error.ArgumentTypeMismatch,
108                 };
109                 const interface = try catalog.require(dynamic.interface);
110                 if (dynamic.version == 0 or dynamic.version > interface.version) {
111                     return error.InvalidVersion;
112                 }
113                 break :dynamic .{ interface, dynamic.version };
114             };
115             const id = try ids.allocate();
116             var registered = false;
117             errdefer if (!registered) ids.abandon(id) catch unreachable;
118             try objects.addClient(id, interface, version);
119             created_ids[created_count] = id;
120             created_count += 1;
121             registered = true;
122             if (argument.interface != null) {
123                 try encoder.newId(try .init(id));
124             } else {
125                 try encoder.dynamicNewId(interface.name, version, try .init(id));
126             }
127         },
128         .array => try encoder.array(switch (value) {
129             .array => |item| item,
130             else => return error.ArgumentTypeMismatch,
131         }),
132         .fd => try encoder.descriptorBorrowed(switch (value) {
133             .descriptor_owned => |item| item,
134             else => return error.ArgumentTypeMismatch,
135         }),
136     };
137 
138     std.debug.assert(created_count == created_ids.len);
139     return .{
140         .encoded = try encoder.finish(metadata),
141         .created_ids = created_ids,
142     };
143 }
144 
145 pub fn rollback(
146     ids: *wayland.ids.Pool,
147     objects: *object.Table,
148     created_ids: []const u32,
149 ) void {
150     var index = created_ids.len;
151     while (index != 0) {
152         index -= 1;
153         _ = objects.removeLive(created_ids[index]) catch unreachable;
154         ids.abandon(created_ids[index]) catch unreachable;
155     }
156 }
157 
158 test "fixed constructors inherit negotiated parent versions transactionally" {
159     const catalog = catalog_model.Catalog.standard();
160     var ids = try wayland.ids.Pool.init(std.testing.allocator, wayland.ids.default_capacity);
161     defer ids.deinit();
162     var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
163     defer objects.deinit();
164     try objects.addDisplay(try catalog.require("wl_display"));
165     const compositor_id = try ids.allocate();
166     try objects.addClient(compositor_id, try catalog.require("wl_compositor"), 4);
167     var encoder = try wayland.protocol.value.Encoder.init(
168         std.testing.allocator,
169         wayland.protocol.value.default_encoder_capacity,
170     );
171     defer encoder.deinit();
172 
173     var created: [1]u32 = undefined;
174     const compositor = try objects.requireLive(compositor_id);
175     const prepared = try prepare(
176         &ids,
177         &objects,
178         catalog,
179         &encoder,
180         compositor,
181         compositor.interface.request(0).?,
182         &.{.{ .new_id = .fixed }},
183         &created,
184     );
185     try std.testing.expectEqualStrings(
186         "wl_surface",
187         (try objects.requireLive(created[0])).interface.name,
188     );
189     try std.testing.expectEqual(@as(u32, 4), (try objects.requireLive(created[0])).version);
190     try wayland.protocol.value.validate.message(
191         prepared.encoded.metadata,
192         prepared.encoded.payload,
193         prepared.encoded.descriptors,
194     );
195     rollback(&ids, &objects, &created);
196     try std.testing.expectEqual(created[0], try ids.allocate());
197 }
198 
199 test "object arguments require live objects of the declared interface" {
200     const catalog = catalog_model.Catalog.standard();
201     var ids = try wayland.ids.Pool.init(std.testing.allocator, wayland.ids.default_capacity);
202     defer ids.deinit();
203     var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
204     defer objects.deinit();
205     try objects.addDisplay(try catalog.require("wl_display"));
206     const device_id = try ids.allocate();
207     try objects.addClient(device_id, try catalog.require("wl_data_device"), 3);
208     const wrong_id = try ids.allocate();
209     try objects.addClient(wrong_id, try catalog.require("wl_output"), 3);
210     var encoder = try wayland.protocol.value.Encoder.init(
211         std.testing.allocator,
212         wayland.protocol.value.default_encoder_capacity,
213     );
214     defer encoder.deinit();
215     var created: [0]u32 = .{};
216     const start_drag = (try objects.requireLive(device_id)).interface.request(0).?;
217     try std.testing.expectError(
218         error.ObjectInterfaceMismatch,
219         prepare(
220             &ids,
221             &objects,
222             catalog,
223             &encoder,
224             try objects.requireLive(device_id),
225             start_drag,
226             &.{
227                 .{ .object = null },
228                 .{ .object = wrong_id },
229                 .{ .object = null },
230                 .{ .uint = 1 },
231             },
232             &created,
233         ),
234     );
235 }
236 
237 test "constructor capacity failure rolls back every staged client object" {
238     const catalog = catalog_model.Catalog.standard();
239     const id_capacity = try wayland.ids.Capacity.derive(.{ .client_id_count = 1 });
240     var ids = try wayland.ids.Pool.init(std.testing.allocator, id_capacity);
241     defer ids.deinit();
242     const object_capacity = try object.Capacity.derive(.{
243         .client_object_count = 1,
244         .server_object_count = 0,
245     });
246     var objects = try object.Table.init(std.testing.allocator, object_capacity);
247     defer objects.deinit();
248     try objects.addDisplay(try catalog.require("wl_display"));
249     var encoder = try wayland.protocol.value.Encoder.init(
250         std.testing.allocator,
251         wayland.protocol.value.default_encoder_capacity,
252     );
253     defer encoder.deinit();
254     const arguments = [_]wayland.protocol.schema.Argument{
255         .{
256             .name = "first",
257             .kind = .new_id,
258             .interface = "wl_callback",
259             .enumeration = null,
260             .nullable = false,
261         },
262         .{
263             .name = "second",
264             .kind = .new_id,
265             .interface = "wl_callback",
266             .enumeration = null,
267             .nullable = false,
268         },
269     };
270     const metadata: wayland.protocol.schema.Message = .{
271         .name = "two_constructors",
272         .opcode = 0,
273         .since = 1,
274         .deprecated_since = null,
275         .destructor = false,
276         .signature = "nn",
277         .descriptor_count = 0,
278         .minimum_payload_size = 8,
279         .arguments = &arguments,
280     };
281     var created: [2]u32 = undefined;
282 
283     try std.testing.expectError(
284         error.ClientIdCapacityExceeded,
285         prepare(
286             &ids,
287             &objects,
288             catalog,
289             &encoder,
290             try objects.requireLive(wayland.ids.display_id),
291             &metadata,
292             &.{ .{ .new_id = .fixed }, .{ .new_id = .fixed } },
293             &created,
294         ),
295     );
296     try std.testing.expect(objects.get(wayland.ids.first_dynamic_id) == null);
297     try std.testing.expectEqual(
298         @as(u64, 1),
299         ids.status().client_id_capacity_rejection_count,
300     );
301     try std.testing.expectEqual(
302         @as(u64, 0),
303         objects.status().client_object_capacity_rejection_count,
304     );
305     try std.testing.expectEqual(wayland.ids.first_dynamic_id, try ids.allocate());
306 }