Skip to content
Snippets Groups Projects
decoder_test.go 40.2 KiB
Newer Older
Oliver Herms's avatar
Oliver Herms committed
							},
							{
Oliver Herms's avatar
Oliver Herms committed
								Type: 1,
Oliver Herms's avatar
Oliver Herms committed
								ASNs: []uint32{
									15169,
									3320,
								},
							},
						},
						Next: &PathAttribute{
							Optional:       false,
							Transitive:     false,
							Partial:        false,
							ExtendedLength: false,
							Length:         4,
							TypeCode:       3,
							Value:          bnet.IPv4FromOctets(10, 11, 12, 13),
Oliver Herms's avatar
Oliver Herms committed
							Next: &PathAttribute{
								Optional:       false,
								Transitive:     false,
								Partial:        false,
								ExtendedLength: false,
								Length:         4,
								TypeCode:       4,
								Value:          uint32(256),
								Next: &PathAttribute{
									Optional:       false,
									Transitive:     false,
									Partial:        false,
									ExtendedLength: false,
									Length:         4,
									TypeCode:       5,
									Value:          uint32(256),
									Next: &PathAttribute{
										Optional:       false,
										Transitive:     false,
										Partial:        false,
										ExtendedLength: false,
										Length:         0,
										TypeCode:       6,
									},
								},
							},
						},
					},
				},
			},
		},
		{
			// 2 withdraws with 7 path attributes (ORIGIN, ASPath, NH, MED, Local Pref, Atomic Aggregate), valid update
			testNum: 10,
			input: []byte{0, 5, 8, 10, 16, 192, 168,
				0, 53, // Total Path Attribute Length

				255,  // Attribute flags
				1,    // Attribute Type code (ORIGIN)
				0, 1, // Length
				2, // INCOMPLETE

				0,      // Attribute flags
				2,      // Attribute Type code (AS Path)
				12,     // Length
				2,      // Type = AS_SEQUENCE
				2,      // Path Segement Length
				59, 65, // AS15169
				12, 248, // AS3320
				1,      // Type = AS_SET
				2,      // Path Segement Length
				59, 65, // AS15169
				12, 248, // AS3320

				0,              // Attribute flags
				3,              // Attribute Type code (Next Hop)
				4,              // Length
				10, 11, 12, 13, // Next Hop

				0,          // Attribute flags
				4,          // Attribute Type code (MED)
				4,          // Length
				0, 0, 1, 0, // MED 256

				0,          // Attribute flags
				5,          // Attribute Type code (Local Pref)
				4,          // Length
				0, 0, 1, 0, // Local Pref 256

				0, // Attribute flags
				6, // Attribute Type code (Atomic Aggregate)
				0, // Length

				0,    // Attribute flags
				7,    // Attribute Type code (Atomic Aggregate)
				6,    // Length
				1, 2, // ASN
				10, 11, 12, 13, // Address

				8, 11, // 11.0.0.0/8
			},
			wantFail: false,
			expected: &BGPUpdate{
				WithdrawnRoutesLen: 5,
				WithdrawnRoutes: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					Prefix: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8),
Oliver Herms's avatar
Oliver Herms committed
					Next: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
						Prefix: bnet.NewPfx(bnet.IPv4FromOctets(192, 168, 0, 0), 16),
Oliver Herms's avatar
Oliver Herms committed
					},
				},
				TotalPathAttrLen: 53,
				PathAttributes: &PathAttribute{
					Optional:       true,
					Transitive:     true,
					Partial:        true,
					ExtendedLength: true,
					Length:         1,
					TypeCode:       1,
					Value:          uint8(2),
					Next: &PathAttribute{
						Optional:       false,
						Transitive:     false,
						Partial:        false,
						ExtendedLength: false,
						Length:         12,
						TypeCode:       2,
Oliver Herms's avatar
Oliver Herms committed
						Value: types.ASPath{
Oliver Herms's avatar
Oliver Herms committed
							{
Oliver Herms's avatar
Oliver Herms committed
								Type: 2,
Oliver Herms's avatar
Oliver Herms committed
								ASNs: []uint32{
									15169,
									3320,
								},
							},
							{
Oliver Herms's avatar
Oliver Herms committed
								Type: 1,
Oliver Herms's avatar
Oliver Herms committed
								ASNs: []uint32{
									15169,
									3320,
								},
							},
						},
						Next: &PathAttribute{
							Optional:       false,
							Transitive:     false,
							Partial:        false,
							ExtendedLength: false,
							Length:         4,
							TypeCode:       3,
							Value:          bnet.IPv4FromOctets(10, 11, 12, 13),
Oliver Herms's avatar
Oliver Herms committed
							Next: &PathAttribute{
								Optional:       false,
								Transitive:     false,
								Partial:        false,
								ExtendedLength: false,
								Length:         4,
								TypeCode:       4,
								Value:          uint32(256),
								Next: &PathAttribute{
									Optional:       false,
									Transitive:     false,
									Partial:        false,
									ExtendedLength: false,
									Length:         4,
									TypeCode:       5,
									Value:          uint32(256),
									Next: &PathAttribute{
										Optional:       false,
										Transitive:     false,
										Partial:        false,
										ExtendedLength: false,
										Length:         0,
										TypeCode:       6,
										Next: &PathAttribute{
											Optional:       false,
											Transitive:     false,
											Partial:        false,
											ExtendedLength: false,
											Length:         6,
											TypeCode:       7,
											Value: types.Aggregator{
												ASN:     uint16(258),
												Address: bnet.IPv4FromOctets(10, 11, 12, 13).ToUint32(),
Oliver Herms's avatar
Oliver Herms committed
											},
										},
									},
								},
							},
						},
					},
				},
				NLRI: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					Prefix: bnet.NewPfx(bnet.IPv4FromOctets(11, 0, 0, 0), 8),
