Skip to content

Commit 984d34d

Browse files
committed
Add IPv6
Bump v0.0.3
1 parent f8d8ff5 commit 984d34d

File tree

7 files changed

+296
-12
lines changed

7 files changed

+296
-12
lines changed

Diff for: WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using org.dmxc.wkdt.Light.ArtNet;
2-
using org.dmxc.wkdt.Tests;
32

43
namespace org.dmxc.wkdt.Tests.Light.ArtNet
54
{
@@ -66,7 +65,7 @@ public void TestSerializable()
6665
{
6766
Address address = new Address(2, 3);
6867
var data = Tools.Serialize(address);
69-
string json= System.Text.Encoding.Default.GetString(data);
68+
string json = System.Text.Encoding.Default.GetString(data);
7069
Address result = Tools.Deserialize<Address>(data);
7170

7271
Assert.That(result, Is.EqualTo(address), json);

Diff for: WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public void TestPortAddress()
5656
[Test]
5757
public void TestSerializable()
5858
{
59-
PortAddress portAddress = new PortAddress(2,3,4);
59+
PortAddress portAddress = new PortAddress(2, 3, 4);
6060
var data = Tools.Serialize(portAddress);
6161
string json = System.Text.Encoding.Default.GetString(data);
6262
PortAddress result = Tools.Deserialize<PortAddress>(data);
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using org.dmxc.wkdt.Network;
2+
using System.Collections.Concurrent;
3+
using System.Numerics;
4+
5+
namespace org.dmxc.wkdt.Tests.Network
6+
{
7+
public class IPv6Address_Tests
8+
{
9+
[Test]
10+
public void TestIPv6Address()
11+
{
12+
var address = IPv6Address.LocalHost;
13+
Assert.Multiple(() =>
14+
{
15+
Assert.That(IPv6Address.Empty.ToString(), Is.EqualTo("::"));
16+
Assert.That(address.ToString(), Is.EqualTo("::1"));
17+
Assert.That(address, Is.EqualTo(new IPv6Address(address.ToString())));
18+
Assert.Throws(typeof(FormatException), () => new IPv6Address("1.2.3."));
19+
Assert.DoesNotThrow(() => new IPv6Address("2::2"));
20+
Assert.DoesNotThrow(() => new IPv6Address("2::"));
21+
Assert.DoesNotThrow(() => new IPv6Address("::2"));
22+
Assert.DoesNotThrow(() => new IPv6Address("::20"));
23+
Assert.DoesNotThrow(() => new IPv6Address("::02"));
24+
Assert.DoesNotThrow(() => new IPv6Address("::200"));
25+
Assert.DoesNotThrow(() => new IPv6Address("::002"));
26+
Assert.DoesNotThrow(() => new IPv6Address("::2000"));
27+
Assert.DoesNotThrow(() => new IPv6Address("::0002"));
28+
Assert.DoesNotThrow(() => new IPv6Address("fff2::202"));
29+
Assert.DoesNotThrow(() => new IPv6Address("fff2:222::202"));
30+
Assert.DoesNotThrow(() => new IPv6Address("fff2:222::202"));
31+
Assert.DoesNotThrow(() => new IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"));
32+
Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(12342152151345345)));
33+
Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(0)));
34+
Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(1)));
35+
36+
var bi = new BigInteger(12342152151345345);
37+
Assert.That(new IPv6Address(bi), Is.EqualTo((IPv6Address)bi));
38+
Assert.That((BigInteger)new IPv6Address(bi), Is.EqualTo(bi));
39+
var bytes = new byte[16];
40+
Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes));
41+
Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes));
42+
bytes = new byte[16] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
43+
Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes));
44+
Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes));
45+
bytes = new byte[16] { 0xEE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0 };
46+
Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes));
47+
Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes));
48+
Assert.That(new IPv6Address(bytes).ToString(), Is.EqualTo("ee00::ff00"));
49+
50+
Assert.That(new IPv6Address(bytes.ToList()), Is.EqualTo((IPv6Address)bytes));
51+
52+
Assert.Throws(typeof(ArgumentOutOfRangeException), () => new IPv6Address(new byte[] { 1 }));
53+
Assert.Throws(typeof(ArgumentOutOfRangeException), () => new IPv6Address(new List<byte>() { 1 }));
54+
55+
56+
System.Net.IPAddress ip = new System.Net.IPAddress(bytes);
57+
address = (IPv6Address)bytes;
58+
Assert.That(ip, Is.EqualTo((System.Net.IPAddress)address));
59+
Assert.That(address, Is.EqualTo((IPv6Address)ip));
60+
61+
// bytes = new byte[] { 1, 1, 1, 1 };
62+
// Assert.That(bytes, Is.EqualTo((byte[])new IPv6Address(bytes)));
63+
64+
Assert.That(new IPv6Address(new BigInteger(1)) == new IPv6Address("::1"), Is.True);
65+
Assert.That(new IPv6Address(new BigInteger(1)) == new IPv6Address("1::"), Is.False);
66+
Assert.That(new IPv6Address(new BigInteger(1)) != new IPv6Address("::1"), Is.False);
67+
Assert.That(((object)new IPv6Address(new BigInteger(1))).Equals(new IPv6Address("::1")), Is.True);
68+
Assert.That((new IPv6Address(new BigInteger(1))).Equals(new IPv6Address("::1")), Is.True);
69+
Assert.That(((object)new IPv6Address(new BigInteger(1))).Equals("::1"), Is.False);
70+
Assert.That((new IPv6Address(new BigInteger(1))).Equals("::1"), Is.False);
71+
72+
73+
ConcurrentDictionary<IPv6Address, string> dict = new ConcurrentDictionary<IPv6Address, string>();
74+
75+
Random rnd = new Random();
76+
for (int i = 0; i < 1000; i++)
77+
{
78+
var bigI = new BigInteger(rnd.NextInt64());
79+
address = new IPv6Address(bigI);
80+
var res = dict.TryAdd(address, address.ToString());
81+
Assert.That(res, Is.True, address.String);
82+
Assert.That(address.Raw, Is.EqualTo(bigI), address.String);
83+
}
84+
85+
Assert.Throws(typeof(ArgumentException), () => { var ip = (IPv6Address)System.Net.IPAddress.Any; });
86+
});
87+
}
88+
89+
[Test]
90+
public void TestSerializable()
91+
{
92+
IPv6Address ipv4Address = new IPv6Address("fe80::ad64:5a9a:8869:1c4f");
93+
var data = Tools.Serialize(ipv4Address);
94+
string json = System.Text.Encoding.Default.GetString(data);
95+
IPv6Address result = Tools.Deserialize<IPv6Address>(data);
96+
97+
Assert.That(result, Is.EqualTo(ipv4Address), json);
98+
}
99+
}
100+
}

