From 423a5d10c211cea7072aa024a6762704d1667b28 Mon Sep 17 00:00:00 2001
From: Oliver Herms <oliver.herms@exaring.de>
Date: Fri, 5 Oct 2018 16:44:28 +0200
Subject: [PATCH] Adding route mirroring

---
 protocols/bmp/packet/BUILD.bazel             |   2 +
 protocols/bmp/packet/decode.go               |  20 +++-
 protocols/bmp/packet/route_mirroring.go      |  41 +++++++
 protocols/bmp/packet/route_mirroring_test.go | 120 +++++++++++++++++++
 4 files changed, 178 insertions(+), 5 deletions(-)
 create mode 100644 protocols/bmp/packet/route_mirroring.go
 create mode 100644 protocols/bmp/packet/route_mirroring_test.go

diff --git a/protocols/bmp/packet/BUILD.bazel b/protocols/bmp/packet/BUILD.bazel
index 9eab9b8a..4f2b195e 100644
--- a/protocols/bmp/packet/BUILD.bazel
+++ b/protocols/bmp/packet/BUILD.bazel
@@ -32,6 +32,8 @@ go_test(
         "peer_down_test.go",
         "peer_up_test.go",
         "per_peer_header_test.go",
+        "route_mirroring_test.go",
+        "route_monitoring_test.go",
         "stats_report_test.go",
         "termination_message_test.go",
     ],
diff --git a/protocols/bmp/packet/decode.go b/protocols/bmp/packet/decode.go
index 522e57a9..2e0a0682 100644
--- a/protocols/bmp/packet/decode.go
+++ b/protocols/bmp/packet/decode.go
@@ -5,11 +5,8 @@ import (
 	"fmt"
 )
 
-type Msg interface {
-	MsgType() uint8
-}
-
 const (
+	// MinLen is the minimal length of a BMP message
 	MinLen = 6
 
 	RouteMonitoringType       = 0
@@ -25,8 +22,16 @@ const (
 
 	ErroredPDU  = 0
 	MessageLost = 1
+
+	// BMPVersion is the supported BMP version
+	BMPVersion = 3
 )
 
+// Msg is an interface that every BMP message must fulfill
+type Msg interface {
+	MsgType() uint8
+}
+
 // Decode decodes a BMP message
 func Decode(msg []byte) (Msg, error) {
 	buf := bytes.NewBuffer(msg)
@@ -36,13 +41,18 @@ func Decode(msg []byte) (Msg, error) {
 		return nil, fmt.Errorf("Unable to decode common header: %v", err)
 	}
 
-	if ch.Version != 3 {
+	if ch.Version != BMPVersion {
 		return nil, fmt.Errorf("Unsupported BMP version: %d", ch.Version)
 	}
 
 	switch ch.MsgType {
 	case RouteMonitoringType:
+		rm, err := decodeRouteMonitoringMsg(buf, ch)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode route monitoring message: %v", err)
+		}
 
+		return rm, err
 	case StatisticsReportType:
 		sr, err := decodeStatsReport(buf, ch)
 		if err != nil {
diff --git a/protocols/bmp/packet/route_mirroring.go b/protocols/bmp/packet/route_mirroring.go
new file mode 100644
index 00000000..007f1262
--- /dev/null
+++ b/protocols/bmp/packet/route_mirroring.go
@@ -0,0 +1,41 @@
+package packet
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// RouteMirroringMsg represents a route mirroring message
+type RouteMirroringMsg struct {
+	CommonHeader  *CommonHeader
+	PerPeerHeader *PerPeerHeader
+	TLVs          []*InformationTLV
+}
+
+func decodeRouteMirroringMsg(buf *bytes.Buffer, ch *CommonHeader) (*RouteMirroringMsg, error) {
+	rm := &RouteMirroringMsg{
+		CommonHeader: ch,
+	}
+
+	pph, err := decodePerPeerHeader(buf)
+	if err != nil {
+		return nil, fmt.Errorf("Unable to decode per peer header: %v", err)
+	}
+
+	rm.PerPeerHeader = pph
+
+	toRead := buf.Len()
+	read := 0
+
+	for read < toRead {
+		tlv, err := decodeInformationTLV(buf)
+		if err != nil {
+			return nil, fmt.Errorf("Unable to decode TLV: %v", err)
+		}
+
+		rm.TLVs = append(rm.TLVs, tlv)
+		read += int(tlv.InformationLength) + MinInformationTLVLen
+	}
+
+	return rm, nil
+}
diff --git a/protocols/bmp/packet/route_mirroring_test.go b/protocols/bmp/packet/route_mirroring_test.go
new file mode 100644
index 00000000..dd885aca
--- /dev/null
+++ b/protocols/bmp/packet/route_mirroring_test.go
@@ -0,0 +1,120 @@
+package packet
+
+import (
+	"bytes"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestDecodeRouteMirroringMsg(t *testing.T) {
+	tests := []struct {
+		name     string
+		input    []byte
+		ch       *CommonHeader
+		wantFail bool
+		expected *RouteMirroringMsg
+	}{
+		{
+			name: "Full",
+			input: []byte{
+				1,
+				2,
+				0, 0, 0, 3,
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 0, 200, 124,
+				0, 0, 0, 123,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+
+				0, 1, 0, 2, 100, 200,
+				0, 1, 0, 2, 100, 200,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12,
+			},
+			wantFail: false,
+			expected: &RouteMirroringMsg{
+				CommonHeader: &CommonHeader{
+					MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12,
+				},
+				PerPeerHeader: &PerPeerHeader{
+					PeerType:              1,
+					PeerFlags:             2,
+					PeerDistinguisher:     3,
+					PeerAddress:           [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16},
+					PeerAS:                51324,
+					PeerBGPID:             123,
+					Timestamp:             100,
+					TimestampMicroSeconds: 200,
+				},
+				TLVs: []*InformationTLV{
+					{
+						InformationType:   1,
+						InformationLength: 2,
+						Information:       []byte{100, 200},
+					},
+					{
+						InformationType:   1,
+						InformationLength: 2,
+						Information:       []byte{100, 200},
+					},
+				},
+			},
+		},
+		{
+			name: "Incomplete",
+			input: []byte{
+				1,
+				2,
+				0, 0, 0, 3,
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 0, 200, 124,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12,
+			},
+			wantFail: true,
+		},
+		{
+			name: "Incomplete TLV",
+			input: []byte{
+				1,
+				2,
+				0, 0, 0, 3,
+				1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+				0, 0, 200, 124,
+				0, 0, 0, 123,
+				0, 0, 0, 100,
+				0, 0, 0, 200,
+
+				0, 1, 0, 2, 100, 200,
+				0, 1, 0, 2,
+			},
+			ch: &CommonHeader{
+				MsgLength: CommonHeaderLen + PerPeerHeaderLen + 12,
+			},
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		buf := bytes.NewBuffer(test.input)
+		r, err := decodeRouteMirroringMsg(buf, test.ch)
+		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, r, "Test %q", test.name)
+	}
+}
-- 
GitLab