2009-02-06 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / System / System.Net / IPv6Address.cs
index 96ed983f7ddc8c883145e0e51e9daaea880f8a17..7e2e4ddcc36946232bd5f33e2dd978f26f60c96d 100644 (file)
 //           IPv6Address in namespace System.\r
 //\r
 \r
+//\r
+// Permission is hereby granted, free of charge, to any person obtaining\r
+// a copy of this software and associated documentation files (the\r
+// "Software"), to deal in the Software without restriction, including\r
+// without limitation the rights to use, copy, modify, merge, publish,\r
+// distribute, sublicense, and/or sell copies of the Software, and to\r
+// permit persons to whom the Software is furnished to do so, subject to\r
+// the following conditions:\r
+// \r
+// The above copyright notice and this permission notice shall be\r
+// included in all copies or substantial portions of the Software.\r
+// \r
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\r
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\r
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+//\r
+\r
 \r
 using System;\r
+using System.Globalization;\r
 using System.Net.Sockets;\r
 using System.Runtime.InteropServices;\r
 using System.Text;\r
@@ -24,17 +46,14 @@ namespace System.Net {
        ///   See RFC 2373 for more info on IPv6 addresses.\r
        /// </remarks>\r
        [Serializable]\r
-       public class IPv6Address {\r
+       internal class IPv6Address {\r
                private ushort [] address;\r
                private int prefixLength;\r
+               private long scopeId = 0;\r
 \r
                public static readonly IPv6Address Loopback = IPv6Address.Parse ("::1");\r
                public static readonly IPv6Address Unspecified = IPv6Address.Parse ("::");\r
 \r
-               /// <summary>\r
-               ///   Constructor from a 32-bit constant with its bytes \r
-               ///   in network order.\r
-               /// </summary>\r
                public IPv6Address (ushort [] addr)\r
                {\r
                        if (addr == null)\r
@@ -49,124 +68,211 @@ namespace System.Net {
                        if (prefixLength < 0 || prefixLength > 128)\r
                                throw new ArgumentException ("prefixLength");\r
                        this.prefixLength = prefixLength;\r
-               }               \r
-               \r
+               }\r
+       \r
+               public IPv6Address (ushort [] addr, int prefixLength, int scopeId) : this (addr, prefixLength)\r
+               {\r
+                       this.scopeId = scopeId;\r
+               }\r
+\r
                public static IPv6Address Parse (string ipString)\r
                {\r
                        if (ipString == null)\r
                                throw new ArgumentNullException ("ipString");\r
 \r
+                       IPv6Address result;\r
+                       if (TryParse (ipString, out result))\r
+                               return result;\r
+                       throw new FormatException ("Not a valid IPv6 address");\r
+               }\r
+\r
+               static int Fill (ushort [] addr, string ipString)\r
+               {\r
+                       int p = 0;\r
+                       int slot = 0;\r
+\r
+                       if (ipString.Length == 0)\r
+                               return 0;\r
+                       \r
+                       // Catch double uses of ::\r
+                       if (ipString.IndexOf ("::") != -1)\r
+                               return -1;\r
+\r
+                       for (int i = 0; i < ipString.Length; i++){\r
+                               char c = ipString [i];\r
+                               int n;\r
+\r
+                               if (c == ':'){\r
+                                       // Trailing : is not allowed.\r
+                                       if (i == ipString.Length-1)\r
+                                               return -1;\r
+                                       \r
+                                       if (slot == 8)\r
+                                               return -1;\r
+                                       \r
+                                       addr [slot++] = (ushort) p;\r
+                                       p = 0;\r
+                                       continue;\r
+                               } if ('0' <= c && c <= '9')\r
+                                       n = (int) (c - '0');\r
+                               else if ('a' <= c && c <= 'f')\r
+                                       n = (int) (c - 'a' + 10);\r
+                               else if ('A' <= c && c <= 'F')\r
+                                       n = (int) (c - 'A' + 10);\r
+                               else \r
+                                       return -1;\r
+                               p = (p << 4) + n;\r
+                               if (p > UInt16.MaxValue)\r
+                                       return -1;\r
+                       }\r
+\r
+                       if (slot == 8)\r
+                               return -1;\r
+                       \r
+                       addr [slot++] = (ushort) p;\r
+\r
+                       return slot;\r
+               }\r
+\r
+               static bool TryParse (string prefix, out int res)\r
+               {\r
+#if NET_2_0\r
+                       return Int32.TryParse (prefix, NumberStyles.Integer, CultureInfo.InvariantCulture, out res);\r
+#else\r
+                       try {\r
+                               res = Int32.Parse (prefix, NumberStyles.Integer, CultureInfo.InvariantCulture);\r
+                       } catch (Exception) {\r
+                               res = -1;\r
+                               return false;\r
+                       }\r
+\r
+                       return true;\r
+#endif\r
+               }\r
+               \r
+               public static bool TryParse (string ipString, out IPv6Address result)\r
+               {\r
+                       result = null;\r
+                       if (ipString == null)\r
+                               return false;\r
+\r
                        if (ipString.Length > 2 && \r
                            ipString [0] == '[' && \r
                            ipString [ipString.Length - 1] == ']')\r
-                               ipString = ipString.Substring (1, ipString.Length - 2);                         \r
+                               ipString = ipString.Substring (1, ipString.Length - 2);\r
 \r
                        if (ipString.Length  < 2)\r
-                               throw new FormatException ("Not a valid IPv6 address");\r
+                               return false;\r
 \r
                        int prefixLen = 0;\r
+                       int scopeId = 0;\r
                        int pos = ipString.LastIndexOf ('/');\r
                        if (pos != -1) {\r
                                string prefix = ipString.Substring (pos + 1);\r
-                               try {\r
-                                       prefixLen = Int32.Parse (prefix);\r
-                               } catch (Exception) {\r
+                               if (!TryParse (prefix , out prefixLen))\r
                                        prefixLen = -1;\r
-                               }\r
                                if (prefixLen < 0 || prefixLen > 128)\r
-                                       throw new FormatException ("Not a valid prefix length");;\r
+                                       return false;\r
                                ipString = ipString.Substring (0, pos);\r
+                       } else {\r
+                               pos = ipString.LastIndexOf ('%');\r
+                               if (pos != -1) {\r
+                                       string prefix = ipString.Substring (pos + 1);\r
+                                       if (!TryParse (prefix, out scopeId))\r
+                                               scopeId = 0;\r
+                                       ipString = ipString.Substring (0, pos);\r
+                               }                       \r
                        }\r
-                       \r
-                       ushort [] addr = new ushort [8];                        \r
-                       \r
+\r
+                       //\r
+                       // At this point the prefix/suffixes have been removed\r
+                       // and we only have to deal with the ipv4 or ipv6 addressed\r
+                       //\r
+                       ushort [] addr = new ushort [8];\r
+\r
+                       //\r
+                       // Is there an ipv4 address at the end?\r
+                       //\r
                        bool ipv4 = false;\r
-                       int pos2 = ipString.LastIndexOf (":");\r
+                       int pos2 = ipString.LastIndexOf (':');\r
                        if (pos2 == -1)\r
-                               throw new FormatException ("Not a valid IPv6 address");\r
+                               return false;\r
+\r
+                       int slots = 0;\r
                        if (pos2 < (ipString.Length - 1)) {\r
                                string ipv4Str = ipString.Substring (pos2 + 1);\r
                                if (ipv4Str.IndexOf ('.') != -1) {\r
-                                       try {\r
-                                               long a = IPAddress.Parse (ipv4Str).Address;\r
-                                               addr [6] = (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff)));\r
-                                               addr [7] = (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff)));\r
-                                               if (ipString [pos2 - 1] == ':') \r
-                                                       ipString = ipString.Substring (0, pos2 + 1);\r
-                                               else\r
-                                                       ipString = ipString.Substring (0, pos2);\r
-                                               ipv4 = true;\r
-                                       } catch (Exception) {\r
-                                               throw new FormatException ("Not a valid IPv6 address");         \r
-                                       }\r
+                                       IPAddress ip;\r
+                                       \r
+                                       if (!IPAddress.TryParse (ipv4Str, out ip))\r
+                                               return false;\r
+                                       \r
+                                       long a = ip.InternalIPv4Address;\r
+                                       addr [6] = (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff)));\r
+                                       addr [7] = (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff)));\r
+                                       if (pos2 > 0 && ipString [pos2 - 1] == ':') \r
+                                               ipString = ipString.Substring (0, pos2 + 1);\r
+                                       else\r
+                                               ipString = ipString.Substring (0, pos2);\r
+                                       ipv4 = true;\r
+                                       slots = 2;\r
                                }\r
                        }       \r