Diff for: WellKnownDataTypes-Tests/Tools.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
7-
namespace org.dmxc.wkdt.Tests
1+
namespace org.dmxc.wkdt.Tests
82
{
93
public static class Tools
104
{

Diff for: WellKnownDataTypes-Tests/Usings.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
global using NUnit.Framework;
2-
global using System.Buffers.Text;
32
global using System.Runtime.Serialization.Formatters.Binary;
43
global using System.Text.Json;

Diff for: WellKnownDataTypes/Network/IPv6Address.cs

+192
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Net;
4+
using System.Numerics;
5+
using System.Text;
6+
using System.Text.RegularExpressions;
7+
8+
namespace org.dmxc.wkdt.Network
9+
{
10+
[Serializable]
11+
public readonly struct IPv6Address : IEquatable<IPv6Address>
12+
{
13+
public static IPv6Address Empty { get => new IPv6Address("::"); }
14+
public static IPv6Address LocalHost { get => new IPv6Address("::1"); }
15+
16+
private static readonly Regex regex = new Regex(@"(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$");
17+
18+
public readonly BigInteger Raw;
19+
#if NET8_0_OR_GREATER
20+
[JsonInclude]
21+
#endif
22+
public readonly string String;
23+
24+
#if NET8_0_OR_GREATER
25+
[JsonConstructor]
26+
#endif
27+
public IPv6Address(string @string)
28+
{
29+
if (!regex.Match(@string).Success)
30+
throw new FormatException("The given string is not a IPv6Address");
31+
// Expand the IPv6 address if it uses the :: shorthand
32+
string expandedAddress = ExpandIPv6Address(@string);
33+
34+
String = @string;
35+
36+
// Split the expanded address into its component hextets
37+
string[] hextets = expandedAddress.Split(':');
38+
39+
// Convert each hextet to its corresponding integer value and combine into a BigInteger
40+
Raw = BigInteger.Zero;
41+
foreach (string hextet in hextets)
42+
Raw = (Raw << 16) + BigInteger.Parse(hextet, System.Globalization.NumberStyles.HexNumber);
43+
44+
}
45+
public IPv6Address(BigInteger bigInteger) : this()
46+
{
47+
Raw = bigInteger;
48+
byte[] bytes = new byte[16];
49+
var bigBytes = bigInteger.ToByteArray();
50+
Array.Copy(bigBytes, bytes, bigBytes.Length);
51+
String = StringFromBytes(bytes);
52+
}
53+
public IPv6Address(byte[] bytes) : this()
54+
{
55+
if (bytes.Length != 16)
56+
throw new ArgumentOutOfRangeException("bytes should be an array with a length of 16");
57+
58+
Raw = new BigInteger(bytes);
59+
String = StringFromBytes(bytes);
60+
}
61+
public IPv6Address(IEnumerable<byte> enumerable) : this(enumerable.ToArray())
62+
{
63+
}
64+
private static string StringFromBytes(byte[] bytes)
65+
{
66+
StringBuilder sb = new StringBuilder();
67+
for (int i = 0; i < bytes.Length; i += 2)
68+
{
69+
sb.Append($"{bytes[i]:x2}{bytes[i + 1]:x2}");
70+
if (i < 14)
71+
sb.Append(':');
72+
}
73+
var str = sb.ToString();
74+
while (str.Contains(":0"))
75+
str = str.Replace(":0", ":");
76+
while (str.Contains(":::"))
77+
str = str.Replace(":::", "::");
78+
return str;
79+
}
80+
private static string ExpandIPv6Address(string ipv6Address)
81+
{
82+
if (ipv6Address == "::") return "0000:0000:0000:0000:0000:0000:0000:0000";
83+
84+
string[] parts = ipv6Address.Split(new string[] { "::" }, StringSplitOptions.None);
85+
string[] leftParts = parts[0].Split(':');
86+
string[] rightParts = parts.Length > 1 ? parts[1].Split(':') : new string[0];
87+
88+
if (string.IsNullOrWhiteSpace(leftParts[0]))
89+
leftParts = leftParts.Skip(1).ToArray();
90+
if (rightParts.Length != 0 && string.IsNullOrWhiteSpace(rightParts.Last()))
91+
rightParts = rightParts.Take(rightParts.Length - 1).ToArray();
92+
93+
int numZeroesToInsert = 8 - (leftParts.Length + rightParts.Length);
94+
95+
string[] expandedAddress = new string[8];
96+
Array.Copy(leftParts, expandedAddress, leftParts.Length);
97+
for (int i = leftParts.Length; i < leftParts.Length + numZeroesToInsert; i++)
98+
{
99+
expandedAddress[i] = "0000";
100+
}
101+
Array.Copy(rightParts, 0, expandedAddress, leftParts.Length + numZeroesToInsert, rightParts.Length);
102+
for (int i = 0; i < expandedAddress.Length; i++)
103+
{
104+
switch (expandedAddress[i].Length)
105+
{
106+
case 1:
107+
expandedAddress[i] = "000" + expandedAddress[i];
108+
break;
109+
case 2:
110+
expandedAddress[i] = "00" + expandedAddress[i];
111+
break;
112+
case 3:
113+
expandedAddress[i] = "0" + expandedAddress[i];
114+
break;
115+
default:
116+
break;
117+
}
118+
}
119+
120+
return string.Join(":", expandedAddress);
121+
}
122+
123+
124+
public static implicit operator IPAddress(IPv6Address address)
125+
{
126+
byte[] bytes = new byte[16];
127+
var bigBytes = address.Raw.ToByteArray();
128+
Array.Copy(bigBytes, bytes, bigBytes.Length);
129+
return new IPAddress(bytes);
130+
}
131+
public static implicit operator IPv6Address(IPAddress ip)
132+
{
133+
if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6)
134+
return new IPv6Address(ip.GetAddressBytes());
135+
throw new ArgumentException($"{ip} is not a Valid IPv4 and cant be converted");
136+
}
137+
public static implicit operator BigInteger(IPv6Address address)
138+
{
139+
return address.Raw;
140+
}
141+
public static implicit operator IPv6Address(BigInteger bigInteger)
142+
{
143+
return new IPv6Address(bigInteger);
144+
}
145+
public static implicit operator byte[](IPv6Address address)
146+
{
147+
byte[] bytes = new byte[16];
148+
var bigBytes = address.Raw.ToByteArray();
149+
Array.Copy(bigBytes, bytes, bigBytes.Length);
150+
return bytes;
151+
}
152+
public static implicit operator IPv6Address(byte[] bytes)
153+
{
154+
return new IPv6Address(bytes);
155+
}
156+
public override string ToString()
157+
{
158+
return String;
159+
}
160+
161+
public static bool operator ==(IPv6Address a, IPv6Address b)
162+
{
163+
return a.Equals(b);
164+
}
165+
166+
public static bool operator !=(IPv6Address a, IPv6Address b)
167+
{
168+
return !a.Equals(b);
169+
}
170+
171+
public bool Equals(IPv6Address other)
172+
{
173+
if (this.Raw != other.Raw)
174+
return false;
175+
176+
return true;
177+
}
178+
179+
public override bool Equals(object obj)
180+
{
181+
return obj is IPv6Address other &&
182+
Raw == other.Raw;
183+
}
184+
185+
public override int GetHashCode()
186+
{
187+
int hashCode = 1916557166;
188+
hashCode = hashCode * -1521134295 + Raw.GetHashCode();
189+
return hashCode;
190+
}
191+
}
192+
}

Diff for: WellKnownDataTypes/WellKnownDataTypes.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
44
<PackageLicenseFile>LICENSE</PackageLicenseFile>
5-
<Version>0.0.2</Version>
5+
<Version>0.0.3</Version>
66
<RepositoryUrl>https://github.com/DMXControl/WellKnownDataTypes</RepositoryUrl>
77
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
88
<PackageReadmeFile>README.md</PackageReadmeFile>

0 commit comments

Comments
 (0)