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
diff --git a/staticlibs/device/android/net/util/Struct.java b/staticlibs/device/android/net/util/Struct.java
index 03f5f22..841829e 100644
--- a/staticlibs/device/android/net/util/Struct.java
+++ b/staticlibs/device/android/net/util/Struct.java
@@ -99,14 +99,16 @@
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 @@
* 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 @@
}
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;
+ }
}