blob: 83fb31a6729492c8ffdb29e34a50c6bc782bfd10 [file] [log] [blame]
Yotam Harcholf3f11152013-09-05 16:47:16 -07001package org.projectfloodlight.openflow.types;
2
Andreas Wundsam5f71b412014-02-18 12:56:35 -08003import java.util.Arrays;
Yotam Harcholf3f11152013-09-05 16:47:16 -07004import java.util.regex.Pattern;
5
Andreas Wundsam3700d162014-03-11 04:43:38 -07006import javax.annotation.Nonnull;
7
Yotam Harcholf3f11152013-09-05 16:47:16 -07008import org.jboss.netty.buffer.ChannelBuffer;
9import org.projectfloodlight.openflow.exceptions.OFParseError;
Andreas Wundsam5f71b412014-02-18 12:56:35 -080010
Andreas Wundsam22ba3af2013-10-04 16:00:30 -070011import com.google.common.hash.PrimitiveSink;
Andreas Wundsam85c961f2013-09-29 21:22:12 -070012import com.google.common.primitives.Longs;
13
Yotam Harcholf3f11152013-09-05 16:47:16 -070014/**
15 * IPv6 address object. Instance controlled, immutable. Internal representation:
16 * two 64 bit longs (not that you'd have to know).
17 *
18 * @author Andreas Wundsam <andreas.wundsam@teleteach.de>
19 */
Yotam Harchol4d634682013-09-26 13:21:06 -070020public class IPv6Address extends IPAddress<IPv6Address> {
Yotam Harcholf3f11152013-09-05 16:47:16 -070021 static final int LENGTH = 16;
22 private final long raw1;
23 private final long raw2;
24
Gregor Maier1acb4502013-12-12 11:25:07 -080025 private static final int NOT_A_CIDR_MASK = -1;
26 private static final int CIDR_MASK_CACHE_UNSET = -2;
Gregor Maier5615b6c2013-12-11 22:29:07 -080027 // Must appear before the static IPv4Address constant assignments
28 private volatile int cidrMaskLengthCache = CIDR_MASK_CACHE_UNSET;
29
Andreas Wundsamb75c4ad2013-09-23 14:45:35 -070030 private final static long NONE_VAL1 = 0x0L;
31 private final static long NONE_VAL2 = 0x0L;
32 public static final IPv6Address NONE = new IPv6Address(NONE_VAL1, NONE_VAL2);
33
Gregor Maier5615b6c2013-12-11 22:29:07 -080034
Yotam Harchola289d552013-09-16 10:10:40 -070035 public static final IPv6Address NO_MASK = IPv6Address.of(0xFFFFFFFFFFFFFFFFl, 0xFFFFFFFFFFFFFFFFl);
36 public static final IPv6Address FULL_MASK = IPv6Address.of(0x0, 0x0);
Yotam Harcholf3f11152013-09-05 16:47:16 -070037
Yotam Harchola289d552013-09-16 10:10:40 -070038 private IPv6Address(final long raw1, final long raw2) {
Yotam Harcholf3f11152013-09-05 16:47:16 -070039 this.raw1 = raw1;
40 this.raw2 = raw2;
41 }
42
Yotam Harchol4d634682013-09-26 13:21:06 -070043 @Override
Yotam Harcholeb023dc2013-09-26 15:45:44 -070044 public IPVersion getIpVersion() {
45 return IPVersion.IPv6;
Yotam Harchol4d634682013-09-26 13:21:06 -070046 }
47
Gregor Maier7f987e62013-12-10 19:34:18 -080048
Gregor Maier5615b6c2013-12-11 22:29:07 -080049 private int computeCidrMask64(long raw) {
50 long mask = raw;
51 if (raw == 0)
52 return 0;
Gregor Maier1acb4502013-12-12 11:25:07 -080053 else if (Long.bitCount((~mask) + 1) == 1) {
Gregor Maier5615b6c2013-12-11 22:29:07 -080054 // represent a true CIDR prefix length
55 return Long.bitCount(mask);
56 }
57 else {
58 // Not a true prefix
59 return NOT_A_CIDR_MASK;
60 }
61 }
62
63 private int asCidrMaskLengthInternal() {
64 if (cidrMaskLengthCache == CIDR_MASK_CACHE_UNSET) {
Gregor Maier1acb4502013-12-12 11:25:07 -080065 // No synchronization needed. Writing cidrMaskLengthCache only once
66 if (raw1 == 0 && raw2 == 0) {
67 cidrMaskLengthCache = 0;
68 } else if (raw1 == -1L) {
69 // top half is all 1 bits
70 int tmpLength = computeCidrMask64(raw2);
71 if (tmpLength != NOT_A_CIDR_MASK)
72 tmpLength += 64;
73 cidrMaskLengthCache = tmpLength;
74 } else if (raw2 == 0) {
75 cidrMaskLengthCache = computeCidrMask64(raw1);
76 } else {
77 cidrMaskLengthCache = NOT_A_CIDR_MASK;
Gregor Maier5615b6c2013-12-11 22:29:07 -080078 }
79 }
80 return cidrMaskLengthCache;
81 }
82
83 @Override
84 public boolean isCidrMask() {
85 return asCidrMaskLengthInternal() != NOT_A_CIDR_MASK;
86 }
87
Gregor Maier7f987e62013-12-10 19:34:18 -080088 @Override
89 public int asCidrMaskLength() {
Gregor Maier5615b6c2013-12-11 22:29:07 -080090 if (!isCidrMask()) {
91 throw new IllegalStateException("IP is not a valid CIDR prefix " +
92 "mask " + toString());
Gregor Maier7f987e62013-12-10 19:34:18 -080093 } else {
Gregor Maier5615b6c2013-12-11 22:29:07 -080094 return asCidrMaskLengthInternal();
Gregor Maier7f987e62013-12-10 19:34:18 -080095 }
96 }
97
Aditya Vaja56b8b182014-03-11 13:13:58 -070098 @Override
99 public boolean isBroadcast() {
100 return this.equals(NO_MASK);
101 }
102
103 @Override
Aditya Vaja98c96e72014-03-11 15:19:01 -0700104 public IPv6Address and(IPv6Address other) {
Aditya Vaja56b8b182014-03-11 13:13:58 -0700105 if (other == null) {
106 throw new NullPointerException("Other IP Address must not be null");
107 }
108 IPv6Address otherIp = (IPv6Address) other;
109 return IPv6Address.of((raw1 & otherIp.raw1), (raw2 & otherIp.raw2));
110 }
111
112 @Override
Aditya Vaja98c96e72014-03-11 15:19:01 -0700113 public IPv6Address or(IPv6Address other) {
Aditya Vaja56b8b182014-03-11 13:13:58 -0700114 if (other == null) {
115 throw new NullPointerException("Other IP Address must not be null");
116 }
117 IPv6Address otherIp = (IPv6Address) other;
118 return IPv6Address.of((raw1 | otherIp.raw1), (raw2 | otherIp.raw2));
119 }
120
121 @Override
122 public IPv6Address not() {
123 return IPv6Address.of(~raw1, ~raw2);
124 }
125
Yotam Harchola289d552013-09-16 10:10:40 -0700126 public static IPv6Address of(final byte[] address) {
Gregor Maier1ff55972013-12-11 02:22:56 -0800127 if (address == null) {
128 throw new NullPointerException("Address must not be null");
129 }
Yotam Harcholf3f11152013-09-05 16:47:16 -0700130 if (address.length != LENGTH) {
131 throw new IllegalArgumentException(
Andreas Wundsamc85b5c52013-09-24 13:01:43 -0700132 "Invalid byte array length for IPv6 address: " + address.length);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700133 }
134
135 long raw1 =
136 (address[0] & 0xFFL) << 56 | (address[1] & 0xFFL) << 48
137 | (address[2] & 0xFFL) << 40 | (address[3] & 0xFFL) << 32
138 | (address[4] & 0xFFL) << 24 | (address[5] & 0xFFL) << 16
139 | (address[6] & 0xFFL) << 8 | (address[7]);
140
141 long raw2 =
142 (address[8] & 0xFFL) << 56 | (address[9] & 0xFFL) << 48
143 | (address[10] & 0xFFL) << 40 | (address[11] & 0xFFL) << 32
144 | (address[12] & 0xFFL) << 24 | (address[13] & 0xFFL) << 16
145 | (address[14] & 0xFFL) << 8 | (address[15]);
146
Yotam Harchola289d552013-09-16 10:10:40 -0700147 return IPv6Address.of(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700148 }
149
150 private static class IPv6Builder {
151 private long raw1, raw2;
152
153 public void setUnsignedShortWord(final int i, final int value) {
154 int shift = 48 - (i % 4) * 16;
155
156 if (value < 0 || value > 0xFFFF)
157 throw new IllegalArgumentException("16 bit word must be in [0, 0xFFFF]");
158
159 if (i >= 0 && i < 4)
160 raw1 = raw1 & ~(0xFFFFL << shift) | (value & 0xFFFFL) << shift;
161 else if (i >= 4 && i < 8)
162 raw2 = raw2 & ~(0xFFFFL << shift) | (value & 0xFFFFL) << shift;
163 else
164 throw new IllegalArgumentException("16 bit word index must be in [0,7]");
165 }
166
Yotam Harchola289d552013-09-16 10:10:40 -0700167 public IPv6Address getIPv6() {
168 return IPv6Address.of(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700169 }
170 }
171
172 private final static Pattern colonPattern = Pattern.compile(":");
173
Andreas Wundsam3700d162014-03-11 04:43:38 -0700174 /** parse an IPv6Address from its conventional string representation.
175 * <p>
176 * Expects up to 8 groups of 16-bit hex words seperated by colons
177 * (e.g., 2001:db8:85a3:8d3:1319:8a2e:370:7348).
178 * <p>
179 * Supports zero compression (e.g., 2001:db8::7348).
180 * Does <b>not</b> currently support embedding a dotted-quad IPv4 address
181 * into the IPv6 address (e.g., 2001:db8::192.168.0.1).
182 *
183 * @param string a string representation of an IPv6 address
184 * @return the parsed IPv6 address
185 * @throws NullPointerException if string is null
186 * @throws IllegalArgumentException if string is not a valid IPv6Address
187 */
188 @Nonnull
189 public static IPv6Address of(@Nonnull final String string) throws IllegalArgumentException {
Gregor Maier1ff55972013-12-11 02:22:56 -0800190 if (string == null) {
191 throw new NullPointerException("String must not be null");
192 }
Yotam Harcholf3f11152013-09-05 16:47:16 -0700193 IPv6Builder builder = new IPv6Builder();
194 String[] parts = colonPattern.split(string, -1);
195
196 int leftWord = 0;
197 int leftIndex = 0;
198
199 boolean hitZeroCompression = false;
200
201 for (leftIndex = 0; leftIndex < parts.length; leftIndex++) {
202 String part = parts[leftIndex];
203 if (part.length() == 0) {
204 // hit empty group of zero compression
205 hitZeroCompression = true;
206 break;
207 }
208 builder.setUnsignedShortWord(leftWord++, Integer.parseInt(part, 16));
209 }
210
211 if (hitZeroCompression) {
212 if (leftIndex == 0) {
213 // if colon is at the start, two columns must be at the start,
214 // move to the second empty group
215 leftIndex = 1;
216 if (parts.length < 2 || parts[1].length() > 0)
217 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
218 }
219
220 int rightWord = 7;
221 int rightIndex;
222 for (rightIndex = parts.length - 1; rightIndex > leftIndex; rightIndex--) {
223 String part = parts[rightIndex];
224 if (part.length() == 0)
225 break;
226 builder.setUnsignedShortWord(rightWord--, Integer.parseInt(part, 16));
227 }
228 if (rightIndex == parts.length - 1) {
229 // if colon is at the end, two columns must be at the end, move
230 // to the second empty group
231 if (rightIndex < 1 || parts[rightIndex - 1].length() > 0)
232 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
233 rightIndex--;
234 }
235 if (leftIndex != rightIndex)
236 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
237 } else {
238 if (leftIndex != 8) {
239 throw new IllegalArgumentException("Malformed IPv6 address: " + string);
240 }
241 }
242 return builder.getIPv6();
243 }
244
Andreas Wundsam3700d162014-03-11 04:43:38 -0700245 /** construct an IPv6 adress from two 64 bit integers representing the first and
246 * second 8-byte blocks of the address.
247 *
248 * @param raw1 - the first 8 byte block of the address
249 * @param raw2 - the second 8 byte block of the address
250 * @return the constructed IPv6Address
251 */
Yotam Harchola289d552013-09-16 10:10:40 -0700252 public static IPv6Address of(final long raw1, final long raw2) {
Andreas Wundsamb75c4ad2013-09-23 14:45:35 -0700253 if(raw1==NONE_VAL1 && raw2 == NONE_VAL2)
254 return NONE;
Yotam Harchola289d552013-09-16 10:10:40 -0700255 return new IPv6Address(raw1, raw2);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700256 }
257
Andreas Wundsam4e2469e2014-02-17 15:32:43 -0800258 private volatile byte[] bytesCache = null;
Yotam Harcholf3f11152013-09-05 16:47:16 -0700259
260 public byte[] getBytes() {
261 if (bytesCache == null) {
262 synchronized (this) {
263 if (bytesCache == null) {
264 bytesCache =
265 new byte[] { (byte) ((raw1 >> 56) & 0xFF),
266 (byte) ((raw1 >> 48) & 0xFF),
267 (byte) ((raw1 >> 40) & 0xFF),
268 (byte) ((raw1 >> 32) & 0xFF),
269 (byte) ((raw1 >> 24) & 0xFF),
270 (byte) ((raw1 >> 16) & 0xFF),
271 (byte) ((raw1 >> 8) & 0xFF),
272 (byte) ((raw1 >> 0) & 0xFF),
273
274 (byte) ((raw2 >> 56) & 0xFF),
275 (byte) ((raw2 >> 48) & 0xFF),
276 (byte) ((raw2 >> 40) & 0xFF),
277 (byte) ((raw2 >> 32) & 0xFF),
278 (byte) ((raw2 >> 24) & 0xFF),
279 (byte) ((raw2 >> 16) & 0xFF),
280 (byte) ((raw2 >> 8) & 0xFF),
281 (byte) ((raw2 >> 0) & 0xFF) };
282 }
283 }
284 }
Andreas Wundsam5f71b412014-02-18 12:56:35 -0800285 return Arrays.copyOf(bytesCache, bytesCache.length);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700286 }
287
288 @Override
289 public int getLength() {
290 return LENGTH;
291 }
292
293 @Override
294 public String toString() {
295 return toString(true, false);
296 }
297
298 public int getUnsignedShortWord(final int i) {
299 if (i >= 0 && i < 4)
300 return (int) ((raw1 >>> (48 - i * 16)) & 0xFFFF);
301 else if (i >= 4 && i < 8)
302 return (int) ((raw2 >>> (48 - (i - 4) * 16)) & 0xFFFF);
303 else
304 throw new IllegalArgumentException("16 bit word index must be in [0,7]");
305 }
306
307 /** get the index of the first word where to apply IPv6 zero compression */
308 public int getZeroCompressStart() {
309 int start = Integer.MAX_VALUE;
310 int maxLength = -1;
311
312 int candidateStart = -1;
313
314 for (int i = 0; i < 8; i++) {
315 if (candidateStart >= 0) {
316 // in a zero octect
317 if (getUnsignedShortWord(i) != 0) {
318 // end of this candidate word
319 int candidateLength = i - candidateStart;
320 if (candidateLength >= maxLength) {
321 start = candidateStart;
322 maxLength = candidateLength;
323 }
324 candidateStart = -1;
325 }
326 } else {
327 // not in a zero octect
328 if (getUnsignedShortWord(i) == 0) {
329 candidateStart = i;
330 }
331 }
332 }
333
334 if (candidateStart >= 0) {
335 int candidateLength = 8 - candidateStart;
336 if (candidateLength >= maxLength) {
337 start = candidateStart;
338 maxLength = candidateLength;
339 }
340 }
341
342 return start;
343 }
344
345 public String toString(final boolean zeroCompression, final boolean leadingZeros) {
346 StringBuilder res = new StringBuilder();
347
348 int compressionStart = zeroCompression ? getZeroCompressStart() : Integer.MAX_VALUE;
349 boolean inCompression = false;
350 boolean colonNeeded = false;
351
352 for (int i = 0; i < 8; i++) {
353 int word = getUnsignedShortWord(i);
354
355 if (word == 0) {
356 if (inCompression)
357 continue;
358 else if (i == compressionStart) {
359 res.append(':').append(':');
360 inCompression = true;
361 colonNeeded = false;
362 continue;
363 }
364 } else {
365 inCompression = false;
366 }
367
368 if (colonNeeded) {
369 res.append(':');
370 colonNeeded = false;
371 }
372
373 res.append(leadingZeros ? String.format("%04x", word) : Integer.toString(word,
374 16));
375 colonNeeded = true;
376 }
377 return res.toString();
378 }
379
380 @Override
381 public int hashCode() {
382 final int prime = 31;
383 int result = 1;
384 result = prime * result + (int) (raw1 ^ (raw1 >>> 32));
385 result = prime * result + (int) (raw2 ^ (raw2 >>> 32));
386 return result;
387 }
388
389 @Override
390 public boolean equals(final Object obj) {
391 if (this == obj)
392 return true;
393 if (obj == null)
394 return false;
395 if (getClass() != obj.getClass())
396 return false;
Yotam Harchola289d552013-09-16 10:10:40 -0700397 IPv6Address other = (IPv6Address) obj;
Yotam Harcholf3f11152013-09-05 16:47:16 -0700398 if (raw1 != other.raw1)
399 return false;
400 if (raw2 != other.raw2)
401 return false;
402 return true;
403 }
404
405 public void write16Bytes(ChannelBuffer c) {
406 c.writeLong(this.raw1);
407 c.writeLong(this.raw2);
408 }
409
Yotam Harchola289d552013-09-16 10:10:40 -0700410 public static IPv6Address read16Bytes(ChannelBuffer c) throws OFParseError {
411 return IPv6Address.of(c.readLong(), c.readLong());
Yotam Harcholf3f11152013-09-05 16:47:16 -0700412 }
413
414 @Override
Yotam Harchola289d552013-09-16 10:10:40 -0700415 public IPv6Address applyMask(IPv6Address mask) {
Aditya Vaja98c96e72014-03-11 15:19:01 -0700416 return and(mask);
Yotam Harcholf3f11152013-09-05 16:47:16 -0700417 }
Andreas Wundsam85c961f2013-09-29 21:22:12 -0700418
419 @Override
420 public int compareTo(IPv6Address o) {
421 int res = Longs.compare(raw1, o.raw1);
422 if(res != 0)
423 return res;
424 else
425 return Longs.compare(raw2, o.raw2);
426 }
Andreas Wundsam22ba3af2013-10-04 16:00:30 -0700427
428 @Override
429 public void putTo(PrimitiveSink sink) {
430 sink.putLong(raw1);
431 sink.putLong(raw2);
432 }
Yotam Harcholf3f11152013-09-05 16:47:16 -0700433}