drmlease_test.go RawHistoryFolder

1package wayland
2
3import (
4	"context"
5	"fmt"
6	"os"
7	"testing"
8	"time"
9
10	dl "github.com/bnema/go-wayland-bindings/server/drmlease"
11	"github.com/bnema/neferwl/internal/logging"
12	"github.com/bnema/neferwl/internal/ports"
13	"github.com/bnema/purego-libwayland/server"
14	"github.com/bnema/wlturbo"
15	"golang.org/x/sys/unix"
16)
17
18type leaseDeviceEvents struct {
19	wlturbo.BaseProxy
20	c          *wlturbo.Display
21	connectors []uint32
22	fds        []int
23}
24
25func (p *leaseDeviceEvents) Dispatch(e *wlturbo.Event) {
26	switch e.Opcode {
27	case uint16(dl.WpDrmLeaseDeviceV1EventDrmFd):
28		p.fds = append(p.fds, int(e.Fd()))
29	case uint16(dl.WpDrmLeaseDeviceV1EventConnector):
30		id := e.Uint32()
31		p.connectors = append(p.connectors, id)
32		child := &leaseConnectorEvents{}
33		child.SetID(id)
34		registerWireProxy(p.c, child)
35	}
36}
37
38type leaseConnectorEvents struct {
39	wlturbo.BaseProxy
40	name string
41}
42
43func (p *leaseConnectorEvents) Dispatch(e *wlturbo.Event) {
44	if e.Opcode == uint16(dl.WpDrmLeaseConnectorV1EventName) {
45		p.name = e.String()
46	}
47}
48
49type leaseResultEvents struct {
50	wlturbo.BaseProxy
51	fds      []int
52	finished int
53}
54
55func (p *leaseResultEvents) Dispatch(e *wlturbo.Event) {
56	switch e.Opcode {
57	case uint16(dl.WpDrmLeaseV1EventLeaseFd):
58		p.fds = append(p.fds, int(e.Fd()))
59	case uint16(dl.WpDrmLeaseV1EventFinished):
60		p.finished++
61	}
62}
63
64// leaseFD opens a device fd for one LeaseConnectors message; the server
65// owns and closes it.
66func leaseFD(t *testing.T) *os.File {
67	t.Helper()
68	f, err := os.Open("/dev/null")
69	if err != nil {
70		t.Fatal(err)
71	}
72	return f
73}
74func leaseServer(t *testing.T) (*wlturbo.Display, chan ports.LeaseMessage, chan ports.LeaseMessage) {
75	c, requests, events, _ := leaseServerWithState(t)
76	return c, requests, events
77}
78func leaseServerWithState(t *testing.T) (*wlturbo.Display, chan ports.LeaseMessage, chan ports.LeaseMessage, *Server) {
79	t.Helper()
80	dir := t.TempDir()
81	requests := make(chan ports.LeaseMessage, 16)
82	events := make(chan ports.LeaseMessage, 16)
83	s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs}, Channels{LeaseRequests: requests, LeaseEvents: events}, logging.For(context.Background(), "wayland"))
84	if err != nil {
85		t.Fatal(err)
86	}
87	ctx, cancel := context.WithCancel(context.Background())
88	done := make(chan error, 1)
89	go func() { done <- s.Run(ctx) }()
90	t.Cleanup(func() {
91		cancel()
92		select {
93		case err := <-done:
94			if err != nil {
95				t.Error(err)
96			}
97		case <-time.After(3 * time.Second):
98			t.Error("server hung")
99		}
100	})
101	c := protocolClient(t, s, dir)
102	events <- ports.LeaseConnectors{Card: "/dev/dri/card-test", Device: leaseFD(t), Connectors: []ports.LeaseConnector{{Card: "/dev/dri/card-test", Name: "DP-1", Description: "VR headset", ConnectorID: 42}}}
103	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
104		if err := c.Roundtrip(); err != nil {
105			t.Fatal(err)
106		}
107		if _, ok := c.Registry().FindGlobal("wp_drm_lease_device_v1"); ok {
108			return c, requests, events, s
109		}
110		time.Sleep(time.Millisecond)
111	}
112	t.Fatal("no lease global")
113	return nil, nil, nil, nil
114}
115func TestDRMLeaseFlow(t *testing.T) {
116	c, requests, events := leaseServer(t)
117	dev := bindProtocol(t, c, "wp_drm_lease_device_v1")
118	p := &leaseDeviceEvents{c: c}
119	p.SetID(dev)
120	registerWireProxy(c, p)
121	if err := c.Roundtrip(); err != nil {
122		t.Fatal(err)
123	}
124	if len(p.fds) != 1 || len(p.connectors) != 1 {
125		t.Fatalf("device: fds=%v connectors=%v", p.fds, p.connectors)
126	}
127	defer unix.Close(p.fds[0])
128	conn := p.connectors[0]
129	req := c.AllocateID()
130	registerProtocol(t, c, req)
131	requestProtocol(t, c, dev, dl.WpDrmLeaseDeviceV1RequestCreateLeaseRequest, req)
132	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, conn)
133	lease := c.AllocateID()
134	l := &leaseResultEvents{}
135	l.SetID(lease)
136	registerWireProxy(c, l)
137	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestSubmit, lease)
138	if err := c.Roundtrip(); err != nil {
139		t.Fatal(err)
140	}
141	var got ports.LeaseRequest
142	select {
143	case ev := <-requests:
144		var ok bool
145		got, ok = ev.(ports.LeaseRequest)
146		if !ok {
147			t.Fatalf("message %T", ev)
148		}
149	case <-time.After(2 * time.Second):
150		t.Fatal("no request")
151	}
152	if len(got.Connectors) != 1 || got.Connectors[0] != "DP-1" {
153		t.Fatalf("request %+v", got)
154	}
155	r, w, err := os.Pipe()
156	if err != nil {
157		t.Fatal(err)
158	}
159	defer r.Close()
160	events <- ports.LeaseReply{ID: got.ID, FD: w, LeaseID: 7}
161	if err := c.Roundtrip(); err != nil {
162		t.Fatal(err)
163	}
164	for deadline := time.Now().Add(2 * time.Second); len(l.fds) == 0 && time.Now().Before(deadline); {
165		if err := c.Roundtrip(); err != nil {
166			t.Fatal(err)
167		}
168	}
169	if len(l.fds) != 1 {
170		t.Fatalf("lease fd count %d", len(l.fds))
171	}
172	defer unix.Close(l.fds[0])
173	requestProtocol(t, c, lease, dl.WpDrmLeaseV1RequestDestroy)
174	if err := c.Roundtrip(); err != nil {
175		t.Fatal(err)
176	}
177	select {
178	case ev := <-requests:
179		if v, ok := ev.(ports.LeaseRevoke); !ok || v.LeaseID != 7 {
180			t.Fatalf("revoke %v", ev)
181		}
182	case <-time.After(2 * time.Second):
183		t.Fatal("no revoke")
184	}
185}
186func TestDRMLeaseErrors(t *testing.T) {
187	for _, tc := range []struct {
188		name      string
189		duplicate bool
190		code      uint32
191	}{{"empty", false, uint32(dl.WpDrmLeaseRequestV1ErrorEmptyLease)}, {"duplicate", true, uint32(dl.WpDrmLeaseRequestV1ErrorDuplicateConnector)}} {
192		t.Run(tc.name, func(t *testing.T) {
193			c, _, _ := leaseServer(t)
194			dev := bindProtocol(t, c, "wp_drm_lease_device_v1")
195			p := &leaseDeviceEvents{c: c}
196			p.SetID(dev)
197			registerWireProxy(c, p)
198			if err := c.Roundtrip(); err != nil {
199				t.Fatal(err)
200			}
201			for _, fd := range p.fds {
202				defer unix.Close(fd)
203			}
204			req := c.AllocateID()
205			registerProtocol(t, c, req)
206			requestProtocol(t, c, dev, dl.WpDrmLeaseDeviceV1RequestCreateLeaseRequest, req)
207			if tc.duplicate {
208				conn := p.connectors[0]
209				requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, conn)
210				requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, conn)
211			} else {
212				lease := c.AllocateID()
213				registerProtocol(t, c, lease)
214				requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestSubmit, lease)
215			}
216			expectProtocolError(t, c, req, tc.code)
217		})
218	}
219}
220func TestDRMLeaseFinished(t *testing.T) {
221	c, requests, events := leaseServer(t)
222	dev := bindProtocol(t, c, "wp_drm_lease_device_v1")
223	p := &leaseDeviceEvents{c: c}
224	p.SetID(dev)
225	registerWireProxy(c, p)
226	if err := c.Roundtrip(); err != nil {
227		t.Fatal(err)
228	}
229	defer unix.Close(p.fds[0])
230	req := c.AllocateID()
231	registerProtocol(t, c, req)
232	requestProtocol(t, c, dev, dl.WpDrmLeaseDeviceV1RequestCreateLeaseRequest, req)
233	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, p.connectors[0])
234	lease := c.AllocateID()
235	l := &leaseResultEvents{}
236	l.SetID(lease)
237	registerWireProxy(c, l)
238	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestSubmit, lease)
239	if err := c.Roundtrip(); err != nil {
240		t.Fatal(err)
241	}
242	got := (<-requests).(ports.LeaseRequest)
243	r, w, err := os.Pipe()
244	if err != nil {
245		t.Fatal(err)
246	}
247	defer r.Close()
248	events <- ports.LeaseReply{ID: got.ID, FD: w, LeaseID: 8}
249	if err := c.Roundtrip(); err != nil {
250		t.Fatal(err)
251	}
252	for deadline := time.Now().Add(2 * time.Second); len(l.fds) == 0 && time.Now().Before(deadline); {
253		if err := c.Roundtrip(); err != nil {
254			t.Fatal(err)
255		}
256	}
257	if len(l.fds) != 1 {
258		t.Fatal("missing lease fd")
259	}
260	defer unix.Close(l.fds[0])
261	events <- ports.LeaseFinished{Card: got.Card, LeaseID: 8}
262	if err := c.Roundtrip(); err != nil {
263		t.Fatal(err)
264	}
265	for deadline := time.Now().Add(2 * time.Second); l.finished == 0 && time.Now().Before(deadline); {
266		if err := c.Roundtrip(); err != nil {
267			t.Fatal(err)
268		}
269	}
270	if l.finished != 1 {
271		t.Fatalf("finished %d", l.finished)
272	}
273}
274
275func TestLeaseMessagesRemainOrderedWhenChannelFull(t *testing.T) {
276	requests := make(chan ports.LeaseMessage, 1)
277	ready := make(chan struct{}, 1)
278	display, err := server.NewDisplay()
279	if err != nil {
280		t.Fatal(err)
281	}
282	defer display.Close()
283	s := &Server{display: display, channels: Channels{LeaseRequests: requests}, leaseReady: ready}
284	requests <- ports.LeaseRequest{ID: 99}
285	ctx, cancel := context.WithCancel(context.Background())
286	done := make(chan struct{})
287	go func() { s.forwardLeaseRequests(ctx); close(done) }()
288	defer func() { cancel(); <-done }()
289	s.sendLeaseRequest(ports.LeaseRequest{ID: 1})
290	s.sendLeaseRequest(ports.LeaseRevoke{Card: "card", LeaseID: 1})
291	if got := <-requests; got.(ports.LeaseRequest).ID != 99 {
292		t.Fatal(got)
293	}
294	select {
295	case got := <-requests:
296		if v, ok := got.(ports.LeaseRequest); !ok || v.ID != 1 {
297			t.Fatalf("first %v", got)
298		}
299	case <-time.After(time.Second):
300		t.Fatal("request timeout")
301	}
302	select {
303	case got := <-requests:
304		if v, ok := got.(ports.LeaseRevoke); !ok || v.LeaseID != 1 {
305			t.Fatalf("second %v", got)
306		}
307	case <-time.After(time.Second):
308		t.Fatal("revoke timeout")
309	}
310}
311
312func TestLeaseIDsAreScopedToCard(t *testing.T) {
313	dir := t.TempDir()
314	requests := make(chan ports.LeaseMessage, 16)
315	events := make(chan ports.LeaseMessage, 16)
316	s, err := New(Options{RuntimeDir: dir, Outputs: testOutputs}, Channels{LeaseRequests: requests, LeaseEvents: events}, logging.For(context.Background(), "wayland"))
317	if err != nil {
318		t.Fatal(err)
319	}
320	ctx, cancel := context.WithCancel(context.Background())
321	done := make(chan error, 1)
322	go func() { done <- s.Run(ctx) }()
323	t.Cleanup(func() {
324		cancel()
325		if err := <-done; err != nil {
326			t.Error(err)
327		}
328	})
329	c := protocolClient(t, s, dir)
330	for i, card := range []string{"card-one", "card-two"} {
331		events <- ports.LeaseConnectors{Card: card, Device: leaseFD(t), Connectors: []ports.LeaseConnector{{Card: card, Name: fmt.Sprintf("DP-%d", i+1), ConnectorID: uint32(i + 40)}}}
332	}
333	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
334		if err := c.Roundtrip(); err != nil {
335			t.Fatal(err)
336		}
337		count := 0
338		for _, g := range c.Registry().GetGlobals() {
339			if g.Interface == "wp_drm_lease_device_v1" {
340				count++
341			}
342		}
343		if count == 2 {
344			break
345		}
346	}
347	var devices []uint32
348	for _, g := range c.Registry().GetGlobals() {
349		if g.Interface == "wp_drm_lease_device_v1" {
350			id, err := bindWireID(c, g.Name, g.Interface, 1)
351			if err != nil {
352				t.Fatal(err)
353			}
354			devices = append(devices, id)
355		}
356	}
357	if len(devices) != 2 {
358		t.Fatalf("device globals: %d", len(devices))
359	}
360	var bound []*leaseDeviceEvents
361	for _, dev := range devices {
362		p := &leaseDeviceEvents{c: c}
363		p.SetID(dev)
364		registerWireProxy(c, p)
365		bound = append(bound, p)
366	}
367	if err := c.Roundtrip(); err != nil {
368		t.Fatal(err)
369	}
370	for i, dev := range devices {
371		p := bound[i]
372		if len(p.connectors) != 1 || len(p.fds) != 1 {
373			t.Fatalf("device %d: %+v", dev, p)
374		}
375		defer unix.Close(p.fds[0])
376		req := c.AllocateID()
377		registerProtocol(t, c, req)
378		requestProtocol(t, c, dev, dl.WpDrmLeaseDeviceV1RequestCreateLeaseRequest, req)
379		requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, p.connectors[0])
380		id := c.AllocateID()
381		l := &leaseResultEvents{}
382		l.SetID(id)
383		registerWireProxy(c, l)
384		requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestSubmit, id)
385	}
386	if err := c.Roundtrip(); err != nil {
387		t.Fatal(err)
388	}
389	first := (<-requests).(ports.LeaseRequest)
390	second := (<-requests).(ports.LeaseRequest)
391	if first.Card == second.Card {
392		t.Fatalf("same card: %v %v", first, second)
393	}
394	for _, req := range []ports.LeaseRequest{first, second} {
395		r, w, err := os.Pipe()
396		if err != nil {
397			t.Fatal(err)
398		}
399		defer r.Close()
400		events <- ports.LeaseReply{ID: req.ID, FD: w, LeaseID: 7}
401	}
402	if err := c.Roundtrip(); err != nil {
403		t.Fatal(err)
404	}
405	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
406		count := 0
407		s.display.Do(func() { count = len(s.activeLeases) })
408		if count == 2 {
409			break
410		}
411		if err := c.Roundtrip(); err != nil {
412			t.Fatal(err)
413		}
414	}
415	if !s.display.Do(func() {
416		if len(s.activeLeases) != 2 {
417			t.Errorf("active leases %v", s.activeLeases)
418		}
419	}) {
420		t.Fatal("display stopped")
421	}
422	events <- ports.LeaseFinished{Card: first.Card, LeaseID: 7}
423	if err := c.Roundtrip(); err != nil {
424		t.Fatal(err)
425	}
426	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
427		count := 0
428		s.display.Do(func() { count = len(s.activeLeases) })
429		if count == 1 {
430			break
431		}
432		if err := c.Roundtrip(); err != nil {
433			t.Fatal(err)
434		}
435	}
436	if !s.display.Do(func() {
437		if len(s.activeLeases) != 1 || s.activeLeases[leaseKey{second.Card, 7}] == nil {
438			t.Errorf("wrong card finished: %v", s.activeLeases)
439		}
440	}) {
441		t.Fatal("display stopped")
442	}
443}
444
445func TestLeaseDestroyPendingRevokesLateReply(t *testing.T) {
446	c, requests, events, s := leaseServerWithState(t)
447	dev := bindProtocol(t, c, "wp_drm_lease_device_v1")
448	p := &leaseDeviceEvents{c: c}
449	p.SetID(dev)
450	registerWireProxy(c, p)
451	if err := c.Roundtrip(); err != nil {
452		t.Fatal(err)
453	}
454	defer unix.Close(p.fds[0])
455	req := c.AllocateID()
456	registerProtocol(t, c, req)
457	requestProtocol(t, c, dev, dl.WpDrmLeaseDeviceV1RequestCreateLeaseRequest, req)
458	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestRequestConnector, p.connectors[0])
459	lease := c.AllocateID()
460	registerProtocol(t, c, lease)
461	requestProtocol(t, c, req, dl.WpDrmLeaseRequestV1RequestSubmit, lease)
462	if err := c.Roundtrip(); err != nil {
463		t.Fatal(err)
464	}
465	var sent ports.LeaseRequest
466	select {
467	case msg := <-requests:
468		sent = msg.(ports.LeaseRequest)
469	case <-time.After(time.Second):
470		t.Fatal("no lease request")
471	}
472	requestProtocol(t, c, lease, dl.WpDrmLeaseV1RequestDestroy)
473	if err := c.Roundtrip(); err != nil {
474		t.Fatal(err)
475	}
476	if !s.display.Do(func() {
477		if len(s.pendingLeases) != 0 || s.pendingLeaseCards[sent.ID] != sent.Card {
478			t.Errorf("pending tombstone: %v %v", s.pendingLeases, s.pendingLeaseCards)
479		}
480	}) {
481		t.Fatal("display stopped")
482	}
483	if !safelyLeaseMaps(t, c, events, requests, sent) {
484		t.Fatal("pending reply not revoked")
485	}
486	if !s.display.Do(func() {
487		if len(s.pendingLeases) != 0 || len(s.pendingLeaseCards) != 0 {
488			t.Errorf("pending leaked: %v %v", s.pendingLeases, s.pendingLeaseCards)
489		}
490	}) {
491		t.Fatal("display stopped")
492	}
493}
494
495// A destroyed lease keeps only the card tombstone until the DRM reply arrives.
496func safelyLeaseMaps(t *testing.T, c *wlturbo.Display, events chan ports.LeaseMessage, requests chan ports.LeaseMessage, sent ports.LeaseRequest) bool {
497	t.Helper()
498	r, w, err := os.Pipe()
499	if err != nil {
500		t.Fatal(err)
501	}
502	defer r.Close()
503	events <- ports.LeaseReply{ID: sent.ID, FD: w, LeaseID: 51}
504	for deadline := time.Now().Add(2 * time.Second); time.Now().Before(deadline); {
505		if err := c.Roundtrip(); err != nil {
506			t.Fatal(err)
507		}
508		select {
509		case event := <-requests:
510			revoked, ok := event.(ports.LeaseRevoke)
511			return ok && revoked.Card == sent.Card && revoked.LeaseID == 51
512		default:
513		}
514	}
515	return false
516}
517
518func TestLeaseGlobalRemoveAndReadd(t *testing.T) {
519	c, _, events := leaseServer(t)
520	count := func() int {
521		n := 0
522		for _, g := range c.Registry().GetGlobals() {
523			if g.Interface == "wp_drm_lease_device_v1" {
524				n++
525			}
526		}
527		return n
528	}
529	if count() != 1 {
530		t.Fatalf("initial globals %d", count())
531	}
532	events <- ports.LeaseConnectors{Card: "/dev/dri/card-test"}
533	for deadline := time.Now().Add(2 * time.Second); count() != 0 && time.Now().Before(deadline); {
534		if err := c.Roundtrip(); err != nil {
535			t.Fatal(err)
536		}
537	}
538	if count() != 0 {
539		t.Fatal("lease global not removed")
540	}
541	events <- ports.LeaseConnectors{Card: "/dev/dri/card-test", Device: leaseFD(t), Connectors: []ports.LeaseConnector{{Card: "/dev/dri/card-test", Name: "DP-1", ConnectorID: 42}}}
542	for deadline := time.Now().Add(2 * time.Second); count() != 1 && time.Now().Before(deadline); {
543		if err := c.Roundtrip(); err != nil {
544			t.Fatal(err)
545		}
546	}
547	if count() != 1 {
548		t.Fatal("lease global not re-added")
549	}
550}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 25

Tags 10

↑↓ moveenter openesc close