From 5bf9d69aef51402dc9fe4355bf57f1220a333fe6 Mon Sep 17 00:00:00 2001
From: Daniel Czerwonk <daniel@dan-nrw.de>
Date: Thu, 31 May 2018 20:48:32 +0200
Subject: [PATCH] community convert methods, decoding

---
 protocols/bgp/packet/bgp.go                  |  1 +
 protocols/bgp/packet/community.go            | 37 ++++++++
 protocols/bgp/packet/community_test.go       | 93 ++++++++++++++++++++
 protocols/bgp/packet/large_community.go      |  2 +-
 protocols/bgp/packet/path_attributes.go      | 31 ++++---
 protocols/bgp/packet/path_attributes_test.go | 59 +++++++++++++
 6 files changed, 209 insertions(+), 14 deletions(-)
 create mode 100644 protocols/bgp/packet/community.go
 create mode 100644 protocols/bgp/packet/community_test.go

diff --git a/protocols/bgp/packet/bgp.go b/protocols/bgp/packet/bgp.go
index b32980db..4835484d 100644
--- a/protocols/bgp/packet/bgp.go
+++ b/protocols/bgp/packet/bgp.go
@@ -11,6 +11,7 @@ const (
 	MinLen            = 19
 	MaxLen            = 4096
 	NLRIMaxLen        = 5
+	CommunityLen      = 4
 	LargeCommunityLen = 12
 
 	OpenMsg         = 1
diff --git a/protocols/bgp/packet/community.go b/protocols/bgp/packet/community.go
new file mode 100644
index 00000000..e97d6788
--- /dev/null
+++ b/protocols/bgp/packet/community.go
@@ -0,0 +1,37 @@
+package packet
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+func CommunityString(v uint32) string {
+	e1 := v >> 16
+	e2 := v - e1<<16
+
+	return fmt.Sprintf("(%d,%d)", e1, e2)
+}
+
+func ParseCommunityString(s string) (uint32, error) {
+	s = strings.Trim(s, "()")
+	t := strings.Split(s, ",")
+
+	if len(t) != 2 {
+		return 0, fmt.Errorf("can not parse community %s", s)
+	}
+
+	v, err := strconv.ParseUint(t[0], 10, 16)
+	if err != nil {
+		return 0, err
+	}
+	e1 := uint32(v)
+
+	v, err = strconv.ParseUint(t[1], 10, 16)
+	if err != nil {
+		return 0, err
+	}
+	e2 := uint16(v)
+
+	return e1<<16 + uint32(e2), nil
+}
diff --git a/protocols/bgp/packet/community_test.go b/protocols/bgp/packet/community_test.go
new file mode 100644
index 00000000..06a22689
--- /dev/null
+++ b/protocols/bgp/packet/community_test.go
@@ -0,0 +1,93 @@
+package packet
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestCommunityString(t *testing.T) {
+	tests := []struct {
+		name     string
+		value    uint32
+		expected string
+	}{
+		{
+			name:     "both elements",
+			value:    131080,
+			expected: "(2,8)",
+		},
+		{
+			name:     "right element only",
+			value:    250,
+			expected: "(0,250)",
+		},
+		{
+			name:     "left element only",
+			value:    131072,
+			expected: "(2,0)",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			assert.Equal(te, test.expected, CommunityString(test.value))
+		})
+	}
+}
+
+func TestParseCommunityString(t *testing.T) {
+	tests := []struct {
+		name     string
+		value    string
+		expected uint32
+		wantFail bool
+	}{
+		{
+			name:     "both elements",
+			expected: 131080,
+			value:    "(2,8)",
+		},
+		{
+			name:     "right element only",
+			expected: 250,
+			value:    "(0,250)",
+		},
+		{
+			name:     "left element only",
+			expected: 131072,
+			value:    "(2,0)",
+		},
+		{
+			name:     "too big",
+			value:    "(131072,256)",
+			wantFail: true,
+		},
+		{
+			name:     "empty string",
+			value:    "",
+			wantFail: true,
+		},
+		{
+			name:     "random string",
+			value:    "foo-bar",
+			wantFail: true,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(te *testing.T) {
+			c, err := ParseCommunityString(test.value)
+
+			if test.wantFail {
+				if err == nil {
+					te.Fatal("test was expected to fail, but did not")
+				}
+
+				return
+			}
+
+			assert.Equal(te, test.expected, c)
+		})
+	}
+}
diff --git a/protocols/bgp/packet/large_community.go b/protocols/bgp/packet/large_community.go
index 135c8639..5b3f7788 100644
--- a/protocols/bgp/packet/large_community.go
+++ b/protocols/bgp/packet/large_community.go
@@ -16,7 +16,7 @@ func (c LargeCommunity) String() string {
 	return fmt.Sprintf("(%d,%d,%d)", c.GlobalAdministrator, c.DataPart1, c.DataPart2)
 }
 
