Repository: netty/netty
Issue: IPv6 address to string rfc5952
Labels: feature

The java docs/spec for [Inet6Address.getHostName](http://docs.oracle.com/javase/7/docs/api/java/net/InetAddress.html#getHostName%28%29) does not specify the RFC's recommended string representation (http://tools.ietf.org/html/rfc5952#section-4).  Therefore implementations of this method may provide varying results.  It would be nice to have a io.netty.util.NetUtil static method to translate Inet6Address object to string according to the RFC's recommendation.

Guava provides this functionality in [InetAddress.toAddrString](https://code.google.com/p/guava-libraries/source/browse/guava/src/com/google/common/net/InetAddresses.java?r=e17759dcf1575e634058443bce106b34e15215cd)


Issue comments:
Addressed with aba28d4fef2e3bc7b5afe41662ad0672828d548d


Pull request review comments:
replace `i * 2` with `i << 1`. Bit-shifting for the win ;)

Good call! Done.

This can be collapsed to else if (...)

Done.

Kill it or fill it ;)

I was sneaky on this one.  Look at the below lines for the `<ul>...</ul>`.  Is OK?

Ah sorry missed this.. Sounds good :)

Does a range check... ?

You got it. Fixed.

@normanmaurer - Note that I introduced this method instead of using `Character.isDigit(c)`. Should I just be using `Character.isDigit(c)` or is this OK?

No preference here.

Do we need to have this package-private? Why not just make it private ?

Missing '.' at end of line

Missing '.' at end of line

Missing '.' at end of line

I think it would be nice to add some comments above the bit-shifting stuff to make it easier to understand what you are doing here when look at it again in 1 year or so ;)

You could just do `i + 1` only one time and reuse the result.

You could do `i + 2` only one time and reuse it

Could you please add some comment why you do this ...

Please add a comment explain this.

We have multiple ip.length() calls here. Maybe we could just only call it one time and re-use the result as we never know what the CharSequence impl actually does and so it may be expensive.

if it should never happen better to throw an Error or RuntimeException

Missing '.' at end of line.

Done.

Done (both).

Done

Done.

Done.

Done.

Good idea. Done.


Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java
index 307c5a3196b..3a1f1057e0c 100644
--- a/common/src/main/java/io/netty/util/NetUtil.java
+++ b/common/src/main/java/io/netty/util/NetUtil.java
@@ -27,6 +27,7 @@
 import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
@@ -68,6 +69,51 @@ public final class NetUtil {
      */
     public static final int SOMAXCONN;
 
+    /**
+     * This defines how many words (represented as ints) are needed to represent an IPv6 address
+     */
+    private static final int IPV6_WORD_COUNT = 8;
+
+    /**
+     * The maximum number of characters for an IPV6 string with no scope
+     */
+    private static final int IPV6_MAX_CHAR_COUNT = 39;
+
+    /**
+     * Number of bytes needed to represent and IPV6 value
+     */
+    private static final int IPV6_BYTE_COUNT = 16;
+
+    /**
+     * Maximum amount of value adding characters in between IPV6 separators
+     */
+    private static final int IPV6_MAX_CHAR_BETWEEN_SEPARATOR = 4;
+
+    /**
+     * Minimum number of separators that must be present in an IPv6 string
+     */
+    private static final int IPV6_MIN_SEPARATORS = 2;
+
+    /**
+     * Maximum number of separators that must be present in an IPv6 string
+     */
+    private static final int IPV6_MAX_SEPARATORS = 8;
+
+    /**
+     * Number of bytes needed to represent and IPV4 value
+     */
+    private static final int IPV4_BYTE_COUNT = 4;
+
+    /**
+     * Maximum amount of value adding characters in between IPV4 separators
+     */
+    private static final int IPV4_MAX_CHAR_BETWEEN_SEPARATOR = 3;
+
+    /**
+     * Number of separators that must be present in an IPv4 string
+     */
+    private static final int IPV4_SEPARATORS = 3;
+
     /**
      * The logger being used by this class
      */
@@ -223,8 +269,8 @@ public static byte[] createByteArrayFromIpAddressString(String ipAddressString)
             StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ".");
             String token;
             int tempInt;
-            byte[] byteAddress = new byte[4];
-            for (int i = 0; i < 4; i ++) {
+            byte[] byteAddress = new byte[IPV4_BYTE_COUNT];
+            for (int i = 0; i < IPV4_BYTE_COUNT; i ++) {
                 token = tokenizer.nextToken();
                 tempInt = Integer.parseInt(token);
                 byteAddress[i] = (byte) tempInt;
@@ -294,11 +340,11 @@ public static byte[] createByteArrayFromIpAddressString(String ipAddressString)
                 }
             }
 
-            byte[] ipByteArray = new byte[16];
+            byte[] ipByteArray = new byte[IPV6_BYTE_COUNT];
 
             // Finally convert these strings to bytes...
             for (int i = 0; i < hexStrings.size(); i ++) {
-                convertToBytes(hexStrings.get(i), ipByteArray, i * 2);
+                convertToBytes(hexStrings.get(i), ipByteArray, i << 1);
             }
 
             // Now if there are any decimal values, we know where they go...
@@ -553,10 +599,14 @@ public static boolean isValidIp4Word(String word) {
         return Integer.parseInt(word) <= 255;
     }
 