Oliver Herms's avatar
Oliver Herms committed
				},
			},
		},
		{
			testNum: 11, // Incomplete Withdraw
			input: []byte{
				0, 5, // Length
			},
			wantFail: true,
		},
		{
			testNum:  12, // Empty buffer
			input:    []byte{},
			wantFail: true,
		},
		{
			testNum: 13,
			input: []byte{
				0, 0, // No Withdraws
				0, 5, // Total Path Attributes Length
			},
			wantFail: true,
		},
		{
			testNum: 14,
			input: []byte{
				0, 0, // No Withdraws
				0, 0, // Total Path Attributes Length
				24, // Incomplete NLRI
			},
			wantFail: true,
		},
		{
			testNum: 15, // Cut at Total Path Attributes Length
			input: []byte{
				0, 0, // No Withdraws
			},
			explicitLength: 5,
			wantFail:       true,
		},
		{
			// Unknown attribute
			testNum: 16,
			input: []byte{
				0, 0, // No Withdraws
				0, 7, // Total Path Attributes Length
				64, 111, 4, 1, 1, 1, 1, // Unknown attribute
			},
			wantFail: false,
			expected: &BGPUpdate{
				TotalPathAttrLen: 7,
				PathAttributes: &PathAttribute{
					Length:     4,
					Transitive: true,
					TypeCode:   111,
					Value:      []byte{1, 1, 1, 1},
				},
			},
		},
		{
			// 2 withdraws with two path attributes (Communities + Origin), valid update
			testNum: 17,
			input: []byte{0, 5, 8, 10, 16, 192, 168,
				0, 16, // Total Path Attribute Length

				0,          // Attribute flags
				8,          // Attribute Type code (Community)
				8,          // Length
				0, 0, 1, 0, // Arbitrary Community
				0, 0, 1, 1, // Arbitrary Community

				255,  // Attribute flags
				1,    // Attribute Type code (ORIGIN)
				0, 1, // Length
				2, // INCOMPLETE

			},
			wantFail: false,
			expected: &BGPUpdate{
				WithdrawnRoutesLen: 5,
				WithdrawnRoutes: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
					Prefix: bnet.NewPfx(bnet.IPv4FromOctets(10, 0, 0, 0), 8),
					Next: &NLRI{
Daniel Czerwonk's avatar
Daniel Czerwonk committed
						Prefix: bnet.NewPfx(bnet.IPv4FromOctets(192, 168, 0, 0), 16),
					},
				},
				TotalPathAttrLen: 16,
				PathAttributes: &PathAttribute{
					Optional:       false,
					Transitive:     false,
					Partial:        false,
					ExtendedLength: false,
					Length:         8,
					TypeCode:       8,
					Value:          []uint32{256, 257},
					Next: &PathAttribute{
						Optional:       true,
						Transitive:     true,
						Partial:        true,
						ExtendedLength: true,
						Length:         1,
						TypeCode:       1,
						Value:          uint8(2),
					},
				},
			},
		},
		{
			// 2 withdraws with two path attributes (ORIGIN + Community), invalid update (too short community)
			testNum: 18,
			input: []byte{0, 5, 8, 10, 16, 192, 168,
				0, 11, // Total Path Attribute Length

				255,  // Attribute flags
				1,    // Attribute Type code (ORIGIN)
				0, 1, // Length
				2, // INCOMPLETE

				0,       // Attribute flags
				8,       // Attribute Type code (Community)
				3,       // Length
				0, 0, 1, // Arbitrary Community
			},
			wantFail: true,
			expected: nil,
		},
		{
			// 2 withdraws with two path attributes (ORIGIN + Community), invalid update (too long community)
			testNum: 19,
			input: []byte{0, 5, 8, 10, 16, 192, 168,
				0, 13, // Total Path Attribute Length

				255,  // Attribute flags
				1,    // Attribute Type code (ORIGIN)
				0, 1, // Length
				2, // INCOMPLETE

				0,             // Attribute flags
				8,             // Attribute Type code (Community)
				5,             // Length
				0, 0, 1, 0, 1, // Arbitrary Community
			},
			wantFail: true,
			expected: nil,
		},
