Add new APIs writeToByteBuffer/writeToBytes for generic Struct class.
Split the original U64/UBE64 into two groups: U63/UBE63 which could be represented by long primitive directly and U64/UBE64 which should be represented by BigInteger class. Also fix the endianness-related issue to support both of big-endian or little-endian input ByteBuffer, and writeToBytes API outputs the bytes array in appropriate order then. Bug: 163492391 Test: atest android.net.util.StructTest --rerun-until-failure Change-Id: Ie9c07fac6dcfceb8efdf1d6b56ce6ff1e845f477
This commit is contained in:
parent
1ba696dbf7
commit
f4cb6d6789
2 changed files with 447 additions and 81 deletions
|
|
@ -99,14 +99,16 @@ public class Struct {
|
|||
U8, // unsigned byte, size = 1 byte
|
||||
U16, // unsigned short, size = 2 bytes
|
||||
U32, // unsigned int, size = 4 bytes
|
||||
U63, // unsigned long(MSB: 0), size = 8 bytes
|
||||
U64, // unsigned long, size = 8 bytes
|
||||
S8, // signed byte, size = 1 byte
|
||||
S16, // signed short, size = 2 bytes
|
||||
S32, // signed int, size = 4 bytes
|
||||
S64, // signed long, size = 8 bytes
|
||||
BE16, // unsigned short in network order, size = 2 bytes
|
||||
BE32, // unsigned int in network order, size = 4 bytes
|
||||
BE64, // unsigned long in network order, size = 8 bytes
|
||||
UBE16, // unsigned short in network order, size = 2 bytes
|
||||
UBE32, // unsigned int in network order, size = 4 bytes
|
||||
UBE63, // unsigned long(MSB: 0) in network order, size = 8 bytes
|
||||
UBE64, // unsigned long in network order, size = 8 bytes
|
||||
ByteArray, // byte array with predefined length
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +122,7 @@ public class Struct {
|
|||
* arraysize: The length of byte array.
|
||||
*
|
||||
* Annotation associated with field MUST have order and type properties at least, padding
|
||||
* and arraysize properties depend on the specific usage, if these properties are absence,
|
||||
* and arraysize properties depend on the specific usage, if these properties are absent,
|
||||
* then default value 0 will be applied.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
|
@ -145,37 +147,76 @@ public class Struct {
|
|||
}
|
||||
private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
|
||||
|
||||
private static void checkAnnotationType(final Type type, final Class fieldType) {
|
||||
switch (type) {
|
||||
private static void checkAnnotationType(final Field annotation, final Class fieldType) {
|
||||
switch (annotation.type()) {
|
||||
case U8:
|
||||
case S16:
|
||||
if (fieldType == Short.TYPE) return;
|
||||
break;
|
||||
case U16:
|
||||
case S32:
|
||||
case BE16:
|
||||
case UBE16:
|
||||
if (fieldType == Integer.TYPE) return;
|
||||
break;
|
||||
case U32:
|
||||
case U63:
|
||||
case S64:
|
||||
case BE32:
|
||||
case UBE32:
|
||||
case UBE63:
|
||||
if (fieldType == Long.TYPE) return;
|
||||
break;
|
||||
case U64:
|
||||
case BE64:
|
||||
if (fieldType == BigInteger.class || fieldType == Long.TYPE) return;
|
||||
case UBE64:
|
||||
if (fieldType == BigInteger.class) return;
|
||||
break;
|
||||
case S8:
|
||||
if (fieldType == Byte.TYPE) return;
|
||||
break;
|
||||
case ByteArray:
|
||||
if (fieldType == byte[].class) return;
|
||||
break;
|
||||
if (fieldType != byte[].class) break;
|
||||
if (annotation.arraysize() <= 0) {
|
||||
throw new IllegalArgumentException("Invalid ByteArray size: "
|
||||
+ annotation.arraysize());
|
||||
}
|
||||
return;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type" + type);
|
||||
throw new IllegalArgumentException("Unknown type" + annotation.type());
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
|
||||
+ "for annotation type: " + type);
|
||||
+ " for annotation type: " + annotation.type());
|
||||
}
|
||||
|
||||
private static int getFieldLength(final Field annotation) {
|
||||
int length = 0;
|
||||
switch (annotation.type()) {
|
||||
case U8:
|
||||
case S8:
|
||||
length = 1;
|
||||
break;
|
||||
case U16:
|
||||
case S16:
|
||||
case UBE16:
|
||||
length = 2;
|
||||
break;
|
||||
case U32:
|
||||
case S32:
|
||||
case UBE32:
|
||||
length = 4;
|
||||
break;
|
||||
case U63:
|
||||
case U64:
|
||||
case S64:
|
||||
case UBE63:
|
||||
case UBE64:
|
||||
length = 8;
|
||||
break;
|
||||
case ByteArray:
|
||||
length = annotation.arraysize();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type" + annotation.type());
|
||||
}
|
||||
return length + annotation.padding();
|
||||
}
|
||||
|
||||
private static boolean isStructSubclass(final Class clazz) {
|
||||
|
|
@ -190,7 +231,7 @@ public class Struct {
|
|||
return count;
|
||||
}
|
||||
|
||||
private static boolean matchModifier(final FieldInfo[] fields, boolean immutable) {
|
||||
private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) {
|
||||
for (FieldInfo fi : fields) {
|
||||
if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
|
||||
}
|
||||
|
|
@ -198,8 +239,8 @@ public class Struct {
|
|||
}
|
||||
|
||||
private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
|
||||
return !matchModifier(fields, true /* immutable */)
|
||||
&& !matchModifier(fields, false /* mutable */);
|
||||
return !allFieldsFinal(fields, true /* immutable */)
|
||||
&& !allFieldsFinal(fields, false /* mutable */);
|
||||
}
|
||||
|
||||
private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
|
||||
|
|
@ -211,21 +252,72 @@ public class Struct {
|
|||
return true;
|
||||
}
|
||||
|
||||
private static BigInteger readBigInteger(final ByteBuffer buf) {
|
||||
// The magnitude argument of BigInteger constructor is a byte array in big-endian order.
|
||||
// If ByteBuffer is read in little-endian, reverse the order of the bytes is required;
|
||||
// if ByteBuffer is read in big-endian, then just keep it as-is.
|
||||
/**
|
||||
* Read U64/UBE64 type data from ByteBuffer and output a BigInteger instance.
|
||||
*
|
||||
* @param buf The byte buffer to read.
|
||||
* @param type The annotation type.
|
||||
*
|
||||
* The magnitude argument of BigInteger constructor is a byte array in big-endian order.
|
||||
* If BigInteger data is read from the byte buffer in little-endian, reverse the order of
|
||||
* the bytes is required; if BigInteger data is read from the byte buffer in big-endian,
|
||||
* then just keep it as-is.
|
||||
*/
|
||||
private static BigInteger readBigInteger(final ByteBuffer buf, final Type type) {
|
||||
final byte[] input = new byte[8];
|
||||
boolean reverseBytes = (type == Type.U64 && buf.order() == ByteOrder.LITTLE_ENDIAN);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
input[(buf.order() == ByteOrder.LITTLE_ENDIAN ? input.length - 1 - i : i)] = buf.get();
|
||||
input[reverseBytes ? input.length - 1 - i : i] = buf.get();
|
||||
}
|
||||
return new BigInteger(1, input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last 8 bytes of a byte array. If there are less than 8 bytes,
|
||||
* the first bytes are replaced with zeroes.
|
||||
*/
|
||||
private static byte[] getLast8Bytes(final byte[] input) {
|
||||
final byte[] tmp = new byte[8];
|
||||
System.arraycopy(
|
||||
input,
|
||||
Math.max(0, input.length - 8), // srcPos: read at most last 8 bytes
|
||||
tmp,
|
||||
Math.max(0, 8 - input.length), // dstPos: pad output with that many zeroes
|
||||
Math.min(8, input.length)); // length
|
||||
return tmp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert U64/UBE64 type data interpreted by BigInteger class to bytes array, output are
|
||||
* always 8 bytes.
|
||||
*
|
||||
* @param bigInteger The number to convert.
|
||||
* @param order Indicate ByteBuffer is read as little-endian or big-endian.
|
||||
* @param type The annotation type.
|
||||
*
|
||||
* BigInteger#toByteArray returns a byte array containing the 2's complement representation
|
||||
* of this BigInteger, in big-endian. If annotation type is U64 and ByteBuffer is read as
|
||||
* little-endian, then reversing the order of the bytes is required.
|
||||
*/
|
||||
private static byte[] bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order,
|
||||
final Type type) {
|
||||
final byte[] bigIntegerBytes = bigInteger.toByteArray();
|
||||
final byte[] output = getLast8Bytes(bigIntegerBytes);
|
||||
|
||||
if (type == Type.U64 && order == ByteOrder.LITTLE_ENDIAN) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
byte tmp = output[i];
|
||||
output[i] = output[7 - i];
|
||||
output[7 - i] = tmp;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
|
||||
throws BufferUnderflowException {
|
||||
final Object value;
|
||||
checkAnnotationType(fieldInfo.annotation.type(), fieldInfo.field.getType());
|
||||
checkAnnotationType(fieldInfo.annotation, fieldInfo.field.getType());
|
||||
switch (fieldInfo.annotation.type()) {
|
||||
case U8:
|
||||
value = (short) (buf.get() & 0xFF);
|
||||
|
|
@ -237,11 +329,7 @@ public class Struct {
|
|||
value = (long) (buf.getInt() & 0xFFFFFFFFL);
|
||||
break;
|
||||
case U64:
|
||||
if (fieldInfo.field.getType() == BigInteger.class) {
|
||||
value = readBigInteger(buf);
|
||||
} else {
|
||||
value = buf.getLong();
|
||||
}
|
||||
value = readBigInteger(buf, Type.U64);
|
||||
break;
|
||||
case S8:
|
||||
value = buf.get();
|
||||
|
|
@ -252,34 +340,34 @@ public class Struct {
|
|||
case S32:
|
||||
value = buf.getInt();
|
||||
break;
|
||||
case U63:
|
||||
case S64:
|
||||
value = buf.getLong();
|
||||
break;
|
||||
case BE16:
|
||||
case UBE16:
|
||||
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
|
||||
} else {
|
||||
value = (int) (buf.getShort() & 0xFFFF);
|
||||
}
|
||||
break;
|
||||
case BE32:
|
||||
case UBE32:
|
||||
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
|
||||
} else {
|
||||
value = (long) (buf.getInt() & 0xFFFFFFFFL);
|
||||
}
|
||||
break;
|
||||
case BE64:
|
||||
if (fieldInfo.field.getType() == BigInteger.class) {
|
||||
value = readBigInteger(buf);
|
||||
case UBE63:
|
||||
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
value = Long.reverseBytes(buf.getLong());
|
||||
} else {
|
||||
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
value = Long.reverseBytes(buf.getLong());
|
||||
} else {
|
||||
value = buf.getLong();
|
||||
}
|
||||
value = buf.getLong();
|
||||
}
|
||||
break;
|
||||
case UBE64:
|
||||
value = readBigInteger(buf, Type.UBE64);
|
||||
break;
|
||||
case ByteArray:
|
||||
final byte[] array = new byte[fieldInfo.annotation.arraysize()];
|
||||
buf.get(array);
|
||||
|
|
@ -296,6 +384,72 @@ public class Struct {
|
|||
return value;
|
||||
}
|
||||
|
||||
private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
|
||||
final Object value) throws BufferUnderflowException {
|
||||
switch (fieldInfo.annotation.type()) {
|
||||
case U8:
|
||||
output.put((byte) (((short) value) & 0xFF));
|
||||
break;
|
||||
case U16:
|
||||
output.putShort((short) (((int) value) & 0xFFFF));
|
||||
break;
|
||||
case U32:
|
||||
output.putInt((int) (((long) value) & 0xFFFFFFFFL));
|
||||
break;
|
||||
case U63:
|
||||
output.putLong((long) value);
|
||||
break;
|
||||
case U64:
|
||||
output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.U64));
|
||||
break;
|
||||
case S8:
|
||||
output.put((byte) value);
|
||||
break;
|
||||
case S16:
|
||||
output.putShort((short) value);
|
||||
break;
|
||||
case S32:
|
||||
output.putInt((int) value);
|
||||
break;
|
||||
case S64:
|
||||
output.putLong((long) value);
|
||||
break;
|
||||
case UBE16:
|
||||
if (output.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
output.putShort(Short.reverseBytes((short) (((int) value) & 0xFFFF)));
|
||||
} else {
|
||||
output.putShort((short) (((int) value) & 0xFFFF));
|
||||
}
|
||||
break;
|
||||
case UBE32:
|
||||
if (output.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
output.putInt(Integer.reverseBytes(
|
||||
(int) (((long) value) & 0xFFFFFFFFL)));
|
||||
} else {
|
||||
output.putInt((int) (((long) value) & 0xFFFFFFFFL));
|
||||
}
|
||||
break;
|
||||
case UBE63:
|
||||
if (output.order() == ByteOrder.LITTLE_ENDIAN) {
|
||||
output.putLong(Long.reverseBytes((long) value));
|
||||
} else {
|
||||
output.putLong((long) value);
|
||||
}
|
||||
break;
|
||||
case UBE64:
|
||||
output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
|
||||
break;
|
||||
case ByteArray:
|
||||
output.put((byte[]) value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
|
||||
}
|
||||
|
||||
// padding zero after field value for alignment.
|
||||
for (int i = 0; i < fieldInfo.annotation.padding(); i++) output.put((byte) 0);
|
||||
}
|
||||
|
||||
private static FieldInfo[] getClassFieldInfo(final Class clazz) {
|
||||
if (!isStructSubclass(clazz)) {
|
||||
throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
|
||||
|
|
@ -346,7 +500,7 @@ public class Struct {
|
|||
try {
|
||||
final FieldInfo[] foundFields = getClassFieldInfo(clazz);
|
||||
if (hasBothMutableAndImmutableFields(foundFields)) {
|
||||
throw new IllegalArgumentException("Class has both immutable and mutable fields");
|
||||
throw new IllegalArgumentException("Class has both final and non-final fields");
|
||||
}
|
||||
|
||||
Constructor<?> constructor = null;
|
||||
|
|
@ -373,12 +527,62 @@ public class Struct {
|
|||
fi.field.set(instance, getFieldValue(buf, fi));
|
||||
}
|
||||
return (T) instance;
|
||||
} catch (IllegalAccessException
|
||||
| InvocationTargetException
|
||||
| InstantiationException e) {
|
||||
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
|
||||
throw new IllegalArgumentException("Fail to create a instance from constructor", e);
|
||||
} catch (BufferUnderflowException e) {
|
||||
throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int getSizeInternal(final FieldInfo[] fieldInfos) {
|
||||
int size = 0;
|
||||
for (FieldInfo fi : fieldInfos) {
|
||||
size += getFieldLength(fi.annotation);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
|
||||
for (FieldInfo fi : fieldInfos) {
|
||||
try {
|
||||
putFieldValue(output, fi, fi.field.get(this));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new IllegalArgumentException("Fail to get the field value from instance", e);
|
||||
} catch (BufferUnderflowException e) {
|
||||
throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of Struct subclass object.
|
||||
*/
|
||||
public static <T extends Struct> int getSize(final Class<T> clazz) {
|
||||
final FieldInfo[] fieldInfos = getClassFieldInfo(clazz);
|
||||
return getSizeInternal(fieldInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the parsed Struct subclass object to ByteBuffer.
|
||||
*
|
||||
* @param output ByteBuffer passed-in from the caller.
|
||||
*/
|
||||
public final void writeToByteBuffer(final ByteBuffer output) {
|
||||
final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
|
||||
writeToByteBufferInternal(output, fieldInfos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the parsed Struct subclass object to byte array.
|
||||
*
|
||||
* @param order indicate ByteBuffer is outputted as little-endian or big-endian.
|
||||
*/
|
||||
public final byte[] writeToBytes(final ByteOrder order) {
|
||||
final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
|
||||
final byte[] output = new byte[getSizeInternal(fieldInfos)];
|
||||
final ByteBuffer buffer = ByteBuffer.wrap(output);
|
||||
buffer.order(order);
|
||||
writeToByteBufferInternal(buffer, fieldInfos);
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package android.net.util;
|
|||
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ import org.junit.runner.RunWith;
|
|||
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -46,8 +48,8 @@ public class StructTest {
|
|||
// IPv6, 0 bytes of options, ifindex 15715755: 0x00efcdab, type 134 (RA), code 0, padding.
|
||||
private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
|
||||
|
||||
// BE16: 0xfeff, BE32: 0xfeffffff, BE64: 0xfffffffffffffffe, 0x7effffffffffffff
|
||||
private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "fffffffffffffffe"
|
||||
// UBE16: 0xfeff, UBE32: 0xfeffffff, UBE64: 0xfeffffffffffffff, UBE63: 0x7effffffffffffff
|
||||
private static final String NETWORK_ORDER_MSG = "feff" + "feffffff" + "feffffffffffffff"
|
||||
+ "7effffffffffffff";
|
||||
|
||||
// S8: 0x7f, S16: 0x7fff, S32: 0x7fffffff, S64: 0x7fffffffffffffff
|
||||
|
|
@ -65,9 +67,10 @@ public class StructTest {
|
|||
// PREF64 option, 2001:db8:3:4:5:6::/96, lifetime: 10064
|
||||
private static final String OPT_PREF64 = "2750" + "20010db80003000400050006";
|
||||
|
||||
private <T> T doParsingMessageTest(final String hexString, Class<T> clazz) {
|
||||
private <T> T doParsingMessageTest(final String hexString, final Class<T> clazz,
|
||||
final ByteOrder order) {
|
||||
final ByteBuffer buf = toByteBuffer(hexString);
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
buf.order(order);
|
||||
return Struct.parse(clazz, buf);
|
||||
}
|
||||
|
||||
|
|
@ -102,12 +105,16 @@ public class StructTest {
|
|||
assertEquals(15715755, msg.mIfindex);
|
||||
assertEquals(134, msg.mIcmpType);
|
||||
assertEquals(0, msg.mIcmpCode);
|
||||
|
||||
assertEquals(16, Struct.getSize(HeaderMsgWithConstructor.class));
|
||||
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassWithExplicitConstructor() {
|
||||
final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithConstructor.class);
|
||||
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
|
||||
verifyHeaderParsing(msg);
|
||||
}
|
||||
|
||||
|
|
@ -128,14 +135,18 @@ public class StructTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testClassWithDefaultConstrcutor() {
|
||||
public void testClassWithDefaultConstructor() {
|
||||
final HeaderMsgWithoutConstructor msg = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithoutConstructor.class);
|
||||
HeaderMsgWithoutConstructor.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(10, msg.mFamily);
|
||||
assertEquals(0, msg.mLen);
|
||||
assertEquals(15715755, msg.mIfindex);
|
||||
assertEquals(134, msg.mIcmpType);
|
||||
assertEquals(0, msg.mIcmpCode);
|
||||
|
||||
assertEquals(16, Struct.getSize(HeaderMsgWithoutConstructor.class));
|
||||
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class HeaderMessage {
|
||||
|
|
@ -177,32 +188,36 @@ public class StructTest {
|
|||
}
|
||||
|
||||
static class NetworkOrderMessage extends Struct {
|
||||
@Field(order = 0, type = Type.BE16)
|
||||
final int mBE16;
|
||||
@Field(order = 1, type = Type.BE32)
|
||||
final long mBE32;
|
||||
@Field(order = 2, type = Type.BE64)
|
||||
final BigInteger mBE64;
|
||||
@Field(order = 3, type = Type.BE64)
|
||||
final long mBE63;
|
||||
@Field(order = 0, type = Type.UBE16)
|
||||
final int mUBE16;
|
||||
@Field(order = 1, type = Type.UBE32)
|
||||
final long mUBE32;
|
||||
@Field(order = 2, type = Type.UBE64)
|
||||
final BigInteger mUBE64;
|
||||
@Field(order = 3, type = Type.UBE63)
|
||||
final long mUBE63;
|
||||
|
||||
NetworkOrderMessage(final int be16, final long be32, final BigInteger be64,
|
||||
final long be63) {
|
||||
mBE16 = be16;
|
||||
mBE32 = be32;
|
||||
mBE64 = be64;
|
||||
mBE63 = be63;
|
||||
mUBE16 = be16;
|
||||
mUBE32 = be32;
|
||||
mUBE64 = be64;
|
||||
mUBE63 = be63;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkOrder() {
|
||||
final NetworkOrderMessage msg = doParsingMessageTest(NETWORK_ORDER_MSG,
|
||||
NetworkOrderMessage.class);
|
||||
assertEquals(65279, msg.mBE16);
|
||||
assertEquals(4278190079L, msg.mBE32);
|
||||
assertEquals(new BigInteger("18374686479671623679"), msg.mBE64);
|
||||
assertEquals(9151314442816847871L, msg.mBE63);
|
||||
NetworkOrderMessage.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(65279, msg.mUBE16);
|
||||
assertEquals(4278190079L, msg.mUBE32);
|
||||
assertEquals(new BigInteger("18374686479671623679"), msg.mUBE64);
|
||||
assertEquals(9151314442816847871L, msg.mUBE63);
|
||||
|
||||
assertEquals(22, Struct.getSize(NetworkOrderMessage.class));
|
||||
assertArrayEquals(toByteBuffer(NETWORK_ORDER_MSG).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class UnsignedDataMessage extends Struct {
|
||||
|
|
@ -213,11 +228,11 @@ public class StructTest {
|
|||
@Field(order = 2, type = Type.U32)
|
||||
final long mU32;
|
||||
@Field(order = 3, type = Type.U64)
|
||||
final BigInteger mU64; // represent U64 with BigInteger.
|
||||
@Field(order = 4, type = Type.U64)
|
||||
final long mU63; // represent U63 with long primitive.
|
||||
@Field(order = 5, type = Type.U64)
|
||||
final long mLU64; // represent U64 with long primitive.
|
||||
final BigInteger mU64;
|
||||
@Field(order = 4, type = Type.U63)
|
||||
final long mU63;
|
||||
@Field(order = 5, type = Type.U63)
|
||||
final long mLU64; // represent U64 data with U63 type
|
||||
|
||||
UnsignedDataMessage(final short u8, final int u16, final long u32, final BigInteger u64,
|
||||
final long u63, final long lu64) {
|
||||
|
|
@ -233,13 +248,57 @@ public class StructTest {
|
|||
@Test
|
||||
public void testUnsignedData() {
|
||||
final UnsignedDataMessage msg = doParsingMessageTest(UNSIGNED_DATA,
|
||||
UnsignedDataMessage.class);
|
||||
UnsignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(255, msg.mU8);
|
||||
assertEquals(65535, msg.mU16);
|
||||
assertEquals(4294967295L, msg.mU32);
|
||||
assertEquals(new BigInteger("18446744073709551615"), msg.mU64);
|
||||
assertEquals(9223372036854775807L, msg.mU63);
|
||||
assertEquals(-1L, msg.mLU64);
|
||||
|
||||
assertEquals(31, Struct.getSize(UnsignedDataMessage.class));
|
||||
assertArrayEquals(toByteBuffer(UNSIGNED_DATA).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class U64DataMessage extends Struct {
|
||||
@Field(order = 0, type = Type.U64) long mU64;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidType_U64WithLongPrimitive() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> Struct.parse(U64DataMessage.class, toByteBuffer("ffffffffffffffff")));
|
||||
}
|
||||
|
||||
// BigInteger U64: 0x0000000000001234, BigInteger UBE64: 0x0000000000001234, BigInteger U64: 0
|
||||
private static final String SMALL_VALUE_BIGINTEGER = "3412000000000000" + "0000000000001234"
|
||||
+ "0000000000000000";
|
||||
|
||||
static class SmallValueBigInteger extends Struct {
|
||||
@Field(order = 0, type = Type.U64) final BigInteger mSmallValue;
|
||||
@Field(order = 1, type = Type.UBE64) final BigInteger mBSmallValue;
|
||||
@Field(order = 2, type = Type.U64) final BigInteger mZero;
|
||||
|
||||
SmallValueBigInteger(final BigInteger smallValue, final BigInteger bSmallValue,
|
||||
final BigInteger zero) {
|
||||
mSmallValue = smallValue;
|
||||
mBSmallValue = bSmallValue;
|
||||
mZero = zero;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBigIntegerSmallValueOrZero() {
|
||||
final SmallValueBigInteger msg = doParsingMessageTest(SMALL_VALUE_BIGINTEGER,
|
||||
SmallValueBigInteger.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(new BigInteger("4660"), msg.mSmallValue);
|
||||
assertEquals(new BigInteger("4660"), msg.mBSmallValue);
|
||||
assertEquals(new BigInteger("0"), msg.mZero);
|
||||
|
||||
assertEquals(24, Struct.getSize(SmallValueBigInteger.class));
|
||||
assertArrayEquals(toByteBuffer(SMALL_VALUE_BIGINTEGER).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class SignedDataMessage extends Struct {
|
||||
|
|
@ -262,21 +321,30 @@ public class StructTest {
|
|||
|
||||
@Test
|
||||
public void testSignedPositiveData() {
|
||||
final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class);
|
||||
final SignedDataMessage msg = doParsingMessageTest(SIGNED_DATA, SignedDataMessage.class,
|
||||
ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(127, msg.mS8);
|
||||
assertEquals(32767, msg.mS16);
|
||||
assertEquals(2147483647, msg.mS32);
|
||||
assertEquals(9223372036854775807L, msg.mS64);
|
||||
|
||||
assertEquals(15, Struct.getSize(SignedDataMessage.class));
|
||||
assertArrayEquals(toByteBuffer(SIGNED_DATA).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignedNegativeData() {
|
||||
final SignedDataMessage msg = doParsingMessageTest(SIGNED_NEGATIVE_DATA,
|
||||
SignedDataMessage.class);
|
||||
SignedDataMessage.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(-127, msg.mS8);
|
||||
assertEquals(-32767, msg.mS16);
|
||||
assertEquals(-2147483647, msg.mS32);
|
||||
assertEquals(-9223372036854775807L, msg.mS64);
|
||||
|
||||
assertEquals(15, Struct.getSize(SignedDataMessage.class));
|
||||
assertArrayEquals(toByteBuffer(SIGNED_NEGATIVE_DATA).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class HeaderMessageWithDuplicateOrder extends Struct {
|
||||
|
|
@ -360,7 +428,7 @@ public class StructTest {
|
|||
}
|
||||
|
||||
static class PrefixMessage extends Struct {
|
||||
@Field(order = 0, type = Type.BE16)
|
||||
@Field(order = 0, type = Type.UBE16)
|
||||
final int mLifetime;
|
||||
@Field(order = 1, type = Type.ByteArray, arraysize = 12)
|
||||
final byte[] mPrefix;
|
||||
|
|
@ -379,11 +447,35 @@ public class StructTest {
|
|||
final IpPrefix prefix = new IpPrefix(addr, 96);
|
||||
assertEquals(10064, msg.mLifetime);
|
||||
assertTrue(prefix.equals(new IpPrefix("2001:db8:3:4:5:6::/96")));
|
||||
|
||||
assertEquals(14, Struct.getSize(PrefixMessage.class));
|
||||
assertArrayEquals(toByteBuffer(OPT_PREF64).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class PrefixMessageWithZeroLengthArray extends Struct {
|
||||
@Field(order = 0, type = Type.UBE16)
|
||||
final int mLifetime;
|
||||
@Field(order = 1, type = Type.ByteArray, arraysize = 0)
|
||||
final byte[] mPrefix;
|
||||
|
||||
PrefixMessageWithZeroLengthArray(final int lifetime, final byte[] prefix) {
|
||||
mLifetime = lifetime;
|
||||
mPrefix = prefix;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidClass_ZeroLengthByteArray() {
|
||||
final ByteBuffer buf = toByteBuffer(OPT_PREF64);
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> Struct.parse(PrefixMessageWithZeroLengthArray.class, buf));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrefixArrayField() throws Exception {
|
||||
final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class);
|
||||
final PrefixMessage msg = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
|
||||
ByteOrder.LITTLE_ENDIAN);
|
||||
verifyPrefixByteArrayParsing(msg);
|
||||
}
|
||||
|
||||
|
|
@ -441,12 +533,16 @@ public class StructTest {
|
|||
@Test
|
||||
public void testStaticConstantField() {
|
||||
final HeaderMsgWithStaticConstant msg = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithStaticConstant.class);
|
||||
HeaderMsgWithStaticConstant.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(10, msg.mFamily);
|
||||
assertEquals(0, msg.mLen);
|
||||
assertEquals(15715755, msg.mIfindex);
|
||||
assertEquals(134, msg.mIcmpType);
|
||||
assertEquals(0, msg.mIcmpCode);
|
||||
|
||||
assertEquals(16, Struct.getSize(HeaderMsgWithStaticConstant.class));
|
||||
assertArrayEquals(toByteBuffer(HDR_EMPTY).array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
static class MismatchedConstructor extends Struct {
|
||||
|
|
@ -481,23 +577,89 @@ public class StructTest {
|
|||
@Test
|
||||
public void testClassWithTwoConstructors() {
|
||||
final ClassWithTwoConstructors msg = doParsingMessageTest("1234" + "5678",
|
||||
ClassWithTwoConstructors.class);
|
||||
ClassWithTwoConstructors.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertEquals(13330 /* 0x3412 */, msg.mInt1);
|
||||
assertEquals(30806 /* 0x7856 */, msg.mInt2);
|
||||
|
||||
assertEquals(4, Struct.getSize(ClassWithTwoConstructors.class));
|
||||
assertArrayEquals(toByteBuffer("1234" + "5678").array(),
|
||||
msg.writeToBytes(ByteOrder.LITTLE_ENDIAN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidOutputByteBuffer_ZeroCapacity() {
|
||||
final ByteBuffer output = ByteBuffer.allocate(0);
|
||||
output.order(ByteOrder.LITTLE_ENDIAN);
|
||||
final HeaderMsgWithConstructor msg = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
|
||||
assertThrows(BufferOverflowException.class, () -> msg.writeToByteBuffer(output));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsecutiveWrites() {
|
||||
final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
|
||||
final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
|
||||
ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
int size = Struct.getSize(HeaderMsgWithConstructor.class)
|
||||
+ Struct.getSize(PrefixMessage.class);
|
||||
final ByteBuffer output = ByteBuffer.allocate(size);
|
||||
output.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
msg1.writeToByteBuffer(output);
|
||||
msg2.writeToByteBuffer(output);
|
||||
output.flip();
|
||||
|
||||
final ByteBuffer concat = ByteBuffer.allocate(size).put(toByteBuffer(HDR_EMPTY))
|
||||
.put(toByteBuffer(OPT_PREF64));
|
||||
assertArrayEquals(output.array(), concat.array());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClassesParsedFromCache() throws Exception {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
final HeaderMsgWithConstructor msg1 =
|
||||
doParsingMessageTest(HDR_EMPTY, HeaderMsgWithConstructor.class);
|
||||
final HeaderMsgWithConstructor msg1 = doParsingMessageTest(HDR_EMPTY,
|
||||
HeaderMsgWithConstructor.class, ByteOrder.LITTLE_ENDIAN);
|
||||
verifyHeaderParsing(msg1);
|
||||
|
||||
final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class);
|
||||
final PrefixMessage msg2 = doParsingMessageTest(OPT_PREF64, PrefixMessage.class,
|
||||
ByteOrder.LITTLE_ENDIAN);
|
||||
verifyPrefixByteArrayParsing(msg2);
|
||||
}
|
||||
}
|
||||
|
||||
static class BigEndianDataMessage extends Struct {
|
||||
@Field(order = 0, type = Type.S32) int mInt1;
|
||||
@Field(order = 1, type = Type.S32) int mInt2;
|
||||
@Field(order = 2, type = Type.UBE16) int mInt3;
|
||||
@Field(order = 3, type = Type.U16) int mInt4;
|
||||
@Field(order = 4, type = Type.U64) BigInteger mBigInteger1;
|
||||
@Field(order = 5, type = Type.UBE64) BigInteger mBigInteger2;
|
||||
@Field(order = 6, type = Type.S64) long mLong;
|
||||
}
|
||||
|
||||
private static final String BIG_ENDIAN_DATA = "00000001" + "fffffffe" + "fffe" + "fffe"
|
||||
+ "ff00004500002301" + "ff00004500002301" + "ff00004500002301";
|
||||
|
||||
@Test
|
||||
public void testBigEndianByteBuffer() {
|
||||
final BigEndianDataMessage msg = doParsingMessageTest(BIG_ENDIAN_DATA,
|
||||
BigEndianDataMessage.class, ByteOrder.BIG_ENDIAN);
|
||||
|
||||
assertEquals(1, msg.mInt1);
|
||||
assertEquals(-2, msg.mInt2);
|
||||
assertEquals(65534, msg.mInt3);
|
||||
assertEquals(65534, msg.mInt4);
|
||||
assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger1);
|
||||
assertEquals(new BigInteger("18374686776024376065"), msg.mBigInteger2);
|
||||
assertEquals(0xff00004500002301L, msg.mLong);
|
||||
|
||||
assertEquals(36, Struct.getSize(BigEndianDataMessage.class));
|
||||
assertArrayEquals(toByteBuffer(BIG_ENDIAN_DATA).array(),
|
||||
msg.writeToBytes(ByteOrder.BIG_ENDIAN));
|
||||
}
|
||||
|
||||
private ByteBuffer toByteBuffer(final String hexString) {
|
||||
return ByteBuffer.wrap(HexDump.hexStringToByteArray(hexString));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue