Skip to content
Snippets Groups Projects
path_attributes_test.go 36.4 KiB
Newer Older
Oliver Herms's avatar
Oliver Herms committed
package packet

import (
	"bytes"
	"testing"

	"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:    strAddr("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 {
		res, err := decodePathAttrs(bytes.NewBuffer(test.input), uint16(len(test.input)), &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,
		},
	}

	for _, test := range tests {
		res, _, err := decodePathAttr(bytes.NewBuffer(test.input), &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",
Oliver Herms's avatar
Oliver Herms committed
			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,
				Value: ASPath{
					ASPathSegment{
						Type:  2,
						Count: 4,
						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,
				Value: ASPath{
					ASPathSegment{
						Type:  1,
						Count: 3,
						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,
				Value: ASPath{
					ASPathSegment{
						Type:  1,
						Count: 3,
						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:  strAddr("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: Aggretator{
					ASN:  222,
					Addr: 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,
				Value: []LargeCommunity{
					{
						GlobalAdministrator: 1,
						DataPart1:           2,
						DataPart2:           3,
					},
					{
						GlobalAdministrator: 4,
						DataPart1:           5,
						DataPart2:           6,
					},
				},
			},
		},
		{
			name:     "Empty input",
			input:    []byte{},
			wantFail: false,
			expected: &PathAttribute{
				Length: 0,
				Value:  []LargeCommunity{},
			},
		},
	}

	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)
	}
}

Oliver Herms's avatar
Oliver Herms committed
func TestSetLength(t *testing.T) {
	tests := []struct {
		name             string
		input            []byte
		ExtendedLength   bool
		wantFail         bool
		expected         *PathAttribute
		expectedConsumed int
	}{
		{
			name:           "Valid input",
			ExtendedLength: false,
			input:          []byte{22},
			expected: &PathAttribute{
				ExtendedLength: false,
				Length:         22,
			},
			expectedConsumed: 1,
		},
		{
			name:           "Valid input (extended)",
			ExtendedLength: true,
			input:          []byte{1, 1},
			expected: &PathAttribute{
				ExtendedLength: true,
				Length:         257,
			},
			expectedConsumed: 2,
		},
		{
			name:           "Invalid input",
			ExtendedLength: true,
			input:          []byte{},
			wantFail:       true,
		},
		{
			name:           "Invalid input (extended)",
			ExtendedLength: true,
			input:          []byte{1},
			wantFail:       true,
		},
	}

	for _, test := range tests {
		pa := &PathAttribute{
			ExtendedLength: test.ExtendedLength,
		}
		consumed, err := pa.setLength(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)
		assert.Equal(t, test.expectedConsumed, consumed)
	}
}

func TestDecodeUint32(t *testing.T) {
Oliver Herms's avatar
Oliver Herms committed
	tests := []struct {
		name           string
		input          []byte
		wantFail       bool
		explicitLength uint16
		expected       uint32
	}{
		{
			name:     "Valid input",
			input:    []byte{0, 0, 1, 1},
			expected: 257,
		},
		{
			name:     "Valid input with additional crap",
			input:    []byte{0, 0, 1, 1, 200},
			expected: 257,
		},
		{
			name:           "Valid input with additional crap and invalid length",
			input:          []byte{0, 0, 1, 1, 200},
			explicitLength: 8,
			wantFail:       true,
		},
		{
			name:     "Invalid input",
			input:    []byte{0, 0, 1},
			wantFail: true,
		},
	}

	for _, test := range tests {
		l := uint16(len(test.input))
		if test.explicitLength > 0 {
			l = test.explicitLength
		}
		pa := &PathAttribute{
			Length: l,
		}
		err := pa.decodeUint32(bytes.NewBuffer(test.input), "test")
Oliver Herms's avatar
Oliver Herms 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.Value)
Oliver Herms's avatar
Oliver Herms committed
	}
}

func TestSetOptional(t *testing.T) {
	tests := []struct {
		name     string
		input    uint8
		expected uint8
	}{
		{
			name:     "Test #1",
			input:    0,
			expected: 128,
		},
	}

	for _, test := range tests {
		res := setOptional(test.input)
		if res != test.expected {
			t.Errorf("Unexpected result for test %q: %d", test.name, res)
		}
	}
}

func TestSetTransitive(t *testing.T) {
	tests := []struct {
		name     string
		input    uint8
		expected uint8
	}{
		{
			name:     "Test #1",
			input:    0,
			expected: 64,
		},
	}

	for _, test := range tests {
		res := setTransitive(test.input)
		if res != test.expected {
			t.Errorf("Unexpected result for test %q: %d", test.name, res)
		}
	}
}

func TestSetPartial(t *testing.T) {
	tests := []struct {
		name     string
		input    uint8
		expected uint8
	}{
		{
			name:     "Test #1",
			input:    0,
			expected: 32,
		},
	}

	for _, test := range tests {
		res := setPartial(test.input)
		if res != test.expected {
			t.Errorf("Unexpected result for test %q: %d", test.name, res)
		}
	}
}

func TestSetExtendedLength(t *testing.T) {
	tests := []struct {
		name     string
		input    uint8
		expected uint8
	}{
		{
			name:     "Test #1",
			input:    0,
			expected: 16,
		},
	}

	for _, test := range tests {
		res := setExtendedLength(test.input)
		if res != test.expected {
			t.Errorf("Unexpected result for test %q: %d", test.name, res)
		}
	}
}

func TestSerializeOrigin(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: OriginAttr,
				Value:    uint8(0), // IGP
			},
			expectedLen: 4,
			expected:    []byte{64, 1, 1, 0},
		},
		{
			name: "Test #2",
			input: &PathAttribute{
				TypeCode: OriginAttr,
				Value:    uint8(2), // INCOMPLETE
			},
			expectedLen: 4,
			expected:    []byte{64, 1, 1, 2},
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeOrigin(buf)
		if test.expectedLen != n {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeNextHop(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: NextHopAttr,
				Value:    strAddr("100.110.120.130"),
			expected:    []byte{64, 3, 4, 100, 110, 120, 130},
			expectedLen: 7,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeNextHop(buf)
		if n != test.expectedLen {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeMED(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: MEDAttr,
				Value:    uint32(1000),
			},
			expected: []byte{
				128,          // Attribute flags
				4,            // Type
				4,            // Length
				0, 0, 3, 232, // Value = 1000
			},
			expectedLen: 7,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeMED(buf)
		if n != test.expectedLen {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeLocalPref(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: LocalPrefAttr,
				Value:    uint32(1000),
			},
			expected: []byte{
				64,           // Attribute flags
				5,            // Type
				4,            // Length
				0, 0, 3, 232, // Value = 1000
			},
			expectedLen: 7,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeLocalpref(buf)
		if n != test.expectedLen {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeAtomicAggregate(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: AtomicAggrAttr,
			},
			expected: []byte{
				64, // Attribute flags
				6,  // Type
				0,  // Length
			},
			expectedLen: 3,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeAtomicAggregate(buf)
		if n != test.expectedLen {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeAggregator(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: AggregatorAttr,
				Value:    uint16(174),
			},
			expected: []byte{
				192,    // Attribute flags
				7,      // Type
				2,      // Length
				0, 174, // Value = 174
			},
			expectedLen: 5,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(nil)
		n := test.input.serializeAggregator(buf)
		if n != test.expectedLen {
			t.Errorf("Unexpected length for test %q: %d", test.name, n)
			continue
		}

		assert.Equal(t, test.expected, buf.Bytes())
	}
}

func TestSerializeASPath(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
		use32BitASN bool
	}{
		{
			name: "Test #1",
			input: &PathAttribute{
				TypeCode: ASPathAttr,
				Value: ASPath{
					{
						Type: 2, // Sequence
						ASNs: []uint32{
							100, 200, 210,
						},
					},
				},
			},
			expected: []byte{
				64,     // Attribute flags
				2,      // Type
Oliver Herms's avatar
Oliver Herms committed
				8,      // Length
				2,      // AS_SEQUENCE
				3,      // ASN count
				0, 100, // ASN 100
				0, 200, // ASN 200
				0, 210, // ASN 210
			},
			expectedLen: 10,
		},
		{
			name: "32bit ASN",
			input: &PathAttribute{
				TypeCode: ASPathAttr,
				Value: ASPath{
					{
						Type: 2, // Sequence
						ASNs: []uint32{
							100, 200, 210,
						},
					},
				},
			},
			expected: []byte{
				64,           // Attribute flags
				2,            // Type
				14,           // Length
				2,            // AS_SEQUENCE
				3,            // ASN count
				0, 0, 0, 100, // ASN 100
				0, 0, 0, 200, // ASN 200
				0, 0, 0, 210, // ASN 210
			},
			expectedLen: 16,
			use32BitASN: true,
		},
	t.Parallel()

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			buf := bytes.NewBuffer(nil)
			opt := &Options{
				Supports4OctetASN: test.use32BitASN,
			}
			n := test.input.serializeASPath(buf, opt)
			if n != test.expectedLen {
				t.Fatalf("Unexpected length for test %q: %d", test.name, n)
			}
			assert.Equal(t, test.expected, buf.Bytes())
		})
func TestSerializeLargeCommunities(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "2 large communities",
			input: &PathAttribute{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
				TypeCode: LargeCommunitiesAttr,
				Value: []LargeCommunity{
					{
						GlobalAdministrator: 1,
						DataPart1:           2,
						DataPart2:           3,
					},
					{
						GlobalAdministrator: 4,
						DataPart1:           5,
						DataPart2:           6,
					},
				},
			},
			expected: []byte{
				0xe0,                                                                   // Attribute flags
				32,                                                                     // Type
				24,                                                                     // Length
				0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, // Communities (1, 2, 3), (4, 5, 6)
			},
			expectedLen: 24,
		},
		{
			name: "empty list of communities",
			input: &PathAttribute{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
				TypeCode: LargeCommunitiesAttr,
				Value:    []LargeCommunity{},
			},
			expected:    []byte{},
			expectedLen: 0,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(te *testing.T) {
			buf := bytes.NewBuffer([]byte{})
			n := test.input.serializeLargeCommunities(buf)
			if n != test.expectedLen {
				t.Fatalf("Unexpected length for test %q: %d", test.name, n)
			}

			assert.Equal(t, test.expected, buf.Bytes())
		})
	}
}

func TestSerializeCommunities(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "2 communities",
			input: &PathAttribute{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
				TypeCode: LargeCommunitiesAttr,
				Value: []uint32{
					131080, 16778241,
				},
			},
			expected: []byte{
				0xe0,                   // Attribute flags
				8,                      // Type
				8,                      // Length
				0, 2, 0, 8, 1, 0, 4, 1, // Communities (2,8), (256,1025)
			},
			expectedLen: 8,
		},
		{
			name: "empty list of communities",
			input: &PathAttribute{
				TypeCode: CommunitiesAttr,
				Value:    []uint32{},
			},
			expected:    []byte{},
			expectedLen: 0,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(te *testing.T) {
			buf := bytes.NewBuffer([]byte{})
			n := test.input.serializeCommunities(buf)
			if n != test.expectedLen {
				t.Fatalf("Unexpected length for test %q: %d", test.name, n)
			}

			assert.Equal(t, test.expected, buf.Bytes())
		})
	}
}

func TestSerializeUnknownAttribute(t *testing.T) {
	tests := []struct {
		name        string
		input       *PathAttribute
		expected    []byte
		expectedLen uint8
	}{
		{
			name: "Arbritary attribute",
			input: &PathAttribute{
				TypeCode:   200,
				Value:      []byte{1, 2, 3, 4},
				Transitive: true,
			},
			expected: []byte{
				64,         // Attribute flags
				200,        // Type
				4,          // Length
				1, 2, 3, 4, // Payload
			},
			expectedLen: 6,
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			buf := bytes.NewBuffer(nil)
			n := test.input.serializeUnknownAttribute(buf)

			assert.Equal(t, test.expectedLen, n)
			assert.Equal(t, test.expected, buf.Bytes())
		})
	}
}

func TestSerialize(t *testing.T) {
	tests := []struct {
		name     string
		msg      *BGPUpdate
		expected []byte
		wantFail bool
	}{
		{
			name: "Withdraw only",
			msg: &BGPUpdate{
				WithdrawnRoutes: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					IP:     strAddr("100.110.120.0"),
					Pfxlen: 24,
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 27, // Length
				2,    // Msg Type
				0, 4, // Withdrawn Routes Length
				24, 100, 110, 120, // NLRI
				0, 0, // Total Path Attribute Length
			},
		},
		{
			name: "NLRI only",
			msg: &BGPUpdate{
				NLRI: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					IP:     strAddr("100.110.128.0"),
					Pfxlen: 17,
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 27, // Length
				2,    // Msg Type
				0, 0, // Withdrawn Routes Length
				0, 0, // Total Path Attribute Length
				17, 100, 110, 128, // NLRI
			},
		},
		{
			name: "Path Attributes only",
			msg: &BGPUpdate{
				PathAttributes: &PathAttribute{
					Optional:   true,
					Transitive: true,
					TypeCode:   OriginAttr,
					Value:      uint8(0), // IGP
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 27, // Length
				2,    // Msg Type
				0, 0, // Withdrawn Routes Length
				0, 4, // Total Path Attribute Length
				64, // Attr. Flags
				1,  // Attr. Type Code
				1,  // Length
				0,  // Value
			},
		},
		{
			name: "Full test",
			msg: &BGPUpdate{
				WithdrawnRoutes: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					IP:     strAddr("10.0.0.0"),
					Pfxlen: 8,
					Next: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
						IP:     strAddr("192.168.0.0"),
						Pfxlen: 16,
					},
				},
				PathAttributes: &PathAttribute{
					TypeCode: OriginAttr,
					Value:    uint8(0),
					Next: &PathAttribute{
						TypeCode: ASPathAttr,
						Value: ASPath{
							{
								Type: 2,
								ASNs: []uint32{100, 155, 200},
							},
							{
								Type: 1,
								ASNs: []uint32{10, 20},
							},
						},
						Next: &PathAttribute{
							TypeCode: NextHopAttr,
							Value:    strAddr("10.20.30.40"),
							Next: &PathAttribute{
								TypeCode: MEDAttr,
								Value:    uint32(100),
								Next: &PathAttribute{
									TypeCode: LocalPrefAttr,
									Value:    uint32(500),
									Next: &PathAttribute{
										TypeCode: AtomicAggrAttr,
										Next: &PathAttribute{
											TypeCode: AggregatorAttr,
											Value:    uint16(200),
										},
									},
								},
							},
						},
					},
				},
				NLRI: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					IP:     strAddr("8.8.8.0"),
					Pfxlen: 24,
					Next: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
						IP:     strAddr("185.65.240.0"),
						Pfxlen: 22,
					},
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				2, // Msg Type

				// Withdraws
				0, 5, // Withdrawn Routes Length
				8, 10, // Withdraw 10/8
				16, 192, 168, // Withdraw 192.168/16

				0, 50, // Total Path Attribute Length

				// ORIGIN
				64, // Attr. Flags
				1,  // Attr. Type Code
				1,  // Length
				0,  // Value
				// ASPath
				64,                     // Attr. Flags
				2,                      // Attr. Type Code
				2,                      // Path Segment Type = AS_SEQUENCE
				3,                      // Path Segment Length
				0, 100, 0, 155, 0, 200, // ASNs
				1,            // Path Segment Type = AS_SET
				2,            // Path Segment Type = AS_SET
				0, 10, 0, 20, // ASNs
				// Next Hop
				64,             // Attr. Flags
				3,              // Attr. Type Code
				4,              // Length
				10, 20, 30, 40, // Next Hop Address
				// MED
				128,          // Attr. Flags
				4,            // Attr Type Code
				4,            // Length
				0, 0, 0, 100, // MED = 100
				// LocalPref
				64,           // Attr. Flags
				5,            // Attr. Type Code
				4,            // Length
				0, 0, 1, 244, // Localpref
				// Atomic Aggregate
				64, // Attr. Flags
				6,  // Attr. Type Code
				0,  // Length
				// Aggregator
				192,    // Attr. Flags
				7,      // Attr. Type Code
				2,      // Length
				0, 200, // Aggregator ASN = 200

				// NLRI
				24, 8, 8, 8, // 8.8.8.0/24
				22, 185, 65, 240, // 185.65.240.0/22
			},
		},
	}

	for _, test := range tests {
		opt := &Options{}
		res, err := test.msg.SerializeUpdate(opt)
		if err != nil {
			if test.wantFail {
				continue
			}

			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
			continue
		}

		if test.wantFail {
			t.Errorf("Unexpected success for test %q", test.name)
			continue
		}

		assert.Equalf(t, test.expected, res, "%s", test.name)
	}
}

