1package drm
2
3import (
4 "context"
5 "os"
6 "sync/atomic"
7 "testing"
8 "testing/synctest"
9 "time"
10
11 portsmocks "github.com/bnema/neferwl/internal/mocks/ports"
12 "github.com/bnema/neferwl/internal/ports"
13 "github.com/stretchr/testify/mock"
14)
15
16func TestSecurityRunWakePendingResumeAndOff(t *testing.T) {
17 synctest.Test(t, securityRunWakePendingResumeAndOff)
18}
19
20func securityRunWakePendingResumeAndOff(t *testing.T) {
21 o, k, commits, mu := testOutputMu(t)
22 o.fbs = [2]uint32{} // the first setup of a run: no image yet
23 o.cursor, o.tearing = nil, false
24 var state atomic.Uint64
25 securityGate(t, o, &state)
26 events := make(chan ports.SecurityBackendEvent, 8)
27 o.SecurityEvents = events
28 wake := make(chan ports.SecurityState, 1)
29 o.SecurityChanges = wake
30 active := make(chan bool, 2)
31 flips := make(chan flipEvent, 2)
32 o.flipped = flips
33 scenes := make(chan ports.Scene, 2)
34 r := portsmocks.NewMockRenderer(t)
35 buf := func() ports.DMABuf {
36 f, w := protectionFence(t, false)
37 w.Close()
38 return ports.DMABuf{Planes: []ports.DMABufPlane{{File: f}}}
39 }
40 r.EXPECT().SetHDR(float64(0)).Return().Maybe()
41 r.EXPECT().ExportTargets(2, mock.Anything, false).Return([]ports.DMABuf{buf(), buf()}, nil).Once()
42 r.EXPECT().UseTarget(mock.Anything).Return()
43 black := make(chan struct{}, 8)
44 desktop := make(chan struct{}, 2)
45 r.EXPECT().Render(mock.Anything, mock.Anything).RunAndReturn(func(s ports.Scene, _ map[ports.WindowID]ports.SurfaceContent) (*os.File, error) {
46 if s.Seq == 99 {
47 desktop <- struct{}{}
48 } else {
49 black <- struct{}{}
50 }
51 return nil, nil
52 })
53 r.EXPECT().Close().Return().Once()
54 k.EXPECT().addFB(mock.Anything, uint32(fourccXRGB)).Return(70, nil)
55 k.EXPECT().createBlob(mock.Anything).Return(99, nil)
56 k.EXPECT().destroyBlob(mock.Anything).Return(nil).Maybe()
57 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
58 defer cancel()
59 done := make(chan error, 1)
60 go func() {
61 done <- o.Run(ctx, func(int, int) (ports.Renderer, error) { return r, nil }, nil, active, scenes, nil, nil, nil, discardSink(t), nil, nil)
62 }()
63 scenes <- ports.Scene{Seq: 99, Scale: 1}
64 select {
65 case <-desktop:
66 case <-ctx.Done():
67 t.Fatal("no desktop frame")
68 }
69 var frame commitRec
70 waitFor(t, func() bool {
71 mu.Lock()
72 defer mu.Unlock()
73 for _, c := range *commits {
74 if c.user&3 == userFrame {
75 frame = c
76 return true
77 }
78 }
79 return false
80 })
81 state.Store(3)
82 wake <- ports.SecurityState{} // stale payload: read gate, not payload
83 synctest.Wait()
84 select {
85 case ev := <-events:
86 t.Fatalf("proof while old frame pending: %+v", ev)
87 default:
88 }
89 flips <- eventOf(frame)
90 proof := func(kind ports.ProtectionKind) {
91 t.Helper()
92 for {
93 select {
94 case ev := <-events:
95 if p, ok := ev.(ports.SecurityOutputProof); ok {
96 if p.Proof != (ports.OutputProtection{Generation: 1, Instance: 77, Kind: kind}) {
97 t.Fatalf("proof %+v", p)
98 }
99 return
100 }
101 case <-ctx.Done():
102 t.Fatal("missing security proof")
103 }
104 }
105 }
106 proof(ports.ProtectionProtectedFrame)
107 scenes <- ports.Scene{Seq: 99} // held desktop scene cannot be replayed
108 synctest.Wait()
109 select {
110 case <-desktop:
111 t.Fatal("stale desktop rendered under protection")
112 default:
113 }
114 active <- false
115 select {
116 case ev := <-events:
117 if _, ok := ev.(ports.SecurityOutputInvalidated); !ok {
118 t.Fatalf("disable did not invalidate before resume: %+v", ev)
119 }
120 case <-ctx.Done():
121 t.Fatal("missing disable invalidation")
122 }
123 active <- true
124 select {
125 case ev := <-events:
126 if _, ok := ev.(ports.SecurityOutputInvalidated); !ok {
127 t.Fatalf("resume proof preceded invalidation: %+v", ev)
128 }
129 case <-ctx.Done():
130 t.Fatal("missing resume invalidation")
131 }
132 proof(ports.ProtectionProtectedFrame) // reacquire requires a fresh black transaction
133 scenes <- ports.Scene{Security: ports.SecurityState{Generation: 1, Protected: true}, Off: true}
134 proof(ports.ProtectionInactiveOutput)
135 cancel()
136 if err := <-done; err != nil {
137 t.Fatal(err)
138 }
139}