Skip to content
Snippets Groups Projects
path_attributes_test.go 47.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • Oliver Herms's avatar
    Oliver Herms committed
    package packet
    
    import (
    	"bytes"
    	"testing"
    
    
    	bnet "github.com/bio-routing/bio-rd/net"
    
    Oliver Herms's avatar
    Oliver Herms committed
    	"github.com/bio-routing/bio-rd/protocols/bgp/types"
    
    Oliver Herms's avatar
    Oliver Herms committed
    	"github.com/stretchr/testify/assert"
    )
    
    func TestDecodePathAttrs(t *testing.T) {
    	tests := []struct {
    		name     string
    		input    []byte
    		wantFail bool
    		expected *PathAttribute
    	}{
    		{
    			name: "Valid attribute set",
    			input: []byte{
    				0,              // Attr. Flags
    				1,              // Attr. Type Code
    				1,              // Attr. Length
    				1,              // EGP
    				0,              // Attr. Flags
    				3,              // Next Hop
    				4,              // Attr. Length
    				10, 20, 30, 40, // IP-Address
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				TypeCode: 1,
    				Length:   1,
    				Value:    uint8(1),
    				Next: &PathAttribute{
    					TypeCode: 3,
    					Length:   4,
    
    					Value:    bnet.IPv4FromOctets(10, 20, 30, 40),
    
    Oliver Herms's avatar
    Oliver Herms committed
    				},
    			},
    		},
    		{
    			name: "Incomplete data",
    			input: []byte{
    				0, // Attr. Flags
    				1, // Attr. Type Code
    				1, // Attr. Length
    			},
    			wantFail: true,
    		},
    	}
    
    	for _, test := range tests {
    
    Oliver Herms's avatar
    Oliver Herms committed
    		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &types.Options{})
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    		if test.wantFail && err == nil {
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if !test.wantFail && err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    
    		assert.Equalf(t, test.expected, res, "%s", test.name)
    
    Oliver Herms's avatar
    Oliver Herms committed
    	}
    }
    
    func TestDecodePathAttr(t *testing.T) {
    	tests := []struct {
    		name     string
    		input    []byte
    		wantFail bool
    		expected *PathAttribute
    	}{
    		{
    			name: "Valid origin",
    			input: []byte{
    				0, // Attr. Flags
    				1, // Attr. Type Code
    				1, // Attr. Length
    				1, // EGP
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length:         1,
    				Optional:       false,
    				Transitive:     false,
    				Partial:        false,
    				ExtendedLength: false,
    				TypeCode:       OriginAttr,
    				Value:          uint8(1),
    			},
    		},
    		{
    			name: "Missing TypeCode",
    			input: []byte{
    				0, // Attr. Flags
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing Length",
    			input: []byte{
    				0, // Attr. Flags
    				1, // Attr. Type Code
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing Value ORIGIN",
    			input: []byte{
    				0, // Attr. Flags
    				1, // Attr. Type Code
    				1, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing value AS_PATH",
    			input: []byte{
    				0, // Attr. Flags
    				2, // Attr. Type Code
    				8, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing value NextHop",
    			input: []byte{
    				0, // Attr. Flags
    				3, // Attr. Type Code
    				4, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing value MED",
    			input: []byte{
    				0, // Attr. Flags
    				4, // Attr. Type Code
    				4, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing value LocalPref",
    			input: []byte{
    				0, // Attr. Flags
    				5, // Attr. Type Code
    				4, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing value AGGREGATOR",
    			input: []byte{
    				0, // Attr. Flags
    				7, // Attr. Type Code
    				4, // Attr. Length
    			},
    			wantFail: true,
    		},
    		{
    			name: "Not supported attribute",
    			input: []byte{
    				0,   // Attr. Flags
    				111, // Attr. Type Code
    				4,   // Attr. Length
    			},
    			wantFail: true,
    		},
    
    		{
    			name: "Missing value OriginatorID",
    			input: []byte{
    				0,
    				OriginatorIDAttr,
    				4,
    			},
    			wantFail: true,
    		},
    		{
    			name: "Valid OriginatorID",
    			input: []byte{
    				128,              // Attr. Flags
    				OriginatorIDAttr, // Attr. Type Code
    				4,                // Attr. Length
    				1, 2, 3, 4,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length:         4,
    				Optional:       true,
    				Transitive:     false,
    				Partial:        false,
    				ExtendedLength: false,
    				TypeCode:       OriginatorIDAttr,
    				Value:          bnet.IPv4FromOctets(1, 2, 3, 4).ToUint32(),
    			},
    		},
    		{
    			name: "one valid ClusterID",
    			input: []byte{
    				128,             // Attr. Flags
    				ClusterListAttr, // Attr. Type Code
    				4,               // Attr. Length
    				1, 1, 1, 1,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				TypeCode: ClusterListAttr,
    				Optional: true,
    				Length:   4,
    				Value: []uint32{
    					bnet.IPv4FromOctets(1, 1, 1, 1).ToUint32(),
    				},
    			},
    		},
    		{
    			name: "two valid ClusterIDs",
    			input: []byte{
    				setOptional(uint8(0)),  // Attr. Flags
    				ClusterListAttr,        // Attr. Type Code
    				8,                      // Attr. Length
    				1, 2, 3, 4, 8, 8, 8, 8, // 1.2.3.4, 8.8.8.8
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				TypeCode: ClusterListAttr,
    				Optional: true,
    				Length:   8,
    				Value: []uint32{
    					bnet.IPv4FromOctets(1, 2, 3, 4).ToUint32(), bnet.IPv4FromOctets(8, 8, 8, 8).ToUint32(),
    				},
    			},
    		},
    		{
    			name: "one and a half ClusterID",
    			input: []byte{
    				setOptional(uint8(0)), // Attr. Flags
    				ClusterListAttr,       // Attr. Type Code
    				8,                     // Attr. Length
    				1, 2, 3, 4, 8, 8,      // 1.2.3.4, 8.8.
    			},
    			wantFail: true,
    		},
    
    Oliver Herms's avatar
    Oliver Herms committed
    	}
    
    	for _, test := range tests {
    
    Oliver Herms's avatar
    Oliver Herms committed
    		res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &types.Options{})
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    		if test.wantFail && err == nil {
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if !test.wantFail && err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, res)
    	}
    }
    
    func TestDecodeOrigin(t *testing.T) {
    	tests := []struct {
    		name     string
    		input    []byte
    		wantFail bool
    		expected *PathAttribute
    	}{
    		{
    			name: "Test #1",
    			input: []byte{
    				0, // Origin: IGP
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Value:  uint8(IGP),
    				Length: 1,
    			},
    		},
    		{
    			name: "Test #2",
    			input: []byte{
    				1, // Origin: EGP
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Value:  uint8(EGP),
    				Length: 1,
    			},
    		},
    		{
    			name: "Test #3",
    			input: []byte{
    				2, // Origin: INCOMPLETE
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Value:  uint8(INCOMPLETE),
    				Length: 1,
    			},
    		},
    		{
    			name:     "Test #4",
    			input:    []byte{},
    			wantFail: true,
    		},
    	}
    
    	for _, test := range tests {
    		pa := &PathAttribute{
    			Length: uint16(len(test.input)),
    		}
    		err := pa.decodeOrigin(bytes.NewBuffer(test.input))
    
    		if test.wantFail && err == nil {
    			t.Errorf("Expected error did not happen for test %q", test.name)
    		}
    
    		if !test.wantFail && err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    		}
    
    		if err != nil {
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    func TestDecodeASPath(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    
    		use4OctetASNs  bool
    
    Oliver Herms's avatar
    Oliver Herms committed
    		expected       *PathAttribute
    	}{
    		{
    			name: "Test #1",
    			input: []byte{
    				2, // AS_SEQUENCE
    				4, // Path Length
    				0, 100, 0, 200, 0, 222, 0, 240,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 10,
    
    Oliver Herms's avatar
    Oliver Herms committed
    				Value: types.ASPath{
    					types.ASPathSegment{
    						Type: 2,
    
    Oliver Herms's avatar
    Oliver Herms committed
    						ASNs: []uint32{
    							100, 200, 222, 240,
    						},
    					},
    				},
    			},
    		},
    		{
    			name: "Test #2",
    			input: []byte{
    				1, // AS_SEQUENCE
    				3, // Path Length
    				0, 100, 0, 222, 0, 240,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 8,
    
    Oliver Herms's avatar
    Oliver Herms committed
    				Value: types.ASPath{
    					types.ASPathSegment{
    						Type: 1,
    
    Oliver Herms's avatar
    Oliver Herms committed
    						ASNs: []uint32{
    							100, 222, 240,
    						},
    					},
    				},
    			},
    		},
    
    		{
    			name: "32 bit ASNs in AS_PATH",
    			input: []byte{
    				1, // AS_SEQUENCE
    				3, // Path Length
    				0, 0, 0, 100, 0, 0, 0, 222, 0, 0, 0, 240,
    			},
    			wantFail:      false,
    			use4OctetASNs: true,
    			expected: &PathAttribute{
    				Length: 14,
    
    Oliver Herms's avatar
    Oliver Herms committed
    				Value: types.ASPath{
    					types.ASPathSegment{
    						Type: 1,
    
    						ASNs: []uint32{
    							100, 222, 240,
    						},
    					},
    				},
    			},
    		},
    
    Oliver Herms's avatar
    Oliver Herms committed
    		{
    			name:           "Empty input",
    			input:          []byte{},
    			explicitLength: 5,
    			wantFail:       true,
    		},
    		{
    			name: "Incomplete AS_PATH",
    			input: []byte{
    				1, // AS_SEQUENCE
    				3, // Path Length
    				0, 100, 0, 222,
    			},
    			wantFail: true,
    		},
    	}
    
    	for _, test := range tests {
    
    		t.Run(test.name, func(t *testing.T) {
    			l := uint16(len(test.input))
    			if test.explicitLength != 0 {
    				l = test.explicitLength
    			}
    			pa := &PathAttribute{
    				Length: l,
    			}
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    
    			asnLength := uint8(2)
    			if test.use4OctetASNs {
    				asnLength = 4
    			}
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    
    			err := pa.decodeASPath(bytes.NewBuffer(test.input), asnLength)
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    
    			if test.wantFail && err == nil {
    				t.Errorf("Expected error did not happen for test %q", test.name)
    			}
    
    Oliver Herms's avatar
    Oliver Herms committed
    
    
    			if !test.wantFail && err != nil {
    				t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			}
    
    			if err != nil {
    				return
    			}
    
    			assert.Equal(t, test.expected, pa)
    		})
    
    Oliver Herms's avatar
    Oliver Herms committed
    	}
    }
    
    func TestDecodeNextHop(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "Test #1",
    			input: []byte{
    				10, 20, 30, 40,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 4,
    
    				Value:  bnet.IPv4FromOctets(10, 20, 30, 40),
    
    Oliver Herms's avatar
    Oliver Herms committed
    			},
    		},
    		{
    			name:           "Test #2",
    			input:          []byte{},
    			explicitLength: 5,
    			wantFail:       true,
    		},
    		{
    			name:           "Incomplete IP-Address",
    			input:          []byte{10, 20, 30},
    			explicitLength: 5,
    			wantFail:       true,
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeNextHop(bytes.NewBuffer(test.input))
    
    		if test.wantFail && err == nil {
    			t.Errorf("Expected error did not happen for test %q", test.name)
    		}
    
    		if !test.wantFail && err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    		}
    
    		if err != nil {
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    func TestDecodeMED(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "Test #1",
    			input: []byte{
    				0, 0, 3, 232,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 4,
    				Value:  uint32(1000),
    			},
    		},
    		{
    			name:           "Test #2",
    			input:          []byte{},
    			explicitLength: 5,
    			wantFail:       true,
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeMED(bytes.NewBuffer(test.input))
    
    		if test.wantFail && err == nil {
    			t.Errorf("Expected error did not happen for test %q", test.name)
    		}
    
    		if !test.wantFail && err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    		}
    
    		if err != nil {
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    func TestDecodeLocalPref(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "Test #1",
    			input: []byte{
    				0, 0, 3, 232,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 4,
    				Value:  uint32(1000),
    			},
    		},
    		{
    			name:           "Test #2",
    			input:          []byte{},
    			explicitLength: 5,
    			wantFail:       true,
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeLocalPref(bytes.NewBuffer(test.input))
    
    		if test.wantFail {
    			if err != nil {
    				continue
    			}
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    func TestDecodeAggregator(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "Valid aggregator",
    			input: []byte{
    				0, 222, // ASN
    				10, 20, 30, 40, // Aggregator IP
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 6,
    
    				Value: types.Aggregator{
    					ASN:     222,
    					Address: strAddr("10.20.30.40"),
    
    Oliver Herms's avatar
    Oliver Herms committed
    				},
    			},
    		},
    		{
    			name: "Incomplete Address",
    			input: []byte{
    				0, 222, // ASN
    				10, 20, // Aggregator IP
    			},
    			wantFail: true,
    		},
    		{
    			name: "Missing Address",
    			input: []byte{
    				0, 222, // ASN
    			},
    			wantFail: true,
    		},
    		{
    			name:     "Empty input",
    			input:    []byte{},
    			wantFail: true,
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeAggregator(bytes.NewBuffer(test.input))
    
    		if test.wantFail {
    			if err != nil {
    				continue
    			}
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    
    Daniel Czerwonk's avatar
    Daniel Czerwonk committed
    func TestDecodeLargeCommunity(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "two valid large communities",
    			input: []byte{
    				0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, // (1, 2, 3), (4, 5, 6)
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 24,
    
    Oliver Herms's avatar
    Oliver Herms committed
    				Value: []types.LargeCommunity{
    
    Daniel Czerwonk's avatar
    Daniel Czerwonk committed
    					{
    						GlobalAdministrator: 1,
    						DataPart1:           2,
    						DataPart2:           3,
    					},
    					{
    						GlobalAdministrator: 4,
    						DataPart1:           5,
    						DataPart2:           6,
    					},
    				},
    			},
    		},
    		{
    			name:     "Empty input",
    			input:    []byte{},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 0,
    
    Oliver Herms's avatar
    Oliver Herms committed
    				Value:  []types.LargeCommunity{},
    
    Daniel Czerwonk's avatar
    Daniel Czerwonk committed
    			},
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeLargeCommunities(bytes.NewBuffer(test.input))
    
    
    		if test.wantFail {
    			if err != nil {
    				continue
    			}
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    func TestDecodeCommunity(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name: "two valid communities",
    			input: []byte{
    				0, 2, 0, 8, 1, 0, 4, 1, // (2,8), (256,1025)
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 8,
    				Value: []uint32{
    
    Daniel Czerwonk's avatar
    Daniel Czerwonk committed
    					131080, 16778241,
    
    				},
    			},
    		},
    		{
    			name:     "Empty input",
    			input:    []byte{},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 0,
    				Value:  []uint32{},
    			},
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeCommunities(bytes.NewBuffer(test.input))
    
    
    Daniel Czerwonk's avatar
    Daniel Czerwonk committed
    		if test.wantFail {
    			if err != nil {
    				continue
    			}
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    
    func TestDecodeClusterList(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name:     "Empty input",
    			input:    []byte{},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 0,
    				Value:  []uint32{},
    			},
    		},
    		{
    			name: "one valid ClusterID",
    			input: []byte{
    				1, 1, 1, 1,
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 4,
    				Value: []uint32{
    					bnet.IPv4FromOctets(1, 1, 1, 1).ToUint32(),
    				},
    			},
    		},
    		{
    			name: "two valid ClusterIDs",
    			input: []byte{
    				1, 2, 3, 4, 8, 8, 8, 8, // 1.2.3.4, 8.8.8.8
    			},
    			wantFail: false,
    			expected: &PathAttribute{
    				Length: 8,
    				Value: []uint32{
    					bnet.IPv4FromOctets(1, 2, 3, 4).ToUint32(), bnet.IPv4FromOctets(8, 8, 8, 8).ToUint32(),
    				},
    			},
    		},
    		{
    			name: "one and a half ClusterID",
    			input: []byte{
    				1, 2, 3, 4, 8, 8, // 1.2.3.4, 8.8.
    			},
    			wantFail: true,
    		},
    	}
    
    	for _, test := range tests {
    		l := uint16(len(test.input))
    		if test.explicitLength != 0 {
    			l = test.explicitLength
    		}
    		pa := &PathAttribute{
    			Length: l,
    		}
    		err := pa.decodeClusterList(bytes.NewBuffer(test.input))
    
    		if test.wantFail {
    			if err != nil {
    				continue
    			}
    			t.Errorf("Expected error did not happen for test %q", test.name)
    			continue
    		}
    
    		if err != nil {
    			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
    			continue
    		}
    
    		assert.Equal(t, test.expected, pa)
    	}
    }
    
    
    func TestDecodeMultiProtocolReachNLRI(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name:           "incomplete",
    			input:          []byte{0, 0, 0, 0},
    			wantFail:       true,
    			explicitLength: 32,
    		},
    		{
    			name: "valid MP_REACH_NLRI",
    			input: []byte{
    				0x00, 0x02, // AFI
    				0x01,                                                                                                 // SAFI
    				0x10, 0x20, 0x01, 0x06, 0x78, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // NextHop
    				0x00,                                     // RESERVED
    				0x30, 0x26, 0x00, 0x00, 0x06, 0xff, 0x05, // Prefix
    			},
    			expected: &PathAttribute{
    				Length: 28,
    				Value: MultiProtocolReachNLRI{
    					AFI:     IPv6AFI,
    					SAFI:    UnicastSAFI,
    					NextHop: bnet.IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0x2),
    
    					Prefixes: []bnet.Prefix{
    						bnet.NewPfx(bnet.IPv6FromBlocks(0x2600, 0x6, 0xff05, 0, 0, 0, 0, 0), 48),
    					},
    
    				},
    			},
    		},
    	}
    
    	t.Parallel()
    
    	for _, test := range tests {
    		t.Run(test.name, func(t *testing.T) {
    			l := uint16(len(test.input))
    			if test.explicitLength != 0 {
    				l = test.explicitLength
    			}
    
    			pa := &PathAttribute{
    				Length: l,
    			}
    			err := pa.decodeMultiProtocolReachNLRI(bytes.NewBuffer(test.input))
    
    			if test.wantFail {
    				if err != nil {
    					return
    				}
    				t.Fatalf("Expected error did not happen for test %q", test.name)
    			}
    
    			if err != nil {
    				t.Fatalf("Unexpected failure for test %q: %v", test.name, err)
    			}
    
    			assert.Equal(t, test.expected, pa)
    		})
    	}
    }
    
    func TestDecodeMultiProtocolUnreachNLRI(t *testing.T) {
    	tests := []struct {
    		name           string
    		input          []byte
    		wantFail       bool
    		explicitLength uint16
    		expected       *PathAttribute
    	}{
    		{
    			name:           "incomplete",
    			input:          []byte{0, 0, 0, 0},
    			wantFail:       true,
    			explicitLength: 32,
    		},
    		{
    			name: "valid MP_UNREACH_NLRI",
    			input: []byte{
    				0x00, 0x02, // AFI
    				0x01,                                     // SAFI
    				0x2c, 0x26, 0x20, 0x01, 0x10, 0x90, 0x00, // Prefix
    			},
    			expected: &PathAttribute{
    				Length: 10,
    				Value: MultiProtocolUnreachNLRI{
    
    					AFI:  IPv6AFI,
    					SAFI: UnicastSAFI,
    					Prefixes: []bnet.Prefix{
    						bnet.NewPfx(bnet.IPv6FromBlocks(0x2620, 0x110, 0x9000, 0, 0, 0, 0, 0), 44),
    					},