2005-06-03 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / System / System.Net / IPv6Address.cs
1 //\r
2 // System.Net.IPv6Address.cs\r
3 //\r
4 // Author:\r
5 //   Lawrence Pit (loz@cable.a2000.nl)\r
6 //\r
7 // Note I: This class is not defined in the specs of .Net\r
8 //\r
9 // Note II : The name of this class is perhaps unfortunate as it turns\r
10 //           out that in ms.net there's an internal class called\r
11 //           IPv6Address in namespace System.\r
12 //\r
13
14 //
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
22 // 
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
25 // 
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 //
34 \r
35 \r
36 using System;\r
37 using System.Net.Sockets;\r
38 using System.Runtime.InteropServices;\r
39 using System.Text;\r
40 \r
41 namespace System.Net {\r
42 \r
43         /// <remarks>\r
44         ///   Encapsulates an IPv6 Address.\r
45         ///   See RFC 2373 for more info on IPv6 addresses.\r
46         /// </remarks>\r
47         [Serializable]\r
48         internal class IPv6Address {\r
49                 private ushort [] address;\r
50                 private int prefixLength;\r
51                 private long scopeId = 0;\r
52 \r
53                 public static readonly IPv6Address Loopback = IPv6Address.Parse ("::1");\r
54                 public static readonly IPv6Address Unspecified = IPv6Address.Parse ("::");\r
55 \r
56                 public IPv6Address (ushort [] addr)\r
57                 {\r
58                         if (addr == null)\r
59                                 throw new ArgumentNullException ("addr");       \r
60                         if (addr.Length != 8)   \r
61                                 throw new ArgumentException ("addr");\r
62                         address = addr;                 \r
63                 }\r
64                 \r
65                 public IPv6Address (ushort [] addr, int prefixLength) : this (addr)\r
66                 {\r
67                         if (prefixLength < 0 || prefixLength > 128)\r
68                                 throw new ArgumentException ("prefixLength");\r
69                         this.prefixLength = prefixLength;\r
70                 }       \r
71         \r
72                 public IPv6Address (ushort [] addr, int prefixLength, int scopeId) : this (addr, prefixLength)\r
73                 {\r
74             this.scopeId = scopeId;\r
75                 }               \r
76                 \r
77                 public static IPv6Address Parse (string ipString)\r
78                 {\r
79                         if (ipString == null)\r
80                                 throw new ArgumentNullException ("ipString");\r
81 \r
82                         if (ipString.Length > 2 && \r
83                             ipString [0] == '[' && \r
84                             ipString [ipString.Length - 1] == ']')\r
85                                 ipString = ipString.Substring (1, ipString.Length - 2);                         \r
86 \r
87                         if (ipString.Length  < 2)\r
88                                 throw new FormatException ("Not a valid IPv6 address");\r
89 \r
90                         int prefixLen = 0;\r
91                         int scopeId = 0;\r
92                         int pos = ipString.LastIndexOf ('/');\r
93                         if (pos != -1) {\r
94                                 string prefix = ipString.Substring (pos + 1);\r
95                                 try {\r
96                                         prefixLen = Int32.Parse (prefix);\r
97                                 } catch (Exception) {\r
98                                         prefixLen = -1;\r
99                                 }\r
100                                 if (prefixLen < 0 || prefixLen > 128)\r
101                                         throw new FormatException ("Not a valid prefix length");\r
102                                 ipString = ipString.Substring (0, pos);\r
103                         } else {\r
104                                 pos = ipString.LastIndexOf ('%');\r
105                                 if (pos != -1) {\r
106                                         string prefix = ipString.Substring (pos + 1);\r
107                                         try  {\r
108                                                 scopeId = Int32.Parse (prefix);\r
109                                         } \r
110                                         catch (Exception) {\r
111                                                 scopeId = 0;\r
112                                         }\r
113                                         ipString = ipString.Substring (0, pos);\r
114                                 }                       \r
115                         }\r
116                         \r
117                         ushort [] addr = new ushort [8];                        \r
118                         \r
119                         bool ipv4 = false;\r
120                         int pos2 = ipString.LastIndexOf (":");\r
121                         if (pos2 == -1)\r
122                                 throw new FormatException ("Not a valid IPv6 address");\r
123                         if (pos2 < (ipString.Length - 1)) {\r
124                                 string ipv4Str = ipString.Substring (pos2 + 1);\r
125                                 if (ipv4Str.IndexOf ('.') != -1) {\r
126                                         try {\r
127                                                 long a = IPAddress.Parse (ipv4Str).InternalIPv4Address;\r
128                                                 addr [6] = (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff)));\r
129                                                 addr [7] = (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff)));\r
130                                                 if (ipString [pos2 - 1] == ':') \r
131                                                         ipString = ipString.Substring (0, pos2 + 1);\r
132                                                 else\r
133                                                         ipString = ipString.Substring (0, pos2);\r
134                                                 ipv4 = true;\r
135                                         } catch (Exception) {\r
136                                                 throw new FormatException ("Not a valid IPv6 address");         \r
137                                         }\r
138                                 }\r
139                         }       \r
140                         \r
141                         int origLen = ipString.Length;\r
142                         if (origLen < 2)\r
143                                 throw new FormatException ("Not a valid IPv6 address");                 \r
144                         ipString = ipString.Replace ("::", ":!:");\r
145                         int len = ipString.Length;\r
146                         if ((len - origLen) > 1) \r
147                                 throw new FormatException ("Not a valid IPv6 address");\r
148                         \r
149                         if (ipString [1] == '!') \r
150                                 ipString = ipString.Remove (0, 1);\r
151                         if (ipString [len - 2] == '!')\r
152                                 ipString = ipString.Remove (len - 1, 1);\r
153                         if ((ipString.Length > 2) && \r
154                             ((ipString [0] == ':') || (ipString [ipString.Length - 1] == ':'))) \r
155                                 throw new FormatException ("Not a valid IPv6 address");\r
156                                 \r
157                         string [] pieces = ipString.Split (new char [] {':'});\r
158                         len = pieces.Length;\r
159                         if (len > (ipv4 ? 6 : 8)) \r
160                                 throw new FormatException ("Not a valid IPv6 address");\r
161                         int piecedouble = -1;\r
162                         bool ipv6 = false;
163                         for (int i = 0; i < len; i++) {\r
164                                 string piece = pieces [i];\r
165                                 if (piece == "!")\r
166                                         piecedouble = i;\r
167                                 else {\r
168                                         int plen = piece.Length;\r
169                                         if (plen > 4)\r
170                                                 throw new FormatException ("Not a valid IPv6 address");\r
171                                         int p = 0;\r
172                                         for (int j = 0; j < plen; j++) \r
173                                                 try {\r
174                                                         p = (p << 4) + Uri.FromHex (piece [j]);\r
175                                                 } catch (ArgumentException) {\r
176                                                         throw new FormatException ("Not a valid IPv6 address");\r
177                                                 }\r
178                                         addr [i] = (ushort) p;
179                                         if (p != 0 || (i == 5 && p != 0xffff))
180                                                 ipv6 = true;
181                                 }\r
182                         }\r
183 \r
184                         //expand the :: token\r
185                         if (piecedouble != -1) {\r
186                                 int totallen = (ipv4 ? 5 : 7);\r
187                                 int i = totallen;\r
188                                 for (i = totallen; i >= (totallen - (len - piecedouble - 1)); i--) {\r
189                                         addr [i] = addr [(len - 1) + i - totallen];\r
190                                 }\r
191                                 for (; i >= piecedouble; i--) {\r
192                                         addr [i] = 0;\r
193                                 }\r
194                         } else if (len != (ipv4 ? 6 : 8)) \r
195                                 throw new FormatException ("Not a valid IPv6 address");\r
196 \r
197                         // check IPv4 validity
198                         if (ipv4 && !ipv6) {
199                                 for (int i = 0; i < 5; i++) {
200                                         if (addr [i] != 0)
201                                                 throw new FormatException ("Not a valid IPv6 address");
202                                 }
203
204                                 if (addr [5] != 0 && addr [5] != 0xffff)
205                                         throw new FormatException ("Not a valid IPv6 address");
206                         }
207
208                         return new IPv6Address (addr, prefixLen, scopeId);
209                 }
210
211                 public ushort [] Address {\r
212                         get { return address; }\r
213                 }\r
214 \r
215                 public int PrefixLength {\r
216                         get { return this.prefixLength; }\r
217                 }\r
218                 \r
219                 public long ScopeId {\r
220                         get {\r
221                                 return scopeId;\r
222                         }\r
223                         set {\r
224                                 scopeId = value;\r
225                         }\r
226                 }\r
227 \r
228                 public ushort this [int index] {\r
229                         get { return address [index]; }\r
230                 }               \r
231 \r
232                 public AddressFamily AddressFamily {\r
233                         get { return AddressFamily.InterNetworkV6; }\r
234                 }\r
235                 \r
236                 /// <summary>\r
237                 ///   Used to tell whether the given address is the loopback address.\r
238                 /// </summary>\r
239                 public static bool IsLoopback (IPv6Address addr)\r
240                 {\r
241                         for (int i = 0; i < 4; i++)\r
242                                 if (addr.address [i] != 0)\r
243                                         return false;\r
244                         if ((addr.address [5] != 0) && (addr.address [5] != 0xffff))\r
245                                 return false;\r
246                         if ((addr.address [6] >> 8) == 0x7f)\r
247                                 return true;\r
248                         return ((addr.address [5] == 0) && \r
249                                 (addr.address [6] == 0) && \r
250                                 (addr.address [7] == 1));\r
251                 }\r
252                 \r
253
254                 private static ushort SwapUShort (ushort number)
255                 {
256                         return (ushort) ( ((number >> 8) & 0xFF) + ((number << 8) & 0xFF00) );
257                 }
258
259                 // Convert the address into a format expected by the IPAddress (long) ctor
260                 private int AsIPv4Int ()
261                 {
262                         return (SwapUShort (address [7]) << 16) + SwapUShort (address [6]);
263                 }                       
264
265                 public bool IsIPv4Compatible ()\r
266                 {\r
267                         for (int i = 0; i < 6; i++) \r
268                                 if (address [i] != 0)\r
269                                         return false;\r
270                         return (AsIPv4Int () > 1);
271                 }\r
272                 \r
273                 public bool IsIPv4Mapped ()\r
274                 {\r
275                         for (int i = 0; i < 5; i++) \r
276                                 if (address [i] != 0)\r
277                                         return false;\r
278                         return address [5] == 0xffff;\r
279                 }\r
280                 \r
281                 /// <summary>\r
282                 ///   Overrides System.Object.ToString to return\r
283                 ///   this object rendered in a canonicalized notation\r
284                 /// </summary>\r
285                 public override string ToString ()\r
286                 {\r
287                         StringBuilder s = new StringBuilder ();\r
288 \r
289 \r
290                         if(IsIPv4Compatible() || IsIPv4Mapped())\r
291                         {\r
292                                 s.Append("::");\r
293 \r
294                                 if(IsIPv4Mapped())\r
295                                         s.Append("ffff:");\r
296 \r
297                                 s.Append(new IPAddress( AsIPv4Int ()).ToString ());
298 \r
299                                 return s.ToString ();\r
300                         }\r
301                         else\r
302                         {\r
303                                 int bestChStart = -1; // Best chain start\r
304                                 int bestChLen = 0; // Best chain length\r
305                                 int currChLen = 0; // Current chain length\r
306 \r
307                                 // Looks for the longest zero chain\r
308                                 for (int i=0; i<8; i++)\r
309                                 {\r
310                                         if (address[i] != 0)\r
311                                         {\r
312                                                 if ((currChLen > bestChLen) \r
313                                                         && (currChLen > 1))\r
314                                                 {\r
315                                                         bestChLen = currChLen;\r
316                                                         bestChStart = i - currChLen;\r
317                                                 }\r
318                                                 currChLen = 0;\r
319                                         }\r
320                                         else\r
321                                                 currChLen++;\r
322                                 }\r
323                                 if ((currChLen > bestChLen) \r
324                                         && (currChLen > 1))\r
325                                 {\r
326                                         bestChLen = currChLen;\r
327                                         bestChStart = 8 - currChLen;\r
328                                 }\r
329 \r
330                                 // makes the string\r
331                                 if (bestChStart == 0)\r
332                                         s.Append(":");\r
333                                 for (int i=0; i<8; i++)\r
334                                 {\r
335                                         if (i == bestChStart)\r
336                                         {\r
337                                                 s.Append (":");\r
338                                                 i += (bestChLen - 1);\r
339                                                 continue;\r
340                                         }\r
341                                         s.AppendFormat("{0:x}", address [i]);\r
342                                         if (i < 7) s.Append (':');\r
343                                 }\r
344                         }\r
345                         return s.ToString ();\r
346                 }\r
347 \r
348                 /// <returns>\r
349                 ///   Whether both objects are equal.\r
350                 /// </returns>\r
351                 public override bool Equals (object other)\r
352                 {\r
353                         System.Net.IPv6Address ipv6 = other as System.Net.IPv6Address;\r
354                         if (ipv6 != null) {\r
355                                 for (int i = 0; i < 8; i++) \r
356                                         if (this.address [i] != ipv6.address [i])\r
357                                                 return false;\r
358                                 return true;                            \r
359                         } \r
360                         \r
361                         System.Net.IPAddress ipv4 = other as System.Net.IPAddress;\r
362                         if (ipv4 != null) {\r
363                                 for (int i = 0; i < 5; i++) \r
364                                         if (address [i] != 0)\r
365                                                 return false;                   \r
366 \r
367                                 if (address [5] != 0 && address [5] != 0xffff)\r
368                                         return false;\r
369 \r
370                                 long a = ipv4.InternalIPv4Address;\r
371                                 if (address [6] != (ushort) (((int) (a & 0xff) << 8) + ((int) ((a >> 8) & 0xff))) ||\r
372                                     address [7] != (ushort) (((int) ((a >> 16) & 0xff) << 8) + ((int) ((a >> 24) & 0xff))))\r
373                                         return false;\r
374                                         \r
375                                 return true;\r
376                         }\r
377                         \r
378                         return false;\r
379                 }\r
380 \r
381                 public override int GetHashCode ()\r
382                 {\r
383                         return Hash (((((int) address [0]) << 16) + address [1]), \r
384                                                 ((((int) address [2]) << 16) + address [3]),\r
385                                                 ((((int) address [4]) << 16) + address [5]),\r
386                                                 ((((int) address [6]) << 16) + address [7]));\r
387                 }\r
388                 \r
389                 private static int Hash (int i, int j, int k, int l) \r
390                 {\r
391                         return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25);\r
392                 }\r
393         }\r
394 }\r