blob: 2f98cfbd201bb0ac7706a77132f63e7181186afb [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/**
31 * Immutable sequence of bytes, assumed to represent a value in
32 * {@link ByteOrder#BIG_ENDIAN BIG_ENDIAN} order.
33 * <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
43 /*
44 Actual bytes are backed by a byte buffer.
45 The order of a newly-created byte buffer is always BIG_ENDIAN.
46 */
47 private ByteBuffer value;
48
49 /**
50 * Private constructor.
51 * Creates a new byte sequence object backed by the passed ByteBuffer.
52 *
53 * @param value a byte buffer
54 */
55 private ImmutableByteSequence(ByteBuffer value) {
56 this.value = value;
57 // Rewind buffer so it's ready to be read.
58 // No write operation should be performed on it from now on.
59 this.value.rewind();
60 }
61
62 /**
63 * Creates a new immutable byte sequence with the same content and order of
64 * the passed byte array.
65 *
66 * @param original a byte array value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -070067 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -070068 */
69 public static ImmutableByteSequence copyFrom(byte[] original) {
70 checkArgument(original != null && original.length > 0,
71 "Cannot copy from an empty or null array");
72 return new ImmutableByteSequence(
73 ByteBuffer.allocate(original.length).put(original));
74 }
75
76 /**
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070077 * Creates a new immutable byte sequence with the same content and order of
78 * the passed byte array, from/to the given indexes (inclusive).
79 *
80 * @param original a byte array value
Carmelo Casconeb10194c2017-08-23 19:16:51 +020081 * @param fromIdx starting index
82 * @param toIdx ending index
Carmelo Cascone17fc9e42016-05-31 11:29:21 -070083 * @return a new immutable byte sequence
84 */
85 public static ImmutableByteSequence copyFrom(byte[] original, int fromIdx, int toIdx) {
86 checkArgument(original != null && original.length > 0,
87 "Cannot copy from an empty or null array");
88 checkArgument(toIdx >= fromIdx && toIdx < original.length, "invalid indexes");
89 ByteBuffer buffer = ByteBuffer.allocate((toIdx - fromIdx) + 1);
90 for (int i = fromIdx; i <= toIdx; i++) {
91 buffer.put(original[i]);
92 }
93 return new ImmutableByteSequence(buffer);
94 }
95
96 /**
Carmelo Cascone6b32c992016-04-13 11:53:09 -070097 * Creates a new immutable byte sequence copying bytes from the given
98 * ByteBuffer {@link ByteBuffer}. If the byte buffer order is not big-endian
99 * bytes will be copied in reverse order.
100 *
101 * @param original a byte buffer
102 * @return a new byte buffer object
103 */
104 public static ImmutableByteSequence copyFrom(ByteBuffer original) {
105 checkArgument(original != null && original.capacity() > 0,
106 "Cannot copy from an empty or null byte buffer");
107
108 byte[] bytes = new byte[original.capacity()];
109
110 // copy bytes from original buffer
111 original.rewind();
112 original.get(bytes);
113
114 if (original.order() == ByteOrder.LITTLE_ENDIAN) {
115 // FIXME: this can be improved, e.g. read bytes in reverse order from original
116 reverse(bytes);
117 }
118
119 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
120 }
121
122 /**
123 * Creates a new byte sequence of 8 bytes containing the given long value.
124 *
125 * @param original a long value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700126 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700127 */
128 public static ImmutableByteSequence copyFrom(long original) {
129 return new ImmutableByteSequence(
130 ByteBuffer.allocate(Long.BYTES).putLong(original));
131 }
132
133 /**
134 * Creates a new byte sequence of 4 bytes containing the given int value.
135 *
136 * @param original an int value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700137 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700138 */
139 public static ImmutableByteSequence copyFrom(int original) {
140 return new ImmutableByteSequence(
141 ByteBuffer.allocate(Integer.BYTES).putInt(original));
142 }
143
144 /**
145 * Creates a new byte sequence of 2 bytes containing the given short value.
146 *
147 * @param original a short value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700148 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700149 */
150 public static ImmutableByteSequence copyFrom(short original) {
151 return new ImmutableByteSequence(
152 ByteBuffer.allocate(Short.BYTES).putShort(original));
153 }
154
155 /**
156 * Creates a new byte sequence of 1 byte containing the given value.
157 *
158 * @param original a byte value
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700159 * @return a new immutable byte sequence
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700160 */
161 public static ImmutableByteSequence copyFrom(byte original) {
162 return new ImmutableByteSequence(
163 ByteBuffer.allocate(Byte.BYTES).put(original));
164 }
165
166 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700167 * Creates a new byte sequence of the given size where all bits are 0.
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700168 *
169 * @param size number of bytes
170 * @return a new immutable byte sequence
171 */
172 public static ImmutableByteSequence ofZeros(int size) {
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700173 // array is initialized to all 0's by default
174 return new ImmutableByteSequence(ByteBuffer.wrap(new byte[size]));
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700175 }
176
177 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700178 * Creates a new byte sequence of the given size where all bits are 1.
Carmelo Casconeaa8b6292016-04-13 14:27:06 -0700179 *
180 * @param size number of bytes
181 * @return a new immutable byte sequence
182 */
183 public static ImmutableByteSequence ofOnes(int size) {
184 byte[] bytes = new byte[size];
185 Arrays.fill(bytes, (byte) 0xFF);
186 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
187 }
188
189 /**
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700190 * Creates a new byte sequence that is prefixed with specified number of
191 * zeros if val = 0 or ones if val = 0xff.
192 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200193 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700194 * @param prefixBits number of bits in prefix
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200195 * @param val 0 for prefix of zeros; 0xff for prefix of ones
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700196 * @return new immutable byte sequence
197 */
198 static ImmutableByteSequence prefix(int size, long prefixBits, byte val) {
199 checkArgument(val == 0 || val == (byte) 0xff, "Val must be 0 or 0xff");
200 byte[] bytes = new byte[size];
201 int prefixBytes = (int) (prefixBits / Byte.SIZE);
202 Arrays.fill(bytes, 0, prefixBytes, val);
203 Arrays.fill(bytes, prefixBytes, bytes.length, (byte) ~val);
204 int partialBits = (int) (prefixBits % Byte.SIZE);
205 if (partialBits != 0) {
206 bytes[prefixBytes] = val == 0 ?
207 (byte) (0xff >> partialBits) : (byte) (0xff << Byte.SIZE - partialBits);
208 }
209 return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
210 }
211
212 /**
213 * Creates a new byte sequence that is prefixed with specified number of zeros.
214 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200215 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700216 * @param prefixBits number of bits in prefix
217 * @return new immutable byte sequence
218 */
219 public static ImmutableByteSequence prefixZeros(int size, long prefixBits) {
220 return prefix(size, prefixBits, (byte) 0);
221 }
222
223 /**
224 * Creates a new byte sequence that is prefixed with specified number of ones.
225 *
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200226 * @param size number of total bytes
Brian O'Connoraf1d12e2017-08-17 12:21:48 -0700227 * @param prefixBits number of bits in prefix
228 * @return new immutable byte sequence
229 */
230 public static ImmutableByteSequence prefixOnes(int size, long prefixBits) {
231 return prefix(size, prefixBits, (byte) 0xff);
232 }
233
234 /**
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700235 * Returns a view of this sequence as a read-only {@link ByteBuffer}.
236 * <p>
237 * The returned buffer will have position 0, while limit and capacity will
238 * be set to this sequence {@link #size()}. The buffer order will be
239 * big-endian.
240 *
241 * @return a read-only byte buffer
242 */
243 public ByteBuffer asReadOnlyBuffer() {
244 // position, limit and capacity set rewind at constructor
245 return value.asReadOnlyBuffer();
246 }
247
248 /**
249 * Gets the number of bytes in this sequence.
250 *
251 * @return an integer value
252 */
253 public int size() {
254 return this.value.capacity();
255 }
256
257 /**
258 * Creates a new byte array view of this sequence.
259 *
260 * @return a new byte array
261 */
262 public byte[] asArray() {
263 ByteBuffer bb = asReadOnlyBuffer();
264 byte[] bytes = new byte[size()];
265 bb.get(bytes);
266 return bytes;
267 }
268
269 @Override
270 public int hashCode() {
271 return value.hashCode();
272 }
273
274 @Override
275 public boolean equals(Object obj) {
276 if (this == obj) {
277 return true;
278 }
279 if (obj == null || getClass() != obj.getClass()) {
280 return false;
281 }
282 final ImmutableByteSequence other = (ImmutableByteSequence) obj;
283 return Objects.equal(this.value, other.value);
284 }
285
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200286 /**
287 * Returns the index of the most significant bit (MSB), assuming a bit numbering scheme of type "LSB 0", i.e. the
288 * bit numbering starts at zero for the least significant bit (LSB). The MSB index of a byte sequence of zeros will
289 * be -1.
290 * <p>
291 * As an example, the following conditions always hold true:
292 * {@code
293 * ImmutableByteSequence.copyFrom(0).msbIndex() == -1
294 * ImmutableByteSequence.copyFrom(1).msbIndex() == 0
295 * ImmutableByteSequence.copyFrom(2).msbIndex() == 1
296 * ImmutableByteSequence.copyFrom(3).msbIndex() == 1
297 * ImmutableByteSequence.copyFrom(4).msbIndex() == 2
298 * ImmutableByteSequence.copyFrom(512).msbIndex() == 9
299 * }
300 *
301 * @return index of the MSB, -1 if the sequence has all bytes set to 0
302 */
303 public int msbIndex() {
304 int index = (size() * 8) - 1;
305 byteLoop:
306 for (int i = 0; i < size(); i++) {
307 byte b = value.get(i);
308 if (b != 0) {
309 for (int j = 7; j >= 0; j--) {
310 byte mask = (byte) ((1 << j) - 1);
311 if ((b & ~mask) != 0) {
312 break byteLoop;
313 }
314 index--;
315 }
316 }
317 index -= 8;
318 }
319 return index;
320 }
321
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700322 @Override
323 public String toString() {
Carmelo Cascone804c0012016-06-23 13:05:40 -0700324 return HexString.toHexString(value.array());
Carmelo Cascone6b32c992016-04-13 11:53:09 -0700325 }
Carmelo Cascone00a59962017-06-16 17:51:49 +0900326
327 /**
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200328 * Trims or expands the given byte sequence so to fit a given bit-width. When trimming, the operations is deemed to
329 * be safe only if the trimmed bits are zero, i.e. it is safe to trim only when {@code bitWidth > msbIndex()},
330 * otherwise an exception will be thrown. When expanding, the sequence will be padded with zeros. The returned byte
Carmelo Cascone00a59962017-06-16 17:51:49 +0900331 * sequence will have minimum size to contain the given bit-width.
332 *
333 * @param original a byte sequence
334 * @param bitWidth a non-zero positive integer
335 * @return a new byte sequence
336 * @throws ByteSequenceTrimException if the byte sequence cannot be fitted
337 */
338 public static ImmutableByteSequence fit(ImmutableByteSequence original, int bitWidth)
339 throws ByteSequenceTrimException {
340
341 checkNotNull(original, "byte sequence cannot be null");
342 checkArgument(bitWidth > 0, "bit-width must be a non-zero positive integer");
343
344 int newByteWidth = (int) Math.ceil((double) bitWidth / 8);
345
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200346 if (bitWidth == original.size() * 8) {
347 // No need to fit.
348 return original;
349 }
350
351 ByteBuffer newBuffer = ByteBuffer.allocate(newByteWidth);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900352
353 if (newByteWidth > original.size()) {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200354 // Pad extra bytes with 0's.
355 int numPadBytes = newByteWidth - original.size();
356 for (int i = 0; i < numPadBytes; i++) {
357 newBuffer.put((byte) 0x00);
358 }
359 newBuffer.put(original.asReadOnlyBuffer());
360 } else {
361 // Trim sequence.
362 if (bitWidth > original.msbIndex()) {
363 int diff = original.size() - newByteWidth;
364 ByteBuffer originalBuffer = original.asReadOnlyBuffer();
365 for (int i = diff; i < original.size(); i++) {
366 newBuffer.put(originalBuffer.get(i));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900367 }
368 } else {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200369 throw new ByteSequenceTrimException(original, bitWidth);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900370 }
Carmelo Cascone00a59962017-06-16 17:51:49 +0900371 }
372
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200373 return new ImmutableByteSequence(newBuffer);
Carmelo Cascone00a59962017-06-16 17:51:49 +0900374 }
375
376 /**
377 * Signals that a byte sequence cannot be trimmed.
378 */
379 public static class ByteSequenceTrimException extends Exception {
Carmelo Casconeb10194c2017-08-23 19:16:51 +0200380 ByteSequenceTrimException(ImmutableByteSequence original, int bitWidth) {
381 super(format("cannot trim %s into a %d bits long value",
382 original, bitWidth));
Carmelo Cascone00a59962017-06-16 17:51:49 +0900383 }
384 }
Ray Milkeybb23e0b2016-08-02 17:00:21 -0700385}