1//go:build linux
2
3package core_test
4
5import (
6 "encoding/binary"
7 "errors"
8 "io"
9 "net"
10 "os"
11 "testing"
12 "time"
13
14 "github.com/bnema/wlturbo"
15 "github.com/bnema/wlturbo/protocol/core"
16 "golang.org/x/sys/unix"
17)
18
19func pair(t *testing.T) (*net.UnixConn, *net.UnixConn) {
20 t.Helper()
21 f, err := unix.Socketpair(unix.AF_UNIX, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
22 if err != nil {
23 t.Fatal(err)
24 }
25 a := os.NewFile(uintptr(f[0]), "a")
26 b := os.NewFile(uintptr(f[1]), "b")
27 ca, e := net.FileConn(a)
28 a.Close()
29 if e != nil {
30 t.Fatal(e)
31 }
32 cb, e := net.FileConn(b)
33 b.Close()
34 if e != nil {
35 t.Fatal(e)
36 }
37 t.Cleanup(func() { ca.Close(); cb.Close() })
38 return ca.(*net.UnixConn), cb.(*net.UnixConn)
39}
40func frame(id uint32, op uint16, body ...uint32) []byte {
41 p := make([]byte, 8+4*len(body))
42 binary.LittleEndian.PutUint32(p, id)
43 binary.LittleEndian.PutUint32(p[4:], uint32(len(p))<<16|uint32(op))
44 for i, v := range body {
45 binary.LittleEndian.PutUint32(p[8+i*4:], v)
46 }
47 return p
48}
49func receiveRequest(t *testing.T, p *net.UnixConn) {
50 t.Helper()
51 p.SetReadDeadline(testDeadline())
52 b := make([]byte, 256)
53 if _, e := p.Read(b); e != nil {
54 t.Fatal(e)
55 }
56}
57func testDeadline() (d time.Time) { return time.Now().Add(2 * time.Second) }
58func send(t *testing.T, p *net.UnixConn, msg []byte, fd ...int) {
59 t.Helper()
60 var oob []byte
61 if len(fd) > 0 {
62 oob = unix.UnixRights(fd...)
63 }
64 if _, _, e := p.WriteMsgUnix(msg, oob, nil); e != nil {
65 t.Fatal(e)
66 }
67}
68func TestGeneratedCoreOverSocketpair(t *testing.T) {
69 c, p := pair(t)
70 d, err := wlturbo.ConnectFromConn(c)
71 if err != nil {
72 t.Fatal(err)
73 }
74 defer d.Close()
75 receiveRequest(t, p) // get_registry
76 seat := core.NewSeat(d.Context())
77 if err := d.Registry().Bind(1, core.SeatInterface, 1, seat); err != nil {
78 t.Fatal(err)
79 }
80 receiveRequest(t, p)
81 kb, err := seat.GetKeyboard()
82 if err != nil {
83 t.Fatal(err)
84 }
85 receiveRequest(t, p)
86 var key uint32
87 kb.OnKey(func(_, _, k, _ uint32) { key = k })
88 send(t, p, frame(kb.ID(), 3, 1, 2, 42, 1))
89 if err := d.Dispatch(); err != nil {
90 t.Fatal(err)
91 }
92 if key != 42 {
93 t.Fatalf("key=%d", key)
94 }
95 compositor := core.NewCompositor(d.Context())
96 if err := d.Registry().Bind(2, core.CompositorInterface, 1, compositor); err != nil {
97 t.Fatal(err)
98 }
99 receiveRequest(t, p)
100 surface, err := compositor.CreateSurface()
101 if err != nil {
102 t.Fatal(err)
103 }
104 receiveRequest(t, p)
105 callback, err := surface.Frame()
106 if err != nil {
107 t.Fatal(err)
108 }
109 receiveRequest(t, p)
110 var done uint32
111 callback.OnDone(func(ms uint32) { done = ms })
112 send(t, p, frame(callback.ID(), 0, 123))
113 if err := d.Dispatch(); err != nil {
114 t.Fatal(err)
115 }
116 if done != 123 {
117 t.Fatalf("done=%d", done)
118 }
119 send(t, p, frame(callback.ID(), 0, 124))
120 if err := d.Dispatch(); !errors.Is(err, wlturbo.ErrUnknownObject) {
121 t.Fatalf("destroyed callback: %v", err)
122 }
123}
124func TestKeymapFDNoHandlerAndMultipleHandlers(t *testing.T) {
125 c, p := pair(t)
126 d, e := wlturbo.ConnectFromConn(c)
127 if e != nil {
128 t.Fatal(e)
129 }
130 defer d.Close()
131 receiveRequest(t, p)
132 kb := core.NewKeyboard(d.Context())
133 kb.SetID(d.AllocateID())
134 d.Context().Register(kb)
135 r, w, e := os.Pipe()
136 if e != nil {
137 t.Fatal(e)
138 }
139 defer r.Close()
140 send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
141 w.Close()
142 if e := d.Dispatch(); e != nil {
143 t.Fatal(e)
144 }
145 buf := make([]byte, 1)
146 if n, e := r.Read(buf); n != 0 || e != io.EOF {
147 t.Fatalf("unclaimed FD n=%d err=%v", n, e)
148 }
149 r2, w2, e := os.Pipe()
150 if e != nil {
151 t.Fatal(e)
152 }
153 defer r2.Close()
154 received := 0
155 kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
156 if fd == nil {
157 t.Fatal("first FD missing")
158 }
159 n, e := fd.Take()
160 if e != nil {
161 t.Fatal(e)
162 }
163 received++
164 unix.Close(n)
165 })
166 kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
167 if fd != nil {
168 t.Fatal("duplicated owner")
169 }
170 received++
171 })
172 send(t, p, frame(kb.ID(), 0, 1, 8), int(w2.Fd()))
173 w2.Close()
174 if e := d.Dispatch(); e != nil {
175 t.Fatal(e)
176 }
177 if received != 2 {
178 t.Fatalf("handlers=%d", received)
179 }
180}
181func TestBufferReleaseAndOfferChild(t *testing.T) {
182 c, p := pair(t)
183 d, e := wlturbo.ConnectFromConn(c)
184 if e != nil {
185 t.Fatal(e)
186 }
187 defer d.Close()
188 receiveRequest(t, p)
189 b := core.NewBuffer(d.Context())
190 b.SetID(d.AllocateID())
191 d.Context().Register(b)
192 releases := 0
193 b.OnRelease(func() { releases++ })
194 send(t, p, frame(b.ID(), 0))
195 if e := d.Dispatch(); e != nil {
196 t.Fatal(e)
197 }
198 if releases != 1 {
199 t.Fatal("release not dispatched")
200 }
201 device := core.NewDataDevice(d.Context())
202 device.SetID(d.AllocateID())
203 d.Context().Register(device)
204 var offer *core.DataOffer
205 device.OnDataOffer(func(o *core.DataOffer) { offer = o })
206 send(t, p, frame(device.ID(), 0, 0xff000001))
207 if e := d.Dispatch(); e != nil {
208 t.Fatal(e)
209 }
210 if offer == nil || offer.ID() != 0xff000001 {
211 t.Fatalf("offer=%v", offer)
212 }
213}
214
215func TestMissingKeymapDescriptor(t *testing.T) {
216 c, p := pair(t)
217 d, e := wlturbo.ConnectFromConn(c)
218 if e != nil {
219 t.Fatal(e)
220 }
221 defer d.Close()
222 receiveRequest(t, p)
223 kb := core.NewKeyboard(d.Context())
224 kb.SetID(d.AllocateID())
225 d.Context().Register(kb)
226 send(t, p, frame(kb.ID(), 0, 1, 8))
227 if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrMalformedFrame) {
228 t.Fatalf("missing FD: %v", e)
229 }
230}
231
232func TestInterleavedFDAndPlainMessages(t *testing.T) {
233 c, p := pair(t)
234 d, e := wlturbo.ConnectFromConn(c)
235 if e != nil {
236 t.Fatal(e)
237 }
238 defer d.Close()
239 receiveRequest(t, p)
240 kb := core.NewKeyboard(d.Context())
241 kb.SetID(d.AllocateID())
242 d.Context().Register(kb)
243 calls := 0
244 kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
245 if fd == nil {
246 t.Fatal("FD not assigned")
247 }
248 _ = fd.Close()
249 calls++
250 })
251 kb.OnKey(func(_, _, key, _ uint32) {
252 if key != 33 {
253 t.Errorf("key=%d", key)
254 }
255 calls++
256 })
257 r, w, e := os.Pipe()
258 if e != nil {
259 t.Fatal(e)
260 }
261 defer r.Close()
262 send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
263 w.Close()
264 send(t, p, frame(kb.ID(), 3, 1, 2, 33, 1))
265 if e := d.Dispatch(); e != nil {
266 t.Fatal(e)
267 }
268 if e := d.Dispatch(); e != nil {
269 t.Fatal(e)
270 }
271 if calls != 2 {
272 t.Fatalf("calls=%d", calls)
273 }
274}
275func TestMalformedGeneratedPayload(t *testing.T) {
276 c, p := pair(t)
277 d, e := wlturbo.ConnectFromConn(c)
278 if e != nil {
279 t.Fatal(e)
280 }
281 defer d.Close()
282 receiveRequest(t, p)
283 kb := core.NewKeyboard(d.Context())
284 kb.SetID(d.AllocateID())
285 d.Context().Register(kb)
286 send(t, p, frame(kb.ID(), 3, 1, 2))
287 if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrMalformedFrame) {
288 t.Fatalf("truncated key: %v", e)
289 }
290}
291
292func TestServerChildEventsAfterOffer(t *testing.T) {
293 c, p := pair(t)
294 d, e := wlturbo.ConnectFromConn(c)
295 if e != nil {
296 t.Fatal(e)
297 }
298 defer d.Close()
299 receiveRequest(t, p)
300 device := core.NewDataDevice(d.Context())
301 device.SetID(d.AllocateID())
302 d.Context().Register(device)
303 const offerID = 0xff000011
304 var offer *core.DataOffer
305 device.OnDataOffer(func(o *core.DataOffer) { offer = o })
306 send(t, p, frame(device.ID(), 0, offerID))
307 if e := d.Dispatch(); e != nil {
308 t.Fatal(e)
309 }
310 if offer == nil {
311 t.Fatal("missing concrete child")
312 }
313 got := ""
314 offer.OnOffer(func(mime string) { got = mime })
315 payload := append(frame(offerID, 0), 4, 0, 0, 0, 'a', 'b', 'c', 0)
316 binary.LittleEndian.PutUint32(payload[4:], uint32(len(payload))<<16)
317 send(t, p, payload)
318 if e := d.Dispatch(); e != nil {
319 t.Fatal(e)
320 }
321 if got != "abc" {
322 t.Fatalf("typed child event: %q", got)
323 }
324}
325
326func TestRegistryBootstrapAnnouncements(t *testing.T) {
327 c, p := pair(t)
328 d, e := wlturbo.ConnectFromConn(c)
329 if e != nil {
330 t.Fatal(e)
331 }
332 defer d.Close()
333 receiveRequest(t, p)
334 var got wlturbo.Global
335 d.Registry().AddHandler("wl_compositor", func(r *wlturbo.Registry, n, v uint32) { got, _ = r.FindGlobalByName(n) })
336 name := "wl_compositor"
337 body := make([]byte, 4+4+((len(name)+1+3)&^3)+4)
338 binary.LittleEndian.PutUint32(body, 17)
339 binary.LittleEndian.PutUint32(body[4:], uint32(len(name)+1))
340 copy(body[8:], name)
341 binary.LittleEndian.PutUint32(body[len(body)-4:], 7)
342 msg := append(frame(d.Registry().ID(), 0), body...)
343 binary.LittleEndian.PutUint32(msg[4:], uint32(len(msg))<<16)
344 send(t, p, msg)
345 if e := d.Dispatch(); e != nil {
346 t.Fatal(e)
347 }
348 if got.Name != 17 || got.Interface != name || got.Version != 7 {
349 t.Fatalf("global: %+v", got)
350 }
351 send(t, p, frame(d.Registry().ID(), 1, 17))
352 if e := d.Dispatch(); e != nil {
353 t.Fatal(e)
354 }
355 if _, ok := d.Registry().FindGlobalByName(17); ok {
356 t.Fatal("global not removed")
357 }
358 send(t, p, frame(d.Registry().ID(), 2))
359 if e := d.Dispatch(); !errors.Is(e, wlturbo.ErrUnknownOpcode) {
360 t.Fatalf("unknown registry opcode: %v", e)
361 }
362}
363func TestUnclaimedFirstHandlerFDClosed(t *testing.T) {
364 c, p := pair(t)
365 d, e := wlturbo.ConnectFromConn(c)
366 if e != nil {
367 t.Fatal(e)
368 }
369 defer d.Close()
370 receiveRequest(t, p)
371 kb := core.NewKeyboard(d.Context())
372 kb.SetID(d.AllocateID())
373 d.Context().Register(kb)
374 kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
375 if fd == nil {
376 t.Fatal("missing first owner")
377 }
378 })
379 kb.OnKeymap(func(_ uint32, fd *wlturbo.OwnedFD, _ uint32) {
380 if fd != nil {
381 t.Fatal("duplicate owner")
382 }
383 })
384 r, w, e := os.Pipe()
385 if e != nil {
386 t.Fatal(e)
387 }
388 defer r.Close()
389 send(t, p, frame(kb.ID(), 0, 1, 8), int(w.Fd()))
390 w.Close()
391 if e := d.Dispatch(); e != nil {
392 t.Fatal(e)
393 }
394 if e := r.SetReadDeadline(testDeadline()); e != nil {
395 t.Fatal(e)
396 }
397 var b [1]byte
398 if n, e := r.Read(b[:]); n != 0 || e != io.EOF {
399 t.Fatalf("unclaimed FD not closed: %d %v", n, e)
400 }
401}
402
403func TestDestroyedProxyRejectsRequests(t *testing.T) {
404 c, p := pair(t)
405 d, err := wlturbo.ConnectFromConn(c)
406 if err != nil {
407 t.Fatal(err)
408 }
409 defer d.Close()
410 receiveRequest(t, p)
411 surface := core.NewSurface(d.Context())
412 surface.SetID(d.AllocateID())
413 d.Context().Register(surface)
414 if err := surface.Destroy(); err != nil {
415 t.Fatal(err)
416 }
417 receiveRequest(t, p)
418 if err := surface.Destroy(); err == nil {
419 t.Fatal("second destroy succeeded")
420 }
421 if err := surface.Commit(); err == nil {
422 t.Fatal("request on destroyed surface succeeded")
423 }
424 if _, err := surface.Frame(); err == nil {
425 t.Fatal("child request on destroyed surface succeeded")
426 }
427 p.SetReadDeadline(time.Now().Add(20 * time.Millisecond))
428 _, err = p.Read(make([]byte, 32))
429 ne, ok := err.(net.Error)
430 if !ok || !ne.Timeout() {
431 t.Fatalf("unexpected bytes on wire: %v", err)
432 }
433}
434
435func TestConcurrentDestroySendsOnce(t *testing.T) {
436 c, p := pair(t)
437 d, err := wlturbo.ConnectFromConn(c)
438 if err != nil {
439 t.Fatal(err)
440 }
441 defer d.Close()
442 receiveRequest(t, p)
443 const rounds, destroyers, committers = 100, 4, 4
444 totalCommits := 0
445 for round := 0; round < rounds; round++ {
446 surface := core.NewSurface(d.Context())
447 surface.SetID(d.AllocateID())
448 d.Context().Register(surface)
449 start := make(chan struct{})
450 destroyed := make(chan error, destroyers)
451 committed := make(chan error, committers)
452 for i := 0; i < destroyers; i++ {
453 go func() { <-start; destroyed <- surface.Destroy() }()
454 }
455 for i := 0; i < committers; i++ {
456 go func() { <-start; committed <- surface.Commit() }()
457 }
458 close(start)
459 okDestroy, okCommit := 0, 0
460 for i := 0; i < destroyers; i++ {
461 if <-destroyed == nil {
462 okDestroy++
463 }
464 }
465 for i := 0; i < committers; i++ {
466 if <-committed == nil {
467 okCommit++
468 }
469 }
470 if okDestroy != 1 {
471 t.Fatalf("round %d: %d destroys succeeded, want 1", round, okDestroy)
472 }
473 totalCommits += okCommit
474 // Decode every frame: exactly okCommit commits (opcode 6), then one
475 // destroy (opcode 0) for this surface, and nothing after it.
476 frames := readFrames(t, p, okCommit+1)
477 for i, f := range frames {
478 if f.id != surface.ID() {
479 t.Fatalf("round %d: frame %d targets object %d", round, i, f.id)
480 }
481 last := i == len(frames)-1
482 if last && f.opcode != 0 || !last && f.opcode != 6 {
483 t.Fatalf("round %d: frame %d opcode %d (last=%v)", round, i, f.opcode, last)
484 }
485 }
486 p.SetReadDeadline(time.Now().Add(2 * time.Millisecond))
487 if n, err := p.Read(make([]byte, 8)); n != 0 {
488 t.Fatalf("round %d: request after destroy (%d bytes, %v)", round, n, err)
489 }
490 p.SetReadDeadline(time.Time{})
491 }
492 // Ordering is only exercised when some commits won the race.
493 if totalCommits == 0 {
494 t.Fatal("no commit succeeded in any round; ordering was not exercised")
495 }
496}
497
498type wireFrame struct {
499 id uint32
500 opcode uint16
501}
502
503func readFrames(t *testing.T, p *net.UnixConn, n int) []wireFrame {
504 t.Helper()
505 p.SetReadDeadline(testDeadline())
506 defer p.SetReadDeadline(time.Time{})
507 var out []wireFrame
508 var buf []byte
509 tmp := make([]byte, 4096)
510 for len(out) < n {
511 for len(buf) >= 8 {
512 size := int(binary.LittleEndian.Uint32(buf[4:8]) >> 16)
513 if size < 8 || len(buf) < size {
514 break
515 }
516 out = append(out, wireFrame{binary.LittleEndian.Uint32(buf[0:4]), uint16(binary.LittleEndian.Uint32(buf[4:8]))})
517 buf = buf[size:]
518 }
519 if len(out) >= n {
520 break
521 }
522 m, err := p.Read(tmp)
523 if err != nil {
524 t.Fatalf("read frames: got %d of %d: %v", len(out), n, err)
525 }
526 buf = append(buf, tmp[:m]...)
527 }
528 if len(out) != n || len(buf) != 0 {
529 t.Fatalf("got %d frames (+%d bytes), want %d", len(out), len(buf), n)
530 }
531 return out
532}