ln.type/IPv6.cs

336 lines
8.6 KiB
C#

using System;
using System.Linq;
using System.Collections.Generic;
using ln.type.arithmetics;
using System.Net;
namespace ln.type
{
public class IPv6
{
public static readonly IPv6 ANY = new IPv6(new byte[17]);
public static readonly IPv6 V4Space = Parse("::ffff:0:0/96");
public static readonly IPv6 Loopback = Parse("::1");
readonly UInt16[] value;
readonly UInt32 mask;
public uint Netmask => mask;
public IPv6()
{
value = new UInt16[8];
mask = 0;
}
public IPv6(uint netmask)
{
value = new UInt16[8];
mask = netmask;
}
public IPv6(IPv6 ip, uint netmask) : this(ip.value, netmask) { }
public IPv6(ushort[] words, uint netmask)
{
if (words.Length > 8)
throw new ArgumentOutOfRangeException(nameof(words));
value = words.Slice(0, 8);
mask = netmask;
}
public IPv6(byte[] bytes,uint netmask)
:this(bytes)
{
mask = netmask;
}
public IPv6(byte[] bytes)
:this()
{
if (bytes.Length == 16)
{
for (int n = 0; n < 8; n++)
{
value[n] = (ushort)((bytes[(n << 1) + 0] << 8) | bytes[(n << 1) + 1]);
}
mask = 128;
}
else if (bytes.Length == 17)
{
for (int n = 0; n < 8; n++)
{
value[n] = (ushort)((bytes[(n << 1) + 0] << 8) | bytes[(n << 1) + 1]);
}
mask = bytes[16];
}
else if (bytes.Length == 4)
{
value[5] = 0xffff;
value[6] = (ushort)((bytes[0] << 8) | bytes[1]);
value[7] = (ushort)((bytes[2] << 8) | bytes[3]);
mask = 96;
} else
throw new ArgumentOutOfRangeException(nameof(bytes));
}
public IPv6 Network
{
get
{
if (mask == 128)
return this;
ushort[] address = value.Slice(0);
uint steps = mask >> 4;
uint shift = mask & 0x0F;
address[steps] &= (ushort)(0xFFFF << (int)(16 - shift));
for (uint n = steps+1; n < 8; n++)
address[n] = 0;
return new IPv6(address, mask);
}
}
public bool Contains(IPv6 ip)
{
if (ip.mask < mask)
return false;
IPv6 ipNetwork = new IPv6(ip, mask).Network;
return Network.Equals(ipNetwork);
}
public byte[] ToBytes()
{
byte[] bytes = new byte[16];
for (int n = 0; n < 8; n++)
{
bytes[(n << 1) + 1] = (byte)((value[n] >> 0) & 0xff);
bytes[(n << 1) + 0] = (byte)((value[n] >> 8) & 0xff);
}
return bytes;
}
public byte[] ToCIDRBytes()
{
byte[] bytes = new byte[17];
for (int n = 0; n < 8; n++)
{
bytes[(n << 1) + 1] = (byte)((value[n] >> 0) & 0xff);
bytes[(n << 1) + 0] = (byte)((value[n] >> 8) & 0xff);
}
bytes[16] = mask.GetBytes()[0];
return bytes;
}
public byte[] ToPackedBytes()
{
byte[] bytes = ToBytes();
if (V4Space.Contains(this))
return bytes.Slice(12);
return bytes;
}
public IEnumerable<IPv6> Split(int splitWidth)
{
List<IPv6> splitted = new List<IPv6>();
if (128 < (mask + splitWidth))
throw new ArgumentOutOfRangeException(nameof(splitWidth),"cannot split to negative host identfier length");
IPv6 ip = new IPv6(value, (uint)(mask + splitWidth));
ushort[] increment = Words.SHL(new ushort[] { 1, 0, 0, 0, 0, 0, 0, 0 }, (int)(128 - ip.mask));
for (int n=0;n<(1<<splitWidth);n++)
{
splitted.Add(ip);
ip = new IPv6(Words.Add(ip.value.BigEndian(), increment).BigEndian(), ip.mask);
}
return splitted;
}
public string ToString(bool shorten)
{
if (!shorten)
{
return string.Join(":", value.Select((w) => String.Format("{0:x4}", w)));
}
else if (V4Space.Contains(this))
{
return string.Format("::ffff:{0}.{1}.{2}.{3}",
((value[6] >> 8) & 0xFF),
((value[6] >> 0) & 0xFF),
((value[7] >> 8) & 0xFF),
((value[7] >> 0) & 0xFF)
);
}
else
{
int zerolen = 0;
int zeropos = -1;
for (int n=0;n<8;n++)
{
if (value[n]==0)
{
int m;
for (m = 1; m < (8 - n); m++)
{
if (value[n + m] != 0)
break;
}
if (m > zerolen)
{
zerolen = m;
zeropos = n;
}
}
}
List<string> words = new List<string>();
for (int n = 0; n < 8; n++)
{
if (n == zeropos)
{
if (n == 0)
words.Add("");
if ((n + zerolen)==8)
words.Add("");
words.Add("");
n += zerolen - 1;
}
else
{
words.Add(String.Format("{0:x}", value[n]));
}
}
return string.Join(":", words);
}
}
public override string ToString() => ToString(true);
public String ToCIDR()
{
return String.Format("{0}/{1}", ToString(true), mask);
}
public static IPv6 Parse(string source)
{
int netmask = 128;
ushort[] address = new ushort[8];
int slash = source.IndexOf('/');
if (slash != -1)
{
netmask = int.Parse(source.Substring(slash + 1));
source = source.Substring(0, slash);
}
bool hasColons = source.Contains(':');
bool hasDots = source.Contains('.');
if (!hasColons && hasDots) // IPv4
{
byte[] v4bytes = source.Split('.').Select((s) => byte.Parse(s)).ToArray();
if (v4bytes.Length != 4)
throw new FormatException();
if (netmask != 128)
netmask += 96;
address[5] = 0xffff;
address[6] = v4bytes.GetUShort(true);
address[7] = v4bytes.GetUShort(2, true);
} else if (hasColons)
{
string[] words = source.Split(':');
if (string.Empty.Equals(words[0]))
words = words.Slice(1);
if (string.Empty.Equals(words[words.Length - 1]))
words = words.Slice(0, words.Length - 1);
if ((words.Length == 1) && (words[0].Equals(String.Empty)))
if (netmask == 0)
return IPv6.ANY;
if (words.Length > 8)
throw new FormatException();
if (hasDots)
{
byte[] v4bytes = words[words.Length-1].Split('.').Select((s) => byte.Parse(s)).ToArray();
if (v4bytes.Length != 4)
throw new FormatException();
address[5] = 0xffff;
address[6] = v4bytes.GetUShort(true);
address[7] = v4bytes.GetUShort(2, true);
}
else
{
int fill = 8 - words.Length;
int n = 0, m = 0;
while (n < words.Length)
{
if (String.Empty.Equals(words[n]))
{
m += fill;
}
else
{
address[m] = Convert.ToUInt16(words[n], 16);
}
m++;
n++;
}
}
}
return new IPv6(address, (uint)netmask);
}
public static IPv6 operator ++(IPv6 ip) => new IPv6(Words.Add(ip.value.BigEndian(), 1).BigEndian(), ip.mask);
public static IPv6 operator --(IPv6 ip) => new IPv6(Words.Del(ip.value.BigEndian(), 1).BigEndian(), ip.mask);
public static IPv6 operator +(IPv6 ip, int b) => new IPv6(Words.Add(ip.value.BigEndian(), b).BigEndian(), ip.mask);
public static IPv6 operator -(IPv6 ip, int b) => new IPv6(Words.Del(ip.value.BigEndian(), b).BigEndian(), ip.mask);
public static implicit operator IPAddress(IPv6 ip) => new IPAddress(ip.ToPackedBytes());
public static implicit operator IPv6(IPAddress ip) => new IPv6(ip.GetAddressBytes());
public static implicit operator IPv6(String s) => IPv6.Parse(s);
public override bool Equals(object obj)
{
if (obj is IPAddress)
obj = (IPv6)obj;
if (obj is IPv6)
{
return value.SequenceEqual((obj as IPv6).value) && Equals(mask,(obj as IPv6).mask) ;
}
return false;
}
public override int GetHashCode()
{
int hash = 0;
for (int n = 0; n < 8; n++)
hash = (hash << 4) | value[n];
return hash;
}
}
}