View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.util;
17  
18  import org.jboss.netty.logging.InternalLogger;
19  import org.jboss.netty.logging.InternalLoggerFactory;
20  
21  import java.net.Inet4Address;
22  import java.net.Inet6Address;
23  import java.net.InetAddress;
24  import java.net.UnknownHostException;
25  import java.util.ArrayList;
26  import java.util.StringTokenizer;
27  
28  /**
29   * A class that holds a number of network-related constants.
30   * <p/>
31   * This class borrowed some of its methods from a  modified fork of the
32   * <a href="http://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/
33   * src/main/java/org/apache/harmony/luni/util/Inet6Util.java">Inet6Util class</a> which was part of Apache Harmony.
34   */
35  public final class NetUtil {
36  
37      /**
38       * This defines how many words (represented as ints) are needed to represent an IPv6 address
39       */
40      private static final int IPV6_WORD_COUNT = 8;
41  
42      /**
43       * The maximum number of characters for an IPV6 string with no scope
44       */
45      private static final int IPV6_MAX_CHAR_COUNT = 39;
46  
47      /**
48       * Number of bytes needed to represent and IPV6 value
49       */
50      private static final int IPV6_BYTE_COUNT = 16;
51  
52      /**
53       * Maximum amount of value adding characters in between IPV6 separators
54       */
55      private static final int IPV6_MAX_CHAR_BETWEEN_SEPARATOR = 4;
56  
57      /**
58       * Minimum number of separators that must be present in an IPv6 string
59       */
60      private static final int IPV6_MIN_SEPARATORS = 2;
61  
62      /**
63       * Maximum number of separators that must be present in an IPv6 string
64       */
65      private static final int IPV6_MAX_SEPARATORS = 8;
66  
67      /**
68       * Number of bytes needed to represent and IPV4 value
69       */
70      private static final int IPV4_BYTE_COUNT = 4;
71  
72      /**
73       * Maximum amount of value adding characters in between IPV4 separators
74       */
75      private static final int IPV4_MAX_CHAR_BETWEEN_SEPARATOR = 3;
76  
77      /**
78       * Number of separators that must be present in an IPv4 string
79       */
80      private static final int IPV4_SEPARATORS = 3;
81  
82      /**
83       * The logger being used by this class
84       */
85      private static final InternalLogger logger = InternalLoggerFactory.getInstance(NetUtil.class);
86  
87      /**
88       * Creates an byte[] based on an ipAddressString. No error handling is
89       * performed here.
90       */
91      public static byte[] createByteArrayFromIpAddressString(String ipAddressString) {
92  
93          if (isValidIpV4Address(ipAddressString)) {
94              StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ".");
95              String token;
96              int tempInt;
97              byte[] byteAddress = new byte[IPV4_BYTE_COUNT];
98              for (int i = 0; i < IPV4_BYTE_COUNT; i ++) {
99                  token = tokenizer.nextToken();
100                 tempInt = Integer.parseInt(token);
101                 byteAddress[i] = (byte) tempInt;
102             }
103 
104             return byteAddress;
105         }
106 
107         if (isValidIpV6Address(ipAddressString)) {
108             if (ipAddressString.charAt(0) == '[') {
109                 ipAddressString = ipAddressString.substring(1, ipAddressString.length() - 1);
110             }
111 
112             int percentPos = ipAddressString.indexOf('%');
113             if (percentPos >= 0) {
114                 ipAddressString = ipAddressString.substring(0, percentPos);
115             }
116 
117             StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ":.", true);
118             ArrayList<String> hexStrings = new ArrayList<String>();
119             ArrayList<String> decStrings = new ArrayList<String>();
120             String token = "";
121             String prevToken = "";
122             int doubleColonIndex = -1; // If a double colon exists, we need to
123             // insert 0s.
124 
125             // Go through the tokens, including the seperators ':' and '.'
126             // When we hit a : or . the previous token will be added to either
127             // the hex list or decimal list. In the case where we hit a ::
128             // we will save the index of the hexStrings so we can add zeros
129             // in to fill out the string
130             while (tokenizer.hasMoreTokens()) {
131                 prevToken = token;
132                 token = tokenizer.nextToken();
133 
134                 if (":".equals(token)) {
135                     if (":".equals(prevToken)) {
136                         doubleColonIndex = hexStrings.size();
137                     } else if (prevToken.length() != 0) {
138                         hexStrings.add(prevToken);
139                     }
140                 } else if (".".equals(token)) {
141                     decStrings.add(prevToken);
142                 }
143             }
144 
145             if (":".equals(prevToken)) {
146                 if (":".equals(token)) {
147                     doubleColonIndex = hexStrings.size();
148                 } else {
149                     hexStrings.add(token);
150                 }
151             } else if (".".equals(prevToken)) {
152                 decStrings.add(token);
153             }
154 
155             // figure out how many hexStrings we should have
156             // also check if it is a IPv4 address
157             int hexStringsLength = 8;
158 
159             // If we have an IPv4 address tagged on at the end, subtract
160             // 4 bytes, or 2 hex words from the total
161             if (!decStrings.isEmpty()) {
162                 hexStringsLength -= 2;
163             }
164 
165             // if we hit a double Colon add the appropriate hex strings
166             if (doubleColonIndex != -1) {
167                 int numberToInsert = hexStringsLength - hexStrings.size();
168                 for (int i = 0; i < numberToInsert; i ++) {
169                     hexStrings.add(doubleColonIndex, "0");
170                 }
171             }
172 
173             byte[] ipByteArray = new byte[IPV6_BYTE_COUNT];
174 
175             // Finally convert these strings to bytes...
176             for (int i = 0; i < hexStrings.size(); i ++) {
177                 convertToBytes(hexStrings.get(i), ipByteArray, i << 1);
178             }
179 
180             // Now if there are any decimal values, we know where they go...
181             for (int i = 0; i < decStrings.size(); i ++) {
182                 ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings.get(i)) & 255);
183             }
184             return ipByteArray;
185         }
186         return null;
187     }
188 
189     /**
190      * Converts a 4 character hex word into a 2 byte word equivalent
191      */
192     private static void convertToBytes(String hexWord, byte[] ipByteArray, int byteIndex) {
193 
194         int hexWordLength = hexWord.length();
195         int hexWordIndex = 0;
196         ipByteArray[byteIndex] = 0;
197         ipByteArray[byteIndex + 1] = 0;
198         int charValue;
199 
200         // high order 4 bits of first byte
201         if (hexWordLength > 3) {
202             charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
203             ipByteArray[byteIndex] |= charValue << 4;
204         }
205 
206         // low order 4 bits of the first byte
207         if (hexWordLength > 2) {
208             charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
209             ipByteArray[byteIndex] |= charValue;
210         }
211 
212         // high order 4 bits of second byte
213         if (hexWordLength > 1) {
214             charValue = getIntValue(hexWord.charAt(hexWordIndex ++));
215             ipByteArray[byteIndex + 1] |= charValue << 4;
216         }
217 
218         // low order 4 bits of the first byte
219         charValue = getIntValue(hexWord.charAt(hexWordIndex));
220         ipByteArray[byteIndex + 1] |= charValue & 15;
221     }
222 
223     static int getIntValue(char c) {
224 
225         switch (c) {
226         case '0':
227             return 0;
228         case '1':
229             return 1;
230         case '2':
231             return 2;
232         case '3':
233             return 3;
234         case '4':
235             return 4;
236         case '5':
237             return 5;
238         case '6':
239             return 6;
240         case '7':
241             return 7;
242         case '8':
243             return 8;
244         case '9':
245             return 9;
246         }
247 
248         c = Character.toLowerCase(c);
249         switch (c) {
250         case 'a':
251             return 10;
252         case 'b':
253             return 11;
254         case 'c':
255             return 12;
256         case 'd':
257             return 13;
258         case 'e':
259             return 14;
260         case 'f':
261             return 15;
262         }
263         return 0;
264     }
265 
266     public static boolean isValidIpV6Address(String ipAddress) {
267         int length = ipAddress.length();
268         boolean doubleColon = false;
269         int numberOfColons = 0;
270         int numberOfPeriods = 0;
271         StringBuilder word = new StringBuilder();
272         char c = 0;
273         char prevChar;
274         int startOffset = 0; // offset for [] ip addresses
275         int endOffset = ipAddress.length();
276 
277         if (endOffset < 2) {
278             return false;
279         }
280 
281         // Strip []
282         if (ipAddress.charAt(0) == '[') {
283             if (ipAddress.charAt(endOffset - 1) != ']') {
284                 return false; // must have a close ]
285             }
286 
287             startOffset = 1;
288             endOffset --;
289         }
290 
291         // Strip the interface name/index after the percent sign.
292         int percentIdx = ipAddress.indexOf('%', startOffset);
293         if (percentIdx >= 0) {
294             endOffset = percentIdx;
295         }
296 
297         for (int i = startOffset; i < endOffset; i ++) {
298             prevChar = c;
299             c = ipAddress.charAt(i);
300             switch (c) {
301             // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d
302             case '.':
303                 numberOfPeriods ++;
304                 if (numberOfPeriods > 3) {
305                     return false;
306                 }
307                 if (!isValidIp4Word(word.toString())) {
308                     return false;
309                 }
310                 if (numberOfColons != 6 && !doubleColon) {
311                     return false;
312                 }
313                 // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
314                 // IPv4 ending, otherwise 7 :'s is bad
315                 if (numberOfColons == 7 && ipAddress.charAt(startOffset) != ':' &&
316                     ipAddress.charAt(1 + startOffset) != ':') {
317                     return false;
318                 }
319                 word.delete(0, word.length());
320                 break;
321 
322             case ':':
323                 // FIX "IP6 mechanism syntax #ip6-bad1"
324                 // An IPV6 address cannot start with a single ":".
325                 // Either it can starti with "::" or with a number.
326                 if (i == startOffset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) {
327                     return false;
328                 }
329                 // END FIX "IP6 mechanism syntax #ip6-bad1"
330                 numberOfColons ++;
331                 if (numberOfColons > 7) {
332                     return false;
333                 }
334                 if (numberOfPeriods > 0) {
335                     return false;
336                 }
337                 if (prevChar == ':') {
338                     if (doubleColon) {
339                         return false;
340                     }
341                     doubleColon = true;
342                 }
343                 word.delete(0, word.length());
344                 break;
345 
346             default:
347                 if (word != null && word.length() > 3) {
348                     return false;
349                 }
350                 if (!isValidHexChar(c)) {
351                     return false;
352                 }
353                 word.append(c);
354             }
355         }
356 
357         // Check if we have an IPv4 ending
358         if (numberOfPeriods > 0) {
359             // There is a test case with 7 colons and valid ipv4 this should resolve it
360             if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && numberOfColons < 7)) {
361                 return false;
362             }
363         } else {
364             // If we're at then end and we haven't had 7 colons then there is a
365             // problem unless we encountered a doubleColon
366             if (numberOfColons != 7 && !doubleColon) {
367                 return false;
368             }
369 
370             // If we have an empty word at the end, it means we ended in either
371             // a : or a .
372             // If we did not end in :: then this is invalid
373             if (word.length() == 0 && ipAddress.charAt(length - 1 - startOffset) == ':' &&
374                 ipAddress.charAt(length - 2 - startOffset) != ':') {
375                 return false;
376             }
377         }
378 
379         return true;
380     }
381 
382     public static boolean isValidIp4Word(String word) {
383         char c;
384         if (word.length() < 1 || word.length() > 3) {
385             return false;
386         }
387         for (int i = 0; i < word.length(); i ++) {
388             c = word.charAt(i);
389             if (!(c >= '0' && c <= '9')) {
390                 return false;
391             }
392         }
393         return Integer.parseInt(word) <= 255;
394     }
395 
396     private static boolean isValidHexChar(char c) {
397         return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
398     }
399 
400     private static boolean isValidNumericChar(char c) {
401         return c >= '0' && c <= '9';
402     }
403 
404     /**
405      * Takes a string and parses it to see if it is a valid IPV4 address.
406      *
407      * @return true, if the string represents an IPV4 address in dotted
408      *         notation, false otherwise
409      */
410     public static boolean isValidIpV4Address(String value) {
411 
412         int periods = 0;
413         int i;
414         int length = value.length();
415 
416         if (length > 15) {
417             return false;
418         }
419         char c;
420         StringBuilder word = new StringBuilder();
421         for (i = 0; i < length; i ++) {
422             c = value.charAt(i);
423             if (c == '.') {
424                 periods ++;
425                 if (periods > 3) {
426                     return false;
427                 }
428                 if (word.length() == 0) {
429                     return false;
430                 }
431                 if (Integer.parseInt(word.toString()) > 255) {
432                     return false;
433                 }
434                 word.delete(0, word.length());
435             } else if (!Character.isDigit(c)) {
436                 return false;
437             } else {
438                 if (word.length() > 2) {
439                     return false;
440                 }
441                 word.append(c);
442             }
443         }
444 
445         if (word.length() == 0 || Integer.parseInt(word.toString()) > 255) {
446             return false;
447         }
448 
449         return periods == 3;
450     }
451 
452     /**
453      * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
454      * <p>
455      * This method will treat all IPv4 type addresses as "IPv4 mapped" (see {@link #getByName(CharSequence, boolean)})
456      * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
457      * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
458      */
459     public static Inet6Address getByName(CharSequence ip) {
460         return getByName(ip, true);
461     }
462 
463     /**
464      * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
465      * <p>
466      * The {@code ipv4Mapped} parameter specifies how IPv4 addresses should be treated.
467      * "IPv4 mapped" format as
468      * defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> is supported.
469      * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
470      * @param ipv4Mapped
471      * <ul>
472      * <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
473      * <li>{@code false} Don't turn IPv4 addressed to mapped addresses</li>
474      * </ul>
475      * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
476      */
477     public static Inet6Address getByName(CharSequence ip, boolean ipv4Mapped) {
478         final byte[] bytes = new byte[IPV6_BYTE_COUNT];
479         final int ipLength = ip.length();
480         int compressBegin = 0;
481         int compressLength = 0;
482         int currentIndex = 0;
483         int value = 0;
484         int begin = -1;
485         int i = 0;
486         int ipv6Seperators = 0;
487         int ipv4Seperators = 0;
488         int tmp;
489         boolean needsShift = false;
490         for (; i < ipLength; ++i) {
491             final char c = ip.charAt(i);
492             switch (c) {
493             case ':':
494                 ++ipv6Seperators;
495                 if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
496                     ipv4Seperators > 0 || ipv6Seperators > IPV6_MAX_SEPARATORS ||
497                     currentIndex + 1 >= bytes.length) {
498                     return null;
499                 }
500                 value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
501 
502                 if (compressLength > 0) {
503                     compressLength -= 2;
504                 }
505 
506                 // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
507                 // The following bit shifting is used to extract and re-order the individual bytes to achieve a
508                 // left (most significant) to right (least significant) ordering.
509                 bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
510                 bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
511                 tmp = i + 1;
512                 if (tmp < ipLength && ip.charAt(tmp) == ':') {
513                     ++tmp;
514                     if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) {
515                         return null;
516                     }
517                     ++ipv6Seperators;
518                     needsShift = ipv6Seperators == 2 && value == 0;
519                     compressBegin = currentIndex;
520                     compressLength = bytes.length - compressBegin - 2;
521                     ++i;
522                 }
523                 value = 0;
524                 begin = -1;
525                 break;
526             case '.':
527                 ++ipv4Seperators;
528                 if (i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR
529                     || ipv4Seperators > IPV4_SEPARATORS
530                     || (ipv6Seperators > 0 && (currentIndex + compressLength < 12))
531                     || i + 1 >= ipLength
532                     || currentIndex >= bytes.length
533                     || begin < 0
534                     || (begin == 0 && (i == 3 && (!isValidNumericChar(ip.charAt(2)) ||
535                                                   !isValidNumericChar(ip.charAt(1)) ||
536                                                   !isValidNumericChar(ip.charAt(0))) ||
537                                        i == 2 && (!isValidNumericChar(ip.charAt(1)) ||
538                                                   !isValidNumericChar(ip.charAt(0))) ||
539                                        i == 1 && !isValidNumericChar(ip.charAt(0))))) {
540                     return null;
541                 }
542                 value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
543 
544                 // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
545                 // The following bit shifting is to restructure the bytes to be left (most significant) to
546                 // right (least significant) while also accounting for each IPv4 digit is base 10.
547                 begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
548                 if (begin < 0 || begin > 255) {
549                     return null;
550                 }
551                 bytes[currentIndex++] = (byte) begin;
552                 value = 0;
553                 begin = -1;
554                 break;
555             default:
556                 if (!isValidHexChar(c) || (ipv4Seperators > 0 && !isValidNumericChar(c))) {
557                     return null;
558                 }
559                 if (begin < 0) {
560                     begin = i;
561                 } else if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
562                     return null;
563                 }
564                 // The value is treated as a sort of array of numbers because we are dealing with
565                 // at most 4 consecutive bytes we can use bit shifting to accomplish this.
566                 // The most significant byte will be encountered first, and reside in the right most
567                 // position of the following integer
568                 value += getIntValue(c) << ((i - begin) << 2);
569                 break;
570             }
571         }
572 
573         final boolean isCompressed = compressBegin > 0;
574         // Finish up last set of data that was accumulated in the loop (or before the loop)
575         if (ipv4Seperators > 0) {
576             if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR ||
577                 ipv4Seperators != IPV4_SEPARATORS ||
578                 currentIndex >= bytes.length) {
579                 return null;
580             }
581             if (ipv6Seperators == 0) {
582                 compressLength = 12;
583             } else if (ipv6Seperators >= IPV6_MIN_SEPARATORS &&
584                        ip.charAt(ipLength - 1) != ':' &&
585                        (!isCompressed && (ipv6Seperators == 6 && ip.charAt(0) != ':') ||
586                         isCompressed && (ipv6Seperators + 1 < IPV6_MAX_SEPARATORS &&
587                                          (ip.charAt(0) != ':' || compressBegin <= 2)))) {
588                 compressLength -= 2;
589             } else {
590                 return null;
591             }
592             value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
593 
594             // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
595             // The following bit shifting is to restructure the bytes to be left (most significant) to
596             // right (least significant) while also accounting for each IPv4 digit is base 10.
597             begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
598             if (begin < 0 || begin > 255) {
599                 return null;
600             }
601             bytes[currentIndex++] = (byte) begin;
602         } else {
603             tmp = ipLength - 1;
604             if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
605                 ipv6Seperators < IPV6_MIN_SEPARATORS ||
606                 !isCompressed && (ipv6Seperators + 1 != IPV6_MAX_SEPARATORS  ||
607                                   ip.charAt(0) == ':' || ip.charAt(tmp) == ':') ||
608                 isCompressed && (ipv6Seperators > IPV6_MAX_SEPARATORS ||
609                                  (ipv6Seperators == IPV6_MAX_SEPARATORS &&
610                                   (compressBegin <= 2 && ip.charAt(0) != ':' ||
611                                    compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
612                 currentIndex + 1 >= bytes.length) {
613                 return null;
614             }
615             if (begin >= 0 && i - begin <= IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
616                 value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
617             }
618             // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
619             // The following bit shifting is used to extract and re-order the individual bytes to achieve a
620             // left (most significant) to right (least significant) ordering.
621             bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
622             bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
623         }
624 
625         i = currentIndex + compressLength;
626         if (needsShift || i >= bytes.length) {
627             // Right shift array
628             if (i >= bytes.length) {
629                 ++compressBegin;
630             }
631             for (i = currentIndex; i < bytes.length; ++i) {
632                 for (begin = bytes.length - 1; begin >= compressBegin; --begin) {
633                     bytes[begin] = bytes[begin - 1];
634                 }
635                 bytes[begin] = 0;
636                 ++compressBegin;
637             }
638         } else {
639             // Selectively move elements
640             for (i = 0; i < compressLength; ++i) {
641                 begin = i + compressBegin;
642                 currentIndex = begin + compressLength;
643                 if (currentIndex < bytes.length) {
644                     bytes[currentIndex] = bytes[begin];
645                     bytes[begin] = 0;
646                 } else {
647                     break;
648                 }
649             }
650         }
651 
652         if (ipv4Mapped && ipv4Seperators > 0 &&
653             bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0 && bytes[4] == 0 &&
654             bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0 && bytes[8] == 0 && bytes[9] == 0) {
655             bytes[10] = bytes[11] = (byte) 0xff;
656         }
657 
658         try {
659             return Inet6Address.getByAddress(null, bytes, -1);
660         } catch (UnknownHostException e) {
661             throw new RuntimeException(e); // Should never happen
662         }
663     }
664 
665     /**
666      * Returns the {@link String} representation of an {@link InetAddress}.
667      * <ul>
668      * <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
669      * <li>Inet6Address results adhere to
670      * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
671      * </ul>
672      * <p>
673      * The output does not include Scope ID.
674      * @param ip {@link InetAddress} to be converted to an address string
675      * @return {@code String} containing the text-formatted IP address
676      */
677     public static String toAddressString(InetAddress ip) {
678         return toAddressString(ip, false);
679     }
680 
681     /**
682      * Returns the {@link String} representation of an {@link InetAddress}.
683      * <ul>
684      * <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
685      * <li>Inet6Address results adhere to
686      * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a> if
687      * {@code ipv4Mapped} is false.  If {@code ipv4Mapped} is true then "IPv4 mapped" format
688      * from <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> will be supported.
689      * The compressed result will always obey the compression rules defined in
690      * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
691      * </ul>
692      * <p>
693      * The output does not include Scope ID.
694      * @param ip {@link InetAddress} to be converted to an address string
695      * @param ipv4Mapped
696      * <ul>
697      * <li>{@code true} to stray from strict rfc 5952 and support the "IPv4 mapped" format
698      * defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> while still
699      * following the updated guidelines in
700      * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
701      * <li>{@code false} to strictly follow rfc 5952</li>
702      * </ul>
703      * @return {@code String} containing the text-formatted IP address
704      */
705     public static String toAddressString(InetAddress ip, boolean ipv4Mapped) {
706         if (ip instanceof Inet4Address) {
707             return ip.getHostAddress();
708         }
709         if (!(ip instanceof Inet6Address)) {
710             throw new IllegalArgumentException("Unhandled type: " + ip.getClass());
711         }
712 
713         final byte[] bytes = ip.getAddress();
714         final int[] words = new int[IPV6_WORD_COUNT];
715         int i;
716         for (i = 0; i < words.length; ++i) {
717             words[i] = ((bytes[i << 1] & 0xff) << 8) | (bytes[(i << 1) + 1] & 0xff);
718         }
719 
720         // Find longest run of 0s, tie goes to first found instance
721         int currentStart = -1;
722         int currentLength = 0;
723         int shortestStart = -1;
724         int shortestLength = 0;
725         for (i = 0; i < words.length; ++i) {
726             if (words[i] == 0) {
727                 if (currentStart < 0) {
728                     currentStart = i;
729                 }
730             } else if (currentStart >= 0) {
731                 currentLength = i - currentStart;
732                 if (currentLength > shortestLength) {
733                     shortestStart = currentStart;
734                     shortestLength = currentLength;
735                 }
736                 currentStart = -1;
737             }
738         }
739         // If the array ends on a streak of zeros, make sure we account for it
740         if (currentStart >= 0) {
741             currentLength = i - currentStart;
742             if (currentLength > shortestLength) {
743                 shortestStart = currentStart;
744                 shortestLength = currentLength;
745             }
746         }
747         // Ignore the longest streak if it is only 1 long
748         if (shortestLength == 1) {
749             shortestLength = 0;
750             shortestStart = -1;
751         }
752 
753         // Translate to string taking into account longest consecutive 0s
754         final int shortestEnd = shortestStart + shortestLength;
755         final StringBuilder b = new StringBuilder(IPV6_MAX_CHAR_COUNT);
756         if (shortestEnd < 0) { // Optimization when there is no compressing needed
757             b.append(Integer.toHexString(words[0]));
758             for (i = 1; i < words.length; ++i) {
759                 b.append(':');
760                 b.append(Integer.toHexString(words[i]));
761             }
762         } else { // General case that can handle compressing (and not compressing)
763             // Loop unroll the first index (so we don't constantly check i==0 cases in loop)
764             final boolean isIpv4Mapped;
765             if (inRangeEndExclusive(0, shortestStart, shortestEnd)) {
766                 b.append("::");
767                 isIpv4Mapped = ipv4Mapped && (shortestEnd == 5 && words[5] == 0xffff);
768             } else {
769                 b.append(Integer.toHexString(words[0]));
770                 isIpv4Mapped = false;
771             }
772             for (i = 1; i < words.length; ++i) {
773                 if (!inRangeEndExclusive(i, shortestStart, shortestEnd)) {
774                     if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
775                         // If the last index was not part of the shortened sequence
776                         if (!isIpv4Mapped || i == 6) {
777                             b.append(':');
778                         } else {
779                             b.append('.');
780                         }
781                     }
782                     if (isIpv4Mapped && i > 5) {
783                         b.append(words[i] >> 8);
784                         b.append('.');
785                         b.append(words[i] & 0xff);
786                     } else {
787                         b.append(Integer.toHexString(words[i]));
788                     }
789                 } else if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
790                     // If we are in the shortened sequence and the last index was not
791                     b.append("::");
792                 }
793             }
794         }
795 
796         return b.toString();
797     }
798 
799     /**
800      * Does a range check on {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive).
801      * @param value The value to checked if is within {@code start} (inclusive) and {@code end} (exclusive)
802      * @param start The start of the range (inclusive)
803      * @param end The end of the range (exclusive)
804      * @return
805      * <ul>
806      * <li>{@code true} if {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive)</li>
807      * <li>{@code false} otherwise</li>
808      * </ul>
809      */
810     private static boolean inRangeEndExclusive(int value, int start, int end) {
811         return value >= start && value < end;
812     }
813 
814     /**
815      * A constructor to stop this class being constructed.
816      */
817     private NetUtil() {
818         // Unused
819     }
820 }