-                       \r
-                       int origLen = ipString.Length;\r
-                       if (origLen < 2)\r
-                               throw new FormatException ("Not a valid IPv6 address");                 \r
-                       ipString = ipString.Replace ("::", ":!:");\r
-                       int len = ipString.Length;\r
-                       if ((len - origLen) > 1) \r
-                               throw new FormatException ("Not a valid IPv6 address");\r
-                       \r
-                       if (ipString [1] == '!') \r
-                               ipString = ipString.Remove (0, 1);\r
-                       if (ipString [len - 2] == '!')\r
-                               ipString = ipString.Remove (len - 1, 1);\r
-                       if ((ipString.Length > 2) && \r
-                           ((ipString [0] == ':') || (ipString [ipString.Length - 1] == ':'))) \r
-                               throw new FormatException ("Not a valid IPv6 address");\r
-                               \r
-                       string [] pieces = ipString.Split (new char [] {':'});\r
-                       len = pieces.Length;\r
-                       if (len > (ipv4 ? 6 : 8)) \r
-                               throw new FormatException ("Not a valid IPv6 address");\r
-                       int piecedouble = -1;\r
-                       for (int i = 0; i < len; i++) {\r
-                               string piece = pieces [i];\r
-                               if (piece == "!")\r
-                                       piecedouble = i;\r
-                               else {\r
-                                       int plen = piece.Length;\r
-                                       if (plen > 4)\r
-                                               throw new FormatException ("Not a valid IPv6 address");\r
-                                       int p = 0;\r
-                                       for (int j = 0; j < plen; j++) \r
-                                               try {\r
-                                                       p = (p << 4) + Uri.FromHex (piece [j]);\r
-                                               } catch (ArgumentException) {\r
-                                                       throw new FormatException ("Not a valid IPv6 address");\r
-                                               }\r
-                                       addr [i] = (ushort) p;\r
+\r
+                       //\r
+                       // Only an ipv6 block remains, either:\r
+                       // "hexnumbers::hexnumbers", "hexnumbers::" or "hexnumbers"\r
+                       //\r
+                       int c = ipString.IndexOf ("::");\r
+                       if (c != -1){\r
+                               int right_slots = Fill (addr, ipString.Substring (c+2));\r
+                               if (right_slots == -1){\r
+                                       return false;\r
                                }\r
-                       }\r
 \r
-                       //expand the :: token\r
-                       if (piecedouble != -1) {\r
-                               int totallen = (ipv4 ? 5 : 7);\r
-                               int i = totallen;\r
-                               for (i = totallen; i >= (totallen - (len - piecedouble - 1)); i--) {\r
-                                       addr [i] = addr [(len - 1) + i - totallen];\r
+                               if (right_slots + slots > 8){\r
+                                       return false;\r
                                }\r
-                               for (; i >= piecedouble; i--) {\r
-                                       addr [i] = 0;\r
+\r
+                               int d = 8-slots-right_slots;\r
+                               for (int i = right_slots; i > 0; i--){\r
+                                       addr [i+d-1] = addr [i-1];\r
+                                       addr [i-1] = 0;\r
                                }\r
-                       } else if (len != (ipv4 ? 6 : 8)) \r
-                               throw new FormatException ("Not a valid IPv6 address");\r
+                               \r
+                               int left_slots = Fill (addr, ipString.Substring (0, c));\r
+                               if (left_slots == -1)\r
+                                       return false;\r
 \r
+                               if (left_slots + right_slots + slots > 7)\r
+                                       return false;\r
+                       } else {\r
+                               if (Fill (addr, ipString) != 8-slots)\r
+                                       return false;\r
+                       }\r
+\r
+                       // Now check the results in the ipv6-address range only\r
+                       bool ipv6 = false;\r
+                       for (int i = 0; i < slots; i++){\r
+                               if (addr [i] != 0 || i == 5 && addr [i] != 0xffff)\r
+                                       ipv6 = true;\r
+                       }\r
+                       \r
                        // check IPv4 validity\r
-                       if (ipv4) {\r
-                               for (int i = 0; i < 5; i++) \r
+                       if (ipv4 && !ipv6) {\r
+                               for (int i = 0; i < 5; i++) {\r
                                        if (addr [i] != 0)\r
-                                               throw new FormatException ("Not a valid IPv6 address");\r
+                                               return false;\r
+                               }\r
+\r
                                if (addr [5] != 0 && addr [5] != 0xffff)\r
-                                       throw new FormatException ("Not a valid IPv6 address");\r
+                                       return false;\r
                        }\r
-                       \r
-                       return new IPv6Address (addr, prefixLen);\r
+\r
+                       result = new IPv6Address (addr, prefixLen, scopeId);\r
+                       return true;\r
                }\r
-               \r
+\r
                public ushort [] Address {\r
                        get { return address; }\r
                }\r
@@ -175,6 +281,15 @@ namespace System.Net {
                        get { return this.prefixLength; }\r
                }\r
                \r
+               public long ScopeId {\r
+                       get {\r
+                               return scopeId;\r
+                       }\r
+                       set {\r
+                               scopeId = value;\r
+                       }\r
+               }\r
+\r
                public ushort this [int index] {\r
                        get { return address [index]; }\r
                }               \r
@@ -182,30 +297,44 @@ namespace System.Net {
                public AddressFamily AddressFamily {\r
                        get { return AddressFamily.InterNetworkV6; }\r
                }\r
-               \r
-               /// <summary>\r
-               ///   Used to tell whether the given address is the loopback address.\r
-               /// </summary>\r
+\r
                public static bool IsLoopback (IPv6Address addr)\r
                {\r
-                       for (int i = 0; i < 4; i++)\r
+                       if (addr.address [7] != 1)\r
+                               return false;\r
+\r
+                       int x = addr.address [6] >> 8;\r
+                       if (x != 0x7f && x != 0)\r
+                               return false;\r
+\r
+                       for (int i = 0; i < 4; i++) {\r
                                if (addr.address [i] != 0)\r
                                        return false;\r
-                       if ((addr.address [5] != 0) && (addr.address [5] != 0xffff))\r
+                       }\r
+\r
+                       if (addr.address [5] != 0 && addr.address [5] != 0xffff)\r
                                return false;\r
-                       if ((addr.address [6] >> 8) == 0x7f)\r
-                               return true;\r
-                       return ((addr.address [5] == 0) && \r
-                               (addr.address [6] == 0) && \r
-                               (addr.address [7] == 1));\r
+\r
+                       return true;\r
                }\r
-               \r
+\r
+               private static ushort SwapUShort (ushort number)\r
+               {\r
+                       return (ushort) ( ((number >> 8) & 0xFF) + ((number << 8) & 0xFF00) );\r
+               }\r
+\r
+               // Convert the address into a format expected by the IPAddress (long) ctor\r
+               private int AsIPv4Int ()\r
+               {\r
+                       return (SwapUShort (address [7]) << 16) + SwapUShort (address [6]);\r
+               }                       \r
+\r
                public bool IsIPv4Compatible ()\r
                {\r
                        for (int i = 0; i < 6; i++) \r
                                if (address [i] != 0)\r
-                                       return false;                   \r
-                       return true;\r
+                                       return false;\r
+                       return (AsIPv4Int () > 1);\r
                }\r
                \r
                public bool IsIPv4Mapped ()\r
@@ -223,12 +352,81 @@ namespace System.Net {
                public override string ToString ()\r
                {\r
                        StringBuilder s = new StringBuilder ();\r
-                       for (int i = 0; i < 7; i++)\r
-                               s.Append (String.Format ("{0:X4}", address [i])).Append (':');\r
-                       s.Append (String.Format ("{0:X4}", address [7]));\r
+\r
+\r
+                       if(IsIPv4Compatible() || IsIPv4Mapped())\r
+                       {\r
+                               s.Append("::");\r
+\r
+                               if(IsIPv4Mapped())\r
+                                       s.Append("ffff:");\r
+\r
+                               s.Append(new IPAddress( AsIPv4Int ()).ToString ());\r
+\r
+                               return s.ToString ();\r
+                       }\r
+                       else\r
+                       {\r
+                               int bestChStart = -1; // Best chain start\r
+                               int bestChLen = 0; // Best chain length\r
+                               int currChLen = 0; // Current chain length\r
+\r
+                               // Looks for the longest zero chain\r
+                               for (int i=0; i<8; i++)\r
+                               {\r
+                                       if (address[i] != 0)\r
+                                       {\r
+                                               if ((currChLen > bestChLen) \r
+                                                       && (currChLen > 1))\r
+                                               {\r
+                                                       bestChLen = currChLen;\r
+                                                       bestChStart = i - currChLen;\r
+                                               }\r
+                                               currChLen = 0;\r
+                                       }\r
+                                       else\r
+                                               currChLen++;\r
+                               }\r
+                               if ((currChLen > bestChLen) \r
+                                       && (currChLen > 1))\r
+                               {\r
+                                       bestChLen = currChLen;\r
+                                       bestChStart = 8 - currChLen;\r
+                               }\r
+\r
+                               // makes the string\r
+                               if (bestChStart == 0)\r
+                                       s.Append(":");\r
+                               for (int i=0; i<8; i++)\r
+                               {\r
+                                       if (i == bestChStart)\r
+                                       {\r
+                                               s.Append (":");\r
+                                               i += (bestChLen - 1);\r
+                                               continue;\r
+                                       }\r
+                                       s.AppendFormat("{0:x}", address [i]);\r
+                                       if (i < 7) s.Append (':');\r
+                               }\r
+                       }\r
+                       if (scopeId != 0)\r
+                               s.Append ('%').Append (scopeId);\r
                        return s.ToString ();\r
                }\r
 \r
+               public string ToString (bool fullLength)\r
+               {\r
+                       if (!fullLength)\r
+                               return ToString ();\r
+\r
+                       StringBuilder sb = new StringBuilder ();\r
+                       for (int i=0; i < address.Length - 1; i++) {\r
+                               sb.AppendFormat ("{0:X4}:", address [i]);\r
+                       }\r
+                       sb.AppendFormat ("{0:X4}", address [address.Length - 1]);\r
+                       return sb.ToString ();\r
+               }\r
+\r
                /// <returns>\r
                ///   Whether both objects are equal.\r
                /// </returns>\r
@@ -239,23 +437,23 @@ namespace System.Net {
                                for (int i = 0; i < 8; i++) \r
                                        if (this.address [i] != ipv6.address [i])\r
                                                return false;\r
-                               return true;                            \r
-                       } \r
+                               return true;\r
+                       }\r
                        \r
                        System.Net.IPAddress ipv4 = other as System.Net.IPAddress;\r
                        if (ipv4 != null) {\r
                                for (int i = 0; i < 5; i++) \r
                                        if (address [i] != 0)\r
-                                               return false;                   \r
+                                               return false;\r
 \r
                                if (address [5] != 0 && address [5] != 0xffff)\r
                                        return false;\r
 \r
-                               long a = ipv4.Address;\r
+                               long a = ipv4.InternalIPv4Address;\r
                                if (address [6] != (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff))) ||\r
                                    address [7] != (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff))))\r
-                                       return false;\r
-                                       \r
+                                       return false;\r
+\r
                                return true;\r
                        }\r
                        \r
@@ -265,14 +463,14 @@ namespace System.Net {
                public override int GetHashCode ()\r
                {\r
                        return Hash (((((int) address [0]) << 16) + address [1]), \r
-                                    ((((int) address [2]) << 16) + address [3]),\r
-                                    ((((int) address [4]) << 16) + address [5]),\r
-                                    ((((int) address [6]) << 16) + address [7]));\r
+                                               ((((int) address [2]) << 16) + address [3]),\r
+                                               ((((int) address [4]) << 16) + address [5]),\r
+                                               ((((int) address [6]) << 16) + address [7]));\r
                }\r
                \r
-               private static int Hash (int i, int j, int k, int l) \r
-               {\r
-                       return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25);\r
-               }\r
+               private static int Hash (int i, int j, int k, int l) \r
+               {\r
+                       return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25);\r
+               }\r
        }\r
 }\r