1package wlturbo
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "io"
8)
9
10const (
11 // HeaderSize is the fixed size of a Wayland message header in bytes.
12 HeaderSize = 8
13
14 // displayObjectID is the well-known object ID of wl_display.
15 displayObjectID = 1
16
17 // DefaultMaxMessageSize bounds a single Wayland message, including its
18 // header. Larger messages are rejected instead of allocating.
19 DefaultMaxMessageSize = 1 << 20
20
21 // readChunkSize is the size of one read from the display socket.
22 readChunkSize = 64 * 1024
23)
24
25// Sentinel errors reported by the transport. Wire-level failures wrap one of
26// them so callers can classify with errors.Is, while ProtocolError carries the
27// offending object, opcode and size.
28var (
29 // ErrMalformedFrame reports a message whose header violates the wire
30 // protocol: too short, misaligned, or larger than the accepted maximum.
31 ErrMalformedFrame = errors.New("malformed wayland frame")
32
33 // ErrUnknownObject reports an event for an object this client never
34 // created or bound.
35 ErrUnknownObject = errors.New("wayland event for unknown object")
36
37 // ErrUnknownOpcode reports an event opcode the target object cannot receive.
38 ErrUnknownOpcode = errors.New("wayland event with unknown opcode")
39
40 // ErrDisplayError reports that the compositor sent wl_display.error.
41 ErrDisplayError = errors.New("wayland display error")
42)
43
44// ProtocolError describes a wire-level protocol violation.
45type ProtocolError struct {
46 Kind string
47 Object uint32
48 Opcode uint16
49 Size uint32
50 Err error
51}
52
53func (e *ProtocolError) Error() string {
54 msg := e.Kind
55 if e.Object != 0 {
56 msg += fmt.Sprintf(" object=%d", e.Object)
57 }
58 if e.Kind == "unknown_opcode" || e.Kind == "display_error" {
59 msg += fmt.Sprintf(" opcode=%d", e.Opcode)
60 }
61 if e.Size != 0 {
62 msg += fmt.Sprintf(" size=%d", e.Size)
63 }
64 if e.Err != nil {
65 return fmt.Sprintf("wlturbo: %s: %v", msg, e.Err)
66 }
67 return "wlturbo: " + msg
68}
69
70// Unwrap returns the sentinel error classifying this violation.
71func (e *ProtocolError) Unwrap() error { return e.Err }
72
73// DisplayError is returned when the compositor reports wl_display.error.
74type DisplayError struct {
75 ObjectID uint32
76 Code uint32
77 Message string
78}
79
80func (e *DisplayError) Error() string {
81 if e.Message == "" {
82 return fmt.Sprintf("wlturbo: %v: object=%d code=%d", ErrDisplayError, e.ObjectID, e.Code)
83 }
84 return fmt.Sprintf("wlturbo: %v: object=%d code=%d: %s", ErrDisplayError, e.ObjectID, e.Code, e.Message)
85}
86
87// Unwrap reports DisplayError as ErrDisplayError.
88func (e *DisplayError) Unwrap() error { return ErrDisplayError }
89
90// receivedFrame is one complete Wayland message read from the connection.
91// Ancillary file descriptors are not part of the frame: they are owned by the
92// connection and consumed in arrival order by Event.Fd.
93type receivedFrame struct {
94 object uint32
95 opcode uint16
96 body []byte
97}
98
99// parseHeader decodes and validates an 8-byte Wayland message header.
100func parseHeader(hdr []byte, maxSize uint32) (object uint32, opcode uint16, size uint32, err error) {
101 if len(hdr) < HeaderSize {
102 return 0, 0, 0, &ProtocolError{Kind: "short_header", Err: ErrMalformedFrame}
103 }
104 object = binary.LittleEndian.Uint32(hdr[0:4])
105 sizeOpcode := binary.LittleEndian.Uint32(hdr[4:8])
106 size = sizeOpcode >> 16
107 opcode = uint16(sizeOpcode & 0xffff)
108
109 switch {
110 case size < HeaderSize:
111 return 0, 0, 0, &ProtocolError{Kind: "header_size", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
112 case size%4 != 0:
113 return 0, 0, 0, &ProtocolError{Kind: "misaligned_size", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
114 case maxSize > 0 && size > maxSize:
115 return 0, 0, 0, &ProtocolError{Kind: "size_over_limit", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
116 }
117 return object, opcode, size, nil
118}
119
120// readFrame reads exactly one Wayland message from r. A short read never
121// yields a partial frame: the header and the declared body are both read in
122// full before returning. A maxSize of 0 means DefaultMaxMessageSize.
123func readFrame(r io.Reader, maxSize uint32) (object uint32, opcode uint16, body []byte, err error) {
124 if maxSize == 0 {
125 maxSize = DefaultMaxMessageSize
126 }
127 var hdr [HeaderSize]byte
128 if _, err = io.ReadFull(r, hdr[:]); err != nil {
129 return 0, 0, nil, err
130 }
131 object, opcode, size, err := parseHeader(hdr[:], maxSize)
132 if err != nil {
133 return 0, 0, nil, err
134 }
135 if size == HeaderSize {
136 return object, opcode, nil, nil
137 }
138 body = make([]byte, size-HeaderSize)
139 if _, err = io.ReadFull(r, body); err != nil {
140 return 0, 0, nil, err
141 }
142 return object, opcode, body, nil
143}