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