Carmelo Cascone
Committed by Gerrit Code Review

New utils class for immutable byte sequences

Helpful when dealing with protocol-independent header match and actions

Change-Id: Iccfc6e09a9ea434caccc198f27e8869db42309c9
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
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 +
17 +package org.onlab.util;
18 +
19 +import com.google.common.base.MoreObjects;
20 +import com.google.common.base.Objects;
21 +
22 +import java.nio.ByteBuffer;
23 +import java.nio.ByteOrder;
24 +
25 +import static com.google.common.base.Preconditions.checkArgument;
26 +import static org.apache.commons.lang3.ArrayUtils.reverse;
27 +
28 +/**
29 + * Immutable sequence of bytes, assumed to represent a value in
30 + * {@link ByteOrder#BIG_ENDIAN BIG_ENDIAN} order.
31 + * <p>
32 + * Sequences can be created copying from an already existing representation of a
33 + * sequence of bytes, such as {@link ByteBuffer} or {@code byte[]}; or by
34 + * copying bytes from a primitive data type, such as {@code long}, {@code int}
35 + * or {@code short}. In the first case, bytes are assumed to be already given in
36 + * big-endian order, while in the second case big-endianness is enforced by this
37 + * class.
38 + */
39 +public final class ImmutableByteSequence {
40 +
41 + /*
42 + Actual bytes are backed by a byte buffer.
43 + The order of a newly-created byte buffer is always BIG_ENDIAN.
44 + */
45 + private ByteBuffer value;
46 +
47 + /**
48 + * Private constructor.
49 + * Creates a new byte sequence object backed by the passed ByteBuffer.
50 + *
51 + * @param value a byte buffer
52 + */
53 + private ImmutableByteSequence(ByteBuffer value) {
54 + this.value = value;
55 + // Rewind buffer so it's ready to be read.
56 + // No write operation should be performed on it from now on.
57 + this.value.rewind();
58 + }
59 +
60 + /**
61 + * Creates a new immutable byte sequence with the same content and order of
62 + * the passed byte array.
63 + *
64 + * @param original a byte array value
65 + * @return a new immutable byte buffer
66 + */
67 + public static ImmutableByteSequence copyFrom(byte[] original) {
68 + checkArgument(original != null && original.length > 0,
69 + "Cannot copy from an empty or null array");
70 + return new ImmutableByteSequence(
71 + ByteBuffer.allocate(original.length).put(original));
72 + }
73 +
74 + /**
75 + * Creates a new immutable byte sequence copying bytes from the given
76 + * ByteBuffer {@link ByteBuffer}. If the byte buffer order is not big-endian
77 + * bytes will be copied in reverse order.
78 + *
79 + * @param original a byte buffer
80 + * @return a new byte buffer object
81 + */
82 + public static ImmutableByteSequence copyFrom(ByteBuffer original) {
83 + checkArgument(original != null && original.capacity() > 0,
84 + "Cannot copy from an empty or null byte buffer");
85 +
86 + byte[] bytes = new byte[original.capacity()];
87 +
88 + // copy bytes from original buffer
89 + original.rewind();
90 + original.get(bytes);
91 +
92 + if (original.order() == ByteOrder.LITTLE_ENDIAN) {
93 + // FIXME: this can be improved, e.g. read bytes in reverse order from original
94 + reverse(bytes);
95 + }
96 +
97 + return new ImmutableByteSequence(ByteBuffer.wrap(bytes));
98 + }
99 +
100 + /**
101 + * Creates a new byte sequence of 8 bytes containing the given long value.
102 + *
103 + * @param original a long value
104 + * @return a new immutable byte buffer
105 + */
106 + public static ImmutableByteSequence copyFrom(long original) {
107 + return new ImmutableByteSequence(
108 + ByteBuffer.allocate(Long.BYTES).putLong(original));
109 + }
110 +
111 + /**
112 + * Creates a new byte sequence of 4 bytes containing the given int value.
113 + *
114 + * @param original an int value
115 + * @return a new immutable byte buffer
116 + */
117 + public static ImmutableByteSequence copyFrom(int original) {
118 + return new ImmutableByteSequence(
119 + ByteBuffer.allocate(Integer.BYTES).putInt(original));
120 + }
121 +
122 + /**
123 + * Creates a new byte sequence of 2 bytes containing the given short value.
124 + *
125 + * @param original a short value
126 + * @return a new immutable byte buffer
127 + */
128 + public static ImmutableByteSequence copyFrom(short original) {
129 + return new ImmutableByteSequence(
130 + ByteBuffer.allocate(Short.BYTES).putShort(original));
131 + }
132 +
133 + /**
134 + * Creates a new byte sequence of 1 byte containing the given value.
135 + *
136 + * @param original a byte value
137 + * @return a new immutable byte buffer
138 + */
139 + public static ImmutableByteSequence copyFrom(byte original) {
140 + return new ImmutableByteSequence(
141 + ByteBuffer.allocate(Byte.BYTES).put(original));
142 + }
143 +
144 + /**
145 + * Returns a view of this sequence as a read-only {@link ByteBuffer}.
146 + * <p>
147 + * The returned buffer will have position 0, while limit and capacity will
148 + * be set to this sequence {@link #size()}. The buffer order will be
149 + * big-endian.
150 + *
151 + * @return a read-only byte buffer
152 + */
153 + public ByteBuffer asReadOnlyBuffer() {
154 + // position, limit and capacity set rewind at constructor
155 + return value.asReadOnlyBuffer();
156 + }
157 +
158 + /**
159 + * Gets the number of bytes in this sequence.
160 + *
161 + * @return an integer value
162 + */
163 + public int size() {
164 + return this.value.capacity();
165 + }
166 +
167 + /**
168 + * Creates a new byte array view of this sequence.
169 + *
170 + * @return a new byte array
171 + */
172 + public byte[] asArray() {
173 + ByteBuffer bb = asReadOnlyBuffer();
174 + byte[] bytes = new byte[size()];
175 + bb.get(bytes);
176 + return bytes;
177 + }
178 +
179 + @Override
180 + public int hashCode() {
181 + return value.hashCode();
182 + }
183 +
184 + @Override
185 + public boolean equals(Object obj) {
186 + if (this == obj) {
187 + return true;
188 + }
189 + if (obj == null || getClass() != obj.getClass()) {
190 + return false;
191 + }
192 + final ImmutableByteSequence other = (ImmutableByteSequence) obj;
193 + return Objects.equal(this.value, other.value);
194 + }
195 +
196 + @Override
197 + public String toString() {
198 + return MoreObjects.toStringHelper(this)
199 + .addValue(HexString.toHexString(asArray()))
200 + .toString();
201 + }
202 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
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 +
17 +package org.onlab.util;
18 +
19 +import com.google.common.testing.EqualsTester;
20 +import org.junit.Test;
21 +
22 +import java.nio.ByteBuffer;
23 +import java.nio.ByteOrder;
24 +import java.util.Random;
25 +
26 +import static org.hamcrest.MatcherAssert.assertThat;
27 +import static org.hamcrest.Matchers.equalTo;
28 +import static org.hamcrest.Matchers.is;
29 +
30 +public class ImmutableByteSequenceTest {
31 +
32 + @Test
33 + public void testCopy() throws Exception {
34 +
35 + byte byteValue = (byte) 1;
36 + short shortValue = (short) byteValue;
37 + int intValue = (int) byteValue;
38 + long longValue = (long) byteValue;
39 + byte[] arrayValue = new byte[64];
40 + arrayValue[63] = byteValue;
41 + ByteBuffer bufferValue = ByteBuffer.allocate(64).put(arrayValue);
42 +
43 + ImmutableByteSequence bsByte = ImmutableByteSequence.copyFrom(byteValue);
44 + ImmutableByteSequence bsShort = ImmutableByteSequence.copyFrom(shortValue);
45 + ImmutableByteSequence bsInt = ImmutableByteSequence.copyFrom(intValue);
46 + ImmutableByteSequence bsLong = ImmutableByteSequence.copyFrom(longValue);
47 + ImmutableByteSequence bsArray = ImmutableByteSequence.copyFrom(arrayValue);
48 + ImmutableByteSequence bsBuffer = ImmutableByteSequence.copyFrom(bufferValue);
49 +
50 + assertThat("byte sequence of a byte value must have size 1",
51 + bsByte.size(), is(equalTo(1)));
52 + assertThat("byte sequence of a short value must have size 2",
53 + bsShort.size(), is(equalTo(2)));
54 + assertThat("byte sequence of an int value must have size 4",
55 + bsInt.size(), is(equalTo(4)));
56 + assertThat("byte sequence of a long value must have size 8",
57 + bsLong.size(), is(equalTo(8)));
58 + assertThat("byte sequence of a byte array value must have same size of the array",
59 + bsArray.size(), is(equalTo(arrayValue.length)));
60 + assertThat("byte sequence of a byte buffer value must have same size of the buffer",
61 + bsBuffer.size(), is(equalTo(bufferValue.capacity())));
62 +
63 + String errStr = "incorrect byte sequence value";
64 +
65 + assertThat(errStr, bsByte.asArray()[0], is(equalTo(byteValue)));
66 + assertThat(errStr, bsShort.asArray()[1], is(equalTo(byteValue)));
67 + assertThat(errStr, bsInt.asArray()[3], is(equalTo(byteValue)));
68 + assertThat(errStr, bsLong.asArray()[7], is(equalTo(byteValue)));
69 + assertThat(errStr, bsArray.asArray()[63], is(equalTo(byteValue)));
70 + assertThat(errStr, bsBuffer.asArray()[63], is(equalTo(byteValue)));
71 + }
72 +
73 + @Test
74 + public void testEndianness() throws Exception {
75 +
76 + long longValue = new Random().nextLong();
77 +
78 + // creates a new sequence from a big-endian buffer
79 + ByteBuffer bbBigEndian = ByteBuffer
80 + .allocate(8)
81 + .order(ByteOrder.BIG_ENDIAN)
82 + .putLong(longValue);
83 + ImmutableByteSequence bsBufferCopyBigEndian =
84 + ImmutableByteSequence.copyFrom(bbBigEndian);
85 +
86 + // creates a new sequence from a little-endian buffer
87 + ByteBuffer bbLittleEndian = ByteBuffer
88 + .allocate(8)
89 + .order(ByteOrder.LITTLE_ENDIAN)
90 + .putLong(longValue);
91 + ImmutableByteSequence bsBufferCopyLittleEndian =
92 + ImmutableByteSequence.copyFrom(bbLittleEndian);
93 +
94 + // creates a new sequence from primitive type
95 + ImmutableByteSequence bsLongCopy =
96 + ImmutableByteSequence.copyFrom(longValue);
97 +
98 +
99 + new EqualsTester()
100 + // big-endian byte array cannot be equal to little-endian array
101 + .addEqualityGroup(bbBigEndian.array())
102 + .addEqualityGroup(bbLittleEndian.array())
103 + // all byte sequences must be equal
104 + .addEqualityGroup(bsBufferCopyBigEndian,
105 + bsBufferCopyLittleEndian,
106 + bsLongCopy)
107 + // byte buffer views of all sequences must be equal
108 + .addEqualityGroup(bsBufferCopyBigEndian.asReadOnlyBuffer(),
109 + bsBufferCopyLittleEndian.asReadOnlyBuffer(),
110 + bsLongCopy.asReadOnlyBuffer())
111 + // byte buffer orders of all sequences must be ByteOrder.BIG_ENDIAN
112 + .addEqualityGroup(bsBufferCopyBigEndian.asReadOnlyBuffer().order(),
113 + bsBufferCopyLittleEndian.asReadOnlyBuffer().order(),
114 + bsLongCopy.asReadOnlyBuffer().order(),
115 + ByteOrder.BIG_ENDIAN)
116 + .testEquals();
117 + }
118 +}
...\ No newline at end of file ...\ No newline at end of file