1package xturbo_test
2
3import (
4 "bytes"
5 "encoding/binary"
6 "testing"
7
8 "bnema.dev/xturbo"
9 "bnema.dev/xturbo/internal/xtest"
10 "bnema.dev/xturbo/render"
11 "bnema.dev/xturbo/shape"
12 "bnema.dev/xturbo/xfixes"
13 "bnema.dev/xturbo/xproto"
14)
15
16// Xvfb negotiated XFIXES as major 138, first event 87, first error 140 when the
17// goldens were captured; the BadRegion and SelectionNotify goldens confirm it.
18const (
19 xfixesMajor, xfixesEvent, xfixesError = 138, 87, 140
20 geMajor = 0x83 // hand-written XGE golden
21)
22
23func loadEvent(t testing.TB, name string) []byte {
24 t.Helper()
25 raw, err := xtest.LoadHex("testdata/golden/" + name + ".event.hex")
26 if err != nil {
27 t.Fatal(err)
28 }
29 return raw
30}
31
32func goldenExt() *xturbo.ExtTable {
33 var ext xturbo.ExtTable
34 ext.Set(xturbo.ExtXFixes, xfixesMajor, xfixesEvent, xfixesError)
35 ext.Set(xturbo.ExtGenericEvent, geMajor, 0, 0)
36 ext.Set(xturbo.ExtShape, 129, 64, 0)
37 return &ext
38}
39
40func decode(t testing.TB, raw []byte, ext *xturbo.ExtTable) xturbo.Event {
41 t.Helper()
42 ev, n, ok := xturbo.DecodeEvent(raw, ext)
43 if !ok || n != len(raw) || !bytes.Equal(ev.Raw[:], raw[:32]) {
44 t.Fatalf("DecodeEvent(%x) = n %d ok %v", raw, n, ok)
45 }
46 return ev
47}
48
49// Expected values follow from the capture script (second client's window at
50// 10,20 300x200, ConfigureWindow x=-5 y=40 width=640, a format-32 ClientMessage
51// with words 0x01020304*(i+1), SetSelectionOwner PRIMARY) or from raw golden
52// offsets defined by the X11 protocol, not from generated code.
53func TestEventGoldens(t *testing.T) {
54 u32 := func(raw []byte, off int) uint32 { return binary.LittleEndian.Uint32(raw[off:]) }
55 ext := goldenExt()
56 core := func(name string, kind xturbo.EventKind) ([]byte, xturbo.Event) {
57 raw := loadEvent(t, "xproto/"+name)
58 ev := decode(t, raw, ext)
59 if ev.Kind != kind || ev.Ext != xturbo.ExtCore || ev.Seq != xturbo.Seq(binary.LittleEndian.Uint16(raw[2:])) || ev.Tail != nil {
60 t.Fatalf("%s: kind %d ext %d seq %d", name, ev.Kind, ev.Ext, ev.Seq)
61 }
62 return raw, ev
63 }
64 t.Run("MapRequest", func(t *testing.T) {
65 raw, ev := core("MapRequest", xproto.KindMapRequest)
66 e := xproto.MapRequestOf(&ev)
67 if ev.SendEvent || e.Parent() != xproto.Window(u32(raw, 4)) || e.Window() != xproto.Window(u32(raw, 8)) || e.Window()&0xffff != 1 {
68 t.Fatal("fields")
69 }
70 d := xproto.MapRequestEventData{Parent: e.Parent(), Window: e.Window()}
71 var out [32]byte
72 d.Put(&out)
73 want := [32]byte(raw)
74 want[2], want[3] = 0, 0 // Put leaves the sequence to the server
75 if out != want {
76 t.Fatalf("Put %x want %x", out, want)
77 }
78 })
79 t.Run("ConfigureRequest", func(t *testing.T) {
80 raw, ev := core("ConfigureRequest", xproto.KindConfigureRequest)
81 e := xproto.ConfigureRequestOf(&ev)
82 if e.StackMode() != xproto.StackModeAbove || e.Parent() != xproto.Window(u32(raw, 4)) || e.Window()&0xffff != 1 ||
83 e.Sibling() != xproto.WindowNone || e.X() != -5 || e.Y() != 40 || e.Width() != 640 || e.Height() != 200 ||
84 e.BorderWidth() != 0 || e.ValueMask() != xproto.ConfigWindowX|xproto.ConfigWindowY|xproto.ConfigWindowWidth {
85 t.Fatalf("fields x=%d y=%d w=%d h=%d mask=%d", e.X(), e.Y(), e.Width(), e.Height(), e.ValueMask())
86 }
87 d := xproto.ConfigureRequestEventData{StackMode: e.StackMode(), Parent: e.Parent(), Window: e.Window(), Sibling: e.Sibling(),
88 X: e.X(), Y: e.Y(), Width: e.Width(), Height: e.Height(), BorderWidth: e.BorderWidth(), ValueMask: e.ValueMask()}
89 var out [32]byte
90 d.Put(&out)
91 if !bytes.Equal(out[4:], raw[4:]) || out[0] != 23 || out[1] != raw[1] {
92 t.Fatalf("Put %x", out)
93 }
94 })
95 t.Run("PropertyNotify", func(t *testing.T) {
96 raw, ev := core("PropertyNotify", xproto.KindPropertyNotify)
97 e := xproto.PropertyNotifyOf(&ev)
98 if e.Window() != xproto.Window(u32(raw, 4)) || e.Atom() != xproto.Atom(u32(raw, 8)) || e.Atom() < 69 ||
99 e.Time() != xproto.Timestamp(u32(raw, 12)) || e.State() != xproto.PropertyNewValue {
100 t.Fatal("fields")
101 }
102 })
103 t.Run("ClientMessage", func(t *testing.T) {
104 raw, ev := core("ClientMessage", xproto.KindClientMessage)
105 e := xproto.ClientMessageOf(&ev)
106 if !ev.SendEvent || e.Format() != 32 || e.Window()&0xffff != 1 || e.Type() != xproto.Atom(u32(raw, 8)) {
107 t.Fatal("fields")
108 }
109 words := e.Data().Data32()
110 for i, w := range words {
111 if w != uint32(0x01020304*(i+1)) {
112 t.Fatalf("data32[%d] = %x", i, w)
113 }
114 }
115 d := xproto.ClientMessageEventData{Format: 32, Window: e.Window(), Type: e.Type(), Data: e.Data()}
116 var out [32]byte
117 d.Put(&out)
118 want := [32]byte(raw)
119 want[0] &^= 0x80
120 want[2], want[3] = 0, 0
121 if out != want {
122 t.Fatalf("Put %x want %x", out, want)
123 }
124 })
125 t.Run("CreateNotify", func(t *testing.T) {
126 _, ev := core("CreateNotify", xproto.KindCreateNotify)
127 e := xproto.CreateNotifyOf(&ev)
128 if e.X() != 10 || e.Y() != 20 || e.Width() != 300 || e.Height() != 200 || e.OverrideRedirect() {
129 t.Fatal("fields")
130 }
131 })
132 t.Run("Map/Unmap/DestroyNotify", func(t *testing.T) {
133 _, ev := core("MapNotify", xproto.KindMapNotify)
134 if m := xproto.MapNotifyOf(&ev); m.Window()&0xffff != 1 || m.OverrideRedirect() {
135 t.Fatal("MapNotify")
136 }
137 _, ev = core("UnmapNotify", xproto.KindUnmapNotify)
138 if u := xproto.UnmapNotifyOf(&ev); u.Window()&0xffff != 1 || u.FromConfigure() {
139 t.Fatal("UnmapNotify")
140 }
141 _, ev = core("DestroyNotify", xproto.KindDestroyNotify)
142 if d := xproto.DestroyNotifyOf(&ev); d.Window()&0xffff != 1 {
143 t.Fatal("DestroyNotify")
144 }
145 })
146 t.Run("SelectionNotify", func(t *testing.T) {
147 _, ev := core("SelectionNotify", xproto.KindSelectionNotify)
148 e := xproto.SelectionNotifyOf(&ev)
149 if e.Requestor()&0xffff != 1 || e.Selection() != xproto.AtomSecondary || e.Target() != xproto.AtomString || e.Property() != xproto.AtomNone {
150 t.Fatal("fields")
151 }
152 })
153 t.Run("xfixes/SelectionNotify", func(t *testing.T) {
154 raw := loadEvent(t, "xfixes/SelectionNotify")
155 ev := decode(t, raw, ext)
156 if xfixes.KindSelectionNotify != xturbo.EventKind(xturbo.ExtXFixes)<<8 {
157 t.Fatal("KindSelectionNotify value")
158 }
159 if ev.Kind != xfixes.KindSelectionNotify || ev.Ext != xturbo.ExtXFixes {
160 t.Fatalf("kind %x", ev.Kind)
161 }
162 e := xfixes.SelectionNotifyOf(&ev)
163 if e.Subtype() != xfixes.SelectionEventSetSelectionOwner || e.Window() != xproto.Window(u32(raw, 4)) ||
164 e.Owner()&0xffff != 1 || e.Selection() != xproto.AtomPrimary || e.Timestamp() != e.SelectionTimestamp() {
165 t.Fatal("fields")
166 }
167 // Without XFIXES negotiated the same bytes are an unknown event.
168 if ev, _, ok := xturbo.DecodeEvent(raw, nil); !ok || ev.Kind != xturbo.KindUnknown {
169 t.Fatal("nil ext must decode as unknown")
170 }
171 })
172 t.Run("KeymapNotify", func(t *testing.T) {
173 raw := loadEvent(t, "xproto/KeymapNotify")
174 ev := decode(t, raw, ext)
175 if ev.Kind != xproto.KindKeymapNotify || ev.Seq != 0 {
176 t.Fatalf("kind %d seq %d", ev.Kind, ev.Seq)
177 }
178 e := xproto.KeymapNotifyOf(&ev)
179 if !bytes.Equal(e.Keys(), raw[1:]) {
180 t.Fatal("keys")
181 }
182 d := xproto.KeymapNotifyEventData{Keys: [31]uint8(raw[1:])}
183 var out [32]byte
184 d.Put(&out)
185 if !bytes.Equal(out[:], raw) {
186 t.Fatalf("Put %x", out)
187 }
188 })
189 t.Run("GeGeneric", func(t *testing.T) {
190 raw := loadEvent(t, "xproto/GeGeneric")
191 ev := decode(t, raw, ext)
192 if ev.Kind != xturbo.EventKind(xturbo.ExtGenericEvent)<<8|0x80|1 || ev.Ext != xturbo.ExtGenericEvent || ev.Seq != 5 ||
193 !bytes.Equal(ev.Tail, raw[32:]) {
194 t.Fatalf("kind %x tail %x", ev.Kind, ev.Tail)
195 }
196 g, ok := xproto.GeGenericOf(&ev)
197 if !ok || g.Extension() != geMajor || g.Length() != 2 || g.EventType() != 1 || !bytes.Equal(g.Tail(), raw[32:]) {
198 t.Fatal("view")
199 }
200 // Unknown major: still framed so Dispatch skips the tail.
201 ev, n, ok := xturbo.DecodeEvent(raw, nil)
202 if !ok || n != 40 || ev.Kind != xturbo.KindUnknown || len(ev.Tail) != 8 {
203 t.Fatal("unknown XGE framing")
204 }
205 ev.Tail = ev.Tail[:4]
206 if _, ok := xproto.GeGenericOf(&ev); ok {
207 t.Fatal("short Tail accepted")
208 }
209 })
210}
211
212// TestEventDataPutInPlace re-encodes events into the Event.Raw their fields
213// were read from. ClientMessage's Data is a union view over Raw; KeymapNotify's
214// Keys is a slice view over Raw. Put must read every field before writing dst.
215func TestEventDataPutInPlace(t *testing.T) {
216 raw := loadEvent(t, "xproto/ClientMessage")
217 ev := decode(t, raw, nil)
218 e := xproto.ClientMessageOf(&ev)
219 d := xproto.ClientMessageEventData{Format: e.Format(), Window: e.Window(), Type: e.Type(), Data: e.Data()}
220 d.Put(&ev.Raw)
221 want := [32]byte(raw)
222 want[0] &^= 0x80
223 want[2], want[3] = 0, 0
224 if ev.Raw != want {
225 t.Fatalf("ClientMessage in place %x want %x", ev.Raw, want)
226 }
227 if n := testing.AllocsPerRun(1000, func() { d.Data = e.Data(); d.Put(&ev.Raw) }); n != 0 {
228 t.Fatalf("in-place Put allocates %v times", n)
229 }
230 raw = loadEvent(t, "xproto/KeymapNotify")
231 ev = decode(t, raw, nil)
232 keys := xproto.KeymapNotifyOf(&ev).Keys()
233 k := xproto.KeymapNotifyEventData{Keys: [31]uint8(keys)}
234 k.Put(&ev.Raw)
235 if ev.Raw != [32]byte(raw) || !bytes.Equal(keys, raw[1:]) {
236 t.Fatalf("KeymapNotify in place %x", ev.Raw)
237 }
238}
239
240func TestEventCopyAliases(t *testing.T) {
241 if xproto.KindButtonRelease != 5 || xproto.KindKeyRelease != 3 || xproto.KindLeaveNotify != 8 || xproto.KindFocusOut != 10 || xproto.KindCirculateRequest != 27 {
242 t.Fatal("eventcopy kinds")
243 }
244 var raw [32]byte
245 raw[0], raw[1] = 5, 3 // ButtonRelease, button 3
246 binary.LittleEndian.PutUint16(raw[24:], 0x100)
247 ev := decode(t, raw[:], nil)
248 var r xproto.ButtonReleaseEvent = xproto.ButtonReleaseOf(&ev)
249 var p xproto.ButtonPressEvent = r // alias: same type
250 if p.Detail() != 3 || r.EventX() != 0x100 {
251 t.Fatal("fields")
252 }
253 d := xproto.ButtonReleaseEventData{Detail: 3, EventX: 0x100}
254 var out [32]byte
255 d.Put(&out)
256 raw[2], raw[3] = 0, 0
257 if out != raw {
258 t.Fatalf("Put %x", out)
259 }
260}
261
262func TestShapeEvent(t *testing.T) {
263 var raw [32]byte
264 raw[0], raw[1] = 64, 2 // first event 64, SK Input
265 binary.LittleEndian.PutUint32(raw[4:], 0x400001)
266 binary.LittleEndian.PutUint16(raw[8:], 0xfffe)
267 raw[20] = 1 // shaped: after window(4) extents(8) server_time(4)
268 ev := decode(t, raw[:], goldenExt())
269 if ev.Kind != shape.KindNotify || ev.Ext != xturbo.ExtShape {
270 t.Fatalf("kind %x", ev.Kind)
271 }
272 e := shape.NotifyOf(&ev)
273 if e.ShapeKind() != shape.SkInput || e.AffectedWindow() != 0x400001 || e.ExtentsX() != -2 || !e.Shaped() {
274 t.Fatal("fields")
275 }
276}
277
278func TestDecodeEventRejects(t *testing.T) {
279 raw := make([]byte, 64)
280 for _, code := range []byte{0, 1, 0x80, 0x81} {
281 raw[0] = code
282 if _, _, ok := xturbo.DecodeEvent(raw, nil); ok {
283 t.Fatalf("code %#x accepted", code)
284 }
285 }
286 raw[0] = 2
287 if _, _, ok := xturbo.DecodeEvent(raw[:31], nil); ok {
288 t.Fatal("short event accepted")
289 }
290 raw[0] = 35
291 binary.LittleEndian.PutUint32(raw[4:], 9) // 36 tail bytes > 32 available
292 if _, _, ok := xturbo.DecodeEvent(raw, nil); ok {
293 t.Fatal("truncated XGE accepted")
294 }
295 binary.LittleEndian.PutUint32(raw[4:], 0xffffffff)
296 if _, _, ok := xturbo.DecodeEvent(raw, nil); ok {
297 t.Fatal("huge XGE length accepted")
298 }
299 raw[0] = 100 // extension range, nothing negotiated
300 if ev, n, ok := xturbo.DecodeEvent(raw, goldenExt()); !ok || n != 32 || ev.Kind != xturbo.KindUnknown {
301 t.Fatal("unknown extension event")
302 }
303 raw[0] = 40 // core range gap 36..63
304 if ev, _, ok := xturbo.DecodeEvent(raw, goldenExt()); !ok || ev.Kind != xturbo.KindUnknown {
305 t.Fatal("unassigned core code")
306 }
307 raw[0] = 35 // XGE evtype >= 128 is never a known kind
308 binary.LittleEndian.PutUint32(raw[4:], 0)
309 raw[1] = geMajor
310 binary.LittleEndian.PutUint16(raw[8:], 0x80)
311 if ev, _, ok := xturbo.DecodeEvent(raw, goldenExt()); !ok || ev.Kind != xturbo.KindUnknown {
312 t.Fatal("XGE evtype >= 128")
313 }
314}
315
316func TestExtTableReverseMaps(t *testing.T) {
317 ext := goldenExt()
318 if id, rel, ok := ext.ByEvent(xfixesEvent + 1); !ok || id != xturbo.ExtXFixes || rel != 1 {
319 t.Fatal("ByEvent CursorNotify")
320 }
321 if _, _, ok := ext.ByEvent(xfixesEvent + 2); ok {
322 t.Fatal("XFIXES declares two events")
323 }
324 if id, rel, ok := ext.ByError(xfixesError); !ok || id != xturbo.ExtXFixes || rel != xfixes.BadRegion {
325 t.Fatal("ByError")
326 }
327 if id, ok := ext.ByMajor(xfixesMajor); !ok || id != xturbo.ExtXFixes {
328 t.Fatal("ByMajor")
329 }
330 ext.Set(xturbo.ExtXFixes, 140, 90, 150) // re-negotiation drops stale entries
331 if _, ok := ext.ByMajor(xfixesMajor); ok {
332 t.Fatal("stale major")
333 }
334 if _, _, ok := ext.ByEvent(xfixesEvent); ok {
335 t.Fatal("stale event")
336 }
337 if id, _, ok := ext.ByError(150); !ok || id != xturbo.ExtXFixes {
338 t.Fatal("new error base")
339 }
340 var render xturbo.ExtTable
341 render.Set(xturbo.ExtRender, 139, 0, 142)
342 for code := uint8(142); code < 147; code++ {
343 if id, rel, ok := render.ByError(code); !ok || id != xturbo.ExtRender || render.Info(id).FirstError+rel != code {
344 t.Fatalf("render error %d", code)
345 }
346 }
347 if _, _, ok := render.ByError(147); ok {
348 t.Fatal("RENDER declares five errors")
349 }
350}
351
352func TestErrorNames(t *testing.T) {
353 if xproto.BadWindow != 3 || xproto.BadAtom != 5 || xproto.BadImplementation != 17 ||
354 xproto.ErrorName(xproto.BadWindow) != "BadWindow" || xproto.ErrorName(0) != "" || xproto.ErrorName(200) != "" {
355 t.Fatal("xproto errors")
356 }
357 if xfixes.BadRegion != 0 || xfixes.ErrorName(0) != "BadRegion" {
358 t.Fatal("xfixes errors")
359 }
360 for code, name := range []string{"BadPictFormat", "BadPicture", "BadPictOp", "BadGlyphSet", "BadGlyph"} {
361 if render.ErrorName(uint8(code)) != name {
362 t.Fatalf("render %d", code)
363 }
364 }
365}
366
367// mixedEvents is 10k events: core, xfixes, XGE with a tail and unknown codes.
368func mixedEvents(t testing.TB) []byte {
369 var golden [][]byte
370 for _, name := range []string{"xproto/MapRequest", "xproto/ConfigureRequest", "xproto/PropertyNotify", "xproto/ClientMessage",
371 "xproto/KeymapNotify", "xfixes/SelectionNotify", "xproto/GeGeneric"} {
372 golden = append(golden, loadEvent(t, name))
373 }
374 unknown := make([]byte, 32)
375 unknown[0] = 100
376 golden = append(golden, unknown)
377 var stream []byte
378 for i := range 10000 {
379 stream = append(stream, golden[i%len(golden)]...)
380 }
381 return stream
382}
383
384// eventSink touches the per-kind view getters a window manager reads.
385func eventSink(ev *xturbo.Event) uint64 {
386 switch ev.Kind {
387 case xproto.KindMapRequest:
388 e := xproto.MapRequestOf(ev)
389 return uint64(e.Window()) + uint64(e.Parent())
390 case xproto.KindConfigureRequest:
391 e := xproto.ConfigureRequestOf(ev)
392 return uint64(e.Window()) + uint64(e.Width()) + uint64(e.ValueMask()) + uint64(int64(e.X()))
393 case xproto.KindPropertyNotify:
394 e := xproto.PropertyNotifyOf(ev)
395 return uint64(e.Atom()) + uint64(e.State())
396 case xproto.KindClientMessage:
397 e := xproto.ClientMessageOf(ev)
398 return uint64(e.Type()) + uint64(e.Data().Data32()[0])
399 case xproto.KindKeymapNotify:
400 return uint64(len(xproto.KeymapNotifyOf(ev).Keys()))
401 case xfixes.KindSelectionNotify:
402 e := xfixes.SelectionNotifyOf(ev)
403 return uint64(e.Owner()) + uint64(e.Selection())
404 case xturbo.EventKind(xturbo.ExtGenericEvent)<<8 | 0x80 | 1:
405 if g, ok := xproto.GeGenericOf(ev); ok {
406 return uint64(len(g.Tail()))
407 }
408 }
409 return 0
410}
411
412func decodeAll(t testing.TB, stream []byte, ext *xturbo.ExtTable) (sum uint64) {
413 for len(stream) > 0 {
414 ev, n, ok := xturbo.DecodeEvent(stream, ext)
415 if !ok {
416 t.Fatal("decode")
417 }
418 sum += eventSink(&ev)
419 stream = stream[n:]
420 }
421 return sum
422}
423
424func TestAllocEvents(t *testing.T) {
425 stream := mixedEvents(t)
426 ext := goldenExt()
427 var sink uint64
428 if n := testing.AllocsPerRun(10, func() { sink += decodeAll(t, stream, ext) }); n != 0 {
429 t.Fatalf("DecodeEvent x10k + views allocate %v times", n)
430 }
431 cm := xproto.ClientMessageEventData{Format: 32, Window: 1, Type: 2}
432 cfg := xproto.ConfigureNotifyEventData{Window: 1, Width: 640, Height: 480}
433 var out [32]byte
434 if n := testing.AllocsPerRun(1000, func() { cm.Put(&out); cfg.Put(&out) }); n != 0 {
435 t.Fatalf("EventData.Put allocates %v times", n)
436 }
437 b := xturbo.NewBuf(0)
438 if n := testing.AllocsPerRun(1000, func() {
439 b.Reset()
440 cm.Put(&out)
441 xproto.SendEvent(b, false, 1, xproto.EventMaskSubstructureRedirect, &out)
442 }); n != 0 {
443 t.Fatalf("SendEvent allocates %v times", n)
444 }
445 _ = sink
446}
447
448func BenchmarkDecodeEvents10k(b *testing.B) {
449 stream := mixedEvents(b)
450 ext := goldenExt()
451 var sink uint64
452 b.SetBytes(int64(len(stream)))
453 b.ReportAllocs()
454 for b.Loop() {
455 sink += decodeAll(b, stream, ext)
456 }
457 _ = sink
458}
459
460// FuzzDecodeEvent: no panic; n is 32 or the XGE length; Raw copies the first
461// 32 bytes; Tail stays within raw; Kind is consistent with Ext. extSeed picks
462// which extensions are negotiated and where their bases lie.
463func FuzzDecodeEvent(f *testing.F) {
464 for _, name := range []string{"xproto/MapRequest", "xproto/ClientMessage", "xproto/KeymapNotify", "xfixes/SelectionNotify", "xproto/GeGeneric"} {
465 f.Add(loadEvent(f, name), uint32(0x8a578c83))
466 }
467 f.Add(make([]byte, 32), uint32(0))
468 f.Fuzz(func(t *testing.T, raw []byte, extSeed uint32) {
469 var ext xturbo.ExtTable
470 ext.Set(xturbo.ExtXFixes, uint8(extSeed>>24), uint8(extSeed>>16), uint8(extSeed>>8))
471 ext.Set(xturbo.ExtGenericEvent, uint8(extSeed), 0, 0)
472 ext.Set(xturbo.ExtShape, uint8(extSeed>>4), uint8(extSeed>>12)|64, 0)
473 table := &ext
474 if extSeed&1 != 0 {
475 table = nil
476 }
477 ev, n, ok := xturbo.DecodeEvent(raw, table)
478 if !ok {
479 if n != 0 {
480 t.Fatal("n on failure")
481 }
482 return
483 }
484 if len(raw) < 32 || raw[0]&0x7f < 2 || n > len(raw) || !bytes.Equal(ev.Raw[:], raw[:32]) || ev.SendEvent != (raw[0]&0x80 != 0) {
485 t.Fatalf("n %d", n)
486 }
487 if raw[0]&0x7f == 35 {
488 if n != 32+4*int(binary.LittleEndian.Uint32(raw[4:])) || len(ev.Tail) != n-32 {
489 t.Fatal("XGE framing")
490 }
491 } else if n != 32 || ev.Tail != nil {
492 t.Fatal("fixed framing")
493 }
494 if ev.Kind != xturbo.KindUnknown && ev.Kind>>8 != xturbo.EventKind(ev.Ext) {
495 t.Fatal("Kind/Ext mismatch")
496 }
497 _ = eventSink(&ev)
498 })
499}