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 }