Skip to content

Commit

Permalink
Add IPv6
Browse files Browse the repository at this point in the history
Bump v0.0.3
  • Loading branch information
patrick-dmxc committed Jun 1, 2024
1 parent f8d8ff5 commit 984d34d
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 12 deletions.
3 changes: 1 addition & 2 deletions WellKnownDataTypes-Tests/Light/ArtNet/Address_Tests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using org.dmxc.wkdt.Light.ArtNet;
using org.dmxc.wkdt.Tests;

namespace org.dmxc.wkdt.Tests.Light.ArtNet
{
Expand Down Expand Up @@ -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<Address>(data);

Assert.That(result, Is.EqualTo(address), json);
Expand Down
2 changes: 1 addition & 1 deletion WellKnownDataTypes-Tests/Light/ArtNet/PortAddress_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PortAddress>(data);
Expand Down
100 changes: 100 additions & 0 deletions WellKnownDataTypes-Tests/Network/IPv6Address_Tests.cs
Original file line number Diff line number Diff line change
@@ -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<byte>() { 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<IPv6Address, string> dict = new ConcurrentDictionary<IPv6Address, string>();
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<IPv6Address>(data);

Assert.That(result, Is.EqualTo(ipv4Address), json);
}
}
}
8 changes: 1 addition & 7 deletions WellKnownDataTypes-Tests/Tools.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
1 change: 0 additions & 1 deletion WellKnownDataTypes-Tests/Usings.cs
Original file line number Diff line number Diff line change
@@ -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;
192 changes: 192 additions & 0 deletions WellKnownDataTypes/Network/IPv6Address.cs
Original file line number Diff line number Diff line change
@@ -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<IPv6Address>
{
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<byte> 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;
}
}
}
2 changes: 1 addition & 1 deletion WellKnownDataTypes/WellKnownDataTypes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net7.0;net8.0</TargetFrameworks>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>0.0.2</Version>
<Version>0.0.3</Version>
<RepositoryUrl>https://github.com/DMXControl/WellKnownDataTypes</RepositoryUrl>
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand Down

0 comments on commit 984d34d

Please sign in to comment.