Oliver Herms's avatar
Oliver Herms committed
	}

Oliver Herms's avatar
Oliver Herms committed
	for _, test := range tests {
		t.Run(strconv.Itoa(test.testNum), func(t *testing.T) {
			buf := bytes.NewBuffer(test.input)
			l := test.explicitLength
			if l == 0 {
				l = uint16(len(test.input))
			}
			msg, err := decodeUpdateMsg(buf, l, &DecodeOptions{})
Oliver Herms's avatar
Oliver Herms committed

			if err != nil && !test.wantFail {
				t.Fatalf("Unexpected error in test %d: %v", test.testNum, err)
			}
Oliver Herms's avatar
Oliver Herms committed

			if err == nil && test.wantFail {
				t.Fatalf("Expected error did not happen in test %d", test.testNum)
			}
Oliver Herms's avatar
Oliver Herms committed

			if err != nil && test.wantFail {
				return
			}
Oliver Herms's avatar
Oliver Herms committed

			assert.Equalf(t, test.expected, msg, "%d", test.testNum)
		})
Oliver Herms's avatar
Oliver Herms committed
	}
}

func TestDecodeMsgBody(t *testing.T) {
	tests := []struct {
		name     string
		buffer   *bytes.Buffer
		msgType  uint8
		length   uint16
		wantFail bool
		expected interface{}
	}{
		{
			name:     "Unknown msgType",
			msgType:  5,
			wantFail: true,
		},
	}

	for _, test := range tests {
		res, err := decodeMsgBody(test.buffer, test.msgType, test.length, &DecodeOptions{})
Oliver Herms's avatar
Oliver Herms committed
		if test.wantFail && err == nil {
			t.Errorf("Expected error dit not happen in test %q", test.name)
		}

		if !test.wantFail && err != nil {
			t.Errorf("Unexpected error in test %q: %v", test.name, err)
		}

		assert.Equal(t, test.expected, res)
	}
}

