blob: 4794a2414f7f2d124357534587436245c35a0e3b [file] [log] [blame]
Carmelo Cascone6b32c992016-04-13 11:53:09 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Carmelo Cascone6b32c992016-04-13 11:53:09 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onlab.util;
18
Carmelo Cascone6b32c992016-04-13 11:53:09 -070019import com.google.common.base.Objects;
20
21import java.nio.ByteBuffer;
22import java.nio.ByteOrder;
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070023import java.util.Arrays;
Carmelo Cascone6b32c992016-04-13 11:53:09 -070024
25import static com.google.common.base.Preconditions.checkArgument;
Carmelo Cascone00a59962017-06-16 17:51:49 +090026import static com.google.common.base.Preconditions.checkNotNull;
27import static java.lang.String.format;
Carmelo Cascone6b32c992016-04-13 11:53:09 -070028import static org.apache.commons.lang3.ArrayUtils.reverse;
29
30/**
Carmelo Cascone8a571af2018-04-06 23:17:04 -070031 * Immutable sequence of bytes, assumed to represent a value in {@link
32 * ByteOrder#BIG_ENDIAN BIG_ENDIAN} order.
Carmelo Cascone6b32c992016-04-13 11:53:09 -070033 * <p>
34 * Sequences can be created copying from an already existing representation of a
35 * sequence of bytes, such as {@link ByteBuffer} or {@code byte[]}; or by
36 * copying bytes from a primitive data type, such as {@code long}, {@code int}
37 * or {@code short}. In the first case, bytes are assumed to be already given in
38 * big-endian order, while in the second case big-endianness is enforced by this
39 * class.
40 */
41public final class ImmutableByteSequence {
42
Carmelo Cascone8a571af2018-04-06 23:17:04 -070043 private enum BitwiseOp {
44 AND,
45 OR,
46 XOR
47 }
48
Carmelo Cascone6b32c992016-04-13 11:53:09 -070049 /*
50 Actual bytes are backed by a byte buffer.
51 The order of a newly-created byte buffer is always BIG_ENDIAN.
52 */
53 private ByteBuffer value;
54
55 /**
Carmelo Cascone8a571af2018-04-06 23:17:04 -070056 * Private constructor. Creates a new byte sequence object backed by the
57 * passed ByteBuffer.
Carmelo Cascone6b32c992016-04-13 11:53:09 -070058 *
59 * @param value a byte buffer
60 */
61 private ImmutableByteSequence(ByteBuffer value) {
62 this.value = value;
63 // Rewind buffer so it's ready to be read.
64 // No write operation should be performed on it from now on.
65 this.value.rewind();
66 }
67
68 /**
69 * Creates a new immutable byte sequence with the same content and order of
70 * the passed byte array.
71 *
72 * @param original a byte array value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070073 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -070074 */
75 public static ImmutableByteSequence copyFrom(byte[] original) {
76 checkArgument(original != null && original.length > 0,
77 "Cannot copy from an empty or null array");
78 return new ImmutableByteSequence(
79 ByteBuffer.allocate(original.length).put(original));
80 }
81
82 /**
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070083 * Creates a new immutable byte sequence with the same content and order of
84 * the passed byte array, from/to the given indexes (inclusive).
85 *
86 * @param original a byte array value
Carmelo Casconeb10194c2017-08-23 19:16:51 +020087 * @param fromIdx starting index
88 * @param toIdx ending index
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070089 * @return a new immutable byte sequence
90 */
91 public static ImmutableByteSequence copyFrom(byte[] original, int fromIdx, int toIdx) {
92 checkArgument(original != null && original.length > 0,
93 "Cannot copy from an empty or null array");
94 checkArgument(toIdx >= fromIdx && toIdx < original.length, "invalid indexes");
95 ByteBuffer buffer = ByteBuffer.allocate((toIdx - fromIdx) + 1);
96 for (int i = fromIdx; i <= toIdx; i++) {
97 buffer.put(original[i]);
98 }
99 return new ImmutableByteSequence(buffer);
100 }
101
102 /**
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700103 * Creates a new immutable byte sequence copying bytes from the given
104 * ByteBuffer {@link ByteBuffer}. If the byte buffer order is not big-endian
105 * bytes will be copied in reverse order.
106 *
107 * @param original a byte buffer
108 * @return a new byte buffer object
109 */
110 public static ImmutableByteSequence copyFrom(ByteBuffer original) {
111 checkArgument(original != null && original.capacity() > 0,
112 "Cannot copy from an empty or null byte buffer");
113
114 byte[] bytes = new byte[original.capacity()];
115
116 // copy bytes from original buffer
117 original.rewind();
118 original.get(bytes);
119
120 if (original.order() == ByteOrder.LITTLE_ENDIAN) {
121 // FIXME: this can be improved, e.g. read bytes in reverse order from original
122 reverse(bytes);
123 }
124
125 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
126 }
127
128 /**
129 * Creates a new byte sequence of 8 bytes containing the given long value.
130 *
131 * @param original a long value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700132 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700133 */
134 public static ImmutableByteSequence copyFrom(long original) {
135 return new ImmutableByteSequence(
136 ByteBuffer.allocate(Long.BYTES).putLong(original));
137 }
138
139 /**
140 * Creates a new byte sequence of 4 bytes containing the given int value.
141 *
142 * @param original an int value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700143 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700144 */
145 public static ImmutableByteSequence copyFrom(int original) {
146 return new ImmutableByteSequence(
147 ByteBuffer.allocate(Integer.BYTES).putInt(original));
148 }
149
150 /**
151 * Creates a new byte sequence of 2 bytes containing the given short value.
152 *
153 * @param original a short value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700154 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700155 */
156 public static ImmutableByteSequence copyFrom(short original) {
157 return new ImmutableByteSequence(
158 ByteBuffer.allocate(Short.BYTES).putShort(original));
159 }
160
161 /**
162 * Creates a new byte sequence of 1 byte containing the given value.
163 *
164 * @param original a byte value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700165 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700166 */
167 public static ImmutableByteSequence copyFrom(byte original) {
168 return new ImmutableByteSequence(
169 ByteBuffer.allocate(Byte.BYTES).put(original));
170 }
171
172 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700173 * Creates a new byte sequence of the given size where all bits are 0.
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700174 *
175 * @param size number of bytes
176 * @return a new immutable byte sequence
177 */
178 public static ImmutableByteSequence ofZeros(int size) {
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700179 // array is initialized to all 0's by default
180 return new ImmutableByteSequence(ByteBuffer.wrap(new byte[size]));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700181 }
182
183 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700184 * Creates a new byte sequence of the given size where all bits are 1.
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700185 *
186 * @param size number of bytes
187 * @return a new immutable byte sequence
188 */
189 public static ImmutableByteSequence ofOnes(int size) {
190 byte[] bytes = new byte[size];
191 Arrays.fill(bytes, (byte) 0xFF);
192 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
193 }
194
195 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700196 * Creates a new byte sequence that is prefixed with specified number of
197 * zeros if val = 0 or ones if val = 0xff.
198 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200199 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700200 * @param prefixBits number of bits in prefix
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200201 * @param val 0 for prefix of zeros; 0xff for prefix of ones
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700202 * @return new immutable byte sequence
203 */
204 static ImmutableByteSequence prefix(int size, long prefixBits, byte val) {
205 checkArgument(val == 0 || val == (byte) 0xff, "Val must be 0 or 0xff");
206 byte[] bytes = new byte[size];
207 int prefixBytes = (int) (prefixBits / Byte.SIZE);
208 Arrays.fill(bytes, 0, prefixBytes, val);
209 Arrays.fill(bytes, prefixBytes, bytes.length, (byte) ~val);
210 int partialBits = (int) (prefixBits % Byte.SIZE);
211 if (partialBits != 0) {
212 bytes[prefixBytes] = val == 0 ?
213 (byte) (0xff >> partialBits) : (byte) (0xff << Byte.SIZE - partialBits);
214 }
215 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
216 }
217
218 /**
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700219 * Creates a new byte sequence that is prefixed with specified number of
220 * zeros.
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700221 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200222 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700223 * @param prefixBits number of bits in prefix
224 * @return new immutable byte sequence
225 */
226 public static ImmutableByteSequence prefixZeros(int size, long prefixBits) {
227 return prefix(size, prefixBits, (byte) 0);
228 }
229
230 /**
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700231 * Creates a new byte sequence that is prefixed with specified number of
232 * ones.
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700233 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200234 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700235 * @param prefixBits number of bits in prefix
236 * @return new immutable byte sequence
237 */
238 public static ImmutableByteSequence prefixOnes(int size, long prefixBits) {
239 return prefix(size, prefixBits, (byte) 0xff);
240 }
241
242 /**
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700243 * Returns a view of this sequence as a read-only {@link ByteBuffer}.
244 * <p>
245 * The returned buffer will have position 0, while limit and capacity will
246 * be set to this sequence {@link #size()}. The buffer order will be
247 * big-endian.
248 *
249 * @return a read-only byte buffer
250 */
251 public ByteBuffer asReadOnlyBuffer() {
252 // position, limit and capacity set rewind at constructor
253 return value.asReadOnlyBuffer();
254 }
255
256 /**
257 * Gets the number of bytes in this sequence.
258 *
259 * @return an integer value
260 */
261 public int size() {
262 return this.value.capacity();
263 }
264
265 /**
266 * Creates a new byte array view of this sequence.
267 *
268 * @return a new byte array
269 */
270 public byte[] asArray() {
271 ByteBuffer bb = asReadOnlyBuffer();
272 byte[] bytes = new byte[size()];
273 bb.get(bytes);
274 return bytes;
275 }
276
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700277 private ImmutableByteSequence doBitwiseOp(ImmutableByteSequence other, BitwiseOp op) {
278 checkArgument(other != null && this.size() == other.size(),
279 "Other sequence must be non null and with same size as this");
280 byte[] newBytes = new byte[this.size()];
281 byte[] thisBytes = this.asArray();
282 byte[] otherBytes = other.asArray();
283 for (int i = 0; i < this.size(); i++) {
284 switch (op) {
285 case AND:
286 newBytes[i] = (byte) (thisBytes[i] & otherBytes[i]);
287 break;
288 case OR:
289 newBytes[i] = (byte) (thisBytes[i] | otherBytes[i]);
290 break;
291 case XOR:
292 newBytes[i] = (byte) (thisBytes[i] ^ otherBytes[i]);
293 break;
294 default:
295 throw new IllegalArgumentException(
296 "Unknown bitwise operator " + op.name());
297 }
298 }
299 return ImmutableByteSequence.copyFrom(newBytes);
300 }
301
302 /**
303 * Returns a new byte sequence corresponding to the result of a bitwise AND
304 * operation between this sequence and the given other, i.e. {@code this &
305 * other}.
306 *
307 * @param other other byte sequence
308 * @return new byte sequence
309 * @throws IllegalArgumentException if other sequence is null or its size is
310 * different than this sequence size
311 */
312 public ImmutableByteSequence bitwiseAnd(ImmutableByteSequence other) {
313 return doBitwiseOp(other, BitwiseOp.AND);
314 }
315
316 /**
317 * Returns a new byte sequence corresponding to the result of a bitwise OR
318 * operation between this sequence and the given other, i.e. {@code this |
319 * other}.
320 *
321 * @param other other byte sequence
322 * @return new byte sequence
323 * @throws IllegalArgumentException if other sequence is null or its size is
324 * different than this sequence size
325 */
326 public ImmutableByteSequence bitwiseOr(ImmutableByteSequence other) {
327 return doBitwiseOp(other, BitwiseOp.OR);
328 }
329
330 /**
331 * Returns a new byte sequence corresponding to the result of a bitwise XOR
332 * operation between this sequence and the given other, i.e. {@code this ^
333 * other}.
334 *
335 * @param other other byte sequence
336 * @return new byte sequence
337 * @throws IllegalArgumentException if other sequence is null or its size is
338 * different than this sequence size
339 */
340 public ImmutableByteSequence bitwiseXor(ImmutableByteSequence other) {
341 return doBitwiseOp(other, BitwiseOp.XOR);
342 }
343
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700344 @Override
345 public int hashCode() {
346 return value.hashCode();
347 }
348
349 @Override
350 public boolean equals(Object obj) {
351 if (this == obj) {
352 return true;
353 }
354 if (obj == null || getClass() != obj.getClass()) {
355 return false;
356 }
357 final ImmutableByteSequence other = (ImmutableByteSequence) obj;
358 return Objects.equal(this.value, other.value);
359 }
360
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200361 /**
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700362 * Returns the index of the most significant bit (MSB), assuming a bit
363 * numbering scheme of type "LSB 0", i.e. the bit numbering starts at zero
364 * for the least significant bit (LSB). The MSB index of a byte sequence of
365 * zeros will be -1.
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200366 * <p>
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700367 * As an example, the following conditions always hold true: {@code
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200368 * ImmutableByteSequence.copyFrom(0).msbIndex() == -1
369 * ImmutableByteSequence.copyFrom(1).msbIndex() == 0
370 * ImmutableByteSequence.copyFrom(2).msbIndex() == 1
371 * ImmutableByteSequence.copyFrom(3).msbIndex() == 1
372 * ImmutableByteSequence.copyFrom(4).msbIndex() == 2
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700373 * ImmutableByteSequence.copyFrom(512).msbIndex() == 9 }
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200374 *
375 * @return index of the MSB, -1 if the sequence has all bytes set to 0
376 */
377 public int msbIndex() {
378 int index = (size() * 8) - 1;
379 byteLoop:
380 for (int i = 0; i < size(); i++) {
381 byte b = value.get(i);
382 if (b != 0) {
383 for (int j = 7; j >= 0; j--) {
384 byte mask = (byte) ((1 << j) - 1);
385 if ((b & ~mask) != 0) {
386 break byteLoop;
387 }
388 index--;
389 }
390 }
391 index -= 8;
392 }
393 return index;
394 }
395
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800396 /**
397 * Returns a hexadecimal representation of this byte sequence, e.g.
398 * 0xbeef. The length of the returned string is not representative of the
399 * length of the byte sequence, as all padding zeros are removed.
400 *
401 * @return hexadecimal representation
402 */
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700403 @Override
404 public String toString() {
Carmelo Casconeb5324e72018-11-25 02:26:32 -0800405 final String hexValue = HexString
406 .toHexString(value.array(), "")
407 // Remove leading zeros, but leave one if string is all zeros.
408 .replaceFirst("^0+(?!$)", "");
409 return "0x" + hexValue;
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700410 }
Carmelo Cascone00a59962017-06-16 17:51:49 +0900411
412 /**
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700413 * Trims or expands a copy of this byte sequence so to fit the given
414 * bit-width. When trimming, the operations is deemed to be safe only if the
415 * trimmed bits are zero, i.e. it is safe to trim only when {@code bitWidth
416 * > msbIndex()}, otherwise an exception will be thrown. When expanding, the
417 * sequence will be padded with zeros. The returned byte sequence will have
418 * minimum size to contain the given bit-width.
419 *
420 * @param bitWidth a non-zero positive integer
421 * @return a new byte sequence
422 * @throws ByteSequenceTrimException if the byte sequence cannot be fitted
423 */
424 public ImmutableByteSequence fit(int bitWidth) throws ByteSequenceTrimException {
425 return doFit(this, bitWidth);
426 }
427
Carmelo Cascone8a571af2018-04-06 23:17:04 -0700428 private static ImmutableByteSequence doFit(ImmutableByteSequence original,
429 int bitWidth)
Carmelo Cascone00a59962017-06-16 17:51:49 +0900430 throws ByteSequenceTrimException {
431
432 checkNotNull(original, "byte sequence cannot be null");
433 checkArgument(bitWidth > 0, "bit-width must be a non-zero positive integer");
434
435 int newByteWidth = (int) Math.ceil((double) bitWidth / 8);
436
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200437 if (bitWidth == original.size() * 8) {
438 // No need to fit.
439 return original;
440 }
441
442 ByteBuffer newBuffer = ByteBuffer.allocate(newByteWidth);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900443
444 if (newByteWidth > original.size()) {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200445 // Pad extra bytes with 0's.
446 int numPadBytes = newByteWidth - original.size();
447 for (int i = 0; i < numPadBytes; i++) {
448 newBuffer.put((byte) 0x00);
449 }
450 newBuffer.put(original.asReadOnlyBuffer());
451 } else {
452 // Trim sequence.
453 if (bitWidth > original.msbIndex()) {
454 int diff = original.size() - newByteWidth;
455 ByteBuffer originalBuffer = original.asReadOnlyBuffer();
456 for (int i = diff; i < original.size(); i++) {
457 newBuffer.put(originalBuffer.get(i));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900458 }
459 } else {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200460 throw new ByteSequenceTrimException(original, bitWidth);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900461 }
Carmelo Cascone00a59962017-06-16 17:51:49 +0900462 }
463
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200464 return new ImmutableByteSequence(newBuffer);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900465 }
466
467 /**
468 * Signals that a byte sequence cannot be trimmed.
469 */
470 public static class ByteSequenceTrimException extends Exception {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200471 ByteSequenceTrimException(ImmutableByteSequence original, int bitWidth) {
472 super(format("cannot trim %s into a %d bits long value",
473 original, bitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900474 }
475 }
Ray Milkeybb23e0b2016-08-02 17:00:21 -0700476}