From 984d34d08eeb8f34f0f4da6072ec161375f4673f Mon Sep 17 00:00:00 2001 From: Patrick Grote Date: Sun, 2 Jun 2024 00:56:22 +0200 Subject: [PATCH] Add IPv6 Bump v0.0.3 --- .../Light/ArtNet/Address_Tests.cs | 3 +- .../Light/ArtNet/PortAddress_Tests.cs | 2 +- .../Network/IPv6Address_Tests.cs | 100 +++++++++ WellKnownDataTypes-Tests/Tools.cs | 8 +- WellKnownDataTypes-Tests/Usings.cs | 1 - WellKnownDataTypes/Network/IPv6Address.cs | 192 ++++++++++++++++++ WellKnownDataTypes/WellKnownDataTypes.csproj | 2 +- 7 files changed, 296 insertions(+), 12 deletions(-) create mode 100644 WellKnownDataTypes-Tests/Network/IPv6Address_Tests.cs create mode 100644 WellKnownDataTypes/Network/IPv6Address.cs diff --git a/WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs b/WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs index 246c9e7..a23934e 100644 --- a/WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs +++ b/WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs @@ -1,5 +1,4 @@ using org.dmxc.wkdt.Light.ArtNet; -using org.dmxc.wkdt.Tests; namespace org.dmxc.wkdt.Tests.Light.ArtNet { @@ -66,7 +65,7 @@ public void TestSerializable() { Address address = new Address(2, 3); var data = Tools.Serialize(address); - string json= System.Text.Encoding.Default.GetString(data); + string json = System.Text.Encoding.Default.GetString(data); Address result = Tools.Deserialize
(data); Assert.That(result, Is.EqualTo(address), json); diff --git a/WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs b/WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs index 85021a0..9c115b6 100644 --- a/WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs +++ b/WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs @@ -56,7 +56,7 @@ public void TestPortAddress() [Test] public void TestSerializable() { - PortAddress portAddress = new PortAddress(2,3,4); + PortAddress portAddress = new PortAddress(2, 3, 4); var data = Tools.Serialize(portAddress); string json = System.Text.Encoding.Default.GetString(data); PortAddress result = Tools.Deserialize(data); diff --git a/WellKnownDataTypes-Tests/Network/IPv6Address_Tests.cs b/WellKnownDataTypes-Tests/Network/IPv6Address_Tests.cs new file mode 100644 index 0000000..b49ff9f --- /dev/null +++ b/WellKnownDataTypes-Tests/Network/IPv6Address_Tests.cs @@ -0,0 +1,100 @@ +using org.dmxc.wkdt.Network; +using System.Collections.Concurrent; +using System.Numerics; + +namespace org.dmxc.wkdt.Tests.Network +{ + public class IPv6Address_Tests + { + [Test] + public void TestIPv6Address() + { + var address = IPv6Address.LocalHost; + Assert.Multiple(() => + { + Assert.That(IPv6Address.Empty.ToString(), Is.EqualTo("::")); + Assert.That(address.ToString(), Is.EqualTo("::1")); + Assert.That(address, Is.EqualTo(new IPv6Address(address.ToString()))); + Assert.Throws(typeof(FormatException), () => new IPv6Address("1.2.3.")); + Assert.DoesNotThrow(() => new IPv6Address("2::2")); + Assert.DoesNotThrow(() => new IPv6Address("2::")); + Assert.DoesNotThrow(() => new IPv6Address("::2")); + Assert.DoesNotThrow(() => new IPv6Address("::20")); + Assert.DoesNotThrow(() => new IPv6Address("::02")); + Assert.DoesNotThrow(() => new IPv6Address("::200")); + Assert.DoesNotThrow(() => new IPv6Address("::002")); + Assert.DoesNotThrow(() => new IPv6Address("::2000")); + Assert.DoesNotThrow(() => new IPv6Address("::0002")); + Assert.DoesNotThrow(() => new IPv6Address("fff2::202")); + Assert.DoesNotThrow(() => new IPv6Address("fff2:222::202")); + Assert.DoesNotThrow(() => new IPv6Address("fff2:222::202")); + Assert.DoesNotThrow(() => new IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334")); + Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(12342152151345345))); + Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(0))); + Assert.DoesNotThrow(() => new IPv6Address(new BigInteger(1))); + + var bi = new BigInteger(12342152151345345); + Assert.That(new IPv6Address(bi), Is.EqualTo((IPv6Address)bi)); + Assert.That((BigInteger)new IPv6Address(bi), Is.EqualTo(bi)); + var bytes = new byte[16]; + Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes)); + Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes)); + bytes = new byte[16] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; + Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes)); + Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes)); + bytes = new byte[16] { 0xEE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0 }; + Assert.That(new IPv6Address(bytes), Is.EqualTo((IPv6Address)bytes)); + Assert.That((byte[])new IPv6Address(bytes), Is.EqualTo(bytes)); + Assert.That(new IPv6Address(bytes).ToString(), Is.EqualTo("ee00::ff00")); + + Assert.That(new IPv6Address(bytes.ToList()), Is.EqualTo((IPv6Address)bytes)); + + Assert.Throws(typeof(ArgumentOutOfRangeException), () => new IPv6Address(new byte[] { 1 })); + Assert.Throws(typeof(ArgumentOutOfRangeException), () => new IPv6Address(new List() { 1 })); + + + System.Net.IPAddress ip = new System.Net.IPAddress(bytes); + address = (IPv6Address)bytes; + Assert.That(ip, Is.EqualTo((System.Net.IPAddress)address)); + Assert.That(address, Is.EqualTo((IPv6Address)ip)); + + // bytes = new byte[] { 1, 1, 1, 1 }; + // Assert.That(bytes, Is.EqualTo((byte[])new IPv6Address(bytes))); + + Assert.That(new IPv6Address(new BigInteger(1)) == new IPv6Address("::1"), Is.True); + Assert.That(new IPv6Address(new BigInteger(1)) == new IPv6Address("1::"), Is.False); + Assert.That(new IPv6Address(new BigInteger(1)) != new IPv6Address("::1"), Is.False); + Assert.That(((object)new IPv6Address(new BigInteger(1))).Equals(new IPv6Address("::1")), Is.True); + Assert.That((new IPv6Address(new BigInteger(1))).Equals(new IPv6Address("::1")), Is.True); + Assert.That(((object)new IPv6Address(new BigInteger(1))).Equals("::1"), Is.False); + Assert.That((new IPv6Address(new BigInteger(1))).Equals("::1"), Is.False); + + + ConcurrentDictionary dict = new ConcurrentDictionary(); + + Random rnd = new Random(); + for (int i = 0; i < 1000; i++) + { + var bigI = new BigInteger(rnd.NextInt64()); + address = new IPv6Address(bigI); + var res = dict.TryAdd(address, address.ToString()); + Assert.That(res, Is.True, address.String); + Assert.That(address.Raw, Is.EqualTo(bigI), address.String); + } + + Assert.Throws(typeof(ArgumentException), () => { var ip = (IPv6Address)System.Net.IPAddress.Any; }); + }); + } + + [Test] + public void TestSerializable() + { + IPv6Address ipv4Address = new IPv6Address("fe80::ad64:5a9a:8869:1c4f"); + var data = Tools.Serialize(ipv4Address); + string json = System.Text.Encoding.Default.GetString(data); + IPv6Address result = Tools.Deserialize(data); + + Assert.That(result, Is.EqualTo(ipv4Address), json); + } + } +} \ No newline at end of file diff --git a/WellKnownDataTypes-Tests/Tools.cs b/WellKnownDataTypes-Tests/Tools.cs index 75810b7..4bbd6b8 100644 --- a/WellKnownDataTypes-Tests/Tools.cs +++ b/WellKnownDataTypes-Tests/Tools.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace org.dmxc.wkdt.Tests +namespace org.dmxc.wkdt.Tests { public static class Tools { diff --git a/WellKnownDataTypes-Tests/Usings.cs b/WellKnownDataTypes-Tests/Usings.cs index 7162c33..209c9c8 100644 --- a/WellKnownDataTypes-Tests/Usings.cs +++ b/WellKnownDataTypes-Tests/Usings.cs @@ -1,4 +1,3 @@ global using NUnit.Framework; -global using System.Buffers.Text; global using System.Runtime.Serialization.Formatters.Binary; global using System.Text.Json; \ No newline at end of file diff --git a/WellKnownDataTypes/Network/IPv6Address.cs b/WellKnownDataTypes/Network/IPv6Address.cs new file mode 100644 index 0000000..51cc480 --- /dev/null +++ b/WellKnownDataTypes/Network/IPv6Address.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Numerics; +using System.Text; +using System.Text.RegularExpressions; + +namespace org.dmxc.wkdt.Network +{ + [Serializable] + public readonly struct IPv6Address : IEquatable + { + public static IPv6Address Empty { get => new IPv6Address("::"); } + public static IPv6Address LocalHost { get => new IPv6Address("::1"); } + + 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]))$"); + + public readonly BigInteger Raw; +#if NET8_0_OR_GREATER + [JsonInclude] +#endif + public readonly string String; + +#if NET8_0_OR_GREATER + [JsonConstructor] +#endif + public IPv6Address(string @string) + { + if (!regex.Match(@string).Success) + throw new FormatException("The given string is not a IPv6Address"); + // Expand the IPv6 address if it uses the :: shorthand + string expandedAddress = ExpandIPv6Address(@string); + + String = @string; + + // Split the expanded address into its component hextets + string[] hextets = expandedAddress.Split(':'); + + // Convert each hextet to its corresponding integer value and combine into a BigInteger + Raw = BigInteger.Zero; + foreach (string hextet in hextets) + Raw = (Raw << 16) + BigInteger.Parse(hextet, System.Globalization.NumberStyles.HexNumber); + + } + public IPv6Address(BigInteger bigInteger) : this() + { + Raw = bigInteger; + byte[] bytes = new byte[16]; + var bigBytes = bigInteger.ToByteArray(); + Array.Copy(bigBytes, bytes, bigBytes.Length); + String = StringFromBytes(bytes); + } + public IPv6Address(byte[] bytes) : this() + { + if (bytes.Length != 16) + throw new ArgumentOutOfRangeException("bytes should be an array with a length of 16"); + + Raw = new BigInteger(bytes); + String = StringFromBytes(bytes); + } + public IPv6Address(IEnumerable enumerable) : this(enumerable.ToArray()) + { + } + private static string StringFromBytes(byte[] bytes) + { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.Length; i += 2) + { + sb.Append($"{bytes[i]:x2}{bytes[i + 1]:x2}"); + if (i < 14) + sb.Append(':'); + } + var str = sb.ToString(); + while (str.Contains(":0")) + str = str.Replace(":0", ":"); + while (str.Contains(":::")) + str = str.Replace(":::", "::"); + return str; + } + private static string ExpandIPv6Address(string ipv6Address) + { + if (ipv6Address == "::") return "0000:0000:0000:0000:0000:0000:0000:0000"; + + string[] parts = ipv6Address.Split(new string[] { "::" }, StringSplitOptions.None); + string[] leftParts = parts[0].Split(':'); + string[] rightParts = parts.Length > 1 ? parts[1].Split(':') : new string[0]; + + if (string.IsNullOrWhiteSpace(leftParts[0])) + leftParts = leftParts.Skip(1).ToArray(); + if (rightParts.Length != 0 && string.IsNullOrWhiteSpace(rightParts.Last())) + rightParts = rightParts.Take(rightParts.Length - 1).ToArray(); + + int numZeroesToInsert = 8 - (leftParts.Length + rightParts.Length); + + string[] expandedAddress = new string[8]; + Array.Copy(leftParts, expandedAddress, leftParts.Length); + for (int i = leftParts.Length; i < leftParts.Length + numZeroesToInsert; i++) + { + expandedAddress[i] = "0000"; + } + Array.Copy(rightParts, 0, expandedAddress, leftParts.Length + numZeroesToInsert, rightParts.Length); + for (int i = 0; i < expandedAddress.Length; i++) + { + switch (expandedAddress[i].Length) + { + case 1: + expandedAddress[i] = "000" + expandedAddress[i]; + break; + case 2: + expandedAddress[i] = "00" + expandedAddress[i]; + break; + case 3: + expandedAddress[i] = "0" + expandedAddress[i]; + break; + default: + break; + } + } + + return string.Join(":", expandedAddress); + } + + + public static implicit operator IPAddress(IPv6Address address) + { + byte[] bytes = new byte[16]; + var bigBytes = address.Raw.ToByteArray(); + Array.Copy(bigBytes, bytes, bigBytes.Length); + return new IPAddress(bytes); + } + public static implicit operator IPv6Address(IPAddress ip) + { + if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) + return new IPv6Address(ip.GetAddressBytes()); + throw new ArgumentException($"{ip} is not a Valid IPv4 and cant be converted"); + } + public static implicit operator BigInteger(IPv6Address address) + { + return address.Raw; + } + public static implicit operator IPv6Address(BigInteger bigInteger) + { + return new IPv6Address(bigInteger); + } + public static implicit operator byte[](IPv6Address address) + { + byte[] bytes = new byte[16]; + var bigBytes = address.Raw.ToByteArray(); + Array.Copy(bigBytes, bytes, bigBytes.Length); + return bytes; + } + public static implicit operator IPv6Address(byte[] bytes) + { + return new IPv6Address(bytes); + } + public override string ToString() + { + return String; + } + + public static bool operator ==(IPv6Address a, IPv6Address b) + { + return a.Equals(b); + } + + public static bool operator !=(IPv6Address a, IPv6Address b) + { + return !a.Equals(b); + } + + public bool Equals(IPv6Address other) + { + if (this.Raw != other.Raw) + return false; + + return true; + } + + public override bool Equals(object obj) + { + return obj is IPv6Address other && + Raw == other.Raw; + } + + public override int GetHashCode() + { + int hashCode = 1916557166; + hashCode = hashCode * -1521134295 + Raw.GetHashCode(); + return hashCode; + } + } +} \ No newline at end of file diff --git a/WellKnownDataTypes/WellKnownDataTypes.csproj b/WellKnownDataTypes/WellKnownDataTypes.csproj index 136267f..fe54cb7 100644 --- a/WellKnownDataTypes/WellKnownDataTypes.csproj +++ b/WellKnownDataTypes/WellKnownDataTypes.csproj @@ -2,7 +2,7 @@ netstandard2.0;net6.0;net7.0;net8.0 LICENSE - 0.0.2 + 0.0.3 https://github.com/DMXControl/WellKnownDataTypes $(RepositoryUrl) README.md