blob: 83fb31a6729492c8ffdb29e34a50c6bc782bfd10 [file] [log] [blame]
alshabib1f44e8e2014-08-14 15:19:57 -07001package org.projectfloodlight.openflow.types;
2
3import java.util.Arrays;
4import java.util.regex.Pattern;
5
6import javax.annotation.Nonnull;
7
8import org.jboss.netty.buffer.ChannelBuffer;
9import org.projectfloodlight.openflow.exceptions.OFParseError;
10
11import com.google.common.hash.PrimitiveSink;
12import com.google.common.primitives.Longs;
13
14/**
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 */
20public class IPv6Address extends IPAddress<IPv6Address> {
21 static final int LENGTH = 16;
22 private final long raw1;
23 private final long raw2;
24
25 private static final int NOT_A_CIDR_MASK = -1;
26 private static final int CIDR_MASK_CACHE_UNSET = -2;
27 // Must appear before the static IPv4Address constant assignments
28 private volatile int cidrMaskLengthCache = CIDR_MASK_CACHE_UNSET;
29
30 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
34
35 public static final IPv6Address NO_MASK = IPv6Address.of(0xFFFFFFFFFFFFFFFFl, 0xFFFFFFFFFFFFFFFFl);
36 public static final IPv6Address FULL_MASK = IPv6Address.of(0x0, 0x0);
37
38 private IPv6Address(final long raw1, final long raw2) {
39 this.raw1 = raw1;
40 this.raw2 = raw2;
41 }
42
43 @Override
44 public IPVersion getIpVersion() {
45 return IPVersion.IPv6;
46 }
47
48
49 private int computeCidrMask64(long raw) {
50 long mask = raw;
51 if (raw == 0)
52 return 0;
53 else if (Long.bitCount((~mask) + 1) == 1) {
54 // 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) {
65 // 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;
78 }
79 }
80 return cidrMaskLengthCache;
81 }
82
83 @Override
84 public boolean isCidrMask() {
85 return asCidrMaskLengthInternal() != NOT_A_CIDR_MASK;
86 }
87
88 @Override
89 public int asCidrMaskLength() {
90 if (!isCidrMask()) {
91 throw new IllegalStateException("IP is not a valid CIDR prefix " +
92 "mask " + toString());
93 } else {
94 return asCidrMaskLengthInternal();
95 }
96 }
97
98 @Override
99 public boolean isBroadcast() {
100 return this.equals(NO_MASK);
101 }
102
103 @Override
104 public IPv6Address and(IPv6Address other) {
105 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
113 public IPv6Address or(IPv6Address other) {
114 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
126 public static IPv6Address of(final byte[] address) {
127 if (address == null) {
128 throw new NullPointerException("Address must not be null");
129 }
130 if (address.length != LENGTH) {
131 throw new IllegalArgumentException(
132 "Invalid byte array length for IPv6 address: " + address.length);
133 }
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
147 return IPv6Address.of(raw1, raw2);
148 }
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
167 public IPv6Address getIPv6() {
168 return IPv6Address.of(raw1, raw2);
169 }
170 }
171
172 private final static Pattern colonPattern = Pattern.compile(":");
173
174 /** 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 {
190 if (string == null) {
191 throw new NullPointerException("String must not be null");
192 }
193 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
245 /** 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 */
252 public static IPv6Address of(final long raw1, final long raw2) {
253 if(raw1==NONE_VAL1 && raw2 == NONE_VAL2)
254 return NONE;
255 return new IPv6Address(raw1, raw2);
256 }
257
258 private volatile byte[] bytesCache = null;
259
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 }
285 return Arrays.copyOf(bytesCache, bytesCache.length);
286 }
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;
397 IPv6Address other = (IPv6Address) obj;
398 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
410 public static IPv6Address read16Bytes(ChannelBuffer c) throws OFParseError {
411 return IPv6Address.of(c.readLong(), c.readLong());
412 }
413
414 @Override
415 public IPv6Address applyMask(IPv6Address mask) {
416 return and(mask);
417 }
418
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 }
427
428 @Override
429 public void putTo(PrimitiveSink sink) {
430 sink.putLong(raw1);
431 sink.putLong(raw2);
432 }
433}