func TestSerializeAddPath(t *testing.T) {
	tests := []struct {
		name     string
		msg      *BGPUpdateAddPath
		expected []byte
		wantFail bool
	}{
		{
			name: "Withdraw only",
			msg: &BGPUpdateAddPath{
				WithdrawnRoutes: &NLRIAddPath{
					PathIdentifier: 257,
					IP:             strAddr("100.110.120.0"),
					Pfxlen:         24,
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 31, // Length
				2,    // Msg Type
				0, 8, // Withdrawn Routes Length
				0, 0, 1, 1, // Path Identifier
				24, 100, 110, 120, // NLRI
				0, 0, // Total Path Attribute Length
			},
		},
		{
			name: "NLRI only",
			msg: &BGPUpdateAddPath{
				NLRI: &NLRIAddPath{
					PathIdentifier: 257,
					IP:             strAddr("100.110.128.0"),
					Pfxlen:         17,
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 31, // Length
				2,    // Msg Type
				0, 0, // Withdrawn Routes Length
				0, 0, // Total Path Attribute Length
				0, 0, 1, 1, // Path Identifier
				17, 100, 110, 128, // NLRI
			},
		},
		{
			name: "Path Attributes only",
			msg: &BGPUpdateAddPath{
				PathAttributes: &PathAttribute{
					Optional:   true,
					Transitive: true,
					TypeCode:   OriginAttr,
					Value:      uint8(0), // IGP
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 27, // Length
				2,    // Msg Type
				0, 0, // Withdrawn Routes Length
				0, 4, // Total Path Attribute Length
				64, // Attr. Flags
				1,  // Attr. Type Code
				1,  // Length
				0,  // Value
			},
		},
		{
			name: "Full test",
			msg: &BGPUpdateAddPath{
				WithdrawnRoutes: &NLRIAddPath{
					IP:     strAddr("10.0.0.0"),
					Pfxlen: 8,
					Next: &NLRIAddPath{
						IP:     strAddr("192.168.0.0"),
						Pfxlen: 16,
					},
				},
				PathAttributes: &PathAttribute{
					TypeCode: OriginAttr,
					Value:    uint8(0),
					Next: &PathAttribute{
						TypeCode: ASPathAttr,
						Value: ASPath{
							{
								Type: 2,
								ASNs: []uint32{100, 155, 200},
							},
							{
								Type: 1,
								ASNs: []uint32{10, 20},
							},
						},
						Next: &PathAttribute{
							TypeCode: NextHopAttr,
							Value:    strAddr("10.20.30.40"),
							Next: &PathAttribute{
								TypeCode: MEDAttr,
								Value:    uint32(100),
								Next: &PathAttribute{
									TypeCode: LocalPrefAttr,
									Value:    uint32(500),
									Next: &PathAttribute{
										TypeCode: AtomicAggrAttr,
										Next: &PathAttribute{
											TypeCode: AggregatorAttr,
											Value:    uint16(200),
										},
									},
								},
							},
						},
					},
				},
				NLRI: &NLRIAddPath{
					IP:     strAddr("8.8.8.0"),
					Pfxlen: 24,
					Next: &NLRIAddPath{
						IP:     strAddr("185.65.240.0"),
						Pfxlen: 22,
					},
				},
			},
			expected: []byte{
				0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
				0, 102, // Length
				2, // Msg Type

				// Withdraws
				0, 13, // Withdrawn Routes Length
				0, 0, 0, 0, // Path Identifier
				8, 10, // Withdraw 10/8
				0, 0, 0, 0, // Path Identifier
				16, 192, 168, // Withdraw 192.168/16

				0, 50, // Total Path Attribute Length

				// ORIGIN
				64, // Attr. Flags
				1,  // Attr. Type Code
				1,  // Length
				0,  // Value
				// ASPath
				64,                     // Attr. Flags
				2,                      // Attr. Type Code
				14,                     // Attr. Length
				2,                      // Path Segment Type = AS_SEQUENCE
				3,                      // Path Segment Length
				0, 100, 0, 155, 0, 200, // ASNs
				1,            // Path Segment Type = AS_SET
				2,            // Path Segment Type = AS_SET
				0, 10, 0, 20, // ASNs
				// Next Hop
				64,             // Attr. Flags
				3,              // Attr. Type Code
				4,              // Length
				10, 20, 30, 40, // Next Hop Address
				// MED
				128,          // Attr. Flags
				4,            // Attr Type Code
				4,            // Length
				0, 0, 0, 100, // MED = 100
				// LocalPref
				64,           // Attr. Flags
				5,            // Attr. Type Code
				4,            // Length
				0, 0, 1, 244, // Localpref
				// Atomic Aggregate
				64, // Attr. Flags
				6,  // Attr. Type Code
				0,  // Length
				// Aggregator
				192,    // Attr. Flags
				7,      // Attr. Type Code
				2,      // Length
				0, 200, // Aggregator ASN = 200

				// NLRI
				0, 0, 0, 0, // Path Identifier
				24, 8, 8, 8, // 8.8.8.0/24
				0, 0, 0, 0, // Path Identifier
				22, 185, 65, 240, // 185.65.240.0/22
			},
		},
	}

	for _, test := range tests {
		opt := &Options{}
		res, err := test.msg.SerializeUpdate(opt)
		if err != nil {
			if test.wantFail {
				continue
			}

			t.Errorf("Unexpected failure for test %q: %v", test.name, err)
			continue
		}

		if test.wantFail {
			t.Errorf("Unexpected success for test %q", test.name)
			continue
		}

		assert.Equalf(t, test.expected, res, "%s", test.name)
Oliver Herms's avatar
Oliver Herms committed

func TestFourBytesToUint32(t *testing.T) {
	tests := []struct {
		name     string
		input    [4]byte
		expected uint32
	}{
		{
			name:     "Test #1",
			input:    [4]byte{0, 0, 0, 200},
			expected: 200,
		},
		{
			name:     "Test #2",
			input:    [4]byte{1, 0, 0, 200},
			expected: 16777416,
		},
	}

	for _, test := range tests {
		res := fourBytesToUint32(test.input)
		if res != test.expected {
			t.Errorf("Unexpected result for test %q: Got: %d Want: %d", test.name, res, test.expected)
		}
	}
}