github.com/cilium/ebpf@v0.15.0/collection_test.go (about) 1 package ebpf 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "os" 8 "reflect" 9 "testing" 10 11 "github.com/go-quicktest/qt" 12 13 "github.com/cilium/ebpf/asm" 14 "github.com/cilium/ebpf/btf" 15 "github.com/cilium/ebpf/internal" 16 "github.com/cilium/ebpf/internal/testutils" 17 "github.com/cilium/ebpf/internal/testutils/fdtrace" 18 ) 19 20 func TestMain(m *testing.M) { 21 fdtrace.TestMain(m) 22 } 23 24 func TestCollectionSpecNotModified(t *testing.T) { 25 cs := CollectionSpec{ 26 Maps: map[string]*MapSpec{ 27 "my-map": { 28 Type: Array, 29 KeySize: 4, 30 ValueSize: 4, 31 MaxEntries: 1, 32 }, 33 }, 34 Programs: map[string]*ProgramSpec{ 35 "test": { 36 Type: SocketFilter, 37 Instructions: asm.Instructions{ 38 asm.LoadImm(asm.R1, 0, asm.DWord).WithReference("my-map"), 39 asm.LoadImm(asm.R0, 0, asm.DWord), 40 asm.Return(), 41 }, 42 License: "MIT", 43 }, 44 }, 45 } 46 47 coll, err := NewCollection(&cs) 48 if err != nil { 49 t.Fatal(err) 50 } 51 coll.Close() 52 53 if cs.Programs["test"].Instructions[0].Constant != 0 { 54 t.Error("Creating a collection modifies input spec") 55 } 56 } 57 58 func TestCollectionSpecCopy(t *testing.T) { 59 cs := &CollectionSpec{ 60 Maps: map[string]*MapSpec{ 61 "my-map": { 62 Type: Array, 63 KeySize: 4, 64 ValueSize: 4, 65 MaxEntries: 1, 66 }, 67 }, 68 Programs: map[string]*ProgramSpec{ 69 "test": { 70 Type: SocketFilter, 71 Instructions: asm.Instructions{ 72 asm.LoadMapPtr(asm.R1, 0), 73 asm.LoadImm(asm.R0, 0, asm.DWord), 74 asm.Return(), 75 }, 76 License: "MIT", 77 }, 78 }, 79 Types: &btf.Spec{}, 80 } 81 cpy := cs.Copy() 82 83 if cpy == cs { 84 t.Error("Copy returned the same pointer") 85 } 86 87 if cpy.Maps["my-map"] == cs.Maps["my-map"] { 88 t.Error("Copy returned same Maps") 89 } 90 91 if cpy.Programs["test"] == cs.Programs["test"] { 92 t.Error("Copy returned same Programs") 93 } 94 95 if cpy.Types != cs.Types { 96 t.Error("Copy returned different Types") 97 } 98 } 99 100 func TestCollectionSpecLoadCopy(t *testing.T) { 101 file := testutils.NativeFile(t, "testdata/loader-%s.elf") 102 spec, err := LoadCollectionSpec(file) 103 if err != nil { 104 t.Fatal(err) 105 } 106 107 spec2 := spec.Copy() 108 109 var objs struct { 110 Prog *Program `ebpf:"xdp_prog"` 111 } 112 113 err = spec.LoadAndAssign(&objs, nil) 114 testutils.SkipIfNotSupported(t, err) 115 if err != nil { 116 t.Fatal("Loading original spec:", err) 117 } 118 defer objs.Prog.Close() 119 120 if err := spec2.LoadAndAssign(&objs, nil); err != nil { 121 t.Fatal("Loading copied spec:", err) 122 } 123 defer objs.Prog.Close() 124 } 125 126 func TestCollectionSpecRewriteMaps(t *testing.T) { 127 insns := asm.Instructions{ 128 // R1 map 129 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 130 // R2 key 131 asm.Mov.Reg(asm.R2, asm.R10), 132 asm.Add.Imm(asm.R2, -4), 133 asm.StoreImm(asm.R2, 0, 0, asm.Word), 134 // Lookup map[0] 135 asm.FnMapLookupElem.Call(), 136 asm.JEq.Imm(asm.R0, 0, "ret"), 137 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 138 asm.Return().WithSymbol("ret"), 139 } 140 141 cs := &CollectionSpec{ 142 Maps: map[string]*MapSpec{ 143 "test-map": { 144 Type: Array, 145 KeySize: 4, 146 ValueSize: 4, 147 MaxEntries: 1, 148 }, 149 }, 150 Programs: map[string]*ProgramSpec{ 151 "test-prog": { 152 Type: SocketFilter, 153 Instructions: insns, 154 License: "MIT", 155 }, 156 }, 157 } 158 159 // Override the map with another one 160 newMap, err := NewMap(cs.Maps["test-map"]) 161 if err != nil { 162 t.Fatal(err) 163 } 164 defer newMap.Close() 165 166 err = newMap.Put(uint32(0), uint32(2)) 167 if err != nil { 168 t.Fatal(err) 169 } 170 171 err = cs.RewriteMaps(map[string]*Map{ 172 "test-map": newMap, 173 }) 174 if err != nil { 175 t.Fatal(err) 176 } 177 178 if cs.Maps["test-map"] != nil { 179 t.Error("RewriteMaps doesn't remove map from CollectionSpec.Maps") 180 } 181 182 coll, err := NewCollection(cs) 183 if err != nil { 184 t.Fatal(err) 185 } 186 defer coll.Close() 187 188 ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext) 189 testutils.SkipIfNotSupported(t, err) 190 if err != nil { 191 t.Fatal(err) 192 } 193 194 if ret != 2 { 195 t.Fatal("new / override map not used") 196 } 197 } 198 199 func TestCollectionSpecMapReplacements(t *testing.T) { 200 insns := asm.Instructions{ 201 // R1 map 202 asm.LoadMapPtr(asm.R1, 0).WithReference("test-map"), 203 // R2 key 204 asm.Mov.Reg(asm.R2, asm.R10), 205 asm.Add.Imm(asm.R2, -4), 206 asm.StoreImm(asm.R2, 0, 0, asm.Word), 207 // Lookup map[0] 208 asm.FnMapLookupElem.Call(), 209 asm.JEq.Imm(asm.R0, 0, "ret"), 210 asm.LoadMem(asm.R0, asm.R0, 0, asm.Word), 211 asm.Return().WithSymbol("ret"), 212 } 213 214 cs := &CollectionSpec{ 215 Maps: map[string]*MapSpec{ 216 "test-map": { 217 Type: Array, 218 KeySize: 4, 219 ValueSize: 4, 220 MaxEntries: 1, 221 }, 222 }, 223 Programs: map[string]*ProgramSpec{ 224 "test-prog": { 225 Type: SocketFilter, 226 Instructions: insns, 227 License: "MIT", 228 }, 229 }, 230 } 231 232 // Replace the map with another one 233 newMap, err := NewMap(cs.Maps["test-map"]) 234 if err != nil { 235 t.Fatal(err) 236 } 237 defer newMap.Close() 238 239 err = newMap.Put(uint32(0), uint32(2)) 240 if err != nil { 241 t.Fatal(err) 242 } 243 244 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 245 MapReplacements: map[string]*Map{ 246 "test-map": newMap, 247 }, 248 }) 249 if err != nil { 250 t.Fatal(err) 251 } 252 defer coll.Close() 253 254 ret, _, err := coll.Programs["test-prog"].Test(internal.EmptyBPFContext) 255 testutils.SkipIfNotSupported(t, err) 256 if err != nil { 257 t.Fatal(err) 258 } 259 260 if ret != 2 { 261 t.Fatal("new / override map not used") 262 } 263 264 // Check that newMap isn't closed when the collection is closed 265 coll.Close() 266 err = newMap.Put(uint32(0), uint32(3)) 267 if err != nil { 268 t.Fatalf("failed to update replaced map: %s", err) 269 } 270 } 271 func TestCollectionSpecMapReplacements_NonExistingMap(t *testing.T) { 272 cs := &CollectionSpec{ 273 Maps: map[string]*MapSpec{ 274 "test-map": { 275 Type: Array, 276 KeySize: 4, 277 ValueSize: 4, 278 MaxEntries: 1, 279 }, 280 }, 281 } 282 283 // Override non-existing map 284 newMap, err := NewMap(cs.Maps["test-map"]) 285 if err != nil { 286 t.Fatal(err) 287 } 288 defer newMap.Close() 289 290 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 291 MapReplacements: map[string]*Map{ 292 "non-existing-map": newMap, 293 }, 294 }) 295 if err == nil { 296 coll.Close() 297 t.Fatal("Overriding a non existing map did not fail") 298 } 299 } 300 301 func TestCollectionSpecMapReplacements_SpecMismatch(t *testing.T) { 302 cs := &CollectionSpec{ 303 Maps: map[string]*MapSpec{ 304 "test-map": { 305 Type: Array, 306 KeySize: 4, 307 ValueSize: 4, 308 MaxEntries: 1, 309 }, 310 }, 311 } 312 313 // Override map with mismatching spec 314 newMap, err := NewMap(&MapSpec{ 315 Type: Array, 316 KeySize: 4, 317 ValueSize: 8, // this is different 318 MaxEntries: 1, 319 }) 320 if err != nil { 321 t.Fatal(err) 322 } 323 // Map fd is duplicated by MapReplacements, this one can be safely closed. 324 defer newMap.Close() 325 326 coll, err := NewCollectionWithOptions(cs, CollectionOptions{ 327 MapReplacements: map[string]*Map{ 328 "test-map": newMap, 329 }, 330 }) 331 if err == nil { 332 coll.Close() 333 t.Fatal("Overriding a map with a mismatching spec did not fail") 334 } 335 if !errors.Is(err, ErrMapIncompatible) { 336 t.Fatalf("Overriding a map with a mismatching spec failed with the wrong error") 337 } 338 } 339 340 func TestCollectionRewriteConstants(t *testing.T) { 341 cs := &CollectionSpec{ 342 Maps: map[string]*MapSpec{ 343 ".rodata": { 344 Type: Array, 345 KeySize: 4, 346 ValueSize: 4, 347 MaxEntries: 1, 348 Value: &btf.Datasec{ 349 Vars: []btf.VarSecinfo{ 350 { 351 Type: &btf.Var{ 352 Name: "the_constant", 353 Type: &btf.Int{Size: 4}, 354 }, 355 Offset: 0, 356 Size: 4, 357 }, 358 }, 359 }, 360 Contents: []MapKV{ 361 {Key: uint32(0), Value: []byte{1, 1, 1, 1}}, 362 }, 363 }, 364 }, 365 } 366 367 err := cs.RewriteConstants(map[string]interface{}{ 368 "fake_constant_one": uint32(1), 369 "fake_constant_two": uint32(2), 370 }) 371 qt.Assert(t, qt.IsNotNil(err), qt.Commentf("RewriteConstants did not fail")) 372 373 var mErr *MissingConstantsError 374 if !errors.As(err, &mErr) { 375 t.Fatal("Error doesn't wrap MissingConstantsError:", err) 376 } 377 qt.Assert(t, qt.ContentEquals(mErr.Constants, []string{"fake_constant_one", "fake_constant_two"})) 378 379 err = cs.RewriteConstants(map[string]interface{}{ 380 "the_constant": uint32(0x42424242), 381 }) 382 qt.Assert(t, qt.IsNil(err)) 383 qt.Assert(t, qt.ContentEquals(cs.Maps[".rodata"].Contents[0].Value.([]byte), []byte{0x42, 0x42, 0x42, 0x42})) 384 } 385 386 func TestCollectionSpec_LoadAndAssign_LazyLoading(t *testing.T) { 387 spec := &CollectionSpec{ 388 Maps: map[string]*MapSpec{ 389 "valid": { 390 Type: Array, 391 KeySize: 4, 392 ValueSize: 4, 393 MaxEntries: 1, 394 }, 395 "bogus": { 396 Type: Array, 397 MaxEntries: 0, 398 }, 399 }, 400 Programs: map[string]*ProgramSpec{ 401 "valid": { 402 Type: SocketFilter, 403 Instructions: asm.Instructions{ 404 asm.LoadImm(asm.R0, 0, asm.DWord), 405 asm.Return(), 406 }, 407 License: "MIT", 408 }, 409 "bogus": { 410 Type: SocketFilter, 411 Instructions: asm.Instructions{ 412 // Undefined return value is rejected 413 asm.Return(), 414 }, 415 License: "MIT", 416 }, 417 }, 418 } 419 420 var objs struct { 421 Prog *Program `ebpf:"valid"` 422 Map *Map `ebpf:"valid"` 423 } 424 425 if err := spec.LoadAndAssign(&objs, nil); err != nil { 426 t.Fatal("Assign loads a map or program that isn't requested in the struct:", err) 427 } 428 defer objs.Prog.Close() 429 defer objs.Map.Close() 430 431 if objs.Prog == nil { 432 t.Error("Program is nil") 433 } 434 435 if objs.Map == nil { 436 t.Error("Map is nil") 437 } 438 } 439 440 func TestCollectionSpecAssign(t *testing.T) { 441 var specs struct { 442 Program *ProgramSpec `ebpf:"prog1"` 443 Map *MapSpec `ebpf:"map1"` 444 } 445 446 mapSpec := &MapSpec{ 447 Type: Array, 448 KeySize: 4, 449 ValueSize: 4, 450 MaxEntries: 1, 451 } 452 progSpec := &ProgramSpec{ 453 Type: SocketFilter, 454 Instructions: asm.Instructions{ 455 asm.LoadImm(asm.R0, 0, asm.DWord), 456 asm.Return(), 457 }, 458 License: "MIT", 459 } 460 461 cs := &CollectionSpec{ 462 Maps: map[string]*MapSpec{ 463 "map1": mapSpec, 464 }, 465 Programs: map[string]*ProgramSpec{ 466 "prog1": progSpec, 467 }, 468 } 469 470 if err := cs.Assign(&specs); err != nil { 471 t.Fatal("Can't assign spec:", err) 472 } 473 474 if specs.Program != progSpec { 475 t.Fatalf("Expected Program to be %p, got %p", progSpec, specs.Program) 476 } 477 478 if specs.Map != mapSpec { 479 t.Fatalf("Expected Map to be %p, got %p", mapSpec, specs.Map) 480 } 481 482 if err := cs.Assign(new(int)); err == nil { 483 t.Fatal("Assign allows to besides *struct") 484 } 485 486 if err := cs.Assign(new(struct{ Foo int })); err != nil { 487 t.Fatal("Assign doesn't ignore untagged fields") 488 } 489 490 unexported := new(struct { 491 foo *MapSpec `ebpf:"map1"` 492 }) 493 494 if err := cs.Assign(unexported); err == nil { 495 t.Error("Assign should return an error on unexported fields") 496 } 497 } 498 499 func TestAssignValues(t *testing.T) { 500 zero := func(t reflect.Type, name string) (interface{}, error) { 501 return reflect.Zero(t).Interface(), nil 502 } 503 504 type t1 struct { 505 Bar int `ebpf:"bar"` 506 } 507 508 type t2 struct { 509 t1 510 Foo int `ebpf:"foo"` 511 } 512 513 type t2ptr struct { 514 *t1 515 Foo int `ebpf:"foo"` 516 } 517 518 invalid := []struct { 519 name string 520 to interface{} 521 }{ 522 {"non-struct", 1}, 523 {"non-pointer struct", t1{}}, 524 {"pointer to non-struct", new(int)}, 525 {"embedded nil pointer", &t2ptr{}}, 526 {"unexported field", new(struct { 527 foo int `ebpf:"foo"` 528 })}, 529 {"identical tag", new(struct { 530 Foo1 int `ebpf:"foo"` 531 Foo2 int `ebpf:"foo"` 532 })}, 533 } 534 535 for _, testcase := range invalid { 536 t.Run(testcase.name, func(t *testing.T) { 537 if err := assignValues(testcase.to, zero); err == nil { 538 t.Fatal("assignValues didn't return an error") 539 } else { 540 t.Log(err) 541 } 542 }) 543 } 544 545 valid := []struct { 546 name string 547 to interface{} 548 }{ 549 {"pointer to struct", new(t1)}, 550 {"embedded struct", new(t2)}, 551 {"embedded struct pointer", &t2ptr{t1: new(t1)}}, 552 {"untagged field", new(struct{ Foo int })}, 553 } 554 555 for _, testcase := range valid { 556 t.Run(testcase.name, func(t *testing.T) { 557 if err := assignValues(testcase.to, zero); err != nil { 558 t.Fatal("assignValues returned", err) 559 } 560 }) 561 } 562 563 } 564 565 func TestCollectionAssign(t *testing.T) { 566 var objs struct { 567 Program *Program `ebpf:"prog1"` 568 Map *Map `ebpf:"map1"` 569 } 570 571 cs := &CollectionSpec{ 572 Maps: map[string]*MapSpec{ 573 "map1": { 574 Type: Array, 575 KeySize: 4, 576 ValueSize: 4, 577 MaxEntries: 1, 578 }, 579 }, 580 Programs: map[string]*ProgramSpec{ 581 "prog1": { 582 Type: SocketFilter, 583 Instructions: asm.Instructions{ 584 asm.LoadImm(asm.R0, 0, asm.DWord), 585 asm.Return(), 586 }, 587 License: "MIT", 588 }, 589 }, 590 } 591 592 coll, err := NewCollection(cs) 593 qt.Assert(t, qt.IsNil(err)) 594 defer coll.Close() 595 596 qt.Assert(t, qt.IsNil(coll.Assign(&objs))) 597 defer objs.Program.Close() 598 defer objs.Map.Close() 599 600 // Check that objs has received ownership of map and prog 601 qt.Assert(t, qt.IsTrue(objs.Program.FD() >= 0)) 602 qt.Assert(t, qt.IsTrue(objs.Map.FD() >= 0)) 603 604 // Check that the collection has lost ownership 605 qt.Assert(t, qt.IsNil(coll.Programs["prog1"])) 606 qt.Assert(t, qt.IsNil(coll.Maps["map1"])) 607 } 608 609 func TestCollectionAssignFail(t *testing.T) { 610 // `map2` does not exist 611 var objs struct { 612 Program *Program `ebpf:"prog1"` 613 Map *Map `ebpf:"map2"` 614 } 615 616 cs := &CollectionSpec{ 617 Maps: map[string]*MapSpec{ 618 "map1": { 619 Type: Array, 620 KeySize: 4, 621 ValueSize: 4, 622 MaxEntries: 1, 623 }, 624 }, 625 Programs: map[string]*ProgramSpec{ 626 "prog1": { 627 Type: SocketFilter, 628 Instructions: asm.Instructions{ 629 asm.LoadImm(asm.R0, 0, asm.DWord), 630 asm.Return(), 631 }, 632 License: "MIT", 633 }, 634 }, 635 } 636 637 coll, err := NewCollection(cs) 638 qt.Assert(t, qt.IsNil(err)) 639 defer coll.Close() 640 641 qt.Assert(t, qt.IsNotNil(coll.Assign(&objs))) 642 643 // Check that the collection has retained ownership 644 qt.Assert(t, qt.IsNotNil(coll.Programs["prog1"])) 645 qt.Assert(t, qt.IsNotNil(coll.Maps["map1"])) 646 } 647 648 func TestIncompleteLoadAndAssign(t *testing.T) { 649 spec := &CollectionSpec{ 650 Programs: map[string]*ProgramSpec{ 651 "valid": { 652 Type: SocketFilter, 653 Instructions: asm.Instructions{ 654 asm.LoadImm(asm.R0, 0, asm.DWord), 655 asm.Return(), 656 }, 657 License: "MIT", 658 }, 659 "invalid": { 660 Type: SocketFilter, 661 Instructions: asm.Instructions{ 662 asm.Return(), 663 }, 664 License: "MIT", 665 }, 666 }, 667 } 668 669 s := struct { 670 // Assignment to Valid should execute and succeed. 671 Valid *Program `ebpf:"valid"` 672 // Assignment to Invalid should fail and cause Valid's fd to be closed. 673 Invalid *Program `ebpf:"invalid"` 674 }{} 675 676 if err := spec.LoadAndAssign(&s, nil); err == nil { 677 t.Fatal("expected error loading invalid ProgramSpec") 678 } 679 680 if s.Valid == nil { 681 t.Fatal("expected valid prog to be non-nil") 682 } 683 684 if fd := s.Valid.FD(); fd != -1 { 685 t.Fatal("expected valid prog to have closed fd -1, got:", fd) 686 } 687 688 if s.Invalid != nil { 689 t.Fatal("expected invalid prog to be nil due to never being assigned") 690 } 691 } 692 693 func BenchmarkNewCollection(b *testing.B) { 694 file := testutils.NativeFile(b, "testdata/loader-%s.elf") 695 spec, err := LoadCollectionSpec(file) 696 if err != nil { 697 b.Fatal(err) 698 } 699 700 spec.Maps["array_of_hash_map"].InnerMap = spec.Maps["hash_map"] 701 for _, m := range spec.Maps { 702 m.Pinning = PinNone 703 } 704 705 b.ReportAllocs() 706 b.ResetTimer() 707 708 for i := 0; i < b.N; i++ { 709 coll, err := NewCollection(spec) 710 if err != nil { 711 b.Fatal(err) 712 } 713 coll.Close() 714 } 715 } 716 717 func BenchmarkNewCollectionManyProgs(b *testing.B) { 718 file := testutils.NativeFile(b, "testdata/manyprogs-%s.elf") 719 spec, err := LoadCollectionSpec(file) 720 if err != nil { 721 b.Fatal(err) 722 } 723 724 b.ReportAllocs() 725 b.ResetTimer() 726 727 for i := 0; i < b.N; i++ { 728 coll, err := NewCollection(spec) 729 if err != nil { 730 b.Fatal(err) 731 } 732 coll.Close() 733 } 734 } 735 736 func BenchmarkLoadCollectionManyProgs(b *testing.B) { 737 file, err := os.Open(testutils.NativeFile(b, "testdata/manyprogs-%s.elf")) 738 qt.Assert(b, qt.IsNil(err)) 739 defer file.Close() 740 741 b.ReportAllocs() 742 b.ResetTimer() 743 744 for i := 0; i < b.N; i++ { 745 _, err := file.Seek(0, io.SeekStart) 746 if err != nil { 747 b.Fatal(err) 748 } 749 750 _, err = LoadCollectionSpecFromReader(file) 751 if err != nil { 752 b.Fatal(err) 753 } 754 } 755 } 756 757 func ExampleCollectionSpec_Assign() { 758 spec := &CollectionSpec{ 759 Maps: map[string]*MapSpec{ 760 "map1": { 761 Type: Array, 762 KeySize: 4, 763 ValueSize: 4, 764 MaxEntries: 1, 765 }, 766 }, 767 Programs: map[string]*ProgramSpec{ 768 "prog1": { 769 Type: SocketFilter, 770 Instructions: asm.Instructions{ 771 asm.LoadImm(asm.R0, 0, asm.DWord), 772 asm.Return(), 773 }, 774 License: "MIT", 775 }, 776 }, 777 } 778 779 type maps struct { 780 Map *MapSpec `ebpf:"map1"` 781 } 782 783 var specs struct { 784 maps 785 Program *ProgramSpec `ebpf:"prog1"` 786 } 787 788 if err := spec.Assign(&specs); err != nil { 789 panic(err) 790 } 791 792 fmt.Println(specs.Program.Type) 793 fmt.Println(specs.Map.Type) 794 795 // Output: SocketFilter 796 // Array 797 } 798 799 func ExampleCollectionSpec_LoadAndAssign() { 800 spec := &CollectionSpec{ 801 Maps: map[string]*MapSpec{ 802 "map1": { 803 Type: Array, 804 KeySize: 4, 805 ValueSize: 4, 806 MaxEntries: 1, 807 }, 808 }, 809 Programs: map[string]*ProgramSpec{ 810 "prog1": { 811 Type: SocketFilter, 812 Instructions: asm.Instructions{ 813 asm.LoadImm(asm.R0, 0, asm.DWord), 814 asm.Return(), 815 }, 816 License: "MIT", 817 }, 818 }, 819 } 820 821 var objs struct { 822 Program *Program `ebpf:"prog1"` 823 Map *Map `ebpf:"map1"` 824 } 825 826 if err := spec.LoadAndAssign(&objs, nil); err != nil { 827 panic(err) 828 } 829 defer objs.Program.Close() 830 defer objs.Map.Close() 831 832 fmt.Println(objs.Program.Type()) 833 fmt.Println(objs.Map.Type()) 834 835 // Output: SocketFilter 836 // Array 837 }