-func ParseCommunityString(s string) (com LargeCommunity, err error) {
+func ParseLargeCommunityString(s string) (com LargeCommunity, err error) {
 	s = strings.Trim(s, "()")
 	t := strings.Split(s, ",")
 
diff --git a/protocols/bgp/packet/path_attributes.go b/protocols/bgp/packet/path_attributes.go
index 055a85e2..8a13ec64 100644
--- a/protocols/bgp/packet/path_attributes.go
+++ b/protocols/bgp/packet/path_attributes.go
@@ -244,13 +244,15 @@ func (pa *PathAttribute) decodeAggregator(buf *bytes.Buffer) error {
 }
 
 func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error {
-	if pa.Length%4 != 0 {
+	if pa.Length%CommunityLen != 0 {
 		return fmt.Errorf("Unable to read community path attribute length %d is not divisible by 4", pa.Length)
 	}
-	comNumber := pa.Length / 4
-	var com = make([]uint32, comNumber)
-	for i := uint16(0); i < comNumber; i++ {
-		c := [4]byte{}
+
+	count := pa.Length / CommunityLen
+	coms := make([]uint32, count)
+
+	for i := uint16(0); i < count; i++ {
+		c := [CommunityLen]byte{}
 		n, err := buf.Read(c[:])
 		if err != nil {
 			return err
@@ -258,16 +260,21 @@ func (pa *PathAttribute) decodeCommunities(buf *bytes.Buffer) error {
 		if n != 4 {
 			return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n)
 		}
-		com[i] = fourBytesToUint32(c)
+
+		v := fourBytesToUint32(c)
+		coms[i] = v
 	}
-	pa.Value = com
+
+	pa.Value = coms
 	return nil
 }
 
 func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error {
-	length := pa.Length
-	count := length / LargeCommunityLen
+	if pa.Length%LargeCommunityLen != 0 {
+		return fmt.Errorf("Unable to read large community path attribute. Length %d is not divisible by 12", pa.Length)
+	}
 
+	count := pa.Length / LargeCommunityLen
 	coms := make([]LargeCommunity, count)
 
 	for i := uint16(0); i < count; i++ {
@@ -295,9 +302,7 @@ func (pa *PathAttribute) decodeLargeCommunities(buf *bytes.Buffer) error {
 	}
 
 	pa.Value = coms
-
-	dump := pa.Length - (count * LargeCommunityLen)
-	return dumpNBytes(buf, dump)
+	return nil
 }
 
 func (pa *PathAttribute) decodeAS4Path(buf *bytes.Buffer) error {
@@ -646,7 +651,7 @@ func LargeCommunityAttributeForString(s string) (*PathAttribute, error) {
 
 	var err error
 	for i, str := range strs {
-		coms[i], err = ParseCommunityString(str)
+		coms[i], err = ParseLargeCommunityString(str)
 		if err != nil {
 			return nil, err
 		}
diff --git a/protocols/bgp/packet/path_attributes_test.go b/protocols/bgp/packet/path_attributes_test.go
index 0e049760..70599584 100644
--- a/protocols/bgp/packet/path_attributes_test.go
+++ b/protocols/bgp/packet/path_attributes_test.go
@@ -657,6 +657,65 @@ func TestDecodeLargeCommunity(t *testing.T) {
 	}
 }
 
+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{
+					131080, 16777216 + 1025,
+				},
+			},
+		},
+		{
+			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))
+
+		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 TestSetLength(t *testing.T) {
 	tests := []struct {
 		name             string
-- 
GitLab