-    static boolean isValidHexChar(char c) {
+    private static boolean isValidHexChar(char c) {
         return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
     }
 
+    private static boolean isValidNumericChar(char c) {
+        return c >= '0' && c <= '9';
+    }
+
     /**
      * Takes a string and parses it to see if it is a valid IPV4 address.
      *
@@ -605,6 +655,368 @@ public static boolean isValidIpV4Address(String value) {
         return periods == 3;
     }
 
+    /**
+     * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
+     * <p>
+     * This method will treat all IPv4 type addresses as "IPv4 mapped" (see {@link #getByName(CharSequence, boolean)})
+     * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
+     * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
+     */
+    public static Inet6Address getByName(CharSequence ip) {
+        return getByName(ip, true);
+    }
+
+    /**
+     * Returns the {@link Inet6Address} representation of a {@link CharSequence} IP address.
+     * <p>
+     * The {@code ipv4Mapped} parameter specifies how IPv4 addresses should be treated.
+     * "IPv4 mapped" format as
+     * defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> is supported.
+     * @param ip {@link CharSequence} IP address to be converted to a {@link Inet6Address}
+     * @param ipv4Mapped
+     * <ul>
+     * <li>{@code true} To allow IPv4 mapped inputs to be translated into {@link Inet6Address}</li>
+     * <li>{@code false} Don't turn IPv4 addressed to mapped addresses</li>
+     * </ul>
+     * @return {@link Inet6Address} representation of the {@code ip} or {@code null} if not a valid IP address.
+     */
+    public static Inet6Address getByName(CharSequence ip, boolean ipv4Mapped) {
+        final byte[] bytes = new byte[IPV6_BYTE_COUNT];
+        final int ipLength = ip.length();
+        int compressBegin = 0;
+        int compressLength = 0;
+        int currentIndex = 0;
+        int value = 0;
+        int begin = -1;
+        int i = 0;
+        int ipv6Seperators = 0;
+        int ipv4Seperators = 0;
+        int tmp = 0;
+        boolean needsShift = false;
+        for (; i < ipLength; ++i) {
+            final char c = ip.charAt(i);
+            switch (c) {
+            case ':':
+                ++ipv6Seperators;
+                if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
+                        ipv4Seperators > 0 || ipv6Seperators > IPV6_MAX_SEPARATORS ||
+                        currentIndex + 1 >= bytes.length) {
+                    return null;
+                }
+                value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+                if (compressLength > 0) {
+                    compressLength -= 2;
+                }
+
+                // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
+                // The following bit shifting is used to extract and re-order the individual bytes to achieve a
+                // left (most significant) to right (least significant) ordering.
+                bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
+                bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
+                tmp = i + 1;
+                if (tmp < ipLength && ip.charAt(tmp) == ':') {
+                    ++tmp;
+                    if (compressBegin != 0 || (tmp < ipLength && ip.charAt(tmp) == ':')) {
+                        return null;
+                    }
+                    ++ipv6Seperators;
+                    needsShift = ipv6Seperators == 2 && value == 0;
+                    compressBegin = currentIndex;
+                    compressLength = bytes.length - compressBegin - 2;
+                    ++i;
+                }
+                value = 0;
+                begin = -1;
+                break;
+            case '.':
+                ++ipv4Seperators;
+                if (i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR
+                        || ipv4Seperators > IPV4_SEPARATORS
+                        || (ipv6Seperators > 0 && (currentIndex + compressLength < 12))
+                        || i + 1 >= ipLength
+                        || currentIndex >= bytes.length
+                        || begin < 0
+                        || (begin == 0 && (i == 3 && (!isValidNumericChar(ip.charAt(2)) ||
+                                                      !isValidNumericChar(ip.charAt(1)) ||
+                                                      !isValidNumericChar(ip.charAt(0))) ||
+                                           i == 2 && (!isValidNumericChar(ip.charAt(1)) ||
+                                                      !isValidNumericChar(ip.charAt(0))) ||
+                                           i == 1 && !isValidNumericChar(ip.charAt(0))))) {
+                    return null;
+                }
+                value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+                // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
+                // The following bit shifting is to restructure the bytes to be left (most significant) to
+                // right (least significant) while also accounting for each IPv4 digit is base 10.
+                begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
+                if (begin < 0 || begin > 255) {
+                    return null;
+                }
+                bytes[currentIndex++] = (byte) begin;
+                value = 0;
+                begin = -1;
+                break;
+            default:
+                if (!isValidHexChar(c) || (ipv4Seperators > 0 && !isValidNumericChar(c))) {
+                    return null;
+                }
+                if (begin < 0) {
+                    begin = i;
+                } else if (i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
+                    return null;
+                }
+                // The value is treated as a sort of array of numbers because we are dealing with
+                // at most 4 consecutive bytes we can use bit shifting to accomplish this.
+                // The most significant byte will be encountered first, and reside in the right most
+                // position of the following integer
+                value += getIntValue(c) << ((i - begin) << 2);
+                break;
+            }
+        }
+
+        final boolean isCompressed = compressBegin > 0;
+        // Finish up last set of data that was accumulated in the loop (or before the loop)
+        if (ipv4Seperators > 0) {
+            if (begin > 0 && i - begin > IPV4_MAX_CHAR_BETWEEN_SEPARATOR ||
+                    ipv4Seperators != IPV4_SEPARATORS ||
+                    currentIndex >= bytes.length) {
+                return null;
+            }
+            if (ipv6Seperators == 0) {
+                compressLength = 12;
+            } else if (ipv6Seperators >= IPV6_MIN_SEPARATORS &&
+                           ip.charAt(ipLength - 1) != ':' &&
+                           (!isCompressed && (ipv6Seperators == 6 && ip.charAt(0) != ':') ||
+                            isCompressed && (ipv6Seperators + 1 < IPV6_MAX_SEPARATORS &&
+                                             (ip.charAt(0) != ':' || compressBegin <= 2)))) {
+                compressLength -= 2;
+            } else {
+                return null;
+            }
+            value <<= (IPV4_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+
+            // The value integer holds at most 3 bytes from right (most significant) to left (least significant).
+            // The following bit shifting is to restructure the bytes to be left (most significant) to
+            // right (least significant) while also accounting for each IPv4 digit is base 10.
+            begin = (value & 0xf) * 100 + ((value >> 4) & 0xf) * 10 + ((value >> 8) & 0xf);
+            if (begin < 0 || begin > 255) {
+                return null;
+            }
+            bytes[currentIndex++] = (byte) begin;
+        } else {
+            tmp = ipLength - 1;
+            if (begin > 0 && i - begin > IPV6_MAX_CHAR_BETWEEN_SEPARATOR ||
+                    ipv6Seperators < IPV6_MIN_SEPARATORS ||
+                    !isCompressed && (ipv6Seperators + 1 != IPV6_MAX_SEPARATORS  ||
+                                      ip.charAt(0) == ':' || ip.charAt(tmp) == ':') ||
+                    isCompressed && (ipv6Seperators > IPV6_MAX_SEPARATORS ||
+                        (ipv6Seperators == IPV6_MAX_SEPARATORS &&
+                          (compressBegin <= 2 && ip.charAt(0) != ':' ||
+                           compressBegin >= 14 && ip.charAt(tmp) != ':'))) ||
+                    currentIndex + 1 >= bytes.length) {
+                return null;
+            }
+            if (begin >= 0 && i - begin <= IPV6_MAX_CHAR_BETWEEN_SEPARATOR) {
+                value <<= (IPV6_MAX_CHAR_BETWEEN_SEPARATOR - (i - begin)) << 2;
+            }
+            // The value integer holds at most 4 bytes from right (most significant) to left (least significant).
+            // The following bit shifting is used to extract and re-order the individual bytes to achieve a
+            // left (most significant) to right (least significant) ordering.
+            bytes[currentIndex++] = (byte) (((value & 0xf) << 4) | ((value >> 4) & 0xf));
+            bytes[currentIndex++] = (byte) ((((value >> 8) & 0xf) << 4) | ((value >> 12) & 0xf));
+        }
+
+        i = currentIndex + compressLength;
+        if (needsShift || i >= bytes.length) {
+            // Right shift array
+            if (i >= bytes.length) {
+                ++compressBegin;
+            }
+            for (i = currentIndex; i < bytes.length; ++i) {
+                for (begin = bytes.length - 1; begin >= compressBegin; --begin) {
+                    bytes[begin] = bytes[begin - 1];
+                }
+                bytes[begin] = 0;
+                ++compressBegin;
+            }
+        } else {
+            // Selectively move elements
+            for (i = 0; i < compressLength; ++i) {
+                begin = i + compressBegin;
+                currentIndex = begin + compressLength;
+                if (currentIndex < bytes.length) {
+                    bytes[currentIndex] = bytes[begin];
+                    bytes[begin] = 0;
+                } else {
+                    break;
+                }
+            }
+        }
+
+        if (ipv4Mapped && ipv4Seperators > 0 &&
+                bytes[0] == 0 && bytes[1] == 0 && bytes[2] == 0 && bytes[3] == 0 && bytes[4] == 0 &&
+                bytes[5] == 0 && bytes[6] == 0 && bytes[7] == 0 && bytes[8] == 0 && bytes[9] == 0) {
+            bytes[10] = bytes[11] = (byte) 0xff;
+        }
+
+        try {
+            return Inet6Address.getByAddress(null, bytes, -1);
+        } catch (UnknownHostException e) {
+            throw new RuntimeException(e); // Should never happen
+        }
+    }
+
+    /**
+     * Returns the {@link String} representation of an {@link InetAddress}.
+     * <ul>
+     * <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
+     * <li>Inet6Address results adhere to
+     * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
+     * </ul>
+     * <p>
+     * The output does not include Scope ID.
+     * @param ip {@link InetAddress} to be converted to an address string
+     * @return {@code String} containing the text-formatted IP address
+     */
+    public static String toAddressString(InetAddress ip) {
+        return toAddressString(ip, false);
+    }
+
+    /**
+     * Returns the {@link String} representation of an {@link InetAddress}.
+     * <ul>
+     * <li>Inet4Address results are identical to {@link InetAddress#getHostAddress()}</li>
+     * <li>Inet6Address results adhere to
+     * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a> if
+     * {@code ipv4Mapped} is false.  If {@code ipv4Mapped} is true then "IPv4 mapped" format
+     * from <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> will be supported.
+     * The compressed result will always obey the compression rules defined in
+     * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
+     * </ul>
+     * <p>
+     * The output does not include Scope ID.
+     * @param ip {@link InetAddress} to be converted to an address string
+     * @param ipv4Mapped
+     * <ul>
+     * <li>{@code true} to stray from strict rfc 5952 and support the "IPv4 mapped" format
+     * defined in <a href="http://tools.ietf.org/html/rfc4291#section-2.5.5">rfc 4291 section 2</a> while still
+     * following the updated guidelines in
+     * <a href="http://tools.ietf.org/html/rfc5952#section-4">rfc 5952 section 4</a></li>
+     * <li>{@code false} to strictly follow rfc 5952</li>
+     * </ul>
+     * @return {@code String} containing the text-formatted IP address
+     */
+    public static String toAddressString(InetAddress ip, boolean ipv4Mapped) {
+        if (ip instanceof Inet4Address) {
+            return ip.getHostAddress();
+        }
+        if (!(ip instanceof Inet6Address)) {
+            throw new IllegalArgumentException("Unhandled type: " + ip.getClass());
+        }
+
+        final byte[] bytes = ip.getAddress();
+        final int[] words = new int[IPV6_WORD_COUNT];
+        int i;
+        for (i = 0; i < words.length; ++i) {
+            words[i] = ((bytes[i << 1] & 0xff) << 8) | (bytes[(i << 1) + 1] & 0xff);
+        }
+
+        // Find longest run of 0s, tie goes to first found instance
+        int currentStart = -1;
+        int currentLength = 0;
+        int shortestStart = -1;
+        int shortestLength = 0;
+        for (i = 0; i < words.length; ++i) {
+            if (words[i] == 0) {
+                if (currentStart < 0) {
+                    currentStart = i;
+                }
+            } else if (currentStart >= 0) {
+                currentLength = i - currentStart;
+                if (currentLength > shortestLength) {
+                    shortestStart = currentStart;
+                    shortestLength = currentLength;
+                }
+                currentStart = -1;
+            }
+        }
+        // If the array ends on a streak of zeros, make sure we account for it
+        if (currentStart >= 0) {
+            currentLength = i - currentStart;
+            if (currentLength > shortestLength) {
+                shortestStart = currentStart;
+                shortestLength = currentLength;
+            }
+        }
+        // Ignore the longest streak if it is only 1 long
+        if (shortestLength == 1) {
+            shortestLength = 0;
+            shortestStart = -1;
+        }
+
+        // Translate to string taking into account longest consecutive 0s
+        final int shortestEnd = shortestStart + shortestLength;
+        final StringBuilder b = new StringBuilder(IPV6_MAX_CHAR_COUNT);
+        if (shortestEnd < 0) { // Optimization when there is no compressing needed
+            b.append(Integer.toHexString(words[0]));
+            for (i = 1; i < words.length; ++i) {
+                b.append(':');
+                b.append(Integer.toHexString(words[i]));
+            }
+        } else { // General case that can handle compressing (and not compressing)
+            // Loop unroll the first index (so we don't constantly check i==0 cases in loop)
+            final boolean isIpv4Mapped;
+            if (inRangeEndExclusive(0, shortestStart, shortestEnd)) {
+                b.append("::");
+                isIpv4Mapped = ipv4Mapped && (shortestEnd == 5 && words[5] == 0xffff);
+            } else {
+                b.append(Integer.toHexString(words[0]));
+                isIpv4Mapped = false;
+            }
+            for (i = 1; i < words.length; ++i) {
+                if (!inRangeEndExclusive(i, shortestStart, shortestEnd)) {
+                    if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
+                        // If the last index was not part of the shortened sequence
+                        if (!isIpv4Mapped || i == 6) {
+                            b.append(':');
+                        } else {
+                            b.append('.');
+                        }
+                    }
+                    if (isIpv4Mapped && i > 5) {
+                        b.append(words[i] >> 8);
+                        b.append('.');
+                        b.append(words[i] & 0xff);
+                    } else {
+                        b.append(Integer.toHexString(words[i]));
+                    }
+                } else if (!inRangeEndExclusive(i - 1, shortestStart, shortestEnd)) {
+                    // If we are in the shortened sequence and the last index was not
+                    b.append("::");
+                }
+            }
+        }
+
+        return b.toString();
+    }
+
+    /**
+     * Does a range check on {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive).
+     * @param value The value to checked if is within {@code start} (inclusive) and {@code end} (exclusive)
+     * @param start The start of the range (inclusive)
+     * @param end The end of the range (exclusive)
+     * @return
+     * <ul>
+     * <li>{@code true} if {@code value} if is within {@code start} (inclusive) and {@code end} (exclusive)</li>
+     * <li>{@code false} otherwise</li>
+     * </ul>
+     */
+    private static boolean inRangeEndExclusive(int value, int start, int end) {
+        return value >= start && value < end;
+    }
+
     /**
      * A constructor to stop this class being constructed.
      */


