/* IPAddress.java This is a wrapper class for the byte array IP value representation that allows IP addresses to be stored and processed in the Ganymede system. Created: 12 August 2013 Module By: Jonathan Abbey, jonabbey@arlut.utexas.edu ----------------------------------------------------------------------- Ganymede Directory Management System Copyright (C) 1996-2014 The University of Texas at Austin Ganymede is a registered trademark of The University of Texas at Austin Contact information Web site: http://www.arlut.utexas.edu/gash2 Author Email: ganymede_author@arlut.utexas.edu Email mailing list: ganymede@arlut.utexas.edu US Mail: Computer Science Division Applied Research Laboratories The University of Texas at Austin PO Box 8029, Austin TX 78713-8029 Telephone: (512) 835-3200 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package arlut.csd.ganymede.common; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; /*------------------------------------------------------------------------------ class IPAddress ------------------------------------------------------------------------------*/ /** * <p>This is an immutable, serializable IPv4 or IPv6 address.</p> */ public final class IPAddress implements Cloneable, java.io.Serializable { static final long serialVersionUID = -2432213571055741805L; private static String IPv4allowedChars = "1234567890."; private static String IPv6allowedChars = "1234567890.abcdefABCDEF:"; // --- /** * <p>Note that Java doesn't have unsigned bytes, so we have to deal * with the bytes in address in terms of signed arithmetic, with * values between -128 and 127.</p> * * <p>While Java uses 2's complement for holding signed numeric * values, we are not, for historical reasons, using the bits in * these bytes as if they were unsigned (i.e., storing a value that * would be held in an unsigned char in C using the 2's complement * interpretation of that bit pattern), as you might expect in * C.</p> * * <p>Instead, they are equal to a value of 0-255, minus 128. So 0 * is -128, 100 is -28, etc., up to 255 is 127.</p> * * <p>This makes it impossible to just mask these values against * 0xff to get the unsigned int value. Fixing this would break * Ganymede's on-disk database and journal format.</p> * * <p>On the other hand, the u2s() encoded bytes can be safely * compared for greater than or less than, and subtracting them from * each other will result in the proper distance between them.</p> * * <p>For compatibility, be sure to use s2u() to do the conversion * to the unsigned value, or you can just add 128 to do it * yourself.</p> */ private final byte[] address; /** * <p>We'll cache the string representation of our IPAddress for * performance.</p> * * <p>(Generating the string for IPv4 addresses is fast, but * generating the string for IPv6 addresses is less so.)</p> */ private transient String text; /* -- */ /** * <p>Primitive byte array constructor</p> * * <p>Because bytes are signed in Java, the bytes submitted for the * octets of this address will range from -128 to 127. You can use * IPAddress.u2s() to convert ints in the range 0 to 255 to the * range used by bytes in this class.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(byte[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of bytes for an IP address: " + address.length); } this.address = Arrays.copyOf(address, address.length); } /** * <p>java.lang.Byte array constructor</p> * * <p>Because bytes are signed in Java, the bytes submitted for the * octets of this address will range from -128 to 127. You can use * IPAddress.u2s() to convert ints in the range 0 to 255 to the * range used by bytes in this class, or just subtract 128.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(Byte[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of bytes for an IP address: " + address.length); } this.address = new byte[address.length]; for (int i = 0; i < address.length; i++) { this.address[i] = address[i].byteValue(); } } /** * <p>Primitive short array constructor</p> * * <p>shorts in the address array must all be between 0 and 255, or * an IllegalArgumentException will be thrown.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(short[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of octets for an IP address: " + address.length); } this.address = u2s(address); } /** * <p>Short object array constructor</p> * * <p>Shorts in the address array must all be between 0 and 255, or * an IllegalArgumentException will be thrown.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(Short[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of octets for an IP address: " + address.length); } this.address = new byte[address.length]; for (int i = 0; i < address.length; i++) { this.address[i] = u2s(address[i].intValue()); } } /** * <p>Primitive int array constructor</p> * * <p>ints in the address array must all be between 0 and 255, or an * IllegalArgumentException will be thrown.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(int[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of octets for an IP address: " + address.length); } this.address = u2s(address); } /** * <p>Integer object array constructor</p> * * <p>Integers in the address array must all be between 0 and 255, * or an IllegalArgumentException will be thrown.</p> * * <p>The address parameter must be of length 4 for an IPv4 address, * and 16 for an IPv6 address.</p> */ public IPAddress(Integer[] address) { if (address.length !=4 && address.length != 16) { throw new IllegalArgumentException("Wrong number of octets for an IP address: " + address.length); } this.address = new byte[address.length]; for (int i = 0; i < address.length; i++) { this.address[i] = u2s(address[i].intValue()); } } /** * <p>String constructor, supports IPv4 dotted decimal and RFC 4291 * style IPv6 encodings.</p> */ public IPAddress(String addressStr) { if (addressStr.indexOf(':') == -1) { this.address = IPAddress.genIPV4bytes(addressStr); } else { this.address = IPAddress.genIPV6bytes(addressStr); } } /** * This method generates the hash key for this object for use in * a Hashtable. */ @Override public int hashCode() { return Arrays.hashCode(this.address); } /** * Equality test. This IPAddress can be compared to either * another IPAddress object or to an array of bytes. */ @Override public boolean equals(Object value) { if (!(value instanceof IPAddress)) { return false; } return Arrays.equals(this.address, ((IPAddress) value).address); } @Override public Object clone() { return new IPAddress(this.address); } /** * <p>Returns a copy of the array of primitive bytes in this * address.</p> * * <p>Because bytes are signed in Java, the range of of the bytes * are from -128 to 127. You can use IPAddress.s2u() to convert the * individual bytes to shorts in the range 0-255, or you can use * getOctets() to get the bytes in this IPAddress in int form.</p> * * <p>Or you can just add 128 to each signed value to get a short or * int ranged value from 0-255.</p> */ public byte[] getBytes() { return Arrays.copyOf(this.address, this.address.length); } /** * <p>Gets an individual byte from this IPAddress.</p> * * <p>Because bytes are signed in Java, the range of of the returned * byte is from -128 to 127. You can use IPAddress.s2u() to convert * the byte returned to a 0-255 int value, or you can just add 128 * to the returned value.</p> */ public byte getByte(int index) { return this.address[index]; } /** * <p>Returns an array of ints containing the octets in this address.</p> * * <p>Each int will be in the range 0-255.</p> */ public int[] getOctets() { int[] result = new int[this.address.length]; for (int i = 0; i < this.address.length; i++) { result[i] = s2u(this.address[i]); } return result; } public boolean isIPv4() { return this.address.length == 4; } public boolean isIPv6() { return this.address.length == 16; } public int length() { return this.address.length; } @Override public String toString() { if (text != null) { return text; } // synchronize on a handy private monitor so we won't accidentally // waste our time generating the string twice if two threads ask // concurrently synchronized (this.address) { if (text == null) { text = IPAddress.genIPString(this.address); } } return text; } /** * <p>Writes this IPAddress to out in a form compatible with the * ganymede.db and/or journal file serializations.</p> */ public void emit(DataOutput out) throws IOException { out.writeByte(this.address.length); out.write(this.address); } /*------------------------------------------------------------ Static Methods ------------------------------------------------------------*/ /** * <p>Reads an IPAddress from in using the Ganymede database and/or * journal file serialization.</p> */ public static IPAddress readIPAddr(DataInput in) throws IOException { byte[] bytes = new byte[in.readByte()]; in.readFully(bytes); return new IPAddress(bytes); } /** * <p>This method maps an int value between 0 and 255 inclusive * to a legal signed byte value.</p> * * <p>Read u2s as 'unsigned to signed'.</p> */ public final static byte u2s(int x) { if (x < 0 || x > 255) { throw new IllegalArgumentException("Out of range: " + x); } return (byte) (x - 128); } /** * <p>This method maps an array of primitive int values between 0 * and 255 to an array of primitive byte values between -128 and 127 * inclusive, as is used in the internal byte array in * IPAddress.</p> * * <p>Read u2s as 'unsigned to signed'.</p> */ public final static byte[] u2s(int[] octets) { byte[] result = new byte[octets.length]; for (int i = 0; i < octets.length; i++) { if (octets[i] < 0 || octets[i] > 255) { throw new IllegalArgumentException("Out of range: " + octets[i]); } result[i] = (byte) (octets[i] - 128); } return result; } /** * <p>This method maps an array of primitive short values between 0 * and 255 to an array of primitive byte values between -128 and 127 * inclusive, as is used in the internal byte array in * IPAddress.</p> * * <p>Read u2s as 'unsigned to signed'.</p> */ public final static byte[] u2s(short[] octets) { byte[] result = new byte[octets.length]; for (int i = 0; i < octets.length; i++) { if (octets[i] < 0 || octets[i] > 255) { throw new IllegalArgumentException("Out of range: " + octets[i]); } result[i] = (byte) (octets[i] - 128); } return result; } /** * <p>This method maps a u2s-encoded signed byte value (-128 to 127) * to a positive primitive short value between 0 and 255 * inclusive.</p> * * <p>Read s2u as 'signed to unsigned'.</p> */ public final static short s2u(byte b) { return (short) (b + 128); } /** * <p>This method maps a u2s-encoded signed array of primitive byte * values (-128 to 127) to an array of positive primitive short * value between 0 and 255 inclusive.</p> * * <p>Read s2u as 'signed to unsigned'.</p> */ public final static short[] s2u(byte[] b) { short[] result = new short[b.length]; for (int i = 0; i < b.length; i++) { result[i] = (short) (b[i] + 128); } return result; } /** * <p>Returns an IPv4 or IPv6 string, based on the length of the * octets parameter.</p> */ public static String genIPString(int[] octets) { return genIPString(u2s(octets)); } /** * <p>Returns an IPv4 or IPv6 string, based on the length of the * octets parameter.</p> */ public static String genIPString(Byte[] octets) { return genIPString(unwrap(octets)); } /** * <p>Returns an IPv4 or IPv6 string, based on the length of the * octets parameter.</p> */ public static String genIPString(byte[] octets) { if (octets == null) { return null; } if (octets.length == 4) { return IPAddress.genIPV4string(octets); } else if (octets.length == 16) { return IPAddress.genIPV6string(octets); } else { throw new IllegalArgumentException("Wrong number of octets for an IP address: " + octets.length); } } /** * <p>This method takes an IPv4 string in standard format and * generates an array of 4 bytes that the Ganymede server can * accept.</p> */ public static byte[] genIPV4bytes(String input) { byte[] result = new byte[4]; List<String> octets = new ArrayList<String>(); char[] cAry; int length = 0; int dotCount = 0; StringBuilder temp = new StringBuilder(); /* -- */ if (input == null) { throw new IllegalArgumentException("null input"); } /* The string will be of the form 255.255.255.255, with each dot separated element being a 8 bit octet in decimal form. Trailing bytes may be excluded, in which case the bytes will be left as 0. */ // initialize the result array for (int i = 0; i < 4; i++) { result[i] = u2s(0); } input = input.trim(); if (input.equals("")) { return result; } cAry = input.toCharArray(); for (int i = 0; i < cAry.length; i++) { if (!isAllowedV4(cAry[i])) { throw new IllegalArgumentException("Invalid IP address: " + input); } if (cAry[i] == '.') { dotCount++; } } if (dotCount > 3) { throw new IllegalArgumentException("Invalid IP address (too many octets for IPv4): " + input); } while (length < cAry.length) { temp.setLength(0); while (length < cAry.length && cAry[length] != '.') { temp.append(cAry[length++]); } length++; // skip the . octets.add(temp.toString()); } for (int i = 0; i < octets.size(); i++) { result[i] = u2s(Integer.parseInt(octets.get(i))); } return result; } /** * <p>This method generates a standard string representation of an * IPv4 address from an array of 4 octets.</p> */ public static String genIPV4string(Byte[] octets) { return genIPV4string(unwrap(octets)); } /** * <p>This method generates a standard string representation of an * IPv4 address from an array of 4 octets.</p> */ public static String genIPV4string(byte[] octets) { if (octets.length != 4) { throw new IllegalArgumentException("bad number of octets."); } StringBuilder result = new StringBuilder(); result.append(Integer.toString(octets[0] + 128)); result.append("."); result.append(Integer.toString(octets[1] + 128)); result.append("."); result.append(Integer.toString(octets[2] + 128)); result.append("."); result.append(Integer.toString(octets[3] + 128)); return result.toString(); } /** * <p>This method takes an IPv6 string in any of the standard RFC * 4291 formats or a standard IPv4 string and generates an array of * 16 bytes that the Ganymede server can accept as an IPv6 * address.</p> */ public static byte[] genIPV6bytes(String input) { byte[] result = new byte[16]; byte[] ipv4bytes = null; List<String> segments = new ArrayList<String>(); char[] cAry; int length = 0, // how far into the input have we processed? dotCount = 0, // how many dots for the IPv4 portion? colonCount = 0, // how many colons for the IPv6 portion? doublecolon = 0, // how many double colons? tailBytes = 0, // how many trailing bytes do we have? v4v6boundary = 0; // what is the index of the last char of the v6 portion? StringBuilder temp = new StringBuilder(); /* -- */ if (input == null) { throw new IllegalArgumentException("null input"); } /* The string may be in one of 3 principle forms. The first is that of a standard IPv4 address, in which case genIPV6bytes will generate an IPv6 compatible address with the assistance of the genIPV4bytes method. The second is that of a standard IPv6 address, with 8 16 bit segments in hex form, :'s between the segments. A pair of :'s in immediate succession indicates a range of 0 segments have been ommitted. Since only one pair of adjacent :'s may appear in a valid IPv6 address, it is possible by looking at the other segments specified to determine the extent of the collapsed segments and properly insert 0 bytes to fill the collapsed region. The third is that of a mixed IPv6/IPv4 address. In this form, the last 4 bytes of the address may be specified in IPv4 dotted decimal form. */ // initialize the result array for (int i = 0; i < 16; i++) { result[i] = u2s(0); } input = input.trim(); v4v6boundary = input.length(); if (input.equals("") || input.equals("::")) { return result; } cAry = input.toCharArray(); for (int i = 0; i < cAry.length; i++) { if (!isAllowedV6(cAry[i])) { throw new IllegalArgumentException("Invalid IP Address: " + input); } if (cAry[i] == '.') { dotCount++; } if (cAry[i] == ':') { colonCount++; if (i > 0 && (cAry[i-1] == ':')) { doublecolon++; } } } if (dotCount > 3) { throw new IllegalArgumentException("Invalid IP Address (too many dots for a mixed IPv4/IPv6 address): " + input); } if (colonCount > 7) { throw new IllegalArgumentException("Invalid IP Address (too many colons for an IPv6 address): " + input); } if (doublecolon > 1) { throw new IllegalArgumentException("Invalid IP Address (error: more than one double-colon): " + input); } if (dotCount > 0 && colonCount > 6) { throw new IllegalArgumentException("Invalid IP Address (bad mixed IPv4/IPv6 address): " + input); } if (colonCount == 0 && dotCount != 0) { // we've got an IPv4 address where we would like an IPv6 address. Convert it. ipv4bytes = genIPV4bytes(input); result[10] = u2s(255); result[11] = u2s(255); result[12] = ipv4bytes[0]; result[13] = ipv4bytes[1]; result[14] = ipv4bytes[2]; result[15] = ipv4bytes[3]; return result; } if (dotCount > 0) { // we've got a mixed address.. get the v4 bytes from the end. v4v6boundary = input.lastIndexOf(':'); ipv4bytes = genIPV4bytes(input.substring(v4v6boundary + 1)); result[12] = ipv4bytes[0]; result[13] = ipv4bytes[1]; result[14] = ipv4bytes[2]; result[15] = ipv4bytes[3]; tailBytes = 4; } // note that the v4v6boundary will be the length of the input // string if we are processing a pure v6 address while (length < v4v6boundary) { temp.setLength(0); while (length < v4v6boundary && cAry[length] != ':') { temp.append(cAry[length++]); } length++; // skip the : segments.add(temp.toString()); } // okay, we now have a vector of segment strings, with (possibly) // an empty string where we had a pair of colons in succession. Find // out if we have a colon pair, and determine the region that it // is compressing. String tmp; boolean beforeRegion = true; int compressBoundary = 0; for (int i = 0; i < segments.size(); i++) { tmp = segments.get(i); if (tmp.equals("")) { beforeRegion = false; compressBoundary = i; } else if (!beforeRegion) { tailBytes += 2; } } // compressBoundary won't be 0 if we had a leading ::, // because we would have 2 empties in a row. if (compressBoundary == 0) { compressBoundary = segments.size(); } else if (compressBoundary == 1) { // if the :: is leading the string, we want to get rid // of the extra empty string tmp = segments.get(0); if (tmp.equals("")) { segments.remove(0); compressBoundary--; } } int tailOffset = 16 - tailBytes; for (int i = 0; i < compressBoundary; i++) { tmp = segments.get(i); if (tmp.length() < 4) { int l = tmp.length(); for (int j = 4; j > l; j--) { tmp = "0" + tmp; } } if (tmp.equals("")) { throw new Error("logic error"); } result[i * 2] = u2s(Integer.parseInt(tmp.substring(0, 2), 16)); result[(i * 2) + 1] = u2s(Integer.parseInt(tmp.substring(2, 4), 16)); } int x; for (int i = compressBoundary+1; i < segments.size(); i++) { x = i - compressBoundary - 1; tmp = segments.get(i); if (tmp.length() < 4) { int l = tmp.length(); for (int j = 4; j > l; j--) { tmp = "0" + tmp; } } if (tmp.equals("")) { throw new Error("logic error"); } result[tailOffset + (x * 2)] = u2s(Integer.parseInt(tmp.substring(0, 2), 16)); result[tailOffset + (x * 2) + 1] = u2s(Integer.parseInt(tmp.substring(2, 4), 16)); } return result; } /** * <p>This method takes an array of 4 or 16 Byte objects and * generates an optimal RFC 5952 string encoding suitable for * display.</p> */ public static String genIPV6string(Byte[] octets) { return genIPV6string(unwrap(octets)); } /** * <p>This method takes an array of 4 or 16 primitive bytes and * generates an optimal RFC 5952 string encoding suitable for * display.</p> */ public static String genIPV6string(byte[] octets) { StringBuilder result = new StringBuilder(); int[] stanzas; String[] stanzaStrings; int i, j; int loCompress, hiCompress; short absoctets[]; /* -- */ if (octets.length == 4) { result.append("::ffff:"); // this is IPV6's compatibility mode result.append(genIPV4string(octets)); return result.toString(); } if (octets.length != 16) { throw new IllegalArgumentException("bad number of octets."); } absoctets = s2u(octets); // now for the challenge.. stanzas = new int[8]; for (i = 0; i < 8; i++) { stanzas[i] = absoctets[i*2] * 256 + absoctets[(i*2) + 1]; } stanzaStrings = new String[8]; // generate hex strings for each 16 bit sequence for (i = 0; i < 8; i++) { stanzaStrings[i] = Integer.toString(stanzas[i], 16); } // okay, we've got 8 stanzas.. now we have to determine // if we want to collapse any part of it to :: loCompress = hiCompress = -1; i = 0; while (i < 8) { if (i < 7 && (stanzas[i] == 0) && (stanzas[i+1] == 0)) { int localLo, localHi; localLo = i; for (j = i; (j<8) && (stanzas[j] == 0); j++) { // just counting up } localHi = j-1; if (localHi - localLo > hiCompress - loCompress) { hiCompress = localHi; loCompress = localLo; i = localHi; // continue our outer loop after this block } } i++; } // System.err.println("loCompress = " + loCompress); // System.err.println("hiCompress = " + hiCompress); // okay, we've calculated our compression block, if any.. // let's also check to see if we want to represent the // last 4 bytes in dotted decimal IPv4 form if (loCompress == 0 && hiCompress == 5) { // RFC 4291 IPv4-compatible return "::" + Short.toString(absoctets[12]) + "." + Short.toString(absoctets[13]) + "." + Short.toString(absoctets[14]) + "." + Short.toString(absoctets[15]); } else if (loCompress == 0 && hiCompress == 4 && absoctets[10] == 255 && absoctets[11] == 255) { // RFC 4291 IPv4-mapped return "::ffff:" + Short.toString(absoctets[12]) + "." + Short.toString(absoctets[13]) + "." + Short.toString(absoctets[14]) + "." + Short.toString(absoctets[15]); } else if (loCompress == 0 && hiCompress == 3 && absoctets[8] == 255 && absoctets[9] == 255 && absoctets[10] == 0 && absoctets[11] == 0) { // RFC 2765 IPv4-translated return "::ffff:0:" + Short.toString(absoctets[12]) + "." + Short.toString(absoctets[13]) + "." + Short.toString(absoctets[14]) + "." + Short.toString(absoctets[15]); } // nope, we're gonna go all the way in IPv6 form.. if (loCompress != hiCompress) { // we've got a compressed area i = 0; while (i < loCompress) { if (i > 0) { result.append(":"); } result.append(stanzaStrings[i++]); } result.append("::"); j = hiCompress + 1; while (j < 8) { if (j > (hiCompress+1)) { result.append(":"); } result.append(stanzaStrings[j++]); } } else { // no compressed area for (i = 0; i < 8; i++) { if (i > 0) { result.append(":"); } result.append(stanzaStrings[i]); } } return result.toString(); } /** * Determines whether a given character is valid or invalid for an * IPDBField * * @param ch the character which is being tested for its validity */ private static final boolean isAllowedV4(char ch) { return IPv4allowedChars.indexOf(ch) != -1; } /** * Determines whether a given character is valid or invalid for an * IPDBField * * @param ch the character which is being tested for its validity */ private static final boolean isAllowedV6(char ch) { return IPv6allowedChars.indexOf(ch) != -1; } /** * Convenience function to copy an array of primitive byte to an * array of Byte objects. */ private static final Byte[] wrap(byte[] octets) { Byte[] results = new Byte[octets.length]; for (int i = 0; i < results.length; i++) { results[i] = Byte.valueOf(octets[i]); } return results; } /** * Convenience function to copy an array of Byte objects to an * array of primitive bytes. */ private static final byte[] unwrap(Byte[] octets) { byte[] results = new byte[octets.length]; for (int i = 0; i < results.length; i++) { results[i] = octets[i].byteValue(); } return results; } /** * Test rig */ public static void main(String argv[]) { Random rand = new Random(); int[][] testOctets = {{192,168,0,1}, {127,0,0,1}, {0x20,0x01,0xdd,0xdd,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x20,0x01,0x0d,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, {0x20,0x01,0x0d,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01}, {0x20,0x01,0x0d,0xb8,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, {0x20,0x01,0x0d,0xb8,0x85,0xa3,0x00,0x00,0x00,0x00,0x8a,0x2e,0x03,0x70,0x73,0x34}, {0x20,0x01,0x0d,0xb8,0x85,0xa3,0x00,0x00,0xaa,0xaa,0x8a,0x2e,0x03,0x70,0x73,0x34}, {0x20,0x01,0x0d,0xb8,0x85,0xa3,0xaa,0xbb,0xcc,0xdd,0x8a,0x2e,0x03,0x70,0x73,0x34}, {0x20,0x01,0x0d,0xb8,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x70,0x73,0x34}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x03,0x70,0x73,0x34}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x03,0x70,0x73,0x34}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}, {0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, {0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f}, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}}; String[] testStrings = {"10", "10.1", "10.1.4", "192.168.0.1", "127.0.0.1", "::ffff:3.112.115.52", "::", "::1", "::192.168.0.1", "cafe:babe::1", "CAFE:babe::01", "cafe:babe:0000:0000:0000:0000:0000:0001", "f::dead:beef", "f2f2:7474:2e2e:0:3030:0:b2b2:0", "F2F2:7474:2E2E:0000:3030:00:B2B2:0000"}; /* -- */ System.out.println("------------------------------------------------------------"); System.out.println("Testing IPAddress creation from byte arrays"); System.out.println(); for (int i = 0; i < testOctets.length; i++) { IPAddress addr = new IPAddress(testOctets[i]); String first = addr.toString(); String second = new IPAddress(first).toString(); if (addr.isIPv4()) { System.out.println("IPv4 (from octets): " + first); System.out.println("IPv4 (roundtrip ): " + second); } else { System.out.println("IPv6 (from octets): " + first); System.out.println("IPv6 (roundtrip ): " + second); } if (!first.equals(second)) { System.out.println("FAILURE"); } if (!first.equals((new IPAddress(addr.getOctets())).toString())) { System.out.println("FAILURE!"); } } System.out.println("------------------------------------------------------------"); System.out.println("Testing IPAddress creation from strings"); System.out.println(); for (int i = 0; i < testStrings.length; i++) { IPAddress addr = new IPAddress(testStrings[i]); IPAddress readdr = new IPAddress(addr.getBytes()); if (addr.isIPv4()) { System.out.println("IPv4 (orig string): " + testStrings[i]); System.out.println("IPv4 (from string): " + addr); System.out.println("IPv4 (roundtrip ): " + readdr); } else { System.out.println("IPv6 (orig string): " + testStrings[i]); System.out.println("IPv6 (from string): " + addr); System.out.println("IPv6 (roundtrip ): " + readdr); } if (!addr.equals(readdr)) { System.out.println("FAILURE"); } if (!addr.toString().equals((new IPAddress(addr.getOctets())).toString())) { System.out.println("FAILURE!"); } } Byte[] octets = new Byte[16]; for (int i = 0; i < 16; i++) { octets[i] = Byte.valueOf(u2s(0)); } System.out.println("All zero v6 string: " + genIPV6string(octets)); octets[15] = Byte.valueOf(u2s(1)); System.out.println("Trailing 1 string: " + genIPV6string(octets)); byte[] randbytes = new byte[16]; rand.nextBytes(randbytes); for (int i = 4; i < 16; i++) { octets[i] = Byte.valueOf(randbytes[i]); } System.out.println("4 Leading zero rand string: " + genIPV6string(octets)); for (int i = 0; i < 16; i++) { octets[i] = Byte.valueOf(u2s(0)); } rand.nextBytes(randbytes); for (int i = 0; i < 8; i++) { if (rand.nextInt() > 0) { octets[i*2] = Byte.valueOf(randbytes[i]); octets[(i*2)+1] = Byte.valueOf(randbytes[i]); System.out.print("**"); } else { System.out.print("00"); } } System.out.println(); System.out.println("Random compression block string: " + genIPV6string(octets)); for (int i = 0; i < 12; i++) { octets[i] = Byte.valueOf(u2s(0)); } rand.nextBytes(randbytes); for (int i = 12; i < 16; i++) { octets[i] = Byte.valueOf(randbytes[i]); } System.out.println("IPv4 compatible string (A): " + genIPV6string(octets)); for (int i = 0; i < 10; i++) { octets[i] = Byte.valueOf(u2s(0)); } octets[10] = Byte.valueOf(u2s(255)); octets[11] = Byte.valueOf(u2s(255)); rand.nextBytes(randbytes); for (int i = 12; i < 16; i++) { octets[i] = Byte.valueOf(randbytes[i]); } System.out.println("IPv4 compatible string (B): " + genIPV6string(octets)); } }