1package xturbo
2
3import (
4 "encoding/binary"
5 "fmt"
6 "io"
7 "os"
8 "time"
9
10 "golang.org/x/sys/unix"
11)
12
13// Fixed layout of the xproto Setup reply (offsets from its status byte).
14const (
15 setupHeaderSize = 8 // status, pad/reason_len, protocol major, minor, length
16 setupFixedSize = 40 // fixed part of a successful Setup
17 screenFixedSize = 40 // fixed part of a SCREEN
18 formatSize = 8 // FORMAT
19 depthFixedSize = 8 // fixed part of a DEPTH
20 visualSize = 24 // VISUALTYPE
21 protocolMajor = 11
22 protocolMinor = 0
23)
24
25// setupInfo is what root keeps from a successful setup reply. Everything else
26// is parsed by callers from Conn.SetupBytes.
27type setupInfo struct {
28 xidBase, xidMask uint32
29 root uint32
30}
31
32func malformed(what string) error { return fmt.Errorf("%w: %s", ErrMalformed, what) }
33
34// encodeSetupRequest builds the little-endian connection setup request.
35func encodeSetupRequest(authName string, authData []byte) []byte {
36 n, d := pad4(len(authName)), pad4(len(authData))
37 p := make([]byte, 12+n+d)
38 p[0] = 'l'
39 binary.LittleEndian.PutUint16(p[2:], protocolMajor)
40 binary.LittleEndian.PutUint16(p[4:], protocolMinor)
41 binary.LittleEndian.PutUint16(p[6:], uint16(len(authName)))
42 binary.LittleEndian.PutUint16(p[8:], uint16(len(authData)))
43 copy(p[12:], authName)
44 copy(p[12+n:], authData)
45 return p
46}
47
48func pad4(n int) int { return (n + 3) &^ 3 }
49
50// handshake runs the setup exchange on the non-blocking fd, waiting with poll
51// until deadline, and returns the full setup reply. A refusal is a
52// *SetupError; a broken reply wraps ErrMalformed; a silent server an error
53// wrapping os.ErrDeadlineExceeded.
54func handshake(fd int, authName string, authData []byte, deadline time.Time) ([]byte, setupInfo, error) {
55 if len(authName) > 0xffff || len(authData) > 0xffff {
56 return nil, setupInfo{}, ErrBadLength
57 }
58 if err := writeFull(fd, encodeSetupRequest(authName, authData), deadline); err != nil {
59 return nil, setupInfo{}, err
60 }
61 var hdr [setupHeaderSize]byte
62 if err := readFull(fd, hdr[:], deadline); err != nil {
63 return nil, setupInfo{}, err
64 }
65 reply := make([]byte, setupHeaderSize+4*int(binary.LittleEndian.Uint16(hdr[6:])))
66 copy(reply, hdr[:])
67 if err := readFull(fd, reply[setupHeaderSize:], deadline); err != nil {
68 return nil, setupInfo{}, err
69 }
70 info, err := parseSetup(reply)
71 if err != nil {
72 return nil, setupInfo{}, err
73 }
74 return reply, info, nil
75}
76
77// parseSetup validates a complete setup reply (header length already
78// honoured) and extracts what root needs.
79func parseSetup(p []byte) (setupInfo, error) {
80 if len(p) < setupHeaderSize {
81 return setupInfo{}, malformed("short setup reply")
82 }
83 switch p[0] {
84 case setupStatusFailed:
85 // SetupFailed: reason_len at byte 1, reason after the header.
86 n := int(p[1])
87 if setupHeaderSize+n > len(p) {
88 return setupInfo{}, malformed("setup failure reason overflows reply")
89 }
90 return setupInfo{}, &SetupError{Status: setupStatusFailed, Reason: string(p[setupHeaderSize : setupHeaderSize+n])}
91 case setupStatusAuthenticate:
92 // SetupAuthenticate: reason fills length*4 bytes, NUL padded.
93 reason := p[setupHeaderSize:]
94 for len(reason) > 0 && reason[len(reason)-1] == 0 {
95 reason = reason[:len(reason)-1]
96 }
97 return setupInfo{}, &SetupError{Status: setupStatusAuthenticate, Reason: string(reason)}
98 case setupStatusSuccess:
99 default:
100 return setupInfo{}, malformed(fmt.Sprintf("setup status %d", p[0]))
101 }
102 if len(p) < setupFixedSize {
103 return setupInfo{}, malformed("short setup reply")
104 }
105 if binary.LittleEndian.Uint16(p[2:]) != protocolMajor {
106 return setupInfo{}, malformed(fmt.Sprintf("protocol major %d", binary.LittleEndian.Uint16(p[2:])))
107 }
108 info := setupInfo{
109 xidBase: binary.LittleEndian.Uint32(p[12:]),
110 xidMask: binary.LittleEndian.Uint32(p[16:]),
111 }
112 if info.xidMask == 0 || info.xidBase&info.xidMask != 0 {
113 return setupInfo{}, malformed("resource id base/mask")
114 }
115 if p[28] == 0 {
116 return setupInfo{}, malformed("setup has no screens")
117 }
118 off := setupFixedSize + pad4(int(binary.LittleEndian.Uint16(p[24:]))) + formatSize*int(p[29])
119 if off > len(p) {
120 return setupInfo{}, malformed("vendor or pixmap formats overflow setup reply")
121 }
122 roots := int(p[28])
123 for i := range roots {
124 if len(p)-off < screenFixedSize {
125 return setupInfo{}, malformed(fmt.Sprintf("screen %d overflows setup reply", i))
126 }
127 if i == 0 {
128 info.root = binary.LittleEndian.Uint32(p[off:])
129 }
130 depths := int(p[off+39])
131 off += screenFixedSize
132 for range depths {
133 if len(p)-off < depthFixedSize {
134 return setupInfo{}, malformed(fmt.Sprintf("screen %d depth overflows setup reply", i))
135 }
136 visuals := visualSize * int(binary.LittleEndian.Uint16(p[off+2:]))
137 off += depthFixedSize
138 if len(p)-off < visuals {
139 return setupInfo{}, malformed(fmt.Sprintf("screen %d visuals overflow setup reply", i))
140 }
141 off += visuals
142 }
143 }
144 return info, nil
145}
146
147// waitFd polls fd for events until deadline.
148func waitFd(fd int, events int16, deadline time.Time) error {
149 left := time.Until(deadline)
150 if left <= 0 {
151 return deadlineError("connection setup")
152 }
153 return pollFd(fd, events, left)
154}
155
156func writeFull(fd int, p []byte, deadline time.Time) error {
157 for len(p) > 0 {
158 n, err := unix.Write(fd, p)
159 switch {
160 case err == unix.EINTR:
161 continue
162 case err == unix.EAGAIN:
163 if err := waitFd(fd, unix.POLLOUT, deadline); err != nil {
164 return err
165 }
166 continue
167 case err == unix.EPIPE || err == unix.ECONNRESET:
168 return ErrClosed
169 case err != nil:
170 return os.NewSyscallError("write", err)
171 case n <= 0:
172 return io.ErrShortWrite
173 }
174 p = p[n:]
175 }
176 return nil
177}
178
179func readFull(fd int, p []byte, deadline time.Time) error {
180 for len(p) > 0 {
181 n, err := unix.Read(fd, p)
182 switch {
183 case err == unix.EINTR:
184 continue
185 case err == unix.EAGAIN:
186 if err := waitFd(fd, unix.POLLIN, deadline); err != nil {
187 return err
188 }
189 continue
190 case err == unix.ECONNRESET:
191 return ErrClosed
192 case err != nil:
193 return os.NewSyscallError("read", err)
194 case n == 0:
195 return fmt.Errorf("%w: truncated setup reply: %w", ErrMalformed, io.ErrUnexpectedEOF)
196 }
197 p = p[n:]
198 }
199 return nil
200}