blob: 4b66a8d4eb5f77cc10fb8a921c88bbf6d77db54d [file] [log] [blame]
Yotam Harcholf3f11152013-09-05 16:47:16 -07001package org.projectfloodlight.openflow.types;
2
Ronald Lifea17892014-07-03 18:36:06 -07003import java.math.BigInteger;
Ronald Licc9c9302014-07-07 00:58:56 -07004import java.net.Inet6Address;
5import java.net.InetAddress;
Andreas Wundsam5f71b412014-02-18 12:56:35 -08006import java.util.Arrays;
Yotam Harcholf3f11152013-09-05 16:47:16 -07007import java.util.regex.Pattern;
8
Andreas Wundsam3700d162014-03-11 04:43:38 -07009import javax.annotation.Nonnull;
10
Yotam Harcholf3f11152013-09-05 16:47:16 -070011import org.jboss.netty.buffer.ChannelBuffer;
12import org.projectfloodlight.openflow.exceptions.OFParseError;
Andreas Wundsam5f71b412014-02-18 12:56:35 -080013
Sovietaced9dfc1ef2014-06-27 11:13:57 -070014import com.google.common.base.Preconditions;
Andreas Wundsam22ba3af2013-10-04 16:00:30 -070015import com.google.common.hash.PrimitiveSink;
Andreas Wundsam85c961f2013-09-29 21:22:12 -070016import com.google.common.primitives.Longs;
17
Yotam Harcholf3f11152013-09-05 16:47:16 -070018/**
19 * IPv6 address object. Instance controlled, immutable. Internal representation:
20 * two 64 bit longs (not that you'd have to know).
21 *
22 * @author Andreas Wundsam <andreas.wundsam@teleteach.de>
23 */
Yotam Harchol4d634682013-09-26 13:21:06 -070024public class IPv6Address extends IPAddress<IPv6Address> {
Yotam Harcholf3f11152013-09-05 16:47:16 -070025 static final int LENGTH = 16;
26 private final long raw1;
27 private final long raw2;
28
Gregor Maier1acb4502013-12-12 11:25:07 -080029 private static final int NOT_A_CIDR_MASK = -1;
30 private static final int CIDR_MASK_CACHE_UNSET = -2;
Gregor Maier5615b6c2013-12-11 22:29:07 -080031 // Must appear before the static IPv4Address constant assignments
32 private volatile int cidrMaskLengthCache = CIDR_MASK_CACHE_UNSET;
33
Andreas Wundsamb75c4ad2013-09-23 14:45:35 -070034 private final static long NONE_VAL1 = 0x0L;
35 private final static long NONE_VAL2 = 0x0L;
36 public static final IPv6Address NONE = new IPv6Address(NONE_VAL1, NONE_VAL2);
37
Gregor Maier5615b6c2013-12-11 22:29:07 -080038
Yotam Harchola289d552013-09-16 10:10:40 -070039 public static final IPv6Address NO_MASK = IPv6Address.of(0xFFFFFFFFFFFFFFFFl, 0xFFFFFFFFFFFFFFFFl);
40 public static final IPv6Address FULL_MASK = IPv6Address.of(0x0, 0x0);
Yotam Harcholf3f11152013-09-05 16:47:16 -070041
Yotam Harchola289d552013-09-16 10:10:40 -070042 private IPv6Address(final long raw1, final long raw2) {
Yotam Harcholf3f11152013-09-05 16:47:16 -070043 this.raw1 = raw1;
44 this.raw2 = raw2;
45 }
46
Yotam Harchol4d634682013-09-26 13:21:06 -070047 @Override
Yotam Harcholeb023dc2013-09-26 15:45:44 -070048 public IPVersion getIpVersion() {
49 return IPVersion.IPv6;
Yotam Harchol4d634682013-09-26 13:21:06 -070050 }
51
Gregor Maier7f987e62013-12-10 19:34:18 -080052
Gregor Maier5615b6c2013-12-11 22:29:07 -080053 private int computeCidrMask64(long raw) {
54 long mask = raw;
55 if (raw == 0)
56 return 0;
Gregor Maier1acb4502013-12-12 11:25:07 -080057 else if (Long.bitCount((~mask) + 1) == 1) {
Gregor Maier5615b6c2013-12-11 22:29:07 -080058 // represent a true CIDR prefix length
59 return Long.bitCount(mask);
60 }
61 else {
62 // Not a true prefix
63 return NOT_A_CIDR_MASK;
64 }
65 }
66
67 private int asCidrMaskLengthInternal() {
68 if (cidrMaskLengthCache == CIDR_MASK_CACHE_UNSET) {
Gregor Maier1acb4502013-12-12 11:25:07 -080069 // No synchronization needed. Writing cidrMaskLengthCache only once
70 if (raw1 == 0 && raw2 == 0) {
71 cidrMaskLengthCache = 0;
72 } else if (raw1 == -1L) {
73 // top half is all 1 bits
74 int tmpLength = computeCidrMask64(raw2);
75 if (tmpLength != NOT_A_CIDR_MASK)
76 tmpLength += 64;
77 cidrMaskLengthCache = tmpLength;
78 } else if (raw2 == 0) {
79 cidrMaskLengthCache = computeCidrMask64(raw1);
80 } else {
81 cidrMaskLengthCache = NOT_A_CIDR_MASK;
Gregor Maier5615b6c2013-12-11 22:29:07 -080082 }
83 }
84 return cidrMaskLengthCache;
85 }
86
87 @Override
88 public boolean isCidrMask() {
89 return asCidrMaskLengthInternal() != NOT_A_CIDR_MASK;
90 }
91
Gregor Maier7f987e62013-12-10 19:34:18 -080092 @Override
93 public int asCidrMaskLength() {
Gregor Maier5615b6c2013-12-11 22:29:07 -080094 if (!isCidrMask()) {
95 throw new IllegalStateException("IP is not a valid CIDR prefix " +
96 "mask " + toString());
Gregor Maier7f987e62013-12-10 19:34:18 -080097 } else {
Gregor Maier5615b6c2013-12-11 22:29:07 -080098 return asCidrMaskLengthInternal();
Gregor Maier7f987e62013-12-10 19:34:18 -080099 }
100 }
101
Aditya Vaja56b8b182014-03-11 13:13:58 -0700102 @Override
103 public boolean isBroadcast() {
104 return this.equals(NO_MASK);
105 }
106
107 @Override
Aditya Vaja98c96e72014-03-11 15:19:01 -0700108 public IPv6Address and(IPv6Address other) {
Sovietaced9dfc1ef2014-06-27 11:13:57 -0700109 Preconditions.checkNotNull(other, "other must not be null");
110
111 IPv6Address otherIp = other;
Aditya Vaja56b8b182014-03-11 13:13:58 -0700112 return IPv6Address.of((raw1 & otherIp.raw1), (raw2 & otherIp.raw2));
113 }
114
115 @Override
Aditya Vaja98c96e72014-03-11 15:19:01 -0700116 public IPv6Address or(IPv6Address other) {
Sovietaced9dfc1ef2014-06-27 11:13:57 -0700117 Preconditions.checkNotNull(other, "other must not be null");
118
119 IPv6Address otherIp = other;
Aditya Vaja56b8b182014-03-11 13:13:58 -0700120 return IPv6Address.of((raw1 | otherIp.raw1), (raw2 | otherIp.raw2));
121 }
122
123 @Override
124 public IPv6Address not() {
125 return IPv6Address.of(~raw1, ~raw2);
126 }
127
Ronald Licc9c9302014-07-07 00:58:56 -0700128 /**
129 * Returns an {@code IPv6Address} object that represents the given
130 * IP address. The argument is in network byte order: the highest
131 * order byte of the address is in {@code address[0]}.
132 * <p>
133 * The address byte array must be 16 bytes long (128 bits long).
134 * <p>
135 * Similar to {@link InetAddress#getByAddress(byte[])}.
136 *
137 * @param address the raw IP address in network byte order
138 * @return an {@code IPv6Address} object that represents the given
139 * raw IP address
140 * @throws NullPointerException if the given address was {@code null}
141 * @throws IllegalArgumentException if the given address was of an invalid
142 * byte array length
143 * @see InetAddress#getByAddress(byte[])
144 */
145 @Nonnull
146 public static IPv6Address of(@Nonnull final byte[] address) {
Sovietaced9dfc1ef2014-06-27 11:13:57 -0700147 Preconditions.checkNotNull(address, "address must not be null");
148
Yotam Harcholf3f11152013-09-05 16:47:16 -0700149 if (address.length != LENGTH) {
150 throw new IllegalArgumentException(
Andreas Wundsamc85b5c52013-09-24 13:01:43 -0700151 "Invalid byte array length for IPv6 address: " + address.length);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700152 }
153
154 long raw1 =
155 (address[0] & 0xFFL) << 56 | (address[1] & 0xFFL) << 48
156 | (address[2] & 0xFFL) << 40 | (address[3] & 0xFFL) << 32
157 | (address[4] & 0xFFL) << 24 | (address[5] & 0xFFL) << 16
158 | (address[6] & 0xFFL) << 8 | (address[7]);
159
160 long raw2 =
161 (address[8] & 0xFFL) << 56 | (address[9] & 0xFFL) << 48
162 | (address[10] & 0xFFL) << 40 | (address[11] & 0xFFL) << 32
163 | (address[12] & 0xFFL) << 24 | (address[13] & 0xFFL) << 16
164 | (address[14] & 0xFFL) << 8 | (address[15]);
165
Yotam Harchola289d552013-09-16 10:10:40 -0700166 return IPv6Address.of(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700167 }
168
169 private static class IPv6Builder {
170 private long raw1, raw2;
171
172 public void setUnsignedShortWord(final int i, final int value) {
173 int shift = 48 - (i % 4) * 16;
174
175 if (value < 0 || value > 0xFFFF)
176 throw new IllegalArgumentException("16 bit word must be in [0, 0xFFFF]");
177
178 if (i >= 0 && i < 4)
179 raw1 = raw1 & ~(0xFFFFL << shift) | (value & 0xFFFFL) << shift;
180 else if (i >= 4 && i < 8)
181 raw2 = raw2 & ~(0xFFFFL << shift) | (value & 0xFFFFL) << shift;
182 else
183 throw new IllegalArgumentException("16 bit word index must be in [0,7]");
184 }
185
Yotam Harchola289d552013-09-16 10:10:40 -0700186 public IPv6Address getIPv6() {
187 return IPv6Address.of(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700188 }
189 }
190
191 private final static Pattern colonPattern = Pattern.compile(":");
192
Ronald Licc9c9302014-07-07 00:58:56 -0700193 /**
194 * Returns an {@code IPv6Address} object that represents the given
195 * IP address. The argument is in the conventional string representation
196 * of IPv6 addresses.
197 * <p>
198 * Expects up to 8 groups of 16-bit hex words seperated by colons
199 * (e.g., 2001:db8:85a3:8d3:1319:8a2e:370:7348).
200 * <p>
201 * Supports zero compression (e.g., 2001:db8::7348).
202 * Does <b>not</b> currently support embedding a dotted-quad IPv4 address
203 * into the IPv6 address (e.g., 2001:db8::192.168.0.1).
Andreas Wundsam3700d162014-03-11 04:43:38 -0700204 *
Ronald Licc9c9302014-07-07 00:58:56 -0700205 * @param string the IP address in the conventional string representation
206 * of IPv6 addresses
207 * @return an {@code IPv6Address} object that represents the given
208 * IP address
209 * @throws NullPointerException if the given string was {@code null}
210 * @throws IllegalArgumentException if the given string was not a valid
211 * IPv6 address
Andreas Wundsam3700d162014-03-11 04:43:38 -0700212 */
213 @Nonnull
214 public static IPv6Address of(@Nonnull final String string) throws IllegalArgumentException {
Sovietaced9dfc1ef2014-06-27 11:13:57 -0700215 Preconditions.checkNotNull(string, "string must not be null");
216
Yotam Harcholf3f11152013-09-05 16:47:16 -0700217 IPv6Builder builder = new IPv6Builder();
218 String[] parts = colonPattern.split(string, -1);
219
220 int leftWord = 0;
221 int leftIndex = 0;
222
223 boolean hitZeroCompression = false;
224
225 for (leftIndex = 0; leftIndex < parts.length; leftIndex++) {
226 String part = parts[leftIndex];
227 if (part.length() == 0) {
228 // hit empty group of zero compression
229 hitZeroCompression = true;
230 break;
231 }
232 builder.setUnsignedShortWord(leftWord++, Integer.parseInt(part, 16));
233 }
234
235 if (hitZeroCompression) {
236 if (leftIndex == 0) {
237 // if colon is at the start, two columns must be at the start,
238 // move to the second empty group
239 leftIndex = 1;
240 if (parts.length < 2 || parts[1].length() > 0)
241 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
242 }
243
244 int rightWord = 7;
245 int rightIndex;
246 for (rightIndex = parts.length - 1; rightIndex > leftIndex; rightIndex--) {
247 String part = parts[rightIndex];
248 if (part.length() == 0)
249 break;
250 builder.setUnsignedShortWord(rightWord--, Integer.parseInt(part, 16));
251 }
252 if (rightIndex == parts.length - 1) {
253 // if colon is at the end, two columns must be at the end, move
254 // to the second empty group
255 if (rightIndex < 1 || parts[rightIndex - 1].length() > 0)
256 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
257 rightIndex--;
258 }
259 if (leftIndex != rightIndex)
260 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
261 } else {
262 if (leftIndex != 8) {
263 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
264 }
265 }
266 return builder.getIPv6();
267 }
268
Ronald Licc9c9302014-07-07 00:58:56 -0700269 /**
270 * Returns an {@code IPv6Address} object that represents the given
271 * IP address. The arguments are the two 64-bit integers representing
272 * the first (higher-order) and second (lower-order) 64-bit blocks
273 * of the IP address.
Andreas Wundsam3700d162014-03-11 04:43:38 -0700274 *
Ronald Licc9c9302014-07-07 00:58:56 -0700275 * @param raw1 the first (higher-order) 64-bit block of the IP address
276 * @param raw2 the second (lower-order) 64-bit block of the IP address
277 * @return an {@code IPv6Address} object that represents the given
278 * raw IP address
Andreas Wundsam3700d162014-03-11 04:43:38 -0700279 */
Ronald Licc9c9302014-07-07 00:58:56 -0700280 @Nonnull
Yotam Harchola289d552013-09-16 10:10:40 -0700281 public static IPv6Address of(final long raw1, final long raw2) {
Andreas Wundsamb75c4ad2013-09-23 14:45:35 -0700282 if(raw1==NONE_VAL1 && raw2 == NONE_VAL2)
283 return NONE;
Yotam Harchola289d552013-09-16 10:10:40 -0700284 return new IPv6Address(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700285 }
286
Ronald Licc9c9302014-07-07 00:58:56 -0700287 /**
288 * Returns an {@code IPv6Address} object that represents the given
289 * IP address. The argument is given as an {@code Inet6Address} object.
290 *
291 * @param address the IP address as an {@code Inet6Address} object
292 * @return an {@code IPv6Address} object that represents the
293 * given IP address
294 * @throws NullPointerException if the given {@code Inet6Address} was
295 * {@code null}
296 */
297 @Nonnull
298 public static IPv6Address of(@Nonnull final Inet6Address address) {
299 Preconditions.checkNotNull(address, "address must not be null");
300 return IPv6Address.of(address.getAddress());
301 }
302
303 /**
304 * Returns an {@code IPv6Address} object that represents the
305 * CIDR subnet mask of the given prefix length.
306 *
307 * @param cidrMaskLength the prefix length of the CIDR subnet mask
308 * (i.e. the number of leading one-bits),
309 * where {@code 0 <= cidrMaskLength <= 128}
310 * @return an {@code IPv6Address} object that represents the
311 * CIDR subnet mask of the given prefix length
312 * @throws IllegalArgumentException if the given prefix length was invalid
313 */
314 @Nonnull
315 public static IPv6Address ofCidrMaskLength(final int cidrMaskLength) {
Ronald Lifea17892014-07-03 18:36:06 -0700316 Preconditions.checkArgument(
317 cidrMaskLength >= 0 && cidrMaskLength <= 128,
318 "Invalid IPv6 CIDR mask length: %s", cidrMaskLength);
319
320 if (cidrMaskLength == 128) {
321 return IPv6Address.NO_MASK;
322 } else if (cidrMaskLength == 0) {
323 return IPv6Address.FULL_MASK;
324 } else {
325 BigInteger mask
326 = BigInteger.ONE.negate().shiftLeft(128 - cidrMaskLength);
327 byte[] maskBytesTemp = mask.toByteArray();
328 byte[] maskBytes;
329 if (maskBytesTemp.length < 16) {
330 maskBytes = new byte[16];
331 System.arraycopy(maskBytesTemp, 0,
332 maskBytes, 16 - maskBytesTemp.length,
333 maskBytesTemp.length);
334 Arrays.fill(maskBytes, 0, 16 - maskBytesTemp.length, (byte)(0xFF));
335 } else if (maskBytesTemp.length > 16) {
336 maskBytes = new byte[16];
337 System.arraycopy(maskBytesTemp, 0, maskBytes, 0, maskBytes.length);
338 } else {
339 maskBytes = maskBytesTemp;
340 }
341 return IPv6Address.of(maskBytes);
342 }
343 }
344
Andreas Wundsam4e2469e2014-02-17 15:32:43 -0800345 private volatile byte[] bytesCache = null;
Yotam Harcholf3f11152013-09-05 16:47:16 -0700346
347 public byte[] getBytes() {
348 if (bytesCache == null) {
349 synchronized (this) {
350 if (bytesCache == null) {
351 bytesCache =
352 new byte[] { (byte) ((raw1 >> 56) & 0xFF),
353 (byte) ((raw1 >> 48) & 0xFF),
354 (byte) ((raw1 >> 40) & 0xFF),
355 (byte) ((raw1 >> 32) & 0xFF),
356 (byte) ((raw1 >> 24) & 0xFF),
357 (byte) ((raw1 >> 16) & 0xFF),
358 (byte) ((raw1 >> 8) & 0xFF),
359 (byte) ((raw1 >> 0) & 0xFF),
360
361 (byte) ((raw2 >> 56) & 0xFF),
362 (byte) ((raw2 >> 48) & 0xFF),
363 (byte) ((raw2 >> 40) & 0xFF),
364 (byte) ((raw2 >> 32) & 0xFF),
365 (byte) ((raw2 >> 24) & 0xFF),
366 (byte) ((raw2 >> 16) & 0xFF),
367 (byte) ((raw2 >> 8) & 0xFF),
368 (byte) ((raw2 >> 0) & 0xFF) };
369 }
370 }
371 }
Andreas Wundsam5f71b412014-02-18 12:56:35 -0800372 return Arrays.copyOf(bytesCache, bytesCache.length);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700373 }
374
375 @Override
376 public int getLength() {
377 return LENGTH;
378 }
379
380 @Override
381 public String toString() {
382 return toString(true, false);
383 }
384
385 public int getUnsignedShortWord(final int i) {
386 if (i >= 0 && i < 4)
387 return (int) ((raw1 >>> (48 - i * 16)) & 0xFFFF);
388 else if (i >= 4 && i < 8)
389 return (int) ((raw2 >>> (48 - (i - 4) * 16)) & 0xFFFF);
390 else
391 throw new IllegalArgumentException("16 bit word index must be in [0,7]");
392 }
393
394 /** get the index of the first word where to apply IPv6 zero compression */
395 public int getZeroCompressStart() {
396 int start = Integer.MAX_VALUE;
397 int maxLength = -1;
398
399 int candidateStart = -1;
400
401 for (int i = 0; i < 8; i++) {
402 if (candidateStart >= 0) {
403 // in a zero octect
404 if (getUnsignedShortWord(i) != 0) {
405 // end of this candidate word
406 int candidateLength = i - candidateStart;
407 if (candidateLength >= maxLength) {
408 start = candidateStart;
409 maxLength = candidateLength;
410 }
411 candidateStart = -1;
412 }
413 } else {
414 // not in a zero octect
415 if (getUnsignedShortWord(i) == 0) {
416 candidateStart = i;
417 }
418 }
419 }
420
421 if (candidateStart >= 0) {
422 int candidateLength = 8 - candidateStart;
423 if (candidateLength >= maxLength) {
424 start = candidateStart;
425 maxLength = candidateLength;
426 }
427 }
428
429 return start;
430 }
431
432 public String toString(final boolean zeroCompression, final boolean leadingZeros) {
433 StringBuilder res = new StringBuilder();
434
435 int compressionStart = zeroCompression ? getZeroCompressStart() : Integer.MAX_VALUE;
436 boolean inCompression = false;
437 boolean colonNeeded = false;
438
439 for (int i = 0; i < 8; i++) {
440 int word = getUnsignedShortWord(i);
441
442 if (word == 0) {
443 if (inCompression)
444 continue;
445 else if (i == compressionStart) {
446 res.append(':').append(':');
447 inCompression = true;
448 colonNeeded = false;
449 continue;
450 }
451 } else {
452 inCompression = false;
453 }
454
455 if (colonNeeded) {
456 res.append(':');
457 colonNeeded = false;
458 }
459
460 res.append(leadingZeros ? String.format("%04x", word) : Integer.toString(word,
461 16));
462 colonNeeded = true;
463 }
464 return res.toString();
465 }
466
467 @Override
468 public int hashCode() {
469 final int prime = 31;
470 int result = 1;
471 result = prime * result + (int) (raw1 ^ (raw1 >>> 32));
472 result = prime * result + (int) (raw2 ^ (raw2 >>> 32));
473 return result;
474 }
475
476 @Override
477 public boolean equals(final Object obj) {
478 if (this == obj)
479 return true;
480 if (obj == null)
481 return false;
482 if (getClass() != obj.getClass())
483 return false;
Yotam Harchola289d552013-09-16 10:10:40 -0700484 IPv6Address other = (IPv6Address) obj;
Yotam Harcholf3f11152013-09-05 16:47:16 -0700485 if (raw1 != other.raw1)
486 return false;
487 if (raw2 != other.raw2)
488 return false;
489 return true;
490 }
491
492 public void write16Bytes(ChannelBuffer c) {
493 c.writeLong(this.raw1);
494 c.writeLong(this.raw2);
495 }
496
Yotam Harchola289d552013-09-16 10:10:40 -0700497 public static IPv6Address read16Bytes(ChannelBuffer c) throws OFParseError {
498 return IPv6Address.of(c.readLong(), c.readLong());
Yotam Harcholf3f11152013-09-05 16:47:16 -0700499 }
500
501 @Override
Yotam Harchola289d552013-09-16 10:10:40 -0700502 public IPv6Address applyMask(IPv6Address mask) {
Aditya Vaja98c96e72014-03-11 15:19:01 -0700503 return and(mask);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700504 }
Andreas Wundsam85c961f2013-09-29 21:22:12 -0700505
506 @Override
507 public int compareTo(IPv6Address o) {
508 int res = Longs.compare(raw1, o.raw1);
509 if(res != 0)
510 return res;
511 else
512 return Longs.compare(raw2, o.raw2);
513 }
Andreas Wundsam22ba3af2013-10-04 16:00:30 -0700514
515 @Override
516 public void putTo(PrimitiveSink sink) {
517 sink.putLong(raw1);
518 sink.putLong(raw2);
519 }
Yotam Harcholf3f11152013-09-05 16:47:16 -0700520}