//
// System.Net.IPv6Address.cs
//
// Author:
// Lawrence Pit (loz@cable.a2000.nl)
//
// Note I: This class is not defined in the specs of .Net
//
// Note II : The name of this class is perhaps unfortunate as it turns
// out that in ms.net there's an internal class called
// IPv6Address in namespace System.
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
namespace System.Net {
///
/// Encapsulates an IPv6 Address.
/// See RFC 2373 for more info on IPv6 addresses.
///
[Serializable]
internal class IPv6Address {
private ushort [] address;
private int prefixLength;
private long scopeId = 0;
public static readonly IPv6Address Loopback = IPv6Address.Parse ("::1");
public static readonly IPv6Address Unspecified = IPv6Address.Parse ("::");
///
/// Constructor from a 32-bit constant with its bytes
/// in network order.
///
public IPv6Address (ushort [] addr)
{
if (addr == null)
throw new ArgumentNullException ("addr");
if (addr.Length != 8)
throw new ArgumentException ("addr");
address = addr;
}
public IPv6Address (ushort [] addr, int prefixLength) : this (addr)
{
if (prefixLength < 0 || prefixLength > 128)
throw new ArgumentException ("prefixLength");
this.prefixLength = prefixLength;
}
public IPv6Address (ushort [] addr, int prefixLength, int scopeId) : this (addr, prefixLength)
{
this.scopeId = scopeId;
}
public static IPv6Address Parse (string ipString)
{
if (ipString == null)
throw new ArgumentNullException ("ipString");
if (ipString.Length > 2 &&
ipString [0] == '[' &&
ipString [ipString.Length - 1] == ']')
ipString = ipString.Substring (1, ipString.Length - 2);
if (ipString.Length < 2)
throw new FormatException ("Not a valid IPv6 address");
int prefixLen = 0;
int scopeId = 0;
int pos = ipString.LastIndexOf ('/');
if (pos != -1) {
string prefix = ipString.Substring (pos + 1);
try {
prefixLen = Int32.Parse (prefix);
} catch (Exception) {
prefixLen = -1;
}
if (prefixLen < 0 || prefixLen > 128)
throw new FormatException ("Not a valid prefix length");
ipString = ipString.Substring (0, pos);
} else {
pos = ipString.LastIndexOf ('%');
if (pos != -1) {
string prefix = ipString.Substring (pos + 1);
try {
scopeId = Int32.Parse (prefix);
}
catch (Exception) {
scopeId = 0;
}
ipString = ipString.Substring (0, pos);
}
}
ushort [] addr = new ushort [8];
bool ipv4 = false;
int pos2 = ipString.LastIndexOf (":");
if (pos2 == -1)
throw new FormatException ("Not a valid IPv6 address");
if (pos2 < (ipString.Length - 1)) {
string ipv4Str = ipString.Substring (pos2 + 1);
if (ipv4Str.IndexOf ('.') != -1) {
try {
long a = IPAddress.Parse (ipv4Str).InternalIPv4Address;
addr [6] = (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff)));
addr [7] = (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff)));
if (ipString [pos2 - 1] == ':')
ipString = ipString.Substring (0, pos2 + 1);
else
ipString = ipString.Substring (0, pos2);
ipv4 = true;
} catch (Exception) {
throw new FormatException ("Not a valid IPv6 address");
}
}
}
int origLen = ipString.Length;
if (origLen < 2)
throw new FormatException ("Not a valid IPv6 address");
ipString = ipString.Replace ("::", ":!:");
int len = ipString.Length;
if ((len - origLen) > 1)
throw new FormatException ("Not a valid IPv6 address");
if (ipString [1] == '!')
ipString = ipString.Remove (0, 1);
if (ipString [len - 2] == '!')
ipString = ipString.Remove (len - 1, 1);
if ((ipString.Length > 2) &&
((ipString [0] == ':') || (ipString [ipString.Length - 1] == ':')))
throw new FormatException ("Not a valid IPv6 address");
string [] pieces = ipString.Split (new char [] {':'});
len = pieces.Length;
if (len > (ipv4 ? 6 : 8))
throw new FormatException ("Not a valid IPv6 address");
int piecedouble = -1;
for (int i = 0; i < len; i++) {
string piece = pieces [i];
if (piece == "!")
piecedouble = i;
else {
int plen = piece.Length;
if (plen > 4)
throw new FormatException ("Not a valid IPv6 address");
int p = 0;
for (int j = 0; j < plen; j++)
try {
p = (p << 4) + Uri.FromHex (piece [j]);
} catch (ArgumentException) {
throw new FormatException ("Not a valid IPv6 address");
}
addr [i] = (ushort) p;
}
}
//expand the :: token
if (piecedouble != -1) {
int totallen = (ipv4 ? 5 : 7);
int i = totallen;
for (i = totallen; i >= (totallen - (len - piecedouble - 1)); i--) {
addr [i] = addr [(len - 1) + i - totallen];
}
for (; i >= piecedouble; i--) {
addr [i] = 0;
}
} else if (len != (ipv4 ? 6 : 8))
throw new FormatException ("Not a valid IPv6 address");
// check IPv4 validity
if (ipv4) {
for (int i = 0; i < 5; i++)
if (addr [i] != 0)
throw new FormatException ("Not a valid IPv6 address");
if (addr [5] != 0 && addr [5] != 0xffff)
throw new FormatException ("Not a valid IPv6 address");
}
return new IPv6Address (addr, prefixLen, scopeId);
}
public ushort [] Address {
get { return address; }
}
public int PrefixLength {
get { return this.prefixLength; }
}
public long ScopeId {
get {
return scopeId;
}
set {
scopeId = value;
}
}
public ushort this [int index] {
get { return address [index]; }
}
public AddressFamily AddressFamily {
get { return AddressFamily.InterNetworkV6; }
}
///
/// Used to tell whether the given address is the loopback address.
///
public static bool IsLoopback (IPv6Address addr)
{
for (int i = 0; i < 4; i++)
if (addr.address [i] != 0)
return false;
if ((addr.address [5] != 0) && (addr.address [5] != 0xffff))
return false;
if ((addr.address [6] >> 8) == 0x7f)
return true;
return ((addr.address [5] == 0) &&
(addr.address [6] == 0) &&
(addr.address [7] == 1));
}
public bool IsIPv4Compatible ()
{
for (int i = 0; i < 6; i++)
if (address [i] != 0)
return false;
return ( (IPAddress.NetworkToHostOrder(address[7]) << 16) | IPAddress.NetworkToHostOrder(address[6])) > 1;
}
public bool IsIPv4Mapped ()
{
for (int i = 0; i < 5; i++)
if (address [i] != 0)
return false;
return address [5] == 0xffff;
}
///
/// Overrides System.Object.ToString to return
/// this object rendered in a canonicalized notation
///
public override string ToString ()
{
StringBuilder s = new StringBuilder ();
if(IsIPv4Compatible() || IsIPv4Mapped())
{
s.Append("::");
if(IsIPv4Mapped())
s.Append("ffff:");
s.Append(new IPAddress( IPAddress.NetworkToHostOrder(address[6]<<16) + IPAddress.NetworkToHostOrder(address[7])).ToString());
return s.ToString ();
}
else
{
int bestChStart = -1; // Best chain start
int bestChLen = 0; // Best chain length
int currChLen = 0; // Current chain length
// Looks for the longest zero chain
for (int i=0; i<8; i++)
{
if (address[i] != 0)
{
if ((currChLen > bestChLen)
&& (currChLen > 1))
{
bestChLen = currChLen;
bestChStart = i - currChLen;
}
currChLen = 0;
}
else
currChLen++;
}
if ((currChLen > bestChLen)
&& (currChLen > 1))
{
bestChLen = currChLen;
bestChStart = 8 - currChLen;
}
// makes the string
if (bestChStart == 0)
s.Append(":");
for (int i=0; i<8; i++)
{
if (i == bestChStart)
{
s.Append (":");
i += (bestChLen - 1);
continue;
}
s.AppendFormat("{0:x}", address [i]);
if (i < 7) s.Append (':');
}
}
return s.ToString ();
}
///
/// Whether both objects are equal.
///
public override bool Equals (object other)
{
System.Net.IPv6Address ipv6 = other as System.Net.IPv6Address;
if (ipv6 != null) {
for (int i = 0; i < 8; i++)
if (this.address [i] != ipv6.address [i])
return false;
return true;
}
System.Net.IPAddress ipv4 = other as System.Net.IPAddress;
if (ipv4 != null) {
for (int i = 0; i < 5; i++)
if (address [i] != 0)
return false;
if (address [5] != 0 && address [5] != 0xffff)
return false;
long a = ipv4.InternalIPv4Address;
if (address [6] != (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff))) ||
address [7] != (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff))))
return false;
return true;
}
return false;
}
public override int GetHashCode ()
{
return Hash (((((int) address [0]) << 16) + address [1]),
((((int) address [2]) << 16) + address [3]),
((((int) address [4]) << 16) + address [5]),
((((int) address [6]) << 16) + address [7]));
}
private static int Hash (int i, int j, int k, int l)
{
return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25);
}
}
}