Test patch:
diff --git a/common/src/test/java/io/netty/util/NetUtilTest.java b/common/src/test/java/io/netty/util/NetUtilTest.java
index fa36514a8cc..1d9867bf0df 100644
--- a/common/src/test/java/io/netty/util/NetUtilTest.java
+++ b/common/src/test/java/io/netty/util/NetUtilTest.java
@@ -15,13 +15,20 @@
  */
 package io.netty.util;
 
-import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import static org.junit.Assert.*;
+import org.junit.Test;
 
 public class NetUtilTest {
     private static final Map<String, byte[]> validIpV4Hosts = new HashMap<String, byte[]>() {
@@ -50,6 +57,18 @@ public class NetUtilTest {
             put("1.256.3.4", null);
             put("256.0.0.1", null);
             put("1.1.1.1.1", null);
+            put("x.255.255.255", null);
+            put("0.1:0.0", null);
+            put("0.1.0.0:", null);
+            put("127.0.0.", null);
+            put("1.2..4", null);
+            put("192.0.1", null);
+            put("192.0.1.1.1", null);
+            put("192.0.1.a", null);
+            put("19a.0.1.1", null);
+            put("a.0.1.1", null);
+            put(".0.1.1", null);
+            put("...", null);
         }
     };
     private static final Map<String, byte[]> validIpV6Hosts = new HashMap<String, byte[]>() {
@@ -177,6 +196,16 @@ public class NetUtilTest {
             put("0:1:2:3:4:5:6:x", null);
             // Test method with preferred style, adjacent :
             put("0:1:2:3:4:5:6::7", null);
+            // Too many : separators trailing
+            put("0:1:2:3:4:5:6:7::", null);
+            // Too many : separators leading
+            put("::0:1:2:3:4:5:6:7", null);
+            // Too many : separators trailing
+            put("1:2:3:4:5:6:7:", null);
+            // Too many : separators leading
+            put(":1:2:3:4:5:6:7", null);
+            // Too many : separators leading 0
+            put("0::1:2:3:4:5:6:7", null);
             // Test method with preferred style, too many digits.
             put("0:1:2:3:4:5:6:789abcdef", null);
             // Test method with compressed style, bad digits.
@@ -207,6 +236,14 @@ public class NetUtilTest {
             put("0:0:0:0:0:0:10.0.1", null);
             // Test method with ipv4 style, adjacent .
             put("0:0:0:0:0:0:10..0.0.1", null);
+            // Test method with ipv4 style, leading .
+            put("0:0:0:0:0:0:.0.0.1", null);
+            // Test method with ipv4 style, leading .
+            put("0:0:0:0:0:0:.10.0.0.1", null);
+            // Test method with ipv4 style, trailing .
+            put("0:0:0:0:0:0:10.0.0.", null);
+            // Test method with ipv4 style, trailing .
+            put("0:0:0:0:0:0:10.0.0.1.", null);
             // Test method with compressed ipv4 style, bad ipv6 digits.
             put("::fffx:192.168.0.1", null);
             // Test method with compressed ipv4 style, bad ipv4 digits.
@@ -239,12 +276,218 @@ public class NetUtilTest {
             put("0:0:0:0:0:0:0:10.0.0.1", null);
             // Test method, not enough :
             put("0:0:0:0:0:10.0.0.1", null);
+            // Test method, out of order trailing :
+            put("0:0:0:0:0:10.0.0.1:", null);
+            // Test method, out of order leading :
+            put(":0:0:0:0:0:10.0.0.1", null);
+            // Test method, out of order leading :
+            put("0:0:0:0::10.0.0.1:", null);
+            // Test method, out of order trailing :
+            put(":0:0:0:0::10.0.0.1", null);
             // Test method, too many .
             put("0:0:0:0:0:0:10.0.0.0.1", null);
             // Test method, not enough .
             put("0:0:0:0:0:0:10.0.1", null);
             // Test method, adjacent .
             put("0:0:0:0:0:0:10.0.0..1", null);
+            // Double compression symbol
+            put("::0::", null);
+            // Empty contents
+            put("", null);
+            // Trailing : (max number of : = 8)
+            put("2001:0:4136:e378:8000:63bf:3fff:fdd2:", null);
+            // Leading : (max number of : = 8)
+            put(":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null);
+            // Invalid character
+            put("1234:2345:3456:4567:5678:6789::X890", null);
+            // Trailing . in IPv4
+            put("::ffff:255.255.255.255.", null);
+            // To many characters in IPv4
+            put("::ffff:0.0.1111.0", null);
+            // Test method, adjacent .
+            put("::ffff:0.0..0", null);
+            // Not enough IPv4 entries trailing .
+            put("::ffff:127.0.0.", null);
+            // Not enough IPv4 entries no trailing .
+            put("::ffff:1.2.4", null);
+            // Extra IPv4 entry
+            put("::ffff:192.168.0.1.255", null);
+            // Not enough IPv6 content
+            put(":ffff:192.168.0.1.255", null);
+            // Intermixed IPv4 and IPv6 symbols
+            put("::ffff:255.255:255.255.", null);
+        }
+    };
+    private static final Map<byte[], String> ipv6ToAddressStrings = new HashMap<byte[], String>() {
+        private static final long serialVersionUID = 2999763170377573184L;
+        {
+            // From the RFC 5952 http://tools.ietf.org/html/rfc5952#section-4
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 0, 0, 1},
+                    "2001:db8::1");
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 2, 0, 1},
+                    "2001:db8::2:1");
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 1,
+                0, 1, 0, 1,
+                0, 1, 0, 1},
+                    "2001:db8:0:1:1:1:1:1");
+
+            // Other examples
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 2, 0, 1},
+                    "2001:db8::2:1");
+            put(new byte[]{
+                32, 1, 0, 0,
+                0, 0, 0, 1,
+                0, 0, 0, 0,
+                0, 0, 0, 1},
+                    "2001:0:0:1::1");
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 1, 0, 0,
+                0, 0, 0, 1},
+                    "2001:db8::1:0:0:1");
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 1, 0, 0,
+                0, 0, 0, 0},
+                    "2001:db8:0:0:1::");
+            put(new byte[]{
+                32, 1, 13, -72,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 2, 0, 0},
+                    "2001:db8::2:0");
+            put(new byte[]{
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 0, 0, 1},
+                    "::1");
+            put(new byte[]{
+                0, 0, 0, 0,
+                0, 0, 0, 1,
+                0, 0, 0, 0,
+                0, 0, 0, 1},
+                    "::1:0:0:0:1");
+            put(new byte[]{
+                0, 0, 0, 0,
+                1, 0, 0, 1,
+                0, 0, 0, 0,
+                1, 0, 0, 0},
+                    "::100:1:0:0:100:0");
+            put(new byte[]{
+                32, 1, 0, 0,
+                65, 54, -29, 120,
+                -128, 0, 99, -65,
+                63, -1, -3, -46},
+                    "2001:0:4136:e378:8000:63bf:3fff:fdd2");
+            put(new byte[]{
+                -86, -86, -69, -69,
+                -52, -52, -35, -35,
+                -18, -18, -1, -1,
+                17, 17, 34, 34},
+                    "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222");
+            put(new byte[]{
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 0, 0, 0,
+                0, 0, 0, 0},
+                    "::");
+        }
+    };
+
+    private static final Map<String, String> ipv4MappedToIPv6AddressStrings = new HashMap<String, String>() {
+        private static final long serialVersionUID = 1999763170377573184L;
+        {
+            // IPv4 addresses
+            put("255.255.255.255", "::ffff:255.255.255.255");
+            put("0.0.0.0", "::ffff:0.0.0.0");
+            put("127.0.0.1", "::ffff:127.0.0.1");
+            put("1.2.3.4", "::ffff:1.2.3.4");
+            put("192.168.0.1", "::ffff:192.168.0.1");
+
+            // IPv6 addresses
+            // Fully specified
+            put("2001:0:4136:e378:8000:63bf:3fff:fdd2", "2001:0:4136:e378:8000:63bf:3fff:fdd2");
+            put("aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", "aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222");
+            put("0:0:0:0:0:0:0:0", "::");
+            put("0:0:0:0:0:0:0:1", "::1");
+
+            // Compressing at the beginning
+            put("::1:0:0:0:1", "::1:0:0:0:1");
+            put("::1:ffff:ffff", "::1:ffff:ffff");
+            put("::", "::");
+            put("::1", "::1");
+            put("::ffff", "::ffff");
+            put("::ffff:0", "::ffff:0");
+            put("::ffff:ffff", "::ffff:ffff");
+            put("::0987:9876:8765", "::987:9876:8765");
+            put("::0987:9876:8765:7654", "::987:9876:8765:7654");
+            put("::0987:9876:8765:7654:6543", "::987:9876:8765:7654:6543");
+            put("::0987:9876:8765:7654:6543:5432", "::987:9876:8765:7654:6543:5432");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("::0987:9876:8765:7654:6543:5432:3210", "0:987:9876:8765:7654:6543:5432:3210");
+
+            // Compressing at the end
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("2001:db8:abcd:bcde:cdef:def1:ef12::", "2001:db8:abcd:bcde:cdef:def1:ef12:0");
+            put("2001:db8:abcd:bcde:cdef:def1::", "2001:db8:abcd:bcde:cdef:def1::");
+            put("2001:db8:abcd:bcde:cdef::", "2001:db8:abcd:bcde:cdef::");
+            put("2001:db8:abcd:bcde::", "2001:db8:abcd:bcde::");
+            put("2001:db8:abcd::", "2001:db8:abcd::");
+            put("2001:1234::", "2001:1234::");
+            put("2001::", "2001::");
+            put("0::", "::");
+
+            // Compressing in the middle
+            put("1234:2345::7890", "1234:2345::7890");
+            put("1234::2345:7890", "1234::2345:7890");
+            put("1234:2345:3456::7890", "1234:2345:3456::7890");
+            put("1234:2345::3456:7890", "1234:2345::3456:7890");
+            put("1234::2345:3456:7890", "1234::2345:3456:7890");
+            put("1234:2345:3456:4567::7890", "1234:2345:3456:4567::7890");
+            put("1234:2345:3456::4567:7890", "1234:2345:3456::4567:7890");
+            put("1234:2345::3456:4567:7890", "1234:2345::3456:4567:7890");
+            put("1234::2345:3456:4567:7890", "1234::2345:3456:4567:7890");
+            put("1234:2345:3456:4567:5678::7890", "1234:2345:3456:4567:5678::7890");
+            put("1234:2345:3456:4567::5678:7890", "1234:2345:3456:4567::5678:7890");
+            put("1234:2345:3456::4567:5678:7890", "1234:2345:3456::4567:5678:7890");
+            put("1234:2345::3456:4567:5678:7890", "1234:2345::3456:4567:5678:7890");
+            put("1234::2345:3456:4567:5678:7890", "1234::2345:3456:4567:5678:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234:2345:3456:4567:5678:6789::7890", "1234:2345:3456:4567:5678:6789:0:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234:2345:3456:4567:5678::6789:7890", "1234:2345:3456:4567:5678:0:6789:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234:2345:3456:4567::5678:6789:7890", "1234:2345:3456:4567:0:5678:6789:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234:2345:3456::4567:5678:6789:7890", "1234:2345:3456:0:4567:5678:6789:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234:2345::3456:4567:5678:6789:7890", "1234:2345:0:3456:4567:5678:6789:7890");
+            // Note the compression is removed (rfc 5952 section 4.2.2)
+            put("1234::2345:3456:4567:5678:6789:7890", "1234:0:2345:3456:4567:5678:6789:7890");
+
+            // IPv4 mapped addresses
+            put("::ffff:255.255.255.255", "::ffff:255.255.255.255");
+            put("::ffff:0.0.0.0", "::ffff:0.0.0.0");
+            put("::ffff:127.0.0.1", "::ffff:127.0.0.1");
+            put("::ffff:1.2.3.4", "::ffff:1.2.3.4");
+            put("::ffff:192.168.0.1", "::ffff:192.168.0.1");
         }
     };
 
