diff --git a/staticlibs/device/android/net/util/Struct.java b/staticlibs/device/android/net/util/Struct.java index 03f5f22b28..841829e502 100644 --- a/staticlibs/device/android/net/util/Struct.java +++ b/staticlibs/device/android/net/util/Struct.java @@ -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 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 int getSize(final Class 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; + } } diff --git a/staticlibs/tests/unit/src/android/net/util/StructTest.java b/staticlibs/tests/unit/src/android/net/util/StructTest.java index 5ca278ac2c..59c94b2beb 100644 --- a/staticlibs/tests/unit/src/android/net/util/StructTest.java +++ b/staticlibs/tests/unit/src/android/net/util/StructTest.java @@ -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 doParsingMessageTest(final String hexString, Class clazz) { + private T doParsingMessageTest(final String hexString, final Class 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)); }