master
go 1,685 lines 42 KB
Raw
1 package protocol
2
3 import (
4 "bytes"
5 "testing"
6 )
7
8 // ---------------------------------------------------------------------------
9 // Utility tests
10 // ---------------------------------------------------------------------------
11
12 func TestAlign8(t *testing.T) {
13 cases := []struct {
14 in, want int
15 }{
16 {0, 0},
17 {1, 8},
18 {7, 8},
19 {8, 8},
20 {9, 16},
21 {15, 16},
22 {16, 16},
23 {17, 24},
24 }
25 for _, tc := range cases {
26 if got := Align8(tc.in); got != tc.want {
27 t.Errorf("Align8(%d) = %d, want %d", tc.in, got, tc.want)
28 }
29 }
30 }
31
32 // ---------------------------------------------------------------------------
33 // Outer message header tests
34 // ---------------------------------------------------------------------------
35
36 func TestHeaderRoundtrip(t *testing.T) {
37 h := Header{
38 Magic: MagicMsg,
39 Version: Version,
40 HeaderLen: HeaderLen,
41 Kind: KindRequest,
42 Flags: FlagBatch,
43 Code: MethodCgroupsSnapshot,
44 TransportStatus: StatusOK,
45 PayloadLen: 12345,
46 ItemCount: 42,
47 MessageID: 0xDEADBEEFCAFEBABE,
48 }
49
50 var buf [64]byte
51 n := h.Encode(buf[:])
52 if n != 32 {
53 t.Fatalf("Encode returned %d, want 32", n)
54 }
55
56 out, err := DecodeHeader(buf[:n])
57 if err != nil {
58 t.Fatalf("Decode error: %v", err)
59 }
60 if out != h {
61 t.Fatalf("roundtrip mismatch:\ngot: %+v\nwant: %+v", out, h)
62 }
63 }
64
65 func TestHeaderEncodeTooSmall(t *testing.T) {
66 h := Header{}
67 var buf [16]byte
68 if n := h.Encode(buf[:]); n != 0 {
69 t.Fatalf("Encode returned %d, want 0", n)
70 }
71 }
72
73 func TestHeaderDecodeTruncated(t *testing.T) {
74 buf := make([]byte, 31)
75 _, err := DecodeHeader(buf)
76 if err != ErrTruncated {
77 t.Fatalf("got %v, want ErrTruncated", err)
78 }
79 }
80
81 func TestHeaderDecodeBadMagic(t *testing.T) {
82 h := Header{
83 Magic: 0x12345678,
84 Version: Version,
85 HeaderLen: HeaderLen,
86 Kind: KindRequest,
87 }
88 var buf [32]byte
89 h.Encode(buf[:])
90 _, err := DecodeHeader(buf[:])
91 if err != ErrBadMagic {
92 t.Fatalf("got %v, want ErrBadMagic", err)
93 }
94 }
95
96 func TestHeaderDecodeBadVersion(t *testing.T) {
97 h := Header{
98 Magic: MagicMsg,
99 Version: 99,
100 HeaderLen: HeaderLen,
101 Kind: KindRequest,
102 }
103 var buf [32]byte
104 h.Encode(buf[:])
105 _, err := DecodeHeader(buf[:])
106 if err != ErrBadVersion {
107 t.Fatalf("got %v, want ErrBadVersion", err)
108 }
109 }
110
111 func TestHeaderDecodeBadHeaderLen(t *testing.T) {
112 h := Header{
113 Magic: MagicMsg,
114 Version: Version,
115 HeaderLen: 64,
116 Kind: KindRequest,
117 }
118 var buf [32]byte
119 h.Encode(buf[:])
120 _, err := DecodeHeader(buf[:])
121 if err != ErrBadHeaderLen {
122 t.Fatalf("got %v, want ErrBadHeaderLen", err)
123 }
124 }
125
126 func TestHeaderDecodeBadKind(t *testing.T) {
127 // kind = 0
128 h := Header{
129 Magic: MagicMsg,
130 Version: Version,
131 HeaderLen: HeaderLen,
132 Kind: 0,
133 }
134 var buf [32]byte
135 h.Encode(buf[:])
136 _, err := DecodeHeader(buf[:])
137 if err != ErrBadKind {
138 t.Fatalf("kind=0: got %v, want ErrBadKind", err)
139 }
140
141 // kind = 4
142 h.Kind = 4
143 h.Encode(buf[:])
144 _, err = DecodeHeader(buf[:])
145 if err != ErrBadKind {
146 t.Fatalf("kind=4: got %v, want ErrBadKind", err)
147 }
148 }
149
150 func TestHeaderAllKinds(t *testing.T) {
151 for k := KindRequest; k <= KindControl; k++ {
152 h := Header{
153 Magic: MagicMsg,
154 Version: Version,
155 HeaderLen: HeaderLen,
156 Kind: k,
157 }
158 var buf [32]byte
159 h.Encode(buf[:])
160 out, err := DecodeHeader(buf[:])
161 if err != nil {
162 t.Fatalf("kind=%d: %v", k, err)
163 }
164 if out.Kind != k {
165 t.Fatalf("kind=%d: got %d", k, out.Kind)
166 }
167 }
168 }
169
170 func TestHeaderWireBytes(t *testing.T) {
171 h := Header{
172 Magic: MagicMsg,
173 Version: Version,
174 HeaderLen: HeaderLen,
175 Kind: KindRequest,
176 Flags: 0,
177 Code: MethodCgroupsSnapshot,
178 TransportStatus: StatusOK,
179 PayloadLen: 4,
180 ItemCount: 1,
181 MessageID: 1,
182 }
183
184 var buf [32]byte
185 h.Encode(buf[:])
186
187 // magic = 0x4e495043 LE: 43 50 49 4e
188 if !bytes.Equal(buf[0:4], []byte{0x43, 0x50, 0x49, 0x4e}) {
189 t.Errorf("magic bytes: %x", buf[0:4])
190 }
191 // version = 1 LE: 01 00
192 if !bytes.Equal(buf[4:6], []byte{0x01, 0x00}) {
193 t.Errorf("version bytes: %x", buf[4:6])
194 }
195 // header_len = 32 LE: 20 00
196 if !bytes.Equal(buf[6:8], []byte{0x20, 0x00}) {
197 t.Errorf("header_len bytes: %x", buf[6:8])
198 }
199 // kind = 1 LE: 01 00
200 if !bytes.Equal(buf[8:10], []byte{0x01, 0x00}) {
201 t.Errorf("kind bytes: %x", buf[8:10])
202 }
203 // code = 2 LE: 02 00
204 if !bytes.Equal(buf[12:14], []byte{0x02, 0x00}) {
205 t.Errorf("code bytes: %x", buf[12:14])
206 }
207 }
208
209 // ---------------------------------------------------------------------------
210 // Chunk continuation header tests
211 // ---------------------------------------------------------------------------
212
213 func TestChunkHeaderRoundtrip(t *testing.T) {
214 c := ChunkHeader{
215 Magic: MagicChunk,
216 Version: Version,
217 Flags: 0,
218 MessageID: 0x1234567890ABCDEF,
219 TotalMessageLen: 100000,
220 ChunkIndex: 3,
221 ChunkCount: 10,
222 ChunkPayloadLen: 8192,
223 }
224
225 var buf [64]byte
226 n := c.Encode(buf[:])
227 if n != 32 {
228 t.Fatalf("Encode returned %d, want 32", n)
229 }
230
231 out, err := DecodeChunkHeader(buf[:n])
232 if err != nil {
233 t.Fatalf("Decode error: %v", err)
234 }
235 if out != c {
236 t.Fatalf("roundtrip mismatch:\ngot: %+v\nwant: %+v", out, c)
237 }
238 }
239
240 func TestChunkDecodeTruncated(t *testing.T) {
241 buf := make([]byte, 31)
242 _, err := DecodeChunkHeader(buf)
243 if err != ErrTruncated {
244 t.Fatalf("got %v, want ErrTruncated", err)
245 }
246 }
247
248 func TestChunkDecodeBadMagic(t *testing.T) {
249 c := ChunkHeader{
250 Magic: MagicMsg, // wrong magic for chunk
251 Version: Version,
252 }
253 var buf [32]byte
254 c.Encode(buf[:])
255 _, err := DecodeChunkHeader(buf[:])
256 if err != ErrBadMagic {
257 t.Fatalf("got %v, want ErrBadMagic", err)
258 }
259 }
260
261 func TestChunkDecodeBadVersion(t *testing.T) {
262 c := ChunkHeader{
263 Magic: MagicChunk,
264 Version: 2,
265 }
266 var buf [32]byte
267 c.Encode(buf[:])
268 _, err := DecodeChunkHeader(buf[:])
269 if err != ErrBadVersion {
270 t.Fatalf("got %v, want ErrBadVersion", err)
271 }
272 }
273
274 func TestChunkEncodeTooSmall(t *testing.T) {
275 c := ChunkHeader{}
276 var buf [16]byte
277 if n := c.Encode(buf[:]); n != 0 {
278 t.Fatalf("Encode returned %d, want 0", n)
279 }
280 }
281
282 func TestChunkWireBytes(t *testing.T) {
283 c := ChunkHeader{
284 Magic: MagicChunk,
285 Version: Version,
286 Flags: 0,
287 MessageID: 1,
288 TotalMessageLen: 256,
289 ChunkIndex: 1,
290 ChunkCount: 3,
291 ChunkPayloadLen: 100,
292 }
293
294 var buf [32]byte
295 c.Encode(buf[:])
296
297 // magic = 0x4e43484b LE: 4b 48 43 4e
298 if !bytes.Equal(buf[0:4], []byte{0x4b, 0x48, 0x43, 0x4e}) {
299 t.Errorf("magic bytes: %x", buf[0:4])
300 }
301 }
302
303 // ---------------------------------------------------------------------------
304 // Batch item directory tests
305 // ---------------------------------------------------------------------------
306
307 func TestBatchDirRoundtrip(t *testing.T) {
308 entries := []BatchEntry{
309 {Offset: 0, Length: 100},
310 {Offset: 104, Length: 200},
311 {Offset: 304, Length: 50},
312 }
313
314 buf := make([]byte, 24)
315 n := BatchDirEncode(entries, buf)
316 if n != 24 {
317 t.Fatalf("BatchDirEncode returned %d, want 24", n)
318 }
319
320 out, err := BatchDirDecode(buf, 3, 1000)
321 if err != nil {
322 t.Fatalf("BatchDirDecode error: %v", err)
323 }
324 for i, e := range entries {
325 if out[i] != e {
326 t.Errorf("entry[%d]: got %+v, want %+v", i, out[i], e)
327 }
328 }
329 }
330
331 func TestBatchDirEncodeTooSmall(t *testing.T) {
332 entries := []BatchEntry{{Offset: 0, Length: 10}}
333 buf := make([]byte, 4)
334 if n := BatchDirEncode(entries, buf); n != 0 {
335 t.Fatalf("got %d, want 0", n)
336 }
337 }
338
339 func TestBatchDirDecodeTruncated(t *testing.T) {
340 buf := make([]byte, 12)
341 _, err := BatchDirDecode(buf, 2, 1000)
342 if err != ErrTruncated {
343 t.Fatalf("got %v, want ErrTruncated", err)
344 }
345 }
346
347 func TestBatchDirDecodeBadAlignment(t *testing.T) {
348 buf := make([]byte, 8)
349 ne.PutUint32(buf[0:4], 3) // offset not aligned to 8
350 ne.PutUint32(buf[4:8], 10)
351 _, err := BatchDirDecode(buf, 1, 100)
352 if err != ErrBadAlignment {
353 t.Fatalf("got %v, want ErrBadAlignment", err)
354 }
355 }
356
357 func TestBatchDirDecodeOutOfBounds(t *testing.T) {
358 buf := make([]byte, 8)
359 ne.PutUint32(buf[0:4], 0)
360 ne.PutUint32(buf[4:8], 200) // exceeds packed area
361 _, err := BatchDirDecode(buf, 1, 100)
362 if err != ErrOutOfBounds {
363 t.Fatalf("got %v, want ErrOutOfBounds", err)
364 }
365 }
366
367 func TestBatchItemGetBasic(t *testing.T) {
368 // Build a batch with 2 items using the builder.
369 buf := make([]byte, 1024)
370 b := NewBatchBuilder(buf, 2)
371
372 item0 := []byte("hello")
373 item1 := []byte("world!!!")
374
375 if err := b.Add(item0); err != nil {
376 t.Fatal(err)
377 }
378 if err := b.Add(item1); err != nil {
379 t.Fatal(err)
380 }
381
382 total, count := b.Finish()
383 if count != 2 {
384 t.Fatalf("count = %d, want 2", count)
385 }
386
387 // Extract items.
388 got0, err := BatchItemGet(buf[:total], count, 0)
389 if err != nil {
390 t.Fatalf("item 0: %v", err)
391 }
392 if !bytes.Equal(got0, item0) {
393 t.Fatalf("item 0: got %q, want %q", got0, item0)
394 }
395
396 got1, err := BatchItemGet(buf[:total], count, 1)
397 if err != nil {
398 t.Fatalf("item 1: %v", err)
399 }
400 if !bytes.Equal(got1, item1) {
401 t.Fatalf("item 1: got %q, want %q", got1, item1)
402 }
403 }
404
405 func TestBatchItemGetOutOfBounds(t *testing.T) {
406 buf := make([]byte, 16)
407 _, err := BatchItemGet(buf, 1, 1) // index >= count
408 if err != ErrOutOfBounds {
409 t.Fatalf("got %v, want ErrOutOfBounds", err)
410 }
411 }
412
413 func TestBatchItemGetTruncated(t *testing.T) {
414 buf := make([]byte, 4) // too small for even 1 directory entry aligned
415 _, err := BatchItemGet(buf, 1, 0)
416 if err != ErrTruncated {
417 t.Fatalf("got %v, want ErrTruncated", err)
418 }
419 }
420
421 // ---------------------------------------------------------------------------
422 // Batch builder tests
423 // ---------------------------------------------------------------------------
424
425 func TestBatchBuilderOverflowMaxItems(t *testing.T) {
426 buf := make([]byte, 1024)
427 b := NewBatchBuilder(buf, 1)
428 if err := b.Add([]byte("one")); err != nil {
429 t.Fatal(err)
430 }
431 if err := b.Add([]byte("two")); err != ErrOverflow {
432 t.Fatalf("got %v, want ErrOverflow", err)
433 }
434 }
435
436 func TestBatchBuilderOverflowBuffer(t *testing.T) {
437 buf := make([]byte, 16) // dir = 8 bytes, 8 bytes data
438 b := NewBatchBuilder(buf, 1)
439 if err := b.Add(make([]byte, 100)); err != ErrOverflow {
440 t.Fatalf("got %v, want ErrOverflow", err)
441 }
442 }
443
444 func TestBatchBuilderEmpty(t *testing.T) {
445 buf := make([]byte, 64)
446 b := NewBatchBuilder(buf, 5)
447 total, count := b.Finish()
448 if count != 0 {
449 t.Fatalf("count = %d, want 0", count)
450 }
451 if total != 0 {
452 t.Fatalf("total = %d, want 0", total)
453 }
454 }
455
456 func TestBatchBuilderCompaction(t *testing.T) {
457 // Reserve space for 10 items, only add 1.
458 buf := make([]byte, 1024)
459 b := NewBatchBuilder(buf, 10)
460 if err := b.Add([]byte("compact")); err != nil {
461 t.Fatal(err)
462 }
463 total, count := b.Finish()
464 if count != 1 {
465 t.Fatalf("count = %d, want 1", count)
466 }
467
468 // Verify the item can be extracted.
469 got, err := BatchItemGet(buf[:total], count, 0)
470 if err != nil {
471 t.Fatal(err)
472 }
473 if !bytes.Equal(got, []byte("compact")) {
474 t.Fatalf("got %q, want %q", got, "compact")
475 }
476 }
477
478 // ---------------------------------------------------------------------------
479 // Hello payload tests
480 // ---------------------------------------------------------------------------
481
482 func TestHelloRoundtrip(t *testing.T) {
483 h := Hello{
484 LayoutVersion: 1,
485 Flags: 0,
486 SupportedProfiles: ProfileBaseline | ProfileSHMFutex,
487 PreferredProfiles: ProfileSHMFutex,
488 MaxRequestPayloadBytes: 4096,
489 MaxRequestBatchItems: 100,
490 MaxResponsePayloadBytes: 1048576,
491 MaxResponseBatchItems: 1,
492 AuthToken: 0xAABBCCDDEEFF0011,
493 PacketSize: 65536,
494 }
495
496 var buf [64]byte
497 n := h.Encode(buf[:])
498 if n != 44 {
499 t.Fatalf("Encode returned %d, want 44", n)
500 }
501
502 out, err := DecodeHello(buf[:n])
503 if err != nil {
504 t.Fatalf("Decode error: %v", err)
505 }
506 if out != h {
507 t.Fatalf("roundtrip mismatch:\ngot: %+v\nwant: %+v", out, h)
508 }
509 }
510
511 func TestHelloEncodeTooSmall(t *testing.T) {
512 h := Hello{LayoutVersion: 1}
513 var buf [20]byte
514 if n := h.Encode(buf[:]); n != 0 {
515 t.Fatalf("Encode returned %d, want 0", n)
516 }
517 }
518
519 func TestHelloDecodeTruncated(t *testing.T) {
520 buf := make([]byte, 43)
521 _, err := DecodeHello(buf)
522 if err != ErrTruncated {
523 t.Fatalf("got %v, want ErrTruncated", err)
524 }
525 }
526
527 func TestHelloDecodeBadLayout(t *testing.T) {
528 h := Hello{LayoutVersion: 2}
529 var buf [44]byte
530 h.Encode(buf[:])
531 _, err := DecodeHello(buf[:])
532 if err != ErrBadLayout {
533 t.Fatalf("got %v, want ErrBadLayout", err)
534 }
535 }
536
537 func TestHelloPaddingIsZero(t *testing.T) {
538 h := Hello{
539 LayoutVersion: 1,
540 MaxResponseBatchItems: 0xFFFFFFFF,
541 AuthToken: 0xAAAAAAAAAAAAAAAA,
542 }
543 var buf [44]byte
544 h.Encode(buf[:])
545
546 // Padding at offset 28..32 must be zero.
547 if !bytes.Equal(buf[28:32], []byte{0, 0, 0, 0}) {
548 t.Errorf("padding not zero: %x", buf[28:32])
549 }
550 }
551
552 func TestHelloDecodeNonzeroPadding(t *testing.T) {
553 h := Hello{
554 LayoutVersion: 1,
555 SupportedProfiles: ProfileBaseline,
556 MaxRequestPayloadBytes: 1024,
557 MaxRequestBatchItems: 1,
558 MaxResponsePayloadBytes: 1024,
559 MaxResponseBatchItems: 1,
560 PacketSize: 65536,
561 }
562 var buf [44]byte
563 h.Encode(buf[:])
564
565 // Valid first
566 if _, err := DecodeHello(buf[:]); err != nil {
567 t.Fatalf("valid hello failed: %v", err)
568 }
569
570 // Corrupt padding
571 buf[28] = 0xFF
572 if _, err := DecodeHello(buf[:]); err != ErrBadLayout {
573 t.Errorf("nonzero padding: got %v, want ErrBadLayout", err)
574 }
575 }
576
577 func TestHelloWireBytes(t *testing.T) {
578 h := Hello{
579 LayoutVersion: 1,
580 Flags: 0,
581 SupportedProfiles: ProfileBaseline | ProfileSHMFutex,
582 PreferredProfiles: ProfileSHMFutex,
583 MaxRequestPayloadBytes: 4096,
584 MaxRequestBatchItems: 100,
585 MaxResponsePayloadBytes: 1048576,
586 MaxResponseBatchItems: 1,
587 AuthToken: 0xAABBCCDDEEFF0011,
588 PacketSize: 65536,
589 }
590 var buf [44]byte
591 h.Encode(buf[:])
592
593 // supported_profiles = 0x05 LE at offset 4
594 if !bytes.Equal(buf[4:8], []byte{0x05, 0x00, 0x00, 0x00}) {
595 t.Errorf("supported_profiles bytes: %x", buf[4:8])
596 }
597 // auth_token at offset 32
598 if !bytes.Equal(buf[32:40], []byte{0x11, 0x00, 0xFF, 0xEE, 0xDD, 0xCC, 0xBB, 0xAA}) {
599 t.Errorf("auth_token bytes: %x", buf[32:40])
600 }
601 }
602
603 // ---------------------------------------------------------------------------
604 // Hello-ack payload tests
605 // ---------------------------------------------------------------------------
606
607 func TestHelloAckRoundtrip(t *testing.T) {
608 h := HelloAck{
609 LayoutVersion: 1,
610 Flags: 0,
611 ServerSupportedProfiles: 0x07,
612 IntersectionProfiles: 0x05,
613 SelectedProfile: ProfileSHMFutex,
614 AgreedMaxRequestPayloadBytes: 2048,
615 AgreedMaxRequestBatchItems: 50,
616 AgreedMaxResponsePayloadBytes: 65536,
617 AgreedMaxResponseBatchItems: 1,
618 AgreedPacketSize: 32768,
619 }
620
621 var buf [64]byte
622 n := h.Encode(buf[:])
623 if n != 48 {
624 t.Fatalf("Encode returned %d, want 48", n)
625 }
626
627 out, err := DecodeHelloAck(buf[:n])
628 if err != nil {
629 t.Fatalf("Decode error: %v", err)
630 }
631 if out != h {
632 t.Fatalf("roundtrip mismatch:\ngot: %+v\nwant: %+v", out, h)
633 }
634 }
635
636 func TestHelloAckEncodeTooSmall(t *testing.T) {
637 h := HelloAck{LayoutVersion: 1}
638 var buf [20]byte
639 if n := h.Encode(buf[:]); n != 0 {
640 t.Fatalf("Encode returned %d, want 0", n)
641 }
642 }
643
644 func TestHelloAckDecodeTruncated(t *testing.T) {
645 buf := make([]byte, 47)
646 _, err := DecodeHelloAck(buf)
647 if err != ErrTruncated {
648 t.Fatalf("got %v, want ErrTruncated", err)
649 }
650 }
651
652 func TestHelloAckDecodeBadLayout(t *testing.T) {
653 h := HelloAck{LayoutVersion: 99}
654 var buf [48]byte
655 h.Encode(buf[:])
656 _, err := DecodeHelloAck(buf[:])
657 if err != ErrBadLayout {
658 t.Fatalf("got %v, want ErrBadLayout", err)
659 }
660 }
661
662 // ---------------------------------------------------------------------------
663 // Cgroups request tests
664 // ---------------------------------------------------------------------------
665
666 func TestCgroupsRequestRoundtrip(t *testing.T) {
667 r := CgroupsRequest{LayoutVersion: 1, Flags: 0}
668
669 var buf [16]byte
670 n := r.Encode(buf[:])
671 if n != 4 {
672 t.Fatalf("Encode returned %d, want 4", n)
673 }
674
675 out, err := DecodeCgroupsRequest(buf[:n])
676 if err != nil {
677 t.Fatalf("Decode error: %v", err)
678 }
679 if out != r {
680 t.Fatalf("roundtrip mismatch:\ngot: %+v\nwant: %+v", out, r)
681 }
682 }
683
684 func TestCgroupsRequestEncodeTooSmall(t *testing.T) {
685 r := CgroupsRequest{LayoutVersion: 1}
686 var buf [2]byte
687 if n := r.Encode(buf[:]); n != 0 {
688 t.Fatalf("Encode returned %d, want 0", n)
689 }
690 }
691
692 func TestCgroupsRequestDecodeTruncated(t *testing.T) {
693 buf := make([]byte, 3)
694 _, err := DecodeCgroupsRequest(buf)
695 if err != ErrTruncated {
696 t.Fatalf("got %v, want ErrTruncated", err)
697 }
698 }
699
700 func TestCgroupsRequestDecodeBadLayout(t *testing.T) {
701 r := CgroupsRequest{LayoutVersion: 2}
702 var buf [4]byte
703 r.Encode(buf[:])
704 _, err := DecodeCgroupsRequest(buf[:])
705 if err != ErrBadLayout {
706 t.Fatalf("got %v, want ErrBadLayout", err)
707 }
708 }
709
710 // ---------------------------------------------------------------------------
711 // CStringView tests
712 // ---------------------------------------------------------------------------
713
714 func TestCStringViewBasic(t *testing.T) {
715 data := []byte("hello\x00")
716 v := NewCStringView(data, 5)
717
718 if v.Len() != 5 {
719 t.Fatalf("Len() = %d, want 5", v.Len())
720 }
721 if !bytes.Equal(v.Bytes(), []byte("hello")) {
722 t.Fatalf("Bytes() = %q, want %q", v.Bytes(), "hello")
723 }
724 if v.String() != "hello" {
725 t.Fatalf("String() = %q, want %q", v.String(), "hello")
726 }
727 }
728
729 func TestCStringViewEmpty(t *testing.T) {
730 data := []byte{0}
731 v := NewCStringView(data, 0)
732
733 if v.Len() != 0 {
734 t.Fatalf("Len() = %d, want 0", v.Len())
735 }
736 if len(v.Bytes()) != 0 {
737 t.Fatalf("Bytes() len = %d, want 0", len(v.Bytes()))
738 }
739 if v.String() != "" {
740 t.Fatalf("String() = %q, want empty", v.String())
741 }
742 }
743
744 // ---------------------------------------------------------------------------
745 // Cgroups snapshot response tests
746 // ---------------------------------------------------------------------------
747
748 func TestCgroupsResponseEmptyRoundtrip(t *testing.T) {
749 buf := make([]byte, 8192)
750 b := NewCgroupsBuilder(buf, 0, 0, 42)
751 total := b.Finish()
752 if total != 24 {
753 t.Fatalf("Finish returned %d, want 24", total)
754 }
755
756 view, err := DecodeCgroupsResponse(buf[:total])
757 if err != nil {
758 t.Fatalf("Decode error: %v", err)
759 }
760 if view.ItemCount != 0 {
761 t.Fatalf("ItemCount = %d, want 0", view.ItemCount)
762 }
763 if view.SystemdEnabled != 0 {
764 t.Fatalf("SystemdEnabled = %d, want 0", view.SystemdEnabled)
765 }
766 if view.Generation != 42 {
767 t.Fatalf("Generation = %d, want 42", view.Generation)
768 }
769 }
770
771 func TestCgroupsResponseSingleItemRoundtrip(t *testing.T) {
772 buf := make([]byte, 8192)
773 b := NewCgroupsBuilder(buf, 1, 1, 100)
774
775 name := []byte("init.scope")
776 path := []byte("/sys/fs/cgroup/init.scope")
777 if err := b.Add(42, 0x01, 1, name, path); err != nil {
778 t.Fatal(err)
779 }
780
781 total := b.Finish()
782
783 view, err := DecodeCgroupsResponse(buf[:total])
784 if err != nil {
785 t.Fatalf("Decode error: %v", err)
786 }
787 if view.ItemCount != 1 {
788 t.Fatalf("ItemCount = %d, want 1", view.ItemCount)
789 }
790 if view.SystemdEnabled != 1 {
791 t.Fatalf("SystemdEnabled = %d, want 1", view.SystemdEnabled)
792 }
793 if view.Generation != 100 {
794 t.Fatalf("Generation = %d, want 100", view.Generation)
795 }
796
797 item, err := view.Item(0)
798 if err != nil {
799 t.Fatalf("Item(0) error: %v", err)
800 }
801 if item.Hash != 42 {
802 t.Fatalf("Hash = %d, want 42", item.Hash)
803 }
804 if item.Options != 0x01 {
805 t.Fatalf("Options = %d, want 1", item.Options)
806 }
807 if item.Enabled != 1 {
808 t.Fatalf("Enabled = %d, want 1", item.Enabled)
809 }
810 if item.Name.String() != "init.scope" {
811 t.Fatalf("Name = %q, want %q", item.Name.String(), "init.scope")
812 }
813 if item.Path.String() != "/sys/fs/cgroup/init.scope" {
814 t.Fatalf("Path = %q, want %q", item.Path.String(), "/sys/fs/cgroup/init.scope")
815 }
816 }
817
818 func TestCgroupsResponseMultiItemRoundtrip(t *testing.T) {
819 buf := make([]byte, 8192)
820 b := NewCgroupsBuilder(buf, 3, 1, 999)
821
822 if err := b.Add(100, 0, 1,
823 []byte("init.scope"),
824 []byte("/sys/fs/cgroup/init.scope")); err != nil {
825 t.Fatal(err)
826 }
827 if err := b.Add(200, 0x02, 0,
828 []byte("system.slice/docker-abc.scope"),
829 []byte("/sys/fs/cgroup/system.slice/docker-abc.scope")); err != nil {
830 t.Fatal(err)
831 }
832 if err := b.Add(300, 0, 1, []byte(""), []byte("")); err != nil {
833 t.Fatal(err)
834 }
835
836 total := b.Finish()
837
838 view, err := DecodeCgroupsResponse(buf[:total])
839 if err != nil {
840 t.Fatalf("Decode error: %v", err)
841 }
842 if view.ItemCount != 3 {
843 t.Fatalf("ItemCount = %d, want 3", view.ItemCount)
844 }
845 if view.SystemdEnabled != 1 {
846 t.Fatalf("SystemdEnabled = %d, want 1", view.SystemdEnabled)
847 }
848 if view.Generation != 999 {
849 t.Fatalf("Generation = %d, want 999", view.Generation)
850 }
851
852 // Item 0
853 item, err := view.Item(0)
854 if err != nil {
855 t.Fatal(err)
856 }
857 if item.Hash != 100 {
858 t.Errorf("item 0 Hash = %d, want 100", item.Hash)
859 }
860 if item.Options != 0 {
861 t.Errorf("item 0 Options = %d, want 0", item.Options)
862 }
863 if item.Enabled != 1 {
864 t.Errorf("item 0 Enabled = %d, want 1", item.Enabled)
865 }
866 if item.Name.String() != "init.scope" {
867 t.Errorf("item 0 Name = %q", item.Name.String())
868 }
869 if item.Path.String() != "/sys/fs/cgroup/init.scope" {
870 t.Errorf("item 0 Path = %q", item.Path.String())
871 }
872
873 // Item 1
874 item, err = view.Item(1)
875 if err != nil {
876 t.Fatal(err)
877 }
878 if item.Hash != 200 {
879 t.Errorf("item 1 Hash = %d, want 200", item.Hash)
880 }
881 if item.Options != 0x02 {
882 t.Errorf("item 1 Options = %d, want 2", item.Options)
883 }
884 if item.Enabled != 0 {
885 t.Errorf("item 1 Enabled = %d, want 0", item.Enabled)
886 }
887 if item.Name.String() != "system.slice/docker-abc.scope" {
888 t.Errorf("item 1 Name = %q", item.Name.String())
889 }
890
891 // Item 2 (empty strings)
892 item, err = view.Item(2)
893 if err != nil {
894 t.Fatal(err)
895 }
896 if item.Hash != 300 {
897 t.Errorf("item 2 Hash = %d, want 300", item.Hash)
898 }
899 if item.Name.Len() != 0 {
900 t.Errorf("item 2 Name.Len() = %d, want 0", item.Name.Len())
901 }
902 if item.Path.Len() != 0 {
903 t.Errorf("item 2 Path.Len() = %d, want 0", item.Path.Len())
904 }
905 }
906
907 func TestCgroupsResponseCompaction(t *testing.T) {
908 // Reserve space for 100 items, add only 2.
909 buf := make([]byte, 8192)
910 b := NewCgroupsBuilder(buf, 100, 0, 7)
911
912 if err := b.Add(1, 0, 1, []byte("a"), []byte("b")); err != nil {
913 t.Fatal(err)
914 }
915 if err := b.Add(2, 0, 1, []byte("c"), []byte("d")); err != nil {
916 t.Fatal(err)
917 }
918
919 total := b.Finish()
920
921 view, err := DecodeCgroupsResponse(buf[:total])
922 if err != nil {
923 t.Fatalf("Decode error: %v", err)
924 }
925 if view.ItemCount != 2 {
926 t.Fatalf("ItemCount = %d, want 2", view.ItemCount)
927 }
928
929 item, err := view.Item(0)
930 if err != nil {
931 t.Fatal(err)
932 }
933 if item.Name.String() != "a" {
934 t.Fatalf("item 0 Name = %q, want %q", item.Name.String(), "a")
935 }
936
937 item, err = view.Item(1)
938 if err != nil {
939 t.Fatal(err)
940 }
941 if item.Name.String() != "c" {
942 t.Fatalf("item 1 Name = %q, want %q", item.Name.String(), "c")
943 }
944 }
945
946 func TestCgroupsResponseItemOutOfBounds(t *testing.T) {
947 buf := make([]byte, 8192)
948 b := NewCgroupsBuilder(buf, 1, 0, 1)
949 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
950 t.Fatal(err)
951 }
952 total := b.Finish()
953
954 view, err := DecodeCgroupsResponse(buf[:total])
955 if err != nil {
956 t.Fatal(err)
957 }
958
959 _, err = view.Item(1) // index out of bounds
960 if err != ErrOutOfBounds {
961 t.Fatalf("got %v, want ErrOutOfBounds", err)
962 }
963 }
964
965 func TestCgroupsResponseDecodeTruncated(t *testing.T) {
966 buf := make([]byte, 23) // less than 24-byte header
967 _, err := DecodeCgroupsResponse(buf)
968 if err != ErrTruncated {
969 t.Fatalf("got %v, want ErrTruncated", err)
970 }
971 }
972
973 func TestCgroupsResponseDecodeBadLayout(t *testing.T) {
974 buf := make([]byte, 24)
975 ne.PutUint16(buf[0:2], 99) // bad layout_version
976 _, err := DecodeCgroupsResponse(buf)
977 if err != ErrBadLayout {
978 t.Fatalf("got %v, want ErrBadLayout", err)
979 }
980 }
981
982 func TestCgroupsResponseDecodeDirectoryTruncated(t *testing.T) {
983 buf := make([]byte, 28) // 24 header + 4 bytes, not enough for 1 dir entry (8)
984 ne.PutUint16(buf[0:2], 1) // layout_version
985 ne.PutUint16(buf[2:4], 0) // flags
986 ne.PutUint32(buf[4:8], 1) // item_count = 1
987 ne.PutUint32(buf[8:12], 0) // systemd_enabled
988 ne.PutUint32(buf[12:16], 0) // reserved
989 ne.PutUint64(buf[16:24], 0) // generation
990
991 _, err := DecodeCgroupsResponse(buf)
992 if err != ErrTruncated {
993 t.Fatalf("got %v, want ErrTruncated", err)
994 }
995 }
996
997 func TestCgroupsResponseDecodeItemBadAlignment(t *testing.T) {
998 // Create a payload with a directory entry that has a non-aligned offset.
999 buf := make([]byte, 128)
1000 ne.PutUint16(buf[0:2], 1) // layout_version
1001 ne.PutUint16(buf[2:4], 0) // flags
1002 ne.PutUint32(buf[4:8], 1) // item_count = 1
1003 ne.PutUint32(buf[8:12], 0) // systemd_enabled
1004 ne.PutUint32(buf[12:16], 0) // reserved
1005 ne.PutUint64(buf[16:24], 0) // generation
1006
1007 // Directory entry at offset 24: offset=3 (not aligned), length=32
1008 ne.PutUint32(buf[24:28], 3) // bad alignment
1009 ne.PutUint32(buf[28:32], 32)
1010
1011 _, err := DecodeCgroupsResponse(buf)
1012 if err != ErrBadAlignment {
1013 t.Fatalf("got %v, want ErrBadAlignment", err)
1014 }
1015 }
1016
1017 func TestCgroupsResponseDecodeItemOutOfBounds(t *testing.T) {
1018 buf := make([]byte, 64)
1019 ne.PutUint16(buf[0:2], 1)
1020 ne.PutUint16(buf[2:4], 0)
1021 ne.PutUint32(buf[4:8], 1) // item_count = 1
1022 ne.PutUint32(buf[8:12], 0)
1023 ne.PutUint32(buf[12:16], 0)
1024 ne.PutUint64(buf[16:24], 0)
1025
1026 // Dir entry: offset=0, length=1000 (exceeds buffer)
1027 ne.PutUint32(buf[24:28], 0)
1028 ne.PutUint32(buf[28:32], 1000)
1029
1030 _, err := DecodeCgroupsResponse(buf)
1031 if err != ErrOutOfBounds {
1032 t.Fatalf("got %v, want ErrOutOfBounds", err)
1033 }
1034 }
1035
1036 func TestCgroupsResponseDecodeItemTooSmall(t *testing.T) {
1037 // Item is present but smaller than 32-byte item header.
1038 buf := make([]byte, 64)
1039 ne.PutUint16(buf[0:2], 1)
1040 ne.PutUint16(buf[2:4], 0)
1041 ne.PutUint32(buf[4:8], 1) // item_count = 1
1042 ne.PutUint32(buf[8:12], 0)
1043 ne.PutUint32(buf[12:16], 0)
1044 ne.PutUint64(buf[16:24], 0)
1045
1046 // Dir entry: offset=0, length=16 (< 32 item header)
1047 ne.PutUint32(buf[24:28], 0)
1048 ne.PutUint32(buf[28:32], 16)
1049
1050 _, err := DecodeCgroupsResponse(buf)
1051 if err != ErrTruncated {
1052 t.Fatalf("got %v, want ErrTruncated", err)
1053 }
1054 }
1055
1056 func TestCgroupsResponseItemBadLayout(t *testing.T) {
1057 // Build a valid response, then corrupt the item's layout_version.
1058 buf := make([]byte, 8192)
1059 b := NewCgroupsBuilder(buf, 1, 0, 1)
1060 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
1061 t.Fatal(err)
1062 }
1063 total := b.Finish()
1064
1065 // Find the item start and corrupt layout_version.
1066 dirBase := cgroupsRespHdr
1067 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1068 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1069 itemStart := packedStart + itemOff
1070 ne.PutUint16(buf[itemStart:itemStart+2], 99) // corrupt layout_version
1071
1072 view, err := DecodeCgroupsResponse(buf[:total])
1073 if err != nil {
1074 t.Fatal(err)
1075 }
1076
1077 _, err = view.Item(0)
1078 if err != ErrBadLayout {
1079 t.Fatalf("got %v, want ErrBadLayout", err)
1080 }
1081 }
1082
1083 func TestCgroupsResponseItemMissingNul(t *testing.T) {
1084 // Build a valid response, then overwrite the name's NUL terminator.
1085 buf := make([]byte, 8192)
1086 b := NewCgroupsBuilder(buf, 1, 0, 1)
1087 if err := b.Add(1, 0, 1, []byte("test"), []byte("path")); err != nil {
1088 t.Fatal(err)
1089 }
1090 total := b.Finish()
1091
1092 // Find item and overwrite the NUL after "test".
1093 dirBase := cgroupsRespHdr
1094 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1095 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1096 itemStart := packedStart + itemOff
1097 // name is at offset 32 within the item, length 4, NUL at 36.
1098 buf[itemStart+32+4] = 'X' // overwrite NUL
1099
1100 view, err := DecodeCgroupsResponse(buf[:total])
1101 if err != nil {
1102 t.Fatal(err)
1103 }
1104 _, err = view.Item(0)
1105 if err != ErrMissingNul {
1106 t.Fatalf("got %v, want ErrMissingNul", err)
1107 }
1108 }
1109
1110 func TestCgroupsResponseItemNameOutOfBounds(t *testing.T) {
1111 // Build valid, then corrupt name_offset to point beyond item bounds.
1112 buf := make([]byte, 8192)
1113 b := NewCgroupsBuilder(buf, 1, 0, 1)
1114 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
1115 t.Fatal(err)
1116 }
1117 total := b.Finish()
1118
1119 dirBase := cgroupsRespHdr
1120 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1121 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1122 itemStart := packedStart + itemOff
1123 // Corrupt name_offset to a huge value.
1124 ne.PutUint32(buf[itemStart+16:itemStart+20], 9999)
1125
1126 view, err := DecodeCgroupsResponse(buf[:total])
1127 if err != nil {
1128 t.Fatal(err)
1129 }
1130 _, err = view.Item(0)
1131 if err != ErrOutOfBounds {
1132 t.Fatalf("got %v, want ErrOutOfBounds", err)
1133 }
1134 }
1135
1136 func TestCgroupsResponseItemNameOffsetBelowHeader(t *testing.T) {
1137 // name_offset < 32 (item header size).
1138 buf := make([]byte, 8192)
1139 b := NewCgroupsBuilder(buf, 1, 0, 1)
1140 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
1141 t.Fatal(err)
1142 }
1143 total := b.Finish()
1144
1145 dirBase := cgroupsRespHdr
1146 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1147 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1148 itemStart := packedStart + itemOff
1149 ne.PutUint32(buf[itemStart+16:itemStart+20], 4) // below 32
1150
1151 view, err := DecodeCgroupsResponse(buf[:total])
1152 if err != nil {
1153 t.Fatal(err)
1154 }
1155 _, err = view.Item(0)
1156 if err != ErrOutOfBounds {
1157 t.Fatalf("got %v, want ErrOutOfBounds", err)
1158 }
1159 }
1160
1161 func TestCgroupsBuilderOverflowMaxItems(t *testing.T) {
1162 buf := make([]byte, 8192)
1163 b := NewCgroupsBuilder(buf, 1, 0, 1)
1164 if err := b.Add(1, 0, 1, []byte("a"), []byte("b")); err != nil {
1165 t.Fatal(err)
1166 }
1167 if err := b.Add(2, 0, 1, []byte("c"), []byte("d")); err != ErrOverflow {
1168 t.Fatalf("got %v, want ErrOverflow", err)
1169 }
1170 }
1171
1172 func TestCgroupsBuilderOverflowBuffer(t *testing.T) {
1173 buf := make([]byte, 40) // too small for header + dir + item
1174 b := NewCgroupsBuilder(buf, 1, 0, 1)
1175 err := b.Add(1, 0, 1, []byte("long-name-that-wont-fit"), []byte("long-path-too"))
1176 if err != ErrOverflow {
1177 t.Fatalf("got %v, want ErrOverflow", err)
1178 }
1179 }
1180
1181 func TestCgroupsResponseEmptyStrings(t *testing.T) {
1182 buf := make([]byte, 8192)
1183 b := NewCgroupsBuilder(buf, 1, 0, 1)
1184 if err := b.Add(42, 0, 1, []byte(""), []byte("")); err != nil {
1185 t.Fatal(err)
1186 }
1187 total := b.Finish()
1188
1189 view, err := DecodeCgroupsResponse(buf[:total])
1190 if err != nil {
1191 t.Fatal(err)
1192 }
1193
1194 item, err := view.Item(0)
1195 if err != nil {
1196 t.Fatal(err)
1197 }
1198 if item.Name.Len() != 0 {
1199 t.Fatalf("Name.Len() = %d, want 0", item.Name.Len())
1200 }
1201 if item.Path.Len() != 0 {
1202 t.Fatalf("Path.Len() = %d, want 0", item.Path.Len())
1203 }
1204 if item.Name.String() != "" {
1205 t.Fatalf("Name.String() = %q, want empty", item.Name.String())
1206 }
1207 if item.Path.String() != "" {
1208 t.Fatalf("Path.String() = %q, want empty", item.Path.String())
1209 }
1210 }
1211
1212 // ---------------------------------------------------------------------------
1213 // Interop reference: test values matching C and Rust interop binaries
1214 // ---------------------------------------------------------------------------
1215
1216 func TestInteropHeaderValues(t *testing.T) {
1217 h := Header{
1218 Magic: MagicMsg,
1219 Version: Version,
1220 HeaderLen: HeaderLen,
1221 Kind: KindRequest,
1222 Flags: FlagBatch,
1223 Code: MethodCgroupsSnapshot,
1224 TransportStatus: StatusOK,
1225 PayloadLen: 12345,
1226 ItemCount: 42,
1227 MessageID: 0xDEADBEEFCAFEBABE,
1228 }
1229
1230 var buf [32]byte
1231 h.Encode(buf[:])
1232
1233 // Decode and verify all fields match.
1234 out, err := DecodeHeader(buf[:])
1235 if err != nil {
1236 t.Fatal(err)
1237 }
1238 if out.Magic != MagicMsg {
1239 t.Errorf("magic = %x", out.Magic)
1240 }
1241 if out.PayloadLen != 12345 {
1242 t.Errorf("payload_len = %d", out.PayloadLen)
1243 }
1244 if out.ItemCount != 42 {
1245 t.Errorf("item_count = %d", out.ItemCount)
1246 }
1247 if out.MessageID != 0xDEADBEEFCAFEBABE {
1248 t.Errorf("message_id = %x", out.MessageID)
1249 }
1250 }
1251
1252 func TestInteropChunkValues(t *testing.T) {
1253 c := ChunkHeader{
1254 Magic: MagicChunk,
1255 Version: Version,
1256 Flags: 0,
1257 MessageID: 0x1234567890ABCDEF,
1258 TotalMessageLen: 100000,
1259 ChunkIndex: 3,
1260 ChunkCount: 10,
1261 ChunkPayloadLen: 8192,
1262 }
1263
1264 var buf [32]byte
1265 c.Encode(buf[:])
1266
1267 out, err := DecodeChunkHeader(buf[:])
1268 if err != nil {
1269 t.Fatal(err)
1270 }
1271 if out.MessageID != 0x1234567890ABCDEF {
1272 t.Errorf("message_id = %x", out.MessageID)
1273 }
1274 if out.TotalMessageLen != 100000 {
1275 t.Errorf("total_message_len = %d", out.TotalMessageLen)
1276 }
1277 if out.ChunkIndex != 3 {
1278 t.Errorf("chunk_index = %d", out.ChunkIndex)
1279 }
1280 if out.ChunkCount != 10 {
1281 t.Errorf("chunk_count = %d", out.ChunkCount)
1282 }
1283 if out.ChunkPayloadLen != 8192 {
1284 t.Errorf("chunk_payload_len = %d", out.ChunkPayloadLen)
1285 }
1286 }
1287
1288 func TestInteropHelloValues(t *testing.T) {
1289 h := Hello{
1290 LayoutVersion: 1,
1291 Flags: 0,
1292 SupportedProfiles: ProfileBaseline | ProfileSHMFutex,
1293 PreferredProfiles: ProfileSHMFutex,
1294 MaxRequestPayloadBytes: 4096,
1295 MaxRequestBatchItems: 100,
1296 MaxResponsePayloadBytes: 1048576,
1297 MaxResponseBatchItems: 1,
1298 AuthToken: 0xAABBCCDDEEFF0011,
1299 PacketSize: 65536,
1300 }
1301
1302 var buf [44]byte
1303 h.Encode(buf[:])
1304
1305 out, err := DecodeHello(buf[:])
1306 if err != nil {
1307 t.Fatal(err)
1308 }
1309 if out.SupportedProfiles != 0x05 {
1310 t.Errorf("supported = %x", out.SupportedProfiles)
1311 }
1312 if out.AuthToken != 0xAABBCCDDEEFF0011 {
1313 t.Errorf("auth_token = %x", out.AuthToken)
1314 }
1315 if out.PacketSize != 65536 {
1316 t.Errorf("packet_size = %d", out.PacketSize)
1317 }
1318 }
1319
1320 func TestInteropHelloAckValues(t *testing.T) {
1321 h := HelloAck{
1322 LayoutVersion: 1,
1323 Flags: 0,
1324 ServerSupportedProfiles: 0x07,
1325 IntersectionProfiles: 0x05,
1326 SelectedProfile: ProfileSHMFutex,
1327 AgreedMaxRequestPayloadBytes: 2048,
1328 AgreedMaxRequestBatchItems: 50,
1329 AgreedMaxResponsePayloadBytes: 65536,
1330 AgreedMaxResponseBatchItems: 1,
1331 AgreedPacketSize: 32768,
1332 }
1333
1334 var buf [48]byte
1335 h.Encode(buf[:])
1336
1337 out, err := DecodeHelloAck(buf[:])
1338 if err != nil {
1339 t.Fatal(err)
1340 }
1341 if out.ServerSupportedProfiles != 0x07 {
1342 t.Errorf("server_supported = %x", out.ServerSupportedProfiles)
1343 }
1344 if out.AgreedPacketSize != 32768 {
1345 t.Errorf("agreed_packet_size = %d", out.AgreedPacketSize)
1346 }
1347 }
1348
1349 func TestInteropCgroupsResponseValues(t *testing.T) {
1350 buf := make([]byte, 8192)
1351 b := NewCgroupsBuilder(buf, 3, 1, 999)
1352
1353 if err := b.Add(100, 0, 1,
1354 []byte("init.scope"),
1355 []byte("/sys/fs/cgroup/init.scope")); err != nil {
1356 t.Fatal(err)
1357 }
1358 if err := b.Add(200, 0x02, 0,
1359 []byte("system.slice/docker-abc.scope"),
1360 []byte("/sys/fs/cgroup/system.slice/docker-abc.scope")); err != nil {
1361 t.Fatal(err)
1362 }
1363 if err := b.Add(300, 0, 1, []byte(""), []byte("")); err != nil {
1364 t.Fatal(err)
1365 }
1366
1367 total := b.Finish()
1368
1369 view, err := DecodeCgroupsResponse(buf[:total])
1370 if err != nil {
1371 t.Fatal(err)
1372 }
1373 if view.ItemCount != 3 {
1374 t.Fatalf("ItemCount = %d", view.ItemCount)
1375 }
1376 if view.SystemdEnabled != 1 {
1377 t.Fatalf("SystemdEnabled = %d", view.SystemdEnabled)
1378 }
1379 if view.Generation != 999 {
1380 t.Fatalf("Generation = %d", view.Generation)
1381 }
1382
1383 item0, _ := view.Item(0)
1384 if item0.Hash != 100 || item0.Name.String() != "init.scope" {
1385 t.Errorf("item 0: hash=%d name=%q", item0.Hash, item0.Name.String())
1386 }
1387
1388 item1, _ := view.Item(1)
1389 if item1.Hash != 200 || item1.Options != 0x02 || item1.Enabled != 0 {
1390 t.Errorf("item 1: hash=%d options=%d enabled=%d",
1391 item1.Hash, item1.Options, item1.Enabled)
1392 }
1393 if item1.Name.String() != "system.slice/docker-abc.scope" {
1394 t.Errorf("item 1 name: %q", item1.Name.String())
1395 }
1396
1397 item2, _ := view.Item(2)
1398 if item2.Hash != 300 || item2.Name.Len() != 0 || item2.Path.Len() != 0 {
1399 t.Errorf("item 2: hash=%d namelen=%d pathlen=%d",
1400 item2.Hash, item2.Name.Len(), item2.Path.Len())
1401 }
1402 }
1403
1404 func TestInteropCgroupsResponseEmptyValues(t *testing.T) {
1405 buf := make([]byte, 8192)
1406 b := NewCgroupsBuilder(buf, 0, 0, 42)
1407 total := b.Finish()
1408
1409 view, err := DecodeCgroupsResponse(buf[:total])
1410 if err != nil {
1411 t.Fatal(err)
1412 }
1413 if view.ItemCount != 0 {
1414 t.Errorf("ItemCount = %d", view.ItemCount)
1415 }
1416 if view.SystemdEnabled != 0 {
1417 t.Errorf("SystemdEnabled = %d", view.SystemdEnabled)
1418 }
1419 if view.Generation != 42 {
1420 t.Errorf("Generation = %d", view.Generation)
1421 }
1422 }
1423
1424 // ---------------------------------------------------------------------------
1425 // Large snapshot test
1426 // ---------------------------------------------------------------------------
1427
1428 func TestCgroupsResponseLargeSnapshot(t *testing.T) {
1429 const n = 200
1430 buf := make([]byte, 1024*1024)
1431 b := NewCgroupsBuilder(buf, n, 1, 12345)
1432
1433 for i := range n {
1434 name := []byte("cgroup-" + string(rune('A'+i%26)))
1435 path := []byte("/sys/fs/cgroup/system.slice/cgroup-" + string(rune('A'+i%26)))
1436 if err := b.Add(uint32(i), uint32(i%4), uint32(i%2), name, path); err != nil {
1437 t.Fatalf("item %d: %v", i, err)
1438 }
1439 }
1440
1441 total := b.Finish()
1442
1443 view, err := DecodeCgroupsResponse(buf[:total])
1444 if err != nil {
1445 t.Fatal(err)
1446 }
1447 if view.ItemCount != n {
1448 t.Fatalf("ItemCount = %d, want %d", view.ItemCount, n)
1449 }
1450
1451 for i := range uint32(n) {
1452 item, err := view.Item(i)
1453 if err != nil {
1454 t.Fatalf("item %d: %v", i, err)
1455 }
1456 if item.Hash != i {
1457 t.Errorf("item %d: hash = %d", i, item.Hash)
1458 }
1459 }
1460 }
1461
1462 // ---------------------------------------------------------------------------
1463 // Error string tests (ensure errors have useful messages)
1464 // ---------------------------------------------------------------------------
1465
1466 func TestErrorStrings(t *testing.T) {
1467 errs := []error{
1468 ErrTruncated, ErrBadMagic, ErrBadVersion, ErrBadHeaderLen,
1469 ErrBadKind, ErrBadLayout, ErrOutOfBounds, ErrMissingNul,
1470 ErrBadAlignment, ErrBadItemCount, ErrOverflow,
1471 }
1472 for _, e := range errs {
1473 if e.Error() == "" {
1474 t.Errorf("error has empty string: %v", e)
1475 }
1476 }
1477 }
1478
1479 // ---------------------------------------------------------------------------
1480 // Decode from zero/garbage bytes (robustness)
1481 // ---------------------------------------------------------------------------
1482
1483 func TestDecodeZeroBytes(t *testing.T) {
1484 buf := make([]byte, 128)
1485
1486 _, err := DecodeHeader(buf)
1487 if err == nil {
1488 t.Error("expected error for zero header")
1489 }
1490
1491 _, err = DecodeChunkHeader(buf)
1492 if err == nil {
1493 t.Error("expected error for zero chunk header")
1494 }
1495
1496 _, err = DecodeHello(buf)
1497 if err == nil {
1498 t.Error("expected error for zero hello")
1499 }
1500
1501 _, err = DecodeHelloAck(buf)
1502 if err == nil {
1503 t.Error("expected error for zero hello_ack")
1504 }
1505
1506 _, err = DecodeCgroupsRequest(buf)
1507 if err == nil {
1508 t.Error("expected error for zero cgroups_req")
1509 }
1510
1511 _, err = DecodeCgroupsResponse(buf)
1512 if err == nil {
1513 t.Error("expected error for zero cgroups_resp")
1514 }
1515 }
1516
1517 func TestDecodeGarbage(t *testing.T) {
1518 buf := make([]byte, 128)
1519 for i := range buf {
1520 buf[i] = 0xFF
1521 }
1522
1523 _, err := DecodeHeader(buf)
1524 if err == nil {
1525 t.Error("expected error for garbage header")
1526 }
1527
1528 _, err = DecodeChunkHeader(buf)
1529 if err == nil {
1530 t.Error("expected error for garbage chunk header")
1531 }
1532
1533 _, err = DecodeHello(buf)
1534 if err == nil {
1535 t.Error("expected error for garbage hello")
1536 }
1537
1538 _, err = DecodeHelloAck(buf)
1539 if err == nil {
1540 t.Error("expected error for garbage hello_ack")
1541 }
1542
1543 _, err = DecodeCgroupsRequest(buf)
1544 if err == nil {
1545 t.Error("expected error for garbage cgroups_req")
1546 }
1547
1548 _, err = DecodeCgroupsResponse(buf)
1549 if err == nil {
1550 t.Error("expected error for garbage cgroups_resp")
1551 }
1552 }
1553
1554 func TestDecodeEmptyBuf(t *testing.T) {
1555 empty := []byte{}
1556 if _, err := DecodeHeader(empty); err != ErrTruncated {
1557 t.Errorf("header: got %v", err)
1558 }
1559 if _, err := DecodeChunkHeader(empty); err != ErrTruncated {
1560 t.Errorf("chunk: got %v", err)
1561 }
1562 if _, err := DecodeHello(empty); err != ErrTruncated {
1563 t.Errorf("hello: got %v", err)
1564 }
1565 if _, err := DecodeHelloAck(empty); err != ErrTruncated {
1566 t.Errorf("hello_ack: got %v", err)
1567 }
1568 if _, err := DecodeCgroupsRequest(empty); err != ErrTruncated {
1569 t.Errorf("cgroups_req: got %v", err)
1570 }
1571 if _, err := DecodeCgroupsResponse(empty); err != ErrTruncated {
1572 t.Errorf("cgroups_resp: got %v", err)
1573 }
1574 }
1575
1576 // ---------------------------------------------------------------------------
1577 // Path-level string validation in cgroups item
1578 // ---------------------------------------------------------------------------
1579
1580 func TestCgroupsResponseItemPathMissingNul(t *testing.T) {
1581 buf := make([]byte, 8192)
1582 b := NewCgroupsBuilder(buf, 1, 0, 1)
1583 if err := b.Add(1, 0, 1, []byte("ok"), []byte("bad")); err != nil {
1584 t.Fatal(err)
1585 }
1586 total := b.Finish()
1587
1588 // Overwrite the NUL after "bad" (the path string).
1589 dirBase := cgroupsRespHdr
1590 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1591 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1592 itemStart := packedStart + itemOff
1593
1594 // path_offset is at item+24, path_len at item+28.
1595 pathOff := int(ne.Uint32(buf[itemStart+24 : itemStart+28]))
1596 pathLen := int(ne.Uint32(buf[itemStart+28 : itemStart+32]))
1597 buf[itemStart+pathOff+pathLen] = 'X' // corrupt NUL
1598
1599 view, err := DecodeCgroupsResponse(buf[:total])
1600 if err != nil {
1601 t.Fatal(err)
1602 }
1603 _, err = view.Item(0)
1604 if err != ErrMissingNul {
1605 t.Fatalf("got %v, want ErrMissingNul", err)
1606 }
1607 }
1608
1609 func TestCgroupsResponseItemPathOutOfBounds(t *testing.T) {
1610 buf := make([]byte, 8192)
1611 b := NewCgroupsBuilder(buf, 1, 0, 1)
1612 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
1613 t.Fatal(err)
1614 }
1615 total := b.Finish()
1616
1617 dirBase := cgroupsRespHdr
1618 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1619 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1620 itemStart := packedStart + itemOff
1621 ne.PutUint32(buf[itemStart+24:itemStart+28], 9999) // corrupt path_offset
1622
1623 view, err := DecodeCgroupsResponse(buf[:total])
1624 if err != nil {
1625 t.Fatal(err)
1626 }
1627 _, err = view.Item(0)
1628 if err != ErrOutOfBounds {
1629 t.Fatalf("got %v, want ErrOutOfBounds", err)
1630 }
1631 }
1632
1633 func TestCgroupsResponseItemPathOffsetBelowHeader(t *testing.T) {
1634 buf := make([]byte, 8192)
1635 b := NewCgroupsBuilder(buf, 1, 0, 1)
1636 if err := b.Add(1, 0, 1, []byte("x"), []byte("y")); err != nil {
1637 t.Fatal(err)
1638 }
1639 total := b.Finish()
1640
1641 dirBase := cgroupsRespHdr
1642 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1643 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1644 itemStart := packedStart + itemOff
1645 ne.PutUint32(buf[itemStart+24:itemStart+28], 4) // below 32
1646
1647 view, err := DecodeCgroupsResponse(buf[:total])
1648 if err != nil {
1649 t.Fatal(err)
1650 }
1651 _, err = view.Item(0)
1652 if err != ErrOutOfBounds {
1653 t.Fatalf("got %v, want ErrOutOfBounds", err)
1654 }
1655 }
1656
1657 func TestCgroupsResponseItemOverlapRejected(t *testing.T) {
1658 buf := make([]byte, 8192)
1659 b := NewCgroupsBuilder(buf, 1, 0, 1)
1660 if err := b.Add(1, 0, 1, []byte("hello"), []byte("/path")); err != nil {
1661 t.Fatal(err)
1662 }
1663 total := b.Finish()
1664
1665 dirBase := cgroupsRespHdr
1666 itemOff := int(ne.Uint32(buf[dirBase : dirBase+4]))
1667 packedStart := cgroupsRespHdr + 1*cgroupsDirEntry
1668 itemStart := packedStart + itemOff
1669
1670 // name region is [32..38) (name_off=32, name_len=5, +1 for NUL)
1671 // Set path_off=34 (inside name), path_len=1
1672 ne.PutUint32(buf[itemStart+24:itemStart+28], 34)
1673 ne.PutUint32(buf[itemStart+28:itemStart+32], 1)
1674 // Ensure NUL at item[35]
1675 buf[itemStart+35] = 0
1676
1677 view, err := DecodeCgroupsResponse(buf[:total])
1678 if err != nil {
1679 t.Fatal(err)
1680 }
1681 _, err = view.Item(0)
1682 if err != ErrBadLayout {
1683 t.Fatalf("got %v, want ErrBadLayout (overlapping fields)", err)
1684 }
1685 }