From e5d0158e4d2b555e980b5f15856e792376f40494 Mon Sep 17 00:00:00 2001
From: Daniel Czerwonk <daniel@dan-nrw.de>
Date: Sun, 1 Jul 2018 19:28:08 +0200
Subject: [PATCH] added implementation for IPv6 supernet calculation

---
 net/ip_test.go     | 12 ++++++++++
 net/prefix.go      | 33 +++++++++++++++++++++++++-
 net/prefix_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/net/ip_test.go b/net/ip_test.go
index 03f78ec9..4720985f 100644
--- a/net/ip_test.go
+++ b/net/ip_test.go
@@ -312,6 +312,12 @@ func TestBitAtPosition(t *testing.T) {
 		position uint8
 		expected bool
 	}{
+		{
+			name:     "IPv4: all ones -> 0",
+			input:    IPv4FromOctets(255, 255, 255, 255),
+			position: 1,
+			expected: true,
+		},
 		{
 			name:     "IPv4: Bit 8 from 1.0.0.0 -> 0",
 			input:    IPv4FromOctets(10, 0, 0, 0),
@@ -348,6 +354,12 @@ func TestBitAtPosition(t *testing.T) {
 			position: 115,
 			expected: false,
 		},
+		{
+			name:     "IPv6: all ones -> 1",
+			input:    IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF),
+			position: 1,
+			expected: true,
+		},
 	}
 
 	for _, test := range tests {
diff --git a/net/prefix.go b/net/prefix.go
index c4042024..f75c2004 100644
--- a/net/prefix.go
+++ b/net/prefix.go
@@ -89,7 +89,7 @@ func (pfx Prefix) GetSupernet(x Prefix) Prefix {
 		return pfx.supernetIPv4(x)
 	}
 
-	panic("No IPv6 support yet!")
+	return pfx.supernetIPv6(x)
 }
 
 func (pfx Prefix) supernetIPv4(x Prefix) Prefix {
@@ -109,6 +109,37 @@ func (pfx Prefix) supernetIPv4(x Prefix) Prefix {
 	}
 }
 
+func (pfx Prefix) supernetIPv6(x Prefix) Prefix {
+	maxPfxLen := min(pfx.pfxlen, x.pfxlen)
+
+	a := pfx.addr.BitAtPosition(1)
+	b := x.addr.BitAtPosition(1)
+	pfxLen := uint8(0)
+	mask := uint64(0)
+	for a == b && pfxLen < maxPfxLen {
+		a = pfx.addr.BitAtPosition(pfxLen + 2)
+		b = x.addr.BitAtPosition(pfxLen + 2)
+		pfxLen++
+
+		if pfxLen == 64 {
+			mask = 0
+		}
+
+		m := pfxLen % 64
+		mask = mask + uint64(1)<<(64-m)
+	}
+
+	if pfxLen == 0 {
+		return NewPfx(IPv6(0, 0), pfxLen)
+	}
+
+	if pfxLen > 64 {
+		return NewPfx(IPv6(pfx.addr.higher, pfx.addr.lower&mask), pfxLen)
+	}
+
+	return NewPfx(IPv6(pfx.addr.higher&mask, 0), pfxLen)
+}
+
 func min(a uint8, b uint8) uint8 {
 	if a < b {
 		return a
diff --git a/net/prefix_test.go b/net/prefix_test.go
index d886a49e..4428a4e8 100644
--- a/net/prefix_test.go
+++ b/net/prefix_test.go
@@ -63,7 +63,7 @@ func TestGetSupernet(t *testing.T) {
 		expected Prefix
 	}{
 		{
-			name: "Test 1",
+			name: "Supernet of 10.0.0.0 and 11.100.123.0 -> 10.0.0.0/7",
 			a: Prefix{
 				addr:   IPv4FromOctets(10, 0, 0, 0),
 				pfxlen: 8,
@@ -78,7 +78,7 @@ func TestGetSupernet(t *testing.T) {
 			},
 		},
 		{
-			name: "Test 2",
+			name: "Supernet of 10.0.0.0 and 192.168.0.0 -> 0.0.0.0/0",
 			a: Prefix{
 				addr:   IPv4FromOctets(10, 0, 0, 0),
 				pfxlen: 8,
@@ -88,15 +88,64 @@ func TestGetSupernet(t *testing.T) {
 				pfxlen: 24,
 			},
 			expected: Prefix{
-				addr:   IPv4(0), // 0.0.0.0/0
+				addr:   IPv4(0),
+				pfxlen: 0,
+			},
+		},
+		{
+			name: "Supernet of 2001:678:1e0:100:23::/64 and 2001:678:1e0:1ff::/64 -> 2001:678:1e0:100::/56",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0x23, 0, 0, 0),
+				pfxlen: 64,
+			},
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x1ff, 0, 0, 0, 0),
+				pfxlen: 64,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0x100, 0, 0, 0, 0),
+				pfxlen: 56,
+			},
+		},
+		{
+			name: "Supernet of 2001:678:1e0::/128 and 2001:678:1e0::1/128 -> 2001:678:1e0:100::/127",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+				pfxlen: 128,
+			},
+			b: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 1),
+				pfxlen: 128,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0x2001, 0x678, 0x1e0, 0, 0, 0, 0, 0),
+				pfxlen: 127,
+			},
+		},
+		{
+			name: "Supernet of all ones and all zeros -> ::/0",
+			a: Prefix{
+				addr:   IPv6FromBlocks(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF),
+				pfxlen: 128,
+			},
+			b: Prefix{
+				addr:   IPv6(0, 0),
+				pfxlen: 128,
+			},
+			expected: Prefix{
+				addr:   IPv6FromBlocks(0, 0, 0, 0, 0, 0, 0, 0),
 				pfxlen: 0,
 			},
 		},
 	}
 
+	t.Parallel()
+
 	for _, test := range tests {
-		s := test.a.GetSupernet(test.b)
-		assert.Equal(t, s, test.expected)
+		t.Run(test.name, func(t *testing.T) {
+			s := test.a.GetSupernet(test.b)
+			assert.Equal(t, test.expected, s)
+		})
 	}
 }
 
-- 
GitLab