master
go 920 lines 25.9 KB
Raw
1 package protocol
2
3 import (
4 "testing"
5 )
6
7 // ---------------------------------------------------------------------------
8 // DecodeChunkHeader error paths
9 // ---------------------------------------------------------------------------
10
11 func TestDecodeChunkHeaderBadFlags(t *testing.T) {
12 // Valid chunk header but with non-zero flags (must be 0)
13 c := ChunkHeader{
14 Magic: MagicChunk,
15 Version: Version,
16 Flags: 1, // invalid
17 MessageID: 1,
18 TotalMessageLen: 256,
19 ChunkIndex: 0,
20 ChunkCount: 3,
21 ChunkPayloadLen: 100,
22 }
23 var buf [HeaderSize]byte
24 c.Encode(buf[:])
25 _, err := DecodeChunkHeader(buf[:])
26 if err != ErrBadLayout {
27 t.Fatalf("expected ErrBadLayout for non-zero flags, got %v", err)
28 }
29 }
30
31 func TestDecodeChunkHeaderZeroPayloadLen(t *testing.T) {
32 // Valid chunk header but with zero chunk_payload_len
33 c := ChunkHeader{
34 Magic: MagicChunk,
35 Version: Version,
36 Flags: 0,
37 MessageID: 1,
38 TotalMessageLen: 256,
39 ChunkIndex: 0,
40 ChunkCount: 3,
41 ChunkPayloadLen: 0, // invalid
42 }
43 var buf [HeaderSize]byte
44 c.Encode(buf[:])
45 _, err := DecodeChunkHeader(buf[:])
46 if err != ErrBadLayout {
47 t.Fatalf("expected ErrBadLayout for zero chunk_payload_len, got %v", err)
48 }
49 }
50
51 func TestDecodeChunkHeaderBadVersion(t *testing.T) {
52 c := ChunkHeader{
53 Magic: MagicChunk,
54 Version: 99,
55 Flags: 0,
56 MessageID: 1,
57 TotalMessageLen: 256,
58 ChunkIndex: 0,
59 ChunkCount: 3,
60 ChunkPayloadLen: 100,
61 }
62 var buf [HeaderSize]byte
63 c.Encode(buf[:])
64 _, err := DecodeChunkHeader(buf[:])
65 if err != ErrBadVersion {
66 t.Fatalf("expected ErrBadVersion, got %v", err)
67 }
68 }
69
70 // ---------------------------------------------------------------------------
71 // DecodeHelloAck error paths
72 // ---------------------------------------------------------------------------
73
74 func TestDecodeHelloAckBadLayout(t *testing.T) {
75 h := HelloAck{
76 LayoutVersion: 99, // invalid
77 }
78 var buf [64]byte
79 h.Encode(buf[:])
80 _, err := DecodeHelloAck(buf[:])
81 if err != ErrBadLayout {
82 t.Fatalf("expected ErrBadLayout for bad layout_version, got %v", err)
83 }
84 }
85
86 func TestDecodeHelloAckBadFlags(t *testing.T) {
87 h := HelloAck{
88 LayoutVersion: 1,
89 Flags: 0x0001, // non-zero flags
90 }
91 var buf [64]byte
92 h.Encode(buf[:])
93 _, err := DecodeHelloAck(buf[:])
94 if err != ErrBadLayout {
95 t.Fatalf("expected ErrBadLayout for non-zero flags, got %v", err)
96 }
97 }
98
99 // ---------------------------------------------------------------------------
100 // BatchDirValidate
101 // ---------------------------------------------------------------------------
102
103 func TestBatchDirValidateSuccess(t *testing.T) {
104 // Build a valid directory with 2 entries
105 var buf [16]byte
106 ne.PutUint32(buf[0:4], 0) // offset=0, aligned
107 ne.PutUint32(buf[4:8], 10) // length=10
108 ne.PutUint32(buf[8:12], 16) // offset=16, aligned
109 ne.PutUint32(buf[12:16], 5) // length=5
110
111 err := BatchDirValidate(buf[:], 2, 100)
112 if err != nil {
113 t.Fatalf("expected success, got %v", err)
114 }
115 }
116
117 func TestBatchDirValidateTruncated(t *testing.T) {
118 err := BatchDirValidate(make([]byte, 4), 2, 100)
119 if err != ErrTruncated {
120 t.Fatalf("expected ErrTruncated, got %v", err)
121 }
122 }
123
124 func TestBatchDirValidateBadAlignment(t *testing.T) {
125 var buf [8]byte
126 ne.PutUint32(buf[0:4], 3) // offset=3, not 8-byte aligned
127 ne.PutUint32(buf[4:8], 5)
128
129 err := BatchDirValidate(buf[:], 1, 100)
130 if err != ErrBadAlignment {
131 t.Fatalf("expected ErrBadAlignment, got %v", err)
132 }
133 }
134
135 func TestBatchDirValidateOutOfBounds(t *testing.T) {
136 var buf [8]byte
137 ne.PutUint32(buf[0:4], 0)
138 ne.PutUint32(buf[4:8], 200) // length=200 exceeds packedAreaLen=100
139
140 err := BatchDirValidate(buf[:], 1, 100)
141 if err != ErrOutOfBounds {
142 t.Fatalf("expected ErrOutOfBounds, got %v", err)
143 }
144 }
145
146 func TestBatchDirValidateZeroItems(t *testing.T) {
147 err := BatchDirValidate(nil, 0, 0)
148 if err != nil {
149 t.Fatalf("expected success for zero items, got %v", err)
150 }
151 }
152
153 // ---------------------------------------------------------------------------
154 // BatchItemGet error paths
155 // ---------------------------------------------------------------------------
156
157 func TestBatchItemGetBadAlignment(t *testing.T) {
158 // Build a batch payload with 1 item, but set misaligned offset
159 var buf [64]byte
160 ne.PutUint32(buf[0:4], 3) // offset=3, not aligned
161 ne.PutUint32(buf[4:8], 5) // length=5
162
163 _, err := BatchItemGet(buf[:], 1, 0)
164 if err != ErrBadAlignment {
165 t.Fatalf("expected ErrBadAlignment, got %v", err)
166 }
167 }
168
169 func TestBatchItemGetDirTruncated(t *testing.T) {
170 // 2 items = 16 bytes dir, aligned to 16. But give only 8 bytes total.
171 _, err := BatchItemGet(make([]byte, 8), 2, 0)
172 if err != ErrTruncated {
173 t.Fatalf("expected ErrTruncated, got %v", err)
174 }
175 }
176
177 // ---------------------------------------------------------------------------
178 // DecodeCgroupsRequest error paths
179 // ---------------------------------------------------------------------------
180
181 func TestDecodeCgroupsRequestBadFlags(t *testing.T) {
182 // Valid layout_version but non-zero flags
183 var buf [4]byte
184 ne.PutUint16(buf[0:2], 1) // layout_version = 1
185 ne.PutUint16(buf[2:4], 0x01) // flags = 1 (invalid)
186
187 _, err := DecodeCgroupsRequest(buf[:])
188 if err != ErrBadLayout {
189 t.Fatalf("expected ErrBadLayout for non-zero flags, got %v", err)
190 }
191 }
192
193 // ---------------------------------------------------------------------------
194 // DecodeCgroupsResponse error paths
195 // ---------------------------------------------------------------------------
196
197 func TestDecodeCgroupsResponseBadFlags(t *testing.T) {
198 var buf [24]byte
199 ne.PutUint16(buf[0:2], 1) // layout_version = 1
200 ne.PutUint16(buf[2:4], 0x01) // flags = 1 (invalid)
201 ne.PutUint32(buf[4:8], 0) // item_count = 0
202 ne.PutUint32(buf[8:12], 0) // systemd_enabled
203 ne.PutUint32(buf[12:16], 0) // reserved
204 ne.PutUint64(buf[16:24], 0) // generation
205
206 _, err := DecodeCgroupsResponse(buf[:])
207 if err != ErrBadLayout {
208 t.Fatalf("expected ErrBadLayout for non-zero flags, got %v", err)
209 }
210 }
211
212 func TestDecodeCgroupsResponseBadReserved(t *testing.T) {
213 var buf [24]byte
214 ne.PutUint16(buf[0:2], 1) // layout_version = 1
215 ne.PutUint16(buf[2:4], 0) // flags = 0
216 ne.PutUint32(buf[4:8], 0) // item_count = 0
217 ne.PutUint32(buf[8:12], 0) // systemd_enabled
218 ne.PutUint32(buf[12:16], 99) // reserved non-zero (invalid)
219 ne.PutUint64(buf[16:24], 0) // generation
220
221 _, err := DecodeCgroupsResponse(buf[:])
222 if err != ErrBadLayout {
223 t.Fatalf("expected ErrBadLayout for non-zero reserved field, got %v", err)
224 }
225 }
226
227 func TestDecodeCgroupsResponseDirTruncated(t *testing.T) {
228 // Declare 1 item but no space for the directory entry
229 var buf [24]byte
230 ne.PutUint16(buf[0:2], 1) // layout_version = 1
231 ne.PutUint16(buf[2:4], 0) // flags = 0
232 ne.PutUint32(buf[4:8], 1) // item_count = 1
233 ne.PutUint32(buf[8:12], 0) // systemd_enabled
234 ne.PutUint32(buf[12:16], 0)
235 ne.PutUint64(buf[16:24], 0)
236
237 _, err := DecodeCgroupsResponse(buf[:])
238 if err != ErrTruncated {
239 t.Fatalf("expected ErrTruncated, got %v", err)
240 }
241 }
242
243 func TestDecodeCgroupsResponseDirBadAlignment(t *testing.T) {
244 // 1 item: dir at offset 24, packed area starts at 32
245 // Set item offset to 3 (misaligned)
246 var buf [128]byte
247 ne.PutUint16(buf[0:2], 1) // layout_version
248 ne.PutUint16(buf[2:4], 0) // flags
249 ne.PutUint32(buf[4:8], 1) // item_count = 1
250 ne.PutUint32(buf[8:12], 0) // systemd_enabled
251 ne.PutUint32(buf[12:16], 0)
252 ne.PutUint64(buf[16:24], 0)
253 // Directory entry at offset 24
254 ne.PutUint32(buf[24:28], 3) // offset = 3 (misaligned)
255 ne.PutUint32(buf[28:32], 40) // length = 40
256
257 _, err := DecodeCgroupsResponse(buf[:])
258 if err != ErrBadAlignment {
259 t.Fatalf("expected ErrBadAlignment, got %v", err)
260 }
261 }
262
263 func TestDecodeCgroupsResponseDirItemTooSmall(t *testing.T) {
264 // 1 item with length < cgroupsItemHdr (32)
265 var buf [128]byte
266 ne.PutUint16(buf[0:2], 1) // layout_version
267 ne.PutUint16(buf[2:4], 0) // flags
268 ne.PutUint32(buf[4:8], 1) // item_count = 1
269 ne.PutUint32(buf[8:12], 0) // systemd_enabled
270 ne.PutUint32(buf[12:16], 0)
271 ne.PutUint64(buf[16:24], 0)
272 // Directory entry at offset 24
273 ne.PutUint32(buf[24:28], 0) // offset = 0
274 ne.PutUint32(buf[28:32], 16) // length = 16 (< cgroupsItemHdr=32)
275
276 _, err := DecodeCgroupsResponse(buf[:])
277 if err != ErrTruncated {
278 t.Fatalf("expected ErrTruncated for item too small, got %v", err)
279 }
280 }
281
282 // ---------------------------------------------------------------------------
283 // CgroupsResponseView.Item error paths
284 // ---------------------------------------------------------------------------
285
286 func TestCgroupsItemBadLayoutVersion(t *testing.T) {
287 // Build a valid snapshot, then corrupt the item's layout_version
288 var buf [4096]byte
289 b := NewCgroupsBuilder(buf[:], 1, 0, 1)
290 if err := b.Add(42, 0, 1, []byte("test"), []byte("/path")); err != nil {
291 t.Fatalf("Add: %v", err)
292 }
293 total := b.Finish()
294 payload := buf[:total]
295
296 // Decode successfully first
297 view, err := DecodeCgroupsResponse(payload)
298 if err != nil {
299 t.Fatalf("DecodeCgroupsResponse: %v", err)
300 }
301
302 // Find the item in the buffer and corrupt layout_version
303 dirBase := cgroupsRespHdr
304 itemOff := int(ne.Uint32(payload[dirBase : dirBase+4]))
305 packedStart := cgroupsRespHdr + int(view.ItemCount)*cgroupsDirEntry
306 itemAbsStart := packedStart + itemOff
307 ne.PutUint16(payload[itemAbsStart:itemAbsStart+2], 99) // bad layout_version
308
309 _, err = view.Item(0)
310 if err != ErrBadLayout {
311 t.Fatalf("expected ErrBadLayout for bad item layout_version, got %v", err)
312 }
313 }
314
315 func TestCgroupsItemBadFlags(t *testing.T) {
316 var buf [4096]byte
317 b := NewCgroupsBuilder(buf[:], 1, 0, 1)
318 if err := b.Add(42, 0, 1, []byte("test"), []byte("/path")); err != nil {
319 t.Fatalf("Add: %v", err)
320 }
321 total := b.Finish()
322 payload := buf[:total]
323
324 view, err := DecodeCgroupsResponse(payload)
325 if err != nil {
326 t.Fatalf("DecodeCgroupsResponse: %v", err)
327 }
328
329 // Corrupt item flags
330 dirBase := cgroupsRespHdr
331 itemOff := int(ne.Uint32(payload[dirBase : dirBase+4]))
332 packedStart := cgroupsRespHdr + int(view.ItemCount)*cgroupsDirEntry
333 itemAbsStart := packedStart + itemOff
334 ne.PutUint16(payload[itemAbsStart+2:itemAbsStart+4], 0x01) // bad flags
335
336 _, err = view.Item(0)
337 if err != ErrBadLayout {
338 t.Fatalf("expected ErrBadLayout for bad item flags, got %v", err)
339 }
340 }
341
342 // ---------------------------------------------------------------------------
343 // CStringView.GoString
344 // ---------------------------------------------------------------------------
345
346 func TestCStringViewGoString(t *testing.T) {
347 data := []byte("hello\x00")
348 v := NewCStringView(data, 5)
349 gs := v.GoString()
350 if gs != `CStringView("hello")` {
351 t.Fatalf("GoString = %q, want CStringView(\"hello\")", gs)
352 }
353 }
354
355 // ---------------------------------------------------------------------------
356 // DispatchCgroupsSnapshot
357 // ---------------------------------------------------------------------------
358
359 func TestDispatchCgroupsSnapshotSuccess(t *testing.T) {
360 // Valid request
361 var reqBuf [4]byte
362 req := CgroupsRequest{LayoutVersion: 1, Flags: 0}
363 req.Encode(reqBuf[:])
364
365 resp := make([]byte, 4096)
366 n, ok := DispatchCgroupsSnapshot(reqBuf[:], resp, 1,
367 func(r *CgroupsRequest, b *CgroupsBuilder) bool {
368 if err := b.Add(1, 0, 1, []byte("cg"), []byte("/path")); err != nil {
369 return false
370 }
371 return true
372 })
373 if !ok {
374 t.Fatal("expected success")
375 }
376 if n == 0 {
377 t.Fatal("expected non-zero payload size")
378 }
379
380 // Verify the result decodes
381 view, err := DecodeCgroupsResponse(resp[:n])
382 if err != nil {
383 t.Fatalf("DecodeCgroupsResponse: %v", err)
384 }
385 if view.ItemCount != 1 {
386 t.Fatalf("expected 1 item, got %d", view.ItemCount)
387 }
388 }
389
390 func TestDispatchCgroupsSnapshotBadRequest(t *testing.T) {
391 // Truncated request
392 _, ok := DispatchCgroupsSnapshot([]byte{0}, make([]byte, 4096), 1,
393 func(r *CgroupsRequest, b *CgroupsBuilder) bool {
394 return true
395 })
396 if ok {
397 t.Fatal("expected failure for bad request")
398 }
399 }
400
401 func TestDispatchCgroupsSnapshotHandlerFails(t *testing.T) {
402 var reqBuf [4]byte
403 req := CgroupsRequest{LayoutVersion: 1, Flags: 0}
404 req.Encode(reqBuf[:])
405
406 _, ok := DispatchCgroupsSnapshot(reqBuf[:], make([]byte, 4096), 1,
407 func(r *CgroupsRequest, b *CgroupsBuilder) bool {
408 return false
409 })
410 if ok {
411 t.Fatal("expected failure when handler returns false")
412 }
413 }
414
415 func TestDispatchCgroupsSnapshotEmptyResult(t *testing.T) {
416 var reqBuf [4]byte
417 req := CgroupsRequest{LayoutVersion: 1, Flags: 0}
418 req.Encode(reqBuf[:])
419
420 // Handler succeeds but adds no items - Finish returns cgroupsRespHdr (24) which is > 0
421 n, ok := DispatchCgroupsSnapshot(reqBuf[:], make([]byte, 4096), 0,
422 func(r *CgroupsRequest, b *CgroupsBuilder) bool {
423 return true
424 })
425 if !ok {
426 t.Fatal("expected success for empty snapshot")
427 }
428 if n != cgroupsRespHdr {
429 t.Fatalf("expected %d bytes, got %d", cgroupsRespHdr, n)
430 }
431 }
432
433 // ---------------------------------------------------------------------------
434 // IncrementEncode / IncrementDecode
435 // ---------------------------------------------------------------------------
436
437 func TestIncrementEncodeSuccess(t *testing.T) {
438 var buf [8]byte
439 n := IncrementEncode(0xDEADBEEFCAFEBABE, buf[:])
440 if n != 8 {
441 t.Fatalf("expected 8, got %d", n)
442 }
443 val, err := IncrementDecode(buf[:])
444 if err != nil {
445 t.Fatalf("decode: %v", err)
446 }
447 if val != 0xDEADBEEFCAFEBABE {
448 t.Fatalf("expected 0xDEADBEEFCAFEBABE, got 0x%x", val)
449 }
450 }
451
452 func TestIncrementEncodeTooSmall(t *testing.T) {
453 var buf [4]byte
454 n := IncrementEncode(42, buf[:])
455 if n != 0 {
456 t.Fatalf("expected 0 for too-small buffer, got %d", n)
457 }
458 }
459
460 func TestIncrementDecodeTruncated(t *testing.T) {
461 _, err := IncrementDecode(make([]byte, 3))
462 if err != ErrTruncated {
463 t.Fatalf("expected ErrTruncated, got %v", err)
464 }
465 }
466
467 // ---------------------------------------------------------------------------
468 // DispatchIncrement
469 // ---------------------------------------------------------------------------
470
471 func TestDispatchIncrementSuccess(t *testing.T) {
472 var reqBuf [8]byte
473 IncrementEncode(100, reqBuf[:])
474
475 var respBuf [8]byte
476 n, ok := DispatchIncrement(reqBuf[:], respBuf[:], func(v uint64) (uint64, bool) {
477 return v + 1, true
478 })
479 if !ok {
480 t.Fatal("expected success")
481 }
482 if n != 8 {
483 t.Fatalf("expected 8, got %d", n)
484 }
485 val, err := IncrementDecode(respBuf[:])
486 if err != nil {
487 t.Fatalf("decode: %v", err)
488 }
489 if val != 101 {
490 t.Fatalf("expected 101, got %d", val)
491 }
492 }
493
494 func TestDispatchIncrementBadRequest(t *testing.T) {
495 _, ok := DispatchIncrement([]byte{0, 1}, make([]byte, 8), func(v uint64) (uint64, bool) {
496 return v, true
497 })
498 if ok {
499 t.Fatal("expected failure for truncated request")
500 }
501 }
502
503 func TestDispatchIncrementHandlerFails(t *testing.T) {
504 var reqBuf [8]byte
505 IncrementEncode(42, reqBuf[:])
506
507 _, ok := DispatchIncrement(reqBuf[:], make([]byte, 8), func(v uint64) (uint64, bool) {
508 return 0, false
509 })
510 if ok {
511 t.Fatal("expected failure when handler returns false")
512 }
513 }
514
515 func TestDispatchIncrementRespTooSmall(t *testing.T) {
516 var reqBuf [8]byte
517 IncrementEncode(42, reqBuf[:])
518
519 n, ok := DispatchIncrement(reqBuf[:], make([]byte, 2), func(v uint64) (uint64, bool) {
520 return 42, true
521 })
522 // IncrementEncode returns 0 for too-small buffer, so n=0, ok = (0>0) = false
523 if ok {
524 t.Fatal("expected failure for too-small response buffer")
525 }
526 if n != 0 {
527 t.Fatalf("expected n=0, got %d", n)
528 }
529 }
530
531 // ---------------------------------------------------------------------------
532 // StringReverseEncode / StringReverseDecode
533 // ---------------------------------------------------------------------------
534
535 func TestStringReverseRoundtrip(t *testing.T) {
536 s := "hello world"
537 buf := make([]byte, StringReverseHdrSize+len(s)+1)
538 n := StringReverseEncode(s, buf)
539 if n != len(buf) {
540 t.Fatalf("expected %d, got %d", len(buf), n)
541 }
542
543 view, err := StringReverseDecode(buf)
544 if err != nil {
545 t.Fatalf("decode: %v", err)
546 }
547 if view.Str != s {
548 t.Fatalf("expected %q, got %q", s, view.Str)
549 }
550 if view.StrLen != uint32(len(s)) {
551 t.Fatalf("expected len=%d, got %d", len(s), view.StrLen)
552 }
553 }
554
555 func TestStringReverseEncodeEmpty(t *testing.T) {
556 buf := make([]byte, StringReverseHdrSize+1) // 8 + 0 + 1 NUL
557 n := StringReverseEncode("", buf)
558 if n != StringReverseHdrSize+1 {
559 t.Fatalf("expected %d, got %d", StringReverseHdrSize+1, n)
560 }
561
562 view, err := StringReverseDecode(buf[:n])
563 if err != nil {
564 t.Fatalf("decode: %v", err)
565 }
566 if view.Str != "" {
567 t.Fatalf("expected empty, got %q", view.Str)
568 }
569 }
570
571 func TestStringReverseEncodeTooSmall(t *testing.T) {
572 n := StringReverseEncode("hello", make([]byte, 5))
573 if n != 0 {
574 t.Fatalf("expected 0 for too-small buffer, got %d", n)
575 }
576 }
577
578 func TestStringReverseDecodeTruncated(t *testing.T) {
579 _, err := StringReverseDecode(make([]byte, 4))
580 if err != ErrTruncated {
581 t.Fatalf("expected ErrTruncated, got %v", err)
582 }
583 }
584
585 func TestStringReverseDecodeOutOfBounds(t *testing.T) {
586 var buf [8]byte
587 ne.PutUint32(buf[0:4], 8) // str_offset
588 ne.PutUint32(buf[4:8], 99) // str_length (overflows)
589
590 _, err := StringReverseDecode(buf[:])
591 if err != ErrOutOfBounds {
592 t.Fatalf("expected ErrOutOfBounds, got %v", err)
593 }
594 }
595
596 func TestStringReverseDecodeMissingNul(t *testing.T) {
597 // Build payload where NUL terminator is wrong
598 buf := make([]byte, StringReverseHdrSize+6)
599 ne.PutUint32(buf[0:4], uint32(StringReverseHdrSize)) // str_offset
600 ne.PutUint32(buf[4:8], 5) // str_length
601 copy(buf[8:13], "hello")
602 buf[13] = 'X' // should be 0
603
604 _, err := StringReverseDecode(buf)
605 if err != ErrMissingNul {
606 t.Fatalf("expected ErrMissingNul, got %v", err)
607 }
608 }
609
610 // ---------------------------------------------------------------------------
611 // DispatchStringReverse
612 // ---------------------------------------------------------------------------
613
614 func TestDispatchStringReverseSuccess(t *testing.T) {
615 input := "hello"
616 reqBuf := make([]byte, StringReverseHdrSize+len(input)+1)
617 StringReverseEncode(input, reqBuf)
618
619 respBuf := make([]byte, 128)
620 n, ok := DispatchStringReverse(reqBuf, respBuf, func(s string) (string, bool) {
621 // Reverse the string
622 b := []byte(s)
623 for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
624 b[i], b[j] = b[j], b[i]
625 }
626 return string(b), true
627 })
628 if !ok {
629 t.Fatal("expected success")
630 }
631 if n == 0 {
632 t.Fatal("expected non-zero response size")
633 }
634
635 view, err := StringReverseDecode(respBuf[:n])
636 if err != nil {
637 t.Fatalf("decode: %v", err)
638 }
639 if view.Str != "olleh" {
640 t.Fatalf("expected 'olleh', got %q", view.Str)
641 }
642 }
643
644 func TestDispatchStringReverseBadRequest(t *testing.T) {
645 _, ok := DispatchStringReverse([]byte{0}, make([]byte, 128), func(s string) (string, bool) {
646 return s, true
647 })
648 if ok {
649 t.Fatal("expected failure for bad request")
650 }
651 }
652
653 func TestDispatchStringReverseHandlerFails(t *testing.T) {
654 input := "test"
655 reqBuf := make([]byte, StringReverseHdrSize+len(input)+1)
656 StringReverseEncode(input, reqBuf)
657
658 _, ok := DispatchStringReverse(reqBuf, make([]byte, 128), func(s string) (string, bool) {
659 return "", false
660 })
661 if ok {
662 t.Fatal("expected failure when handler returns false")
663 }
664 }
665
666 func TestDispatchStringReverseRespTooSmall(t *testing.T) {
667 input := "hello"
668 reqBuf := make([]byte, StringReverseHdrSize+len(input)+1)
669 StringReverseEncode(input, reqBuf)
670
671 // Response buffer too small for result
672 n, ok := DispatchStringReverse(reqBuf, make([]byte, 2), func(s string) (string, bool) {
673 return "very long response string", true
674 })
675 if ok {
676 t.Fatal("expected failure for too-small response buffer")
677 }
678 if n != 0 {
679 t.Fatalf("expected n=0, got %d", n)
680 }
681 }
682
683 // ---------------------------------------------------------------------------
684 // CgroupsBuilder edge cases
685 // ---------------------------------------------------------------------------
686
687 func TestCgroupsBuilderOverflow(t *testing.T) {
688 // Buffer too small for the item
689 var buf [64]byte
690 b := NewCgroupsBuilder(buf[:], 1, 0, 1)
691 // name + path would overflow the small buffer
692 err := b.Add(1, 0, 1,
693 []byte("this-name-is-quite-long-to-overflow"),
694 []byte("/this/path/is/also/long/enough/to/overflow"))
695 if err != ErrOverflow {
696 t.Fatalf("expected ErrOverflow, got %v", err)
697 }
698 }
699
700 func TestCgroupsBuilderMaxItemsExceeded(t *testing.T) {
701 var buf [4096]byte
702 b := NewCgroupsBuilder(buf[:], 1, 0, 1)
703 // First item succeeds
704 if err := b.Add(1, 0, 1, []byte("a"), []byte("/b")); err != nil {
705 t.Fatalf("first Add: %v", err)
706 }
707 // Second item exceeds maxItems
708 err := b.Add(2, 0, 1, []byte("c"), []byte("/d"))
709 if err != ErrOverflow {
710 t.Fatalf("expected ErrOverflow, got %v", err)
711 }
712 }
713
714 func TestCgroupsBuilderFinishCompaction(t *testing.T) {
715 // Reserve space for 4 items but only add 2 — tests compaction path
716 var buf [4096]byte
717 b := NewCgroupsBuilder(buf[:], 4, 1, 42)
718 if err := b.Add(1, 0, 1, []byte("name1"), []byte("/path1")); err != nil {
719 t.Fatalf("Add 1: %v", err)
720 }
721 if err := b.Add(2, 0, 1, []byte("name2"), []byte("/path2")); err != nil {
722 t.Fatalf("Add 2: %v", err)
723 }
724 total := b.Finish()
725
726 view, err := DecodeCgroupsResponse(buf[:total])
727 if err != nil {
728 t.Fatalf("decode: %v", err)
729 }
730 if view.ItemCount != 2 {
731 t.Fatalf("expected 2 items, got %d", view.ItemCount)
732 }
733 if view.SystemdEnabled != 1 {
734 t.Fatalf("expected systemd_enabled=1, got %d", view.SystemdEnabled)
735 }
736 if view.Generation != 42 {
737 t.Fatalf("expected generation=42, got %d", view.Generation)
738 }
739
740 // Verify both items decode correctly
741 item0, err := view.Item(0)
742 if err != nil {
743 t.Fatalf("item 0: %v", err)
744 }
745 if item0.Name.String() != "name1" || item0.Path.String() != "/path1" {
746 t.Fatalf("item 0 mismatch: name=%q path=%q", item0.Name.String(), item0.Path.String())
747 }
748
749 item1, err := view.Item(1)
750 if err != nil {
751 t.Fatalf("item 1: %v", err)
752 }
753 if item1.Name.String() != "name2" || item1.Path.String() != "/path2" {
754 t.Fatalf("item 1 mismatch: name=%q path=%q", item1.Name.String(), item1.Path.String())
755 }
756 }
757
758 // ---------------------------------------------------------------------------
759 // BatchBuilder edge cases
760 // ---------------------------------------------------------------------------
761
762 func TestBatchBuilderCompactionWide(t *testing.T) {
763 // Reserve 8 slots, use only 2 (wider gap than existing test)
764 var buf [4096]byte
765 bb := NewBatchBuilder(buf[:], 8)
766 if err := bb.Add([]byte{1, 2, 3}); err != nil {
767 t.Fatalf("Add 1: %v", err)
768 }
769 if err := bb.Add([]byte{4, 5}); err != nil {
770 t.Fatalf("Add 2: %v", err)
771 }
772 total, count := bb.Finish()
773 if count != 2 {
774 t.Fatalf("expected count=2, got %d", count)
775 }
776
777 // Verify items are accessible
778 item0, err := BatchItemGet(buf[:total], count, 0)
779 if err != nil {
780 t.Fatalf("get item 0: %v", err)
781 }
782 if len(item0) != 3 || item0[0] != 1 {
783 t.Fatalf("item 0 mismatch: %v", item0)
784 }
785
786 item1, err := BatchItemGet(buf[:total], count, 1)
787 if err != nil {
788 t.Fatalf("get item 1: %v", err)
789 }
790 if len(item1) != 2 || item1[0] != 4 {
791 t.Fatalf("item 1 mismatch: %v", item1)
792 }
793 }
794
795 func TestBatchBuilderOverflowMaxItemsSingle(t *testing.T) {
796 var buf [4096]byte
797 bb := NewBatchBuilder(buf[:], 1)
798 if err := bb.Add([]byte{1}); err != nil {
799 t.Fatalf("first Add: %v", err)
800 }
801 err := bb.Add([]byte{2})
802 if err != ErrOverflow {
803 t.Fatalf("expected ErrOverflow, got %v", err)
804 }
805 }
806
807 func TestBatchBuilderOverflowBufferFull(t *testing.T) {
808 // Tiny buffer: dir for 1 item = 8 bytes aligned, so 8 bytes.
809 // Total buf = 16 bytes, leaving 8 for data.
810 var buf [16]byte
811 bb := NewBatchBuilder(buf[:], 1)
812 err := bb.Add(make([]byte, 100)) // too large
813 if err != ErrOverflow {
814 t.Fatalf("expected ErrOverflow, got %v", err)
815 }
816 }
817
818 func TestBatchBuilderFinishNoCompaction(t *testing.T) {
819 // Use exactly the reserved number of slots - no compaction needed
820 var buf [4096]byte
821 bb := NewBatchBuilder(buf[:], 2)
822 if err := bb.Add([]byte{1, 2, 3, 4, 5, 6, 7, 8}); err != nil {
823 t.Fatalf("Add 1: %v", err)
824 }
825 if err := bb.Add([]byte{10, 20}); err != nil {
826 t.Fatalf("Add 2: %v", err)
827 }
828 total, count := bb.Finish()
829 if count != 2 {
830 t.Fatalf("expected count=2, got %d", count)
831 }
832
833 // Verify access
834 for i := range count {
835 _, err := BatchItemGet(buf[:total], count, i)
836 if err != nil {
837 t.Fatalf("item %d: %v", i, err)
838 }
839 }
840 }
841
842 // ---------------------------------------------------------------------------
843 // DecodeHello padding validation
844 // ---------------------------------------------------------------------------
845
846 func TestDecodeHelloBadPadding(t *testing.T) {
847 h := Hello{
848 LayoutVersion: 1,
849 SupportedProfiles: ProfileBaseline,
850 MaxRequestPayloadBytes: 1024,
851 MaxRequestBatchItems: 1,
852 MaxResponsePayloadBytes: 1024,
853 MaxResponseBatchItems: 1,
854 AuthToken: 42,
855 PacketSize: 65536,
856 }
857 var buf [64]byte
858 h.Encode(buf[:])
859
860 // Corrupt padding bytes at offset 28..32
861 ne.PutUint32(buf[28:32], 0xFFFF)
862
863 _, err := DecodeHello(buf[:44])
864 if err != ErrBadLayout {
865 t.Fatalf("expected ErrBadLayout for bad padding, got %v", err)
866 }
867 }
868
869 // ---------------------------------------------------------------------------
870 // CgroupsBuilder utility functions
871 // ---------------------------------------------------------------------------
872
873 func TestCgroupsBuilderSetHeader(t *testing.T) {
874 var buf [4096]byte
875 b := NewCgroupsBuilder(buf[:], 10, 0, 0)
876
877 // Initial values should be zero
878 if b.systemdEnabled != 0 {
879 t.Fatalf("initial systemdEnabled should be 0, got %d", b.systemdEnabled)
880 }
881 if b.generation != 0 {
882 t.Fatalf("initial generation should be 0, got %d", b.generation)
883 }
884
885 // Set header values
886 b.SetHeader(1, 12345)
887
888 if b.systemdEnabled != 1 {
889 t.Fatalf("SetHeader systemdEnabled should be 1, got %d", b.systemdEnabled)
890 }
891 if b.generation != 12345 {
892 t.Fatalf("SetHeader generation should be 12345, got %d", b.generation)
893 }
894 }
895
896 func TestEstimateCgroupsMaxItems(t *testing.T) {
897 // Buffer too small - should return 0
898 maxItems := EstimateCgroupsMaxItems(cgroupsRespHdr)
899 if maxItems != 0 {
900 t.Fatalf("EstimateCgroupsMaxItems(%d) should be 0, got %d", cgroupsRespHdr, maxItems)
901 }
902
903 // Small buffer - should return 0
904 maxItems = EstimateCgroupsMaxItems(cgroupsRespHdr + 10)
905 if maxItems != 0 {
906 t.Fatalf("EstimateCgroupsMaxItems(small) should be 0, got %d", maxItems)
907 }
908
909 // Reasonable buffer - should return positive value
910 maxItems = EstimateCgroupsMaxItems(4096)
911 if maxItems == 0 {
912 t.Fatalf("EstimateCgroupsMaxItems(4096) should be > 0, got 0")
913 }
914
915 // Larger buffer - should return larger value
916 maxItemsLarge := EstimateCgroupsMaxItems(65536)
917 if maxItemsLarge <= maxItems {
918 t.Fatalf("EstimateCgroupsMaxItems(65536)=%d should be > EstimateCgroupsMaxItems(4096)=%d", maxItemsLarge, maxItems)
919 }
920 }