lib/wayland/src/runtime/semantic.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 creation = @import("creation.zig");
  6 const object = @import("object.zig");
  7 
  8 pub fn validateEvent(
  9     objects: *object.Table,
 10     catalog: catalog_model.Catalog,
 11     sender: object.Entry,
 12     metadata: *const wayland.protocol.schema.Message,
 13     payload: []const u8,
 14     descriptors: []const sys.fd.Descriptor,
 15     creations: *creation.Storage,
 16 ) !void {
 17     try creations.begin(metadata.newIdCount());
 18     errdefer creations.reset();
 19     var decoder = try wayland.protocol.value.Decoder.init(metadata, payload, descriptors);
 20     for (metadata.arguments) |argument| switch (argument.kind) {
 21         .int => _ = try decoder.signed(),
 22         .uint => _ = try decoder.unsigned(),
 23         .fixed => _ = try decoder.fixed(),
 24         .string => {
 25             if (argument.nullable) {
 26                 _ = try decoder.optionalString();
 27             } else {
 28                 _ = try decoder.string();
 29             }
 30         },
 31         .object => {
 32             const id = if (argument.nullable)
 33                 if (try decoder.optionalObject()) |item| item.raw else continue
 34             else
 35                 (try decoder.object()).raw;
 36             const referenced = referencedObject(objects, creations.items(), id) orelse {
 37                 return error.UnknownObject;
 38             };
 39             if (argument.interface) |name| {
 40                 if (referenced.interface != try catalog.require(name)) {
 41                     return error.ObjectInterfaceMismatch;
 42                 }
 43             }
 44         },
 45         .new_id => {
 46             const id, const interface, const version = if (argument.interface) |name| fixed: {
 47                 const interface = try catalog.require(name);
 48                 break :fixed .{
 49                     (try decoder.newId()).raw,
 50                     interface,
 51                     @min(sender.version, interface.version),
 52                 };
 53             } else dynamic: {
 54                 const item = try decoder.dynamicNewId();
 55                 const interface = try catalog.require(item.interface);
 56                 if (item.version > interface.version) return error.InvalidVersion;
 57                 break :dynamic .{ item.id.raw, interface, item.version };
 58             };
 59             try objects.validateServerInsertion(id);
 60             for (creations.items()) |item| {
 61                 if (item.id == id) return error.DuplicateObject;
 62             }
 63             creations.appendAssumeCapacity(.{
 64                 .id = id,
 65                 .interface = interface,
 66                 .version = version,
 67             });
 68         },
 69         .array => _ = try decoder.array(),
 70         .fd => _ = try decoder.descriptor(),
 71     };
 72     try decoder.finish();
 73     std.debug.assert(creations.items().len == metadata.newIdCount());
 74     try objects.ensureServerCapacity(creations.items().len);
 75 }
 76 
 77 pub fn commit(
 78     objects: *object.Table,
 79     creations: *creation.Storage,
 80 ) void {
 81     const items = creations.items();
 82     for (items) |item| {
 83         objects.addServerAssumeCapacity(
 84             item.id,
 85             item.interface,
 86             item.version,
 87         );
 88     }
 89     creations.reset();
 90 }
 91 
 92 fn referencedObject(
 93     objects: *const object.Table,
 94     creations: []const creation.Creation,
 95     id: u32,
 96 ) ?object.Entry {
 97     if (objects.getLive(id)) |entry| return entry;
 98     for (creations) |item| {
 99         if (item.id == id) return .{
100             .interface = item.interface,
101             .version = item.version,
102             .origin = .server,
103         };
104     }
105     return null;
106 }
107 
108 test "event creation demand rejects before decoding or object mutation" {
109     const catalog = catalog_model.Catalog.standard();
110     var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
111     defer objects.deinit();
112     try objects.addDisplay(try catalog.require("wl_display"));
113     const data_offer = try catalog.require("wl_data_offer");
114     const arguments = [_]wayland.protocol.schema.Argument{
115         .{
116             .name = "first",
117             .kind = .new_id,
118             .interface = "wl_data_offer",
119             .enumeration = null,
120             .nullable = false,
121         },
122         .{
123             .name = "second",
124             .kind = .new_id,
125             .interface = "wl_data_offer",
126             .enumeration = null,
127             .nullable = false,
128         },
129     };
130     const metadata: wayland.protocol.schema.Message = .{
131         .name = "two_creations",
132         .opcode = 0,
133         .since = 1,
134         .deprecated_since = null,
135         .destructor = false,
136         .signature = "nn",
137         .descriptor_count = 0,
138         .minimum_payload_size = 8,
139         .arguments = &arguments,
140     };
141     var creations = try creation.Storage.init(
142         std.testing.allocator,
143         creation.default_capacity,
144     );
145     defer creations.deinit();
146     try creations.begin(1);
147     creations.appendAssumeCapacity(.{
148         .id = wayland.ids.first_server_id,
149         .interface = data_offer,
150         .version = 1,
151     });
152 
153     try std.testing.expectError(
154         error.EventCreationCapacityExceeded,
155         validateEvent(
156             &objects,
157             catalog,
158             try objects.requireLive(wayland.ids.display_id),
159             &metadata,
160             &.{},
161             &.{},
162             &creations,
163         ),
164     );
165     try std.testing.expectEqual(@as(usize, 0), creations.items().len);
166     try std.testing.expect(objects.get(wayland.ids.first_server_id) == null);
167     try std.testing.expectEqual(
168         @as(u64, 1),
169         creations.status().event_creation_capacity_rejection_count,
170     );
171 }
172 
173 test "server object capacity rejects before commit or object mutation" {
174     const catalog = catalog_model.Catalog.standard();
175     const object_capacity = try object.Capacity.derive(.{
176         .client_object_count = 1,
177         .server_object_count = 0,
178     });
179     var objects = try object.Table.init(std.testing.allocator, object_capacity);
180     defer objects.deinit();
181     try objects.addDisplay(try catalog.require("wl_display"));
182     const device = try catalog.require("wl_data_device");
183     try objects.addClient(wayland.ids.first_dynamic_id, device, 3);
184     var encoder = try wayland.protocol.value.Encoder.init(
185         std.testing.allocator,
186         wayland.protocol.value.default_encoder_capacity,
187     );
188     defer encoder.deinit();
189     try encoder.newId(try .init(wayland.ids.first_server_id));
190     const encoded = try encoder.finish(device.event(0).?);
191     var creations = try creation.Storage.init(
192         std.testing.allocator,
193         creation.default_capacity,
194     );
195     defer creations.deinit();
196 
197     try std.testing.expectError(
198         error.ServerObjectCapacityExceeded,
199         validateEvent(
200             &objects,
201             catalog,
202             try objects.requireLive(wayland.ids.first_dynamic_id),
203             encoded.metadata,
204             encoded.payload,
205             encoded.descriptors,
206             &creations,
207         ),
208     );
209     try std.testing.expectEqual(@as(usize, 0), creations.items().len);
210     try std.testing.expect(objects.get(wayland.ids.first_server_id) == null);
211     try std.testing.expectEqual(
212         @as(u64, 1),
213         objects.status().server_object_capacity_rejection_count,
214     );
215 }
216 
217 test "incoming fixed new IDs inherit sender versions and commit atomically" {
218     const catalog = catalog_model.Catalog.standard();
219     var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
220     defer objects.deinit();
221     try objects.addDisplay(try catalog.require("wl_display"));
222     const device = try catalog.require("wl_data_device");
223     try objects.addClient(2, device, 3);
224     var encoder = try wayland.protocol.value.Encoder.init(
225         std.testing.allocator,
226         wayland.protocol.value.default_encoder_capacity,
227     );
228     defer encoder.deinit();
229     try encoder.newId(try .init(wayland.ids.first_server_id));
230     const encoded = try encoder.finish(device.event(0).?);
231     var creations = try creation.Storage.init(
232         std.testing.allocator,
233         creation.default_capacity,
234     );
235     defer creations.deinit();
236 
237     try validateEvent(
238         &objects,
239         catalog,
240         try objects.requireLive(2),
241         encoded.metadata,
242         encoded.payload,
243         encoded.descriptors,
244         &creations,
245     );
246     try std.testing.expect(objects.get(wayland.ids.first_server_id) == null);
247     commit(&objects, &creations);
248     const offer = try objects.requireLive(wayland.ids.first_server_id);
249     try std.testing.expectEqualStrings("wl_data_offer", offer.interface.name);
250     try std.testing.expectEqual(@as(u32, 3), offer.version);
251 
252     try objects.retire(wayland.ids.first_server_id, .retired_request);
253     encoder.reset();
254     try encoder.newId(try .init(wayland.ids.first_server_id));
255     const replacement = try encoder.finish(device.event(0).?);
256     try validateEvent(
257         &objects,
258         catalog,
259         try objects.requireLive(2),
260         replacement.metadata,
261         replacement.payload,
262         replacement.descriptors,
263         &creations,
264     );
265     commit(&objects, &creations);
266     try std.testing.expectEqual(
267         object.State.live,
268         (try objects.require(wayland.ids.first_server_id)).state,
269     );
270 }
271 
272 test "incoming object references require the declared live interface" {
273     const catalog = catalog_model.Catalog.standard();
274     var objects = try object.Table.init(std.testing.allocator, object.default_capacity);
275     defer objects.deinit();
276     try objects.addDisplay(try catalog.require("wl_display"));
277     const pointer = try catalog.require("wl_pointer");
278     try objects.addServer(wayland.ids.first_server_id, pointer, 8);
279     try objects.addClient(2, try catalog.require("wl_output"), 3);
280     var encoder = try wayland.protocol.value.Encoder.init(
281         std.testing.allocator,
282         wayland.protocol.value.default_encoder_capacity,
283     );
284     defer encoder.deinit();
285     try encoder.unsigned(1);
286     try encoder.object(try .init(2));
287     try encoder.fixed(.fromRaw(0));
288     try encoder.fixed(.fromRaw(0));
289     const encoded = try encoder.finish(pointer.event(0).?);
290     var creations = try creation.Storage.init(
291         std.testing.allocator,
292         creation.default_capacity,
293     );
294     defer creations.deinit();
295 
296     try std.testing.expectError(
297         error.ObjectInterfaceMismatch,
298         validateEvent(
299             &objects,
300             catalog,
301             try objects.requireLive(wayland.ids.first_server_id),
302             encoded.metadata,
303             encoded.payload,
304             encoded.descriptors,
305             &creations,
306         ),
307     );
308 }