@@ -293,4 +536,39 @@ public void testCreateByteArrayFromIpAddressString() {
             assertArrayEquals(stringEntry.getValue(), NetUtil.createByteArrayFromIpAddressString(stringEntry.getKey()));
         }
     }
+
+    @Test
+    public void testIp6AddressToString() throws UnknownHostException {
+        for (Entry<byte[], String> testEntry : ipv6ToAddressStrings.entrySet()) {
+            assertEquals(testEntry.getValue(),
+                    NetUtil.toAddressString(InetAddress.getByAddress(testEntry.getKey())));
+        }
+    }
+
+    @Test
+    public void testIp4AddressToString() throws UnknownHostException {
+        for (Entry<String, byte[]> stringEntry : validIpV4Hosts.entrySet()) {
+            assertEquals(stringEntry.getKey(),
+                    NetUtil.toAddressString(InetAddress.getByAddress(stringEntry.getValue())));
+        }
+    }
+
+    @Test
+    public void testIpv4MappedIp6GetByName() {
+        for (Entry<String, String> testEntry : ipv4MappedToIPv6AddressStrings.entrySet()) {
+            assertEquals(testEntry.getValue(),
+                            NetUtil.toAddressString(NetUtil.getByName(testEntry.getKey(), true), true));
+        }
+    }
+
+    @Test
+    public void testinvalidIpv4MappedIp6GetByName() {
+        for (String testEntry : invalidIpV4Hosts.keySet()) {
+            assertNull(NetUtil.getByName(testEntry, true));
+        }
+
+        for (String testEntry : invalidIpV6Hosts.keySet()) {
+            assertNull(NetUtil.getByName(testEntry, true));
+        }
+    }
 }
