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