lib/wayland/src/protocol/value/decode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const builtin = @import("builtin");
3 const sys = @import("sys");
4 const protocol = @import("../root.zig");
5 const value = @import("root.zig");
6
7 pub const Error = error{
8 DescriptorCountMismatch,
9 IncompletePayload,
10 InvalidAlignment,
11 InvalidDescriptor,
12 InvalidInterface,
13 InvalidNewId,
14 InvalidObject,
15 InvalidString,
16 InvalidUtf8,
17 InvalidVersion,
18 MessageTooLarge,
19 PayloadTooSmall,
20 TrailingDescriptors,
21 TrailingPayload,
22 };
23
24 pub const Decoder = struct {
25 metadata: *const protocol.schema.Message,
26 payload: []const u8,
27 descriptors: []const sys.fd.Descriptor,
28 byte_offset: usize = 0,
29 descriptor_offset: usize = 0,
30
31 pub fn init(
32 metadata: *const protocol.schema.Message,
33 payload: []const u8,
34 descriptors: []const sys.fd.Descriptor,
35 ) Error!Decoder {
36 if (payload.len % 4 != 0) return error.InvalidAlignment;
37 if (payload.len > value.maximum_payload_size) return error.MessageTooLarge;
38 if (payload.len < metadata.minimum_payload_size) return error.PayloadTooSmall;
39 if (descriptors.len != metadata.descriptor_count) return error.DescriptorCountMismatch;
40 return .{
41 .metadata = metadata,
42 .payload = payload,
43 .descriptors = descriptors,
44 };
45 }
46
47 pub fn signed(self: *Decoder) Error!i32 {
48 const bytes = try self.word();
49 return std.mem.readInt(i32, bytes, builtin.cpu.arch.endian());
50 }
51
52 pub fn unsigned(self: *Decoder) Error!u32 {
53 const bytes = try self.word();
54 return std.mem.readInt(u32, bytes, builtin.cpu.arch.endian());
55 }
56
57 pub fn fixed(self: *Decoder) Error!value.Fixed {
58 return .fromRaw(try self.signed());
59 }
60
61 pub fn string(self: *Decoder) Error![]const u8 {
62 return (try self.optionalString()) orelse error.InvalidString;
63 }
64
65 pub fn optionalString(self: *Decoder) Error!?[]const u8 {
66 const length = try self.unsigned();
67 if (length == 0) return null;
68 const bytes = try self.delimited(length);
69 if (bytes[bytes.len - 1] != 0) return error.InvalidString;
70 const text = bytes[0 .. bytes.len - 1];
71 if (std.mem.indexOfScalar(u8, text, 0) != null) return error.InvalidString;
72 if (!std.unicode.utf8ValidateSlice(text)) return error.InvalidUtf8;
73 return text;
74 }
75
76 pub fn object(self: *Decoder) Error!value.ObjectId {
77 return value.ObjectId.init(try self.unsigned()) catch return error.InvalidObject;
78 }
79
80 pub fn optionalObject(self: *Decoder) Error!?value.ObjectId {
81 const raw = try self.unsigned();
82 return if (raw == 0) null else value.ObjectId.init(raw) catch return error.InvalidObject;
83 }
84
85 pub fn newId(self: *Decoder) Error!value.NewId {
86 return value.NewId.init(try self.unsigned()) catch return error.InvalidNewId;
87 }
88
89 pub fn dynamicNewId(self: *Decoder) Error!value.DynamicNewId {
90 const interface = try self.string();
91 if (interface.len == 0) return error.InvalidInterface;
92 const version = try self.unsigned();
93 if (version == 0) return error.InvalidVersion;
94 return .{
95 .interface = interface,
96 .version = version,
97 .id = try self.newId(),
98 };
99 }
100
101 pub fn array(self: *Decoder) Error![]const u8 {
102 return self.delimited(try self.unsigned());
103 }
104
105 pub fn descriptor(self: *Decoder) Error!sys.fd.Descriptor {
106 if (self.descriptor_offset == self.descriptors.len) return error.IncompletePayload;
107 const item = self.descriptors[self.descriptor_offset];
108 if (item < 0) return error.InvalidDescriptor;
109 self.descriptor_offset += 1;
110 return item;
111 }
112
113 pub fn finish(self: *const Decoder) Error!void {
114 if (self.byte_offset != self.payload.len) return error.TrailingPayload;
115 if (self.descriptor_offset != self.descriptors.len) return error.TrailingDescriptors;
116 }
117
118 fn word(self: *Decoder) Error!*const [4]u8 {
119 if (self.payload.len - self.byte_offset < 4) return error.IncompletePayload;
120 const bytes: *const [4]u8 = self.payload[self.byte_offset..][0..4];
121 self.byte_offset += 4;
122 return bytes;
123 }
124
125 fn delimited(self: *Decoder, length: u32) Error![]const u8 {
126 const rounded = std.math.add(usize, length, 3) catch return error.IncompletePayload;
127 const padded_len = rounded & ~@as(usize, 3);
128 if (padded_len > self.payload.len - self.byte_offset) return error.IncompletePayload;
129 const bytes = self.payload[self.byte_offset..][0..length];
130 self.byte_offset += padded_len;
131 return bytes;
132 }
133 };
134
135 test "decoder accepts undefined nonzero padding" {
136 const metadata: protocol.schema.Message = .{
137 .name = "array",
138 .opcode = 0,
139 .since = 1,
140 .deprecated_since = null,
141 .destructor = false,
142 .signature = "a",
143 .descriptor_count = 0,
144 .minimum_payload_size = 4,
145 .arguments = &.{},
146 };
147 const payload = [_]u8{ 1, 0, 0, 0, 7, 0xaa, 0xbb, 0xcc };
148 var decoder = try Decoder.init(&metadata, &payload, &.{});
149 try std.testing.expectEqualSlices(u8, &.{7}, try decoder.array());
150 try decoder.finish();
151 }
152
153 test "decoder rejects malformed strings and trailing values" {
154 const metadata: protocol.schema.Message = .{
155 .name = "string",
156 .opcode = 0,
157 .since = 1,
158 .deprecated_since = null,
159 .destructor = false,
160 .signature = "s",
161 .descriptor_count = 0,
162 .minimum_payload_size = 8,
163 .arguments = &.{},
164 };
165 const unterminated = [_]u8{ 4, 0, 0, 0, 'a', 'b', 'c', 'd' };
166 var first = try Decoder.init(&metadata, &unterminated, &.{});
167 try std.testing.expectError(error.InvalidString, first.string());
168
169 const embedded = [_]u8{ 4, 0, 0, 0, 'a', 0, 'b', 0 };
170 var second = try Decoder.init(&metadata, &embedded, &.{});
171 try std.testing.expectError(error.InvalidString, second.string());
172
173 const trailing = [_]u8{ 2, 0, 0, 0, 'a', 0, 0, 0, 9, 0, 0, 0 };
174 var third = try Decoder.init(&metadata, &trailing, &.{});
175 try std.testing.expectEqualStrings("a", try third.string());
176 try std.testing.expectError(error.TrailingPayload, third.finish());
177 }
178
179 test "decoder rejects truncated lengths and wire nulls for required IDs" {
180 const string_metadata: protocol.schema.Message = .{
181 .name = "string",
182 .opcode = 0,
183 .since = 1,
184 .deprecated_since = null,
185 .destructor = false,
186 .signature = "s",
187 .descriptor_count = 0,
188 .minimum_payload_size = 8,
189 .arguments = &.{},
190 };
191 const truncated = [_]u8{ 5, 0, 0, 0, 'a', 0, 0, 0 };
192 var string_decoder = try Decoder.init(&string_metadata, &truncated, &.{});
193 try std.testing.expectError(error.IncompletePayload, string_decoder.string());
194
195 const object_metadata: protocol.schema.Message = .{
196 .name = "ids",
197 .opcode = 0,
198 .since = 1,
199 .deprecated_since = null,
200 .destructor = false,
201 .signature = "on",
202 .descriptor_count = 0,
203 .minimum_payload_size = 8,
204 .arguments = &.{},
205 };
206 const null_ids = @as([8]u8, @splat(0));
207 var object_decoder = try Decoder.init(&object_metadata, &null_ids, &.{});
208 try std.testing.expectError(error.InvalidObject, object_decoder.object());
209 var new_id_decoder = try Decoder.init(&object_metadata, &null_ids, &.{});
210 try std.testing.expectError(error.InvalidNewId, new_id_decoder.newId());
211 }
212
213 test "decoder accounts for descriptors independently and exactly" {
214 const metadata: protocol.schema.Message = .{
215 .name = "descriptor",
216 .opcode = 0,
217 .since = 1,
218 .deprecated_since = null,
219 .destructor = false,
220 .signature = "h",
221 .descriptor_count = 1,
222 .minimum_payload_size = 0,
223 .arguments = &.{},
224 };
225 try std.testing.expectError(
226 error.DescriptorCountMismatch,
227 Decoder.init(&metadata, &.{}, &.{}),
228 );
229 var decoder = try Decoder.init(&metadata, &.{}, &.{41});
230 try std.testing.expectError(error.TrailingDescriptors, decoder.finish());
231 try std.testing.expectEqual(@as(sys.fd.Descriptor, 41), try decoder.descriptor());
232 try decoder.finish();
233 }
234
235 test "decoder rejects unaligned and undersized payloads at the message boundary" {
236 const metadata: protocol.schema.Message = .{
237 .name = "word",
238 .opcode = 0,
239 .since = 1,
240 .deprecated_since = null,
241 .destructor = false,
242 .signature = "u",
243 .descriptor_count = 0,
244 .minimum_payload_size = 4,
245 .arguments = &.{},
246 };
247 try std.testing.expectError(error.InvalidAlignment, Decoder.init(&metadata, &.{ 0, 0, 0, 0, 0 }, &.{}));
248 try std.testing.expectError(error.PayloadTooSmall, Decoder.init(&metadata, &.{}, &.{}));
249 }