diff --git a/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java b/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java
new file mode 100644
index 00000000000..0512fe33d88
--- /dev/null
+++ b/microbench/src/test/java/io/netty/microbenchmark/common/NetUtilBenchmark.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2014 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.microbenchmark.common;
+
+import io.netty.microbench.util.AbstractMicrobenchmark;
+import io.netty.util.NetUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+@Threads(4)
+@Warmup(iterations = 10)
+@Measurement(iterations = 10)
+public class NetUtilBenchmark extends AbstractMicrobenchmark {
+
+    @Benchmark
+    public void useGetByNameIpv4() {
+        for (String testEntry : invalidIpV4Hosts.keySet()) {
+            if (NetUtil.getByName(testEntry, true) != null) {
+                throw new RuntimeException("error");
+            }
+        }
+    }
+
+    @Benchmark
+    public void useGetByNameIpv6() {
+        for (String testEntry : invalidIpV6Hosts.keySet()) {
+            if (NetUtil.getByName(testEntry, true) != null) {
+                throw new RuntimeException("error");
+            }
+        }
+    }
+
+    @Benchmark
+    public void useIsValidIpv6() {
+        for (String host : invalidIpV6Hosts.keySet()) {
+            if (NetUtil.isValidIpV6Address(host)) {
+                throw new RuntimeException("error");
+            }
+        }
+    }
+
+    @Benchmark
+    public void useIsValidIpv4() {
+        for (String host : invalidIpV4Hosts.keySet()) {
+            if (NetUtil.isValidIpV4Address(host)) {
+                throw new RuntimeException("error");
+            }
+        }
+    }
+
+    private static final Map<String, byte[]> invalidIpV4Hosts = new HashMap<String, byte[]>() {
+        private static final long serialVersionUID = 1299215199895717282L;
+        {
+            put("1.256.3.4", null);
+            put("256.0.0.1", null);
+            put("1.1.1.1.1", null);
+            put("x.255.255.255", null);
+            put("0.1:0.0", null);
+            put("0.1.0.0:", null);
+            put("127.0.0.", null);
+            put("1.2..4", null);
+            put("192.0.1", null);
+            put("192.0.1.1.1", null);
+            put("192.0.1.a", null);
+            put("19a.0.1.1", null);
+            put("a.0.1.1", null);
+            put(".0.1.1", null);
+            put("...", null);
+        }
+    };
+
+    private static final Map<String, byte[]> invalidIpV6Hosts = new HashMap<String, byte[]>() {
+        private static final long serialVersionUID = -5870810805409009696L;
+        {
+            // Test method with garbage.
+            put("Obvious Garbage", null);
+            // Test method with preferred style, too many :
+            put("0:1:2:3:4:5:6:7:8", null);
+            // Test method with preferred style, not enough :
+            put("0:1:2:3:4:5:6", null);
+            // Test method with preferred style, bad digits.
+            put("0:1:2:3:4:5:6:x", null);
+            // Test method with preferred style, adjacent :
+            put("0:1:2:3:4:5:6::7", null);
+            // Too many : separators trailing
+            put("0:1:2:3:4:5:6:7::", null);
+            // Too many : separators leading
+            put("::0:1:2:3:4:5:6:7", null);
+            // Too many : separators trailing
+            put("1:2:3:4:5:6:7:", null);
+            // Too many : separators leading
+            put(":1:2:3:4:5:6:7", null);
+            // Too many : separators leading 0
+            put("0::1:2:3:4:5:6:7", null);
+            // Test method with preferred style, too many digits.
+            put("0:1:2:3:4:5:6:789abcdef", null);
+            // Test method with compressed style, bad digits.
+            put("0:1:2:3::x", null);
+            // Test method with compressed style, too many adjacent :
+            put("0:1:2:::3", null);
+            // Test method with compressed style, too many digits.
+            put("0:1:2:3::abcde", null);
+            // Test method with preferred style, too many :
+            put("0:1:2:3:4:5:6:7:8", null);
+            // Test method with compressed style, not enough :
+            put("0:1", null);
+            // Test method with ipv4 style, bad ipv6 digits.
+            put("0:0:0:0:0:x:10.0.0.1", null);
+            // Test method with ipv4 style, bad ipv4 digits.
+            put("0:0:0:0:0:0:10.0.0.x", null);
+            // Test method with ipv4 style, adjacent :
+            put("0:0:0:0:0::0:10.0.0.1", null);
+            // Test method with ipv4 style, too many ipv6 digits.
+            put("0:0:0:0:0:00000:10.0.0.1", null);
+            // Test method with ipv4 style, too many :
+            put("0:0:0:0:0:0:0:10.0.0.1", null);
+            // Test method with ipv4 style, not enough :
+            put("0:0:0:0:0:10.0.0.1", null);
+            // Test method with ipv4 style, too many .
+            put("0:0:0:0:0:0:10.0.0.0.1", null);
+            // Test method with ipv4 style, not enough .
+            put("0:0:0:0:0:0:10.0.1", null);
+            // Test method with ipv4 style, adjacent .
+            put("0:0:0:0:0:0:10..0.0.1", null);
+            // Test method with ipv4 style, leading .
+            put("0:0:0:0:0:0:.0.0.1", null);
+            // Test method with ipv4 style, leading .
+            put("0:0:0:0:0:0:.10.0.0.1", null);
+            // Test method with ipv4 style, trailing .
+            put("0:0:0:0:0:0:10.0.0.", null);
+            // Test method with ipv4 style, trailing .
+            put("0:0:0:0:0:0:10.0.0.1.", null);
+            // Test method with compressed ipv4 style, bad ipv6 digits.
+            put("::fffx:192.168.0.1", null);
+            // Test method with compressed ipv4 style, bad ipv4 digits.
+            put("::ffff:192.168.0.x", null);
+            // Test method with compressed ipv4 style, too many adjacent :
+            put(":::ffff:192.168.0.1", null);
+            // Test method with compressed ipv4 style, too many ipv6 digits.
+            put("::fffff:192.168.0.1", null);
+            // Test method with compressed ipv4 style, too many ipv4 digits.
+            put("::ffff:1923.168.0.1", null);
+            // Test method with compressed ipv4 style, not enough :
+            put(":ffff:192.168.0.1", null);
+            // Test method with compressed ipv4 style, too many .
+            put("::ffff:192.168.0.1.2", null);
+            // Test method with compressed ipv4 style, not enough .
+            put("::ffff:192.168.0", null);
+            // Test method with compressed ipv4 style, adjacent .
+            put("::ffff:192.168..0.1", null);
+            // Test method, garbage.
+            put("absolute, and utter garbage", null);
+            // Test method, bad ipv6 digits.
+            put("x:0:0:0:0:0:10.0.0.1", null);
+            // Test method, bad ipv4 digits.
+            put("0:0:0:0:0:0:x.0.0.1", null);
+            // Test method, too many ipv6 digits.
+            put("00000:0:0:0:0:0:10.0.0.1", null);
+            // Test method, too many ipv4 digits.
+            put("0:0:0:0:0:0:10.0.0.1000", null);
+            // Test method, too many :
+            put("0:0:0:0:0:0:0:10.0.0.1", null);
+            // Test method, not enough :
+            put("0:0:0:0:0:10.0.0.1", null);
+            // Test method, out of order trailing :
+            put("0:0:0:0:0:10.0.0.1:", null);
+            // Test method, out of order leading :
+            put(":0:0:0:0:0:10.0.0.1", null);
+            // Test method, out of order leading :
+            put("0:0:0:0::10.0.0.1:", null);
+            // Test method, out of order trailing :
+            put(":0:0:0:0::10.0.0.1", null);
+            // Test method, too many .
+            put("0:0:0:0:0:0:10.0.0.0.1", null);
+            // Test method, not enough .
+            put("0:0:0:0:0:0:10.0.1", null);
+            // Test method, adjacent .
+            put("0:0:0:0:0:0:10.0.0..1", null);
+            // Double compression symbol
+            put("::0::", null);
+            // Empty contents
+            put("", null);
+            // Trailing : (max number of : = 8)
+            put("2001:0:4136:e378:8000:63bf:3fff:fdd2:", null);
+            // Leading : (max number of : = 8)
+            put(":aaaa:bbbb:cccc:dddd:eeee:ffff:1111:2222", null);
+            // Invalid character
+            put("1234:2345:3456:4567:5678:6789::X890", null);
+            // Trailing . in IPv4
+            put("::ffff:255.255.255.255.", null);
+            // To many characters in IPv4
+            put("::ffff:0.0.1111.0", null);
+            // Test method, adjacent .
+            put("::ffff:0.0..0", null);
+            // Not enough IPv4 entries trailing .
+            put("::ffff:127.0.0.", null);
+            // Not enough IPv4 entries no trailing .
+            put("::ffff:1.2.4", null);
+            // Extra IPv4 entry
+            put("::ffff:192.168.0.1.255", null);
+            // Not enough IPv6 content
+            put(":ffff:192.168.0.1.255", null);
+            // Intermixed IPv4 and IPv6 symbols
+            put("::ffff:255.255:255.255.", null);
+        }
+    };
+}