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 }