func TestDecodeOpenMsg(t *testing.T) {
	tests := []test{
		{
			// Valid message
			testNum:  1,
			input:    []byte{4, 1, 1, 0, 15, 10, 20, 30, 40, 0},
			wantFail: false,
			expected: &BGPOpen{
				Version:       4,
Oliver Herms's avatar
Oliver Herms committed
				HoldTime:      15,
				BGPIdentifier: 169090600,
				OptParmLen:    0,
				OptParams:     make([]OptParam, 0),
Oliver Herms's avatar
Oliver Herms committed
			},
		},
		{
			// Invalid Version
			testNum:  2,
			input:    []byte{3, 1, 1, 0, 15, 10, 10, 10, 11, 0},
			wantFail: true,
		},
	}

	genericTest(_decodeOpenMsg, tests, t)
}

func TestDecodeHeader(t *testing.T) {
	tests := []test{
		{
			// Valid header
			testNum:  1,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, KeepaliveMsg},
			wantFail: false,
			expected: &BGPHeader{
				Length: 19,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Invalid length too short
			testNum:  2,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 18, KeepaliveMsg},
			wantFail: true,
			expected: &BGPHeader{
				Length: 18,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Invalid length too long
			testNum:  3,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 16, 1, KeepaliveMsg},
			wantFail: true,
			expected: &BGPHeader{
				Length: 18,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Invalid message type 5
			testNum:  4,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 5},
			wantFail: true,
			expected: &BGPHeader{
				Length: 19,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Invalid message type 0
			testNum:  5,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19, 0},
			wantFail: true,
			expected: &BGPHeader{
				Length: 19,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Invalid marker
			testNum:  6,
			input:    []byte{1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 19, KeepaliveMsg},
			wantFail: true,
			expected: &BGPHeader{
				Length: 19,
				Type:   KeepaliveMsg,
			},
		},
		{
			// Incomplete Marker
			testNum:  7,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
			wantFail: true,
		},
		{
			// Incomplete Header
			testNum:  8,
			input:    []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 19},
			wantFail: true,
		},
		{
			// Empty input
			testNum:  9,
			input:    []byte{},
			wantFail: true,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(test.input)
		res, err := decodeHeader(buf)

		if err != nil {
			if test.wantFail {
				continue
			}
			t.Errorf("Unexpected failure for test %d: %v", test.testNum, err)
			continue
		}

		if test.wantFail {
			t.Errorf("Unexpected success fo test %d", test.testNum)
		}

		assert.Equal(t, test.expected, res)
	}
}

func genericTest(f decodeFunc, tests []test, t *testing.T) {
	for _, test := range tests {
		buf := bytes.NewBuffer(test.input)
		msg, err := f(buf)

		if err != nil && !test.wantFail {
			t.Errorf("Unexpected error in test %d: %v", test.testNum, err)
			continue
		}

		if err == nil && test.wantFail {
			t.Errorf("Expected error did not happen in test %d", test.testNum)
			continue
		}

		if err != nil && test.wantFail {
			continue
		}

		if msg == nil {
			t.Errorf("Unexpected nil result in test %d. Expected: %v", test.testNum, test.expected)
			continue
		}

		assert.Equalf(t, test.expected, msg, "%d", test.testNum)
Oliver Herms's avatar
Oliver Herms committed
	}
}

func TestIsValidIdentifier(t *testing.T) {
	tests := []struct {
		name     string
		input    uint32
		expected bool
	}{
		{
			name:     "Valid #1",
			input:    convert.Uint32b([]byte{8, 8, 8, 8}),
			expected: true,
		},
		{
			name:     "Multicast",
			input:    convert.Uint32b([]byte{239, 8, 8, 8}),
			expected: false,
		},
		{
			name:     "Loopback",
			input:    convert.Uint32b([]byte{127, 8, 8, 8}),
			expected: false,
		},
		{
			name:     "First byte 0",
			input:    convert.Uint32b([]byte{0, 8, 8, 8}),
			expected: false,
		},
		{
			name:     "All bytes 255",
			input:    convert.Uint32b([]byte{255, 255, 255, 255}),
			expected: false,
		},
	}

	for _, test := range tests {
		res := isValidIdentifier(test.input)
		assert.Equal(t, test.expected, res)
	}
}

func TestValidateOpenMessage(t *testing.T) {
	tests := []struct {
		name     string
		input    *BGPOpen
		wantFail bool
	}{
		{
			name: "Valid #1",
			input: &BGPOpen{
				Version:       4,
				BGPIdentifier: convert.Uint32b([]byte{8, 8, 8, 8}),
			},
			wantFail: false,
		},
		{
			name: "Invalid Identifier",
			input: &BGPOpen{
				Version:       4,
				BGPIdentifier: convert.Uint32b([]byte{0, 8, 8, 8}),
			},
			wantFail: true,
		},
	}

	for _, test := range tests {
		res := validateOpen(test.input)

		if res != nil {
			if test.wantFail {
				continue
			}
			t.Errorf("Unexpected failure for test %q: %v", test.name, res)
			continue
		}

		if test.wantFail {
			t.Errorf("Unexpected success for test %q", test.name)
		}
	}
}
func TestDecodeOptParams(t *testing.T) {
	tests := []struct {
		name     string
		input    []byte
		wantFail bool
		expected []OptParam
	}{
		{
			name: "Add path capability",
			input: []byte{
				2,    // Type
				6,    // Length
				69,   // Code
				4,    // Length
				0, 1, // AFI
				1, // SAFI
				3, // Send/Receive
			},
			wantFail: false,
			expected: []OptParam{
				{
					Type:   2,
					Length: 6,
Oliver Herms's avatar
Oliver Herms committed
					Value: Capabilities{
						{
							Code:   69,
							Length: 4,
							Value: AddPathCapability{
								AFI:         1,
								SAFI:        1,
								SendReceive: 3,
							},
						},
					},
				},
			},
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(test.input)
		res, err := decodeOptParams(buf, uint8(len(test.input)))
		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.Equal(t, test.expected, res)
	}
}

func TestDecodeCapability(t *testing.T) {
	tests := []struct {
		name     string
		input    []byte
		expected Capability
		wantFail bool
	}{
		{
			name:  "Add Path",
			input: []byte{69, 4, 0, 1, 1, 3},
			expected: Capability{
				Code:   69,
				Length: 4,
				Value: AddPathCapability{
					AFI:         1,
					SAFI:        1,
					SendReceive: 3,
				},
			},
			wantFail: false,
		},
		{
			name:  "MP Capability (IPv6)",
			input: []byte{1, 4, 0, 2, 0, 1},
			expected: Capability{
				Code:   MultiProtocolCapabilityCode,
				Length: 4,
				Value: MultiProtocolCapability{
					AFI:  IPv6AFI,
					SAFI: UnicastSAFI,
				},
			},
			wantFail: false,
		},
		{
			name:     "Fail",
			input:    []byte{69, 4, 0, 1},
			wantFail: true,
		},
	}

	for _, test := range tests {
		cap, err := decodeCapability(bytes.NewBuffer(test.input))
		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", err)
			continue
		}

		assert.Equal(t, test.expected, cap)
	}
}

func TestDecodeAddPathCapability(t *testing.T) {
	tests := []struct {
		name     string
		input    []byte
		expected AddPathCapability
		wantFail bool
	}{
		{
			name:     "ok",
			input:    []byte{0, 1, 1, 3},
			wantFail: false,
			expected: AddPathCapability{
				AFI:         1,
				SAFI:        1,
				SendReceive: 3,
			},
		},
		{
			name:     "Incomplete",
			input:    []byte{0, 1, 1},
			wantFail: true,
		},
	}

	for _, test := range tests {
		buf := bytes.NewBuffer(test.input)
		cap, err := decodeAddPathCapability(buf)
		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.Equal(t, test.expected, cap)
	}
}