⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/desktop/wayland/
protocol.rs

1//! Wayland Wire Protocol Parser/Serializer
2//!
3//! Implements the Wayland wire protocol message format for kernel-side
4//! protocol handling. Messages use a fixed header followed by typed arguments.
5//!
6//! Wire format: `[object_id: u32][size_opcode: u32][arguments...]`
7//! - size = upper 16 bits of second word (total message size in bytes)
8//! - opcode = lower 16 bits of second word
9//!
10//! Constants and error variants define the complete Wayland core protocol
11//! surface area. Items not yet wired into dispatch are retained for protocol
12//! completeness.
13#![allow(dead_code)]
14
15use alloc::{vec, vec::Vec};
16
17use crate::error::KernelError;
18
19// ---------------------------------------------------------------------------
20// Wayland protocol constants
21// ---------------------------------------------------------------------------
22
23/// Minimum message size: object_id (4) + size_opcode (4)
24const HEADER_SIZE: usize = 8;
25
26/// Maximum message size (64 KB per Wayland spec)
27const MAX_MESSAGE_SIZE: usize = 65536;
28
29// -- Well-known interface identifiers used during dispatch -------------------
30
31/// wl_display interface -- object ID 1 is always the display
32pub const WL_DISPLAY_ID: u32 = 1;
33
34// wl_display opcodes (requests)
35/// Client requests: sync
36pub const WL_DISPLAY_SYNC: u16 = 0;
37/// Client requests: get_registry
38pub const WL_DISPLAY_GET_REGISTRY: u16 = 1;
39
40// wl_display event opcodes (server -> client)
41/// Server events: error
42pub const WL_DISPLAY_ERROR: u16 = 0;
43/// Server events: delete_id
44pub const WL_DISPLAY_DELETE_ID: u16 = 1;
45
46// wl_registry opcodes (events server -> client)
47/// Registry announces a global
48pub const WL_REGISTRY_GLOBAL: u16 = 0;
49/// Registry removes a global
50pub const WL_REGISTRY_GLOBAL_REMOVE: u16 = 1;
51
52// wl_registry opcodes (requests client -> server)
53/// Client binds a global
54pub const WL_REGISTRY_BIND: u16 = 0;
55
56// wl_compositor opcodes (requests)
57/// create_surface
58pub const WL_COMPOSITOR_CREATE_SURFACE: u16 = 0;
59/// create_region
60pub const WL_COMPOSITOR_CREATE_REGION: u16 = 1;
61
62// wl_shm opcodes (requests)
63/// create_pool
64pub const WL_SHM_CREATE_POOL: u16 = 0;
65
66// wl_shm event opcodes
67/// format announcement
68pub const WL_SHM_FORMAT: u16 = 0;
69
70// wl_shm_pool opcodes (requests)
71/// create_buffer
72pub const WL_SHM_POOL_CREATE_BUFFER: u16 = 0;
73/// destroy
74pub const WL_SHM_POOL_DESTROY: u16 = 2;
75
76// wl_surface opcodes (requests)
77/// destroy
78pub const WL_SURFACE_DESTROY: u16 = 0;
79/// attach
80pub const WL_SURFACE_ATTACH: u16 = 1;
81/// damage
82pub const WL_SURFACE_DAMAGE: u16 = 2;
83/// frame
84pub const WL_SURFACE_FRAME: u16 = 3;
85/// commit
86pub const WL_SURFACE_COMMIT: u16 = 6;
87
88// wl_surface event opcodes
89/// enter output
90pub const WL_SURFACE_ENTER: u16 = 0;
91
92// Pixel format constants matching Wayland wl_shm.format enum
93/// ARGB8888 format code
94pub const WL_SHM_FORMAT_ARGB8888: u32 = 0;
95/// XRGB8888 format code
96pub const WL_SHM_FORMAT_XRGB8888: u32 = 1;
97
98// ---------------------------------------------------------------------------
99// Error type
100// ---------------------------------------------------------------------------
101
102/// Wayland protocol-specific error
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum WaylandError {
105    /// Message is too short to contain a valid header
106    MessageTooShort,
107    /// Declared size in header does not match available data
108    SizeMismatch { declared: usize, available: usize },
109    /// String argument is not properly NUL-terminated
110    InvalidString,
111    /// Unsupported argument encoding encountered
112    InvalidArgument,
113    /// Object ID not found in client object map
114    UnknownObject { id: u32 },
115    /// Opcode not recognized for the target interface
116    UnknownOpcode { object_id: u32, opcode: u16 },
117    /// A required new_id argument was missing
118    MissingNewId,
119}
120
121impl From<WaylandError> for KernelError {
122    fn from(e: WaylandError) -> Self {
123        match e {
124            WaylandError::MessageTooShort => KernelError::InvalidArgument {
125                name: "wayland_message",
126                value: "too short",
127            },
128            WaylandError::SizeMismatch { .. } => KernelError::InvalidArgument {
129                name: "wayland_message_size",
130                value: "mismatch",
131            },
132            WaylandError::InvalidString => KernelError::InvalidArgument {
133                name: "wayland_string",
134                value: "invalid encoding",
135            },
136            WaylandError::InvalidArgument => KernelError::InvalidArgument {
137                name: "wayland_argument",
138                value: "invalid",
139            },
140            WaylandError::UnknownObject { id } => KernelError::NotFound {
141                resource: "wayland_object",
142                id: id as u64,
143            },
144            WaylandError::UnknownOpcode { .. } => KernelError::OperationNotSupported {
145                operation: "wayland opcode",
146            },
147            WaylandError::MissingNewId => KernelError::InvalidArgument {
148                name: "wayland_new_id",
149                value: "missing",
150            },
151        }
152    }
153}
154
155// ---------------------------------------------------------------------------
156// Message header
157// ---------------------------------------------------------------------------
158
159/// Wayland message header (8 bytes on the wire)
160#[repr(C)]
161#[derive(Debug, Clone, Copy)]
162pub struct MessageHeader {
163    /// Target object ID
164    pub object_id: u32,
165    /// Combined size (upper 16) and opcode (lower 16)
166    pub size_opcode: u32,
167}
168
169impl MessageHeader {
170    /// Extract the opcode from the combined word.
171    pub fn opcode(&self) -> u16 {
172        (self.size_opcode & 0xFFFF) as u16
173    }
174
175    /// Extract the total message size from the combined word.
176    pub fn size(&self) -> u16 {
177        ((self.size_opcode >> 16) & 0xFFFF) as u16
178    }
179
180    /// Build the combined size_opcode word.
181    pub fn encode(opcode: u16, size: u16) -> u32 {
182        ((size as u32) << 16) | (opcode as u32)
183    }
184}
185
186// ---------------------------------------------------------------------------
187// Argument types
188// ---------------------------------------------------------------------------
189
190/// Typed argument in a Wayland message.
191#[derive(Debug, Clone)]
192pub enum Argument {
193    /// Signed 32-bit integer
194    Int(i32),
195    /// Unsigned 32-bit integer
196    Uint(u32),
197    /// Fixed-point number (24.8 format, stored as i32)
198    Fixed(i32),
199    /// Length-prefixed, NUL-terminated UTF-8 string
200    String(Vec<u8>),
201    /// Reference to an existing object (0 = null)
202    Object(u32),
203    /// Newly allocated object ID
204    NewId(u32),
205    /// Length-prefixed byte array
206    Array(Vec<u8>),
207    /// File descriptor (out-of-band, stored as i32 index)
208    Fd(i32),
209}
210
211// ---------------------------------------------------------------------------
212// Parsed message
213// ---------------------------------------------------------------------------
214
215/// A fully parsed Wayland protocol message.
216#[derive(Debug, Clone)]
217pub struct WaylandMessage {
218    /// Target object ID
219    pub object_id: u32,
220    /// Opcode (method on the target interface)
221    pub opcode: u16,
222    /// Parsed argument list
223    pub args: Vec<Argument>,
224}
225
226impl WaylandMessage {
227    /// Convenience constructor.
228    pub fn new(object_id: u32, opcode: u16, args: Vec<Argument>) -> Self {
229        Self {
230            object_id,
231            opcode,
232            args,
233        }
234    }
235}
236
237// ---------------------------------------------------------------------------
238// Parser helpers
239// ---------------------------------------------------------------------------
240
241/// Read a little-endian u32 from a byte slice at the given offset.
242/// Returns `None` if out of bounds.
243fn read_u32(data: &[u8], offset: usize) -> Option<u32> {
244    if offset + 4 > data.len() {
245        return None;
246    }
247    Some(u32::from_ne_bytes([
248        data[offset],
249        data[offset + 1],
250        data[offset + 2],
251        data[offset + 3],
252    ]))
253}
254
255/// Read a little-endian i32 from a byte slice at the given offset.
256fn read_i32(data: &[u8], offset: usize) -> Option<i32> {
257    read_u32(data, offset).map(|v| v as i32)
258}
259
260/// Round up to the next multiple of 4 (Wayland wire alignment).
261fn align4(n: usize) -> usize {
262    (n + 3) & !3
263}
264
265// ---------------------------------------------------------------------------
266// Public API: parse
267// ---------------------------------------------------------------------------
268
269/// Parse a single Wayland wire-protocol message from the front of `data`.
270///
271/// Returns the parsed message and the number of bytes consumed so that the
272/// caller can advance through a buffer containing multiple messages.
273pub fn parse_message(data: &[u8]) -> Result<(WaylandMessage, usize), WaylandError> {
274    if data.len() < HEADER_SIZE {
275        return Err(WaylandError::MessageTooShort);
276    }
277
278    let object_id = read_u32(data, 0).ok_or(WaylandError::MessageTooShort)?;
279    let size_opcode = read_u32(data, 4).ok_or(WaylandError::MessageTooShort)?;
280
281    let header = MessageHeader {
282        object_id,
283        size_opcode,
284    };
285    let total_size = header.size() as usize;
286    let opcode = header.opcode();
287
288    if total_size < HEADER_SIZE || total_size > data.len() {
289        return Err(WaylandError::SizeMismatch {
290            declared: total_size,
291            available: data.len(),
292        });
293    }
294
295    // The argument payload starts right after the 8-byte header.
296    let payload = &data[HEADER_SIZE..total_size];
297
298    let msg = WaylandMessage {
299        object_id,
300        opcode,
301        args: Vec::new(),
302    };
303
304    // We parse raw arguments; the caller (interface dispatch) interprets the
305    // meaning of each positional argument. Here we just return the header +
306    // the raw payload bytes as a flat Uint sequence (each 4-byte word).
307    let mut args = Vec::new();
308    let mut off = 0;
309    while off + 4 <= payload.len() {
310        let word = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)?;
311        args.push(Argument::Uint(word));
312        off += 4;
313    }
314
315    Ok((
316        WaylandMessage {
317            object_id: msg.object_id,
318            opcode: msg.opcode,
319            args,
320        },
321        total_size,
322    ))
323}
324
325/// Parse a typed argument list from raw payload bytes according to a format
326/// string where each character describes one argument:
327///   i = Int, u = Uint, f = Fixed, s = String, o = Object, n = NewId,
328///   a = Array, h = Fd
329///
330/// This is used by interface dispatchers that know the expected signature.
331pub fn parse_args(payload: &[u8], signature: &[u8]) -> Result<Vec<Argument>, WaylandError> {
332    let mut args = Vec::new();
333    let mut off: usize = 0;
334
335    for &ch in signature {
336        match ch {
337            b'i' => {
338                let v = read_i32(payload, off).ok_or(WaylandError::InvalidArgument)?;
339                args.push(Argument::Int(v));
340                off += 4;
341            }
342            b'u' => {
343                let v = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)?;
344                args.push(Argument::Uint(v));
345                off += 4;
346            }
347            b'f' => {
348                let v = read_i32(payload, off).ok_or(WaylandError::InvalidArgument)?;
349                args.push(Argument::Fixed(v));
350                off += 4;
351            }
352            b's' => {
353                let len = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)? as usize;
354                off += 4;
355                if off + len > payload.len() {
356                    return Err(WaylandError::InvalidArgument);
357                }
358                // String includes trailing NUL in the length.
359                let bytes = if len > 0 && payload[off + len - 1] == 0 {
360                    payload[off..off + len - 1].to_vec()
361                } else if len == 0 {
362                    Vec::new()
363                } else {
364                    return Err(WaylandError::InvalidString);
365                };
366                args.push(Argument::String(bytes));
367                off += align4(len);
368            }
369            b'o' => {
370                let v = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)?;
371                args.push(Argument::Object(v));
372                off += 4;
373            }
374            b'n' => {
375                let v = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)?;
376                args.push(Argument::NewId(v));
377                off += 4;
378            }
379            b'a' => {
380                let len = read_u32(payload, off).ok_or(WaylandError::InvalidArgument)? as usize;
381                off += 4;
382                if off + len > payload.len() {
383                    return Err(WaylandError::InvalidArgument);
384                }
385                let bytes = payload[off..off + len].to_vec();
386                args.push(Argument::Array(bytes));
387                off += align4(len);
388            }
389            b'h' => {
390                let v = read_i32(payload, off).ok_or(WaylandError::InvalidArgument)?;
391                args.push(Argument::Fd(v));
392                off += 4;
393            }
394            _ => return Err(WaylandError::InvalidArgument),
395        }
396    }
397    Ok(args)
398}
399
400// ---------------------------------------------------------------------------
401// Public API: serialize
402// ---------------------------------------------------------------------------
403
404/// Serialize a `WaylandMessage` into its wire-protocol byte representation.
405pub fn serialize_message(msg: &WaylandMessage) -> Vec<u8> {
406    // First, serialize arguments to compute total size.
407    let mut arg_bytes = Vec::new();
408    for arg in &msg.args {
409        serialize_arg(&mut arg_bytes, arg);
410    }
411
412    let total_size = (HEADER_SIZE + arg_bytes.len()) as u16;
413
414    let mut out = Vec::with_capacity(total_size as usize);
415
416    // Object ID
417    out.extend_from_slice(&msg.object_id.to_ne_bytes());
418    // size_opcode
419    let size_opcode = MessageHeader::encode(msg.opcode, total_size);
420    out.extend_from_slice(&size_opcode.to_ne_bytes());
421    // Arguments
422    out.extend_from_slice(&arg_bytes);
423
424    out
425}
426
427/// Serialize a single argument, appending bytes to `buf`.
428fn serialize_arg(buf: &mut Vec<u8>, arg: &Argument) {
429    match arg {
430        Argument::Int(v) => buf.extend_from_slice(&v.to_ne_bytes()),
431        Argument::Uint(v) => buf.extend_from_slice(&v.to_ne_bytes()),
432        Argument::Fixed(v) => buf.extend_from_slice(&v.to_ne_bytes()),
433        Argument::String(bytes) => {
434            // Wire format: u32 length (including NUL), data, NUL, padding to 4
435            let len_with_nul = bytes.len() + 1;
436            buf.extend_from_slice(&(len_with_nul as u32).to_ne_bytes());
437            buf.extend_from_slice(bytes);
438            buf.push(0); // NUL terminator
439                         // Pad to 4-byte alignment
440            let padded = align4(len_with_nul);
441            for _ in len_with_nul..padded {
442                buf.push(0);
443            }
444        }
445        Argument::Object(v) => buf.extend_from_slice(&v.to_ne_bytes()),
446        Argument::NewId(v) => buf.extend_from_slice(&v.to_ne_bytes()),
447        Argument::Array(bytes) => {
448            buf.extend_from_slice(&(bytes.len() as u32).to_ne_bytes());
449            buf.extend_from_slice(bytes);
450            let padded = align4(bytes.len());
451            for _ in bytes.len()..padded {
452                buf.push(0);
453            }
454        }
455        Argument::Fd(v) => buf.extend_from_slice(&v.to_ne_bytes()),
456    }
457}
458
459// ---------------------------------------------------------------------------
460// Event builder helpers (server -> client)
461// ---------------------------------------------------------------------------
462
463/// Build a wl_display.error event.
464pub fn build_display_error(object_id: u32, code: u32, message: &[u8]) -> Vec<u8> {
465    let msg = WaylandMessage::new(
466        WL_DISPLAY_ID,
467        WL_DISPLAY_ERROR,
468        vec![
469            Argument::Object(object_id),
470            Argument::Uint(code),
471            Argument::String(message.to_vec()),
472        ],
473    );
474    serialize_message(&msg)
475}
476
477/// Build a wl_display.delete_id event.
478pub fn build_display_delete_id(id: u32) -> Vec<u8> {
479    let msg = WaylandMessage::new(
480        WL_DISPLAY_ID,
481        WL_DISPLAY_DELETE_ID,
482        vec![Argument::Uint(id)],
483    );
484    serialize_message(&msg)
485}
486
487/// Build a wl_registry.global event.
488pub fn build_registry_global(
489    registry_id: u32,
490    name: u32,
491    interface: &[u8],
492    version: u32,
493) -> Vec<u8> {
494    let msg = WaylandMessage::new(
495        registry_id,
496        WL_REGISTRY_GLOBAL,
497        vec![
498            Argument::Uint(name),
499            Argument::String(interface.to_vec()),
500            Argument::Uint(version),
501        ],
502    );
503    serialize_message(&msg)
504}
505
506/// Build a wl_shm.format event.
507pub fn build_shm_format(shm_id: u32, format: u32) -> Vec<u8> {
508    let msg = WaylandMessage::new(shm_id, WL_SHM_FORMAT, vec![Argument::Uint(format)]);
509    serialize_message(&msg)
510}
511
512/// Build a wl_callback.done event (for sync and frame callbacks).
513pub fn build_callback_done(callback_id: u32, serial: u32) -> Vec<u8> {
514    let msg = WaylandMessage::new(callback_id, 0, vec![Argument::Uint(serial)]);
515    serialize_message(&msg)
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn test_header_encode_decode() {
524        let opcode: u16 = 3;
525        let size: u16 = 24;
526        let combined = MessageHeader::encode(opcode, size);
527        let header = MessageHeader {
528            object_id: 7,
529            size_opcode: combined,
530        };
531        assert_eq!(header.opcode(), opcode);
532        assert_eq!(header.size(), size);
533    }
534
535    #[test]
536    fn test_roundtrip_simple_message() {
537        let msg = WaylandMessage::new(5, 2, vec![Argument::Uint(42), Argument::Int(-1)]);
538        let bytes = serialize_message(&msg);
539        let (parsed, consumed) = parse_message(&bytes).unwrap();
540        assert_eq!(consumed, bytes.len());
541        assert_eq!(parsed.object_id, 5);
542        assert_eq!(parsed.opcode, 2);
543        // Raw parse returns Uint words; first should be 42
544        assert_eq!(parsed.args.len(), 2);
545    }
546
547    #[test]
548    fn test_parse_too_short() {
549        let data = [0u8; 4];
550        assert_eq!(
551            parse_message(&data).unwrap_err(),
552            WaylandError::MessageTooShort
553        );
554    }
555
556    #[test]
557    fn test_serialize_string_alignment() {
558        let msg = WaylandMessage::new(1, 0, vec![Argument::String(b"hi".to_vec())]);
559        let bytes = serialize_message(&msg);
560        // Header(8) + len(4) + "hi\0"(3) + pad(1) = 16
561        assert_eq!(bytes.len(), 16);
562    }
563
564    #[test]
565    fn test_parse_args_string() {
566        // Build a payload with a string "abc"
567        let mut payload = Vec::new();
568        // length including NUL = 4
569        payload.extend_from_slice(&4u32.to_ne_bytes());
570        payload.extend_from_slice(b"abc\0");
571        let args = parse_args(&payload, b"s").unwrap();
572        assert_eq!(args.len(), 1);
573        if let Argument::String(s) = &args[0] {
574            assert_eq!(s, b"abc");
575        } else {
576            panic!("expected String argument");
577        }
578    }
579}