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
588 lines
23 KiB
Java
588 lines
23 KiB
Java
/*
|
|
* Copyright (C) 2020 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.net.util;
|
|
|
|
import android.annotation.NonNull;
|
|
|
|
import java.lang.annotation.ElementType;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.lang.annotation.Target;
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Modifier;
|
|
import java.math.BigInteger;
|
|
import java.nio.BufferUnderflowException;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
/**
|
|
* Define a generic class that helps to parse the structured message.
|
|
*
|
|
* Example usage:
|
|
*
|
|
* // C-style NduserOption message header definition in the kernel:
|
|
* struct nduseroptmsg {
|
|
* unsigned char nduseropt_family;
|
|
* unsigned char nduseropt_pad1;
|
|
* unsigned short nduseropt_opts_len;
|
|
* int nduseropt_ifindex;
|
|
* __u8 nduseropt_icmp_type;
|
|
* __u8 nduseropt_icmp_code;
|
|
* unsigned short nduseropt_pad2;
|
|
* unsigned int nduseropt_pad3;
|
|
* }
|
|
*
|
|
* - Declare a subclass with explicit constructor or not which extends from this class to parse
|
|
* NduserOption header from raw bytes array.
|
|
*
|
|
* - Option w/ explicit constructor:
|
|
* static class NduserOptHeaderMessage extends Struct {
|
|
* @Field(order = 0, type = Type.U8, padding = 1)
|
|
* final short family;
|
|
* @Field(order = 1, type = Type.U16)
|
|
* final int len;
|
|
* @Field(order = 2, type = Type.S32)
|
|
* final int ifindex;
|
|
* @Field(order = 3, type = Type.U8)
|
|
* final short type;
|
|
* @Field(order = 4, type = Type.U8, padding = 6)
|
|
* final short code;
|
|
*
|
|
* NduserOptHeaderMessage(final short family, final int len, final int ifindex,
|
|
* final short type, final short code) {
|
|
* this.family = family;
|
|
* this.len = len;
|
|
* this.ifindex = ifindex;
|
|
* this.type = type;
|
|
* this.code = code;
|
|
* }
|
|
* }
|
|
*
|
|
* - Option w/o explicit constructor:
|
|
* static class NduserOptHeaderMessage extends Struct {
|
|
* @Field(order = 0, type = Type.U8, padding = 1)
|
|
* short family;
|
|
* @Field(order = 1, type = Type.U16)
|
|
* int len;
|
|
* @Field(order = 2, type = Type.S32)
|
|
* int ifindex;
|
|
* @Field(order = 3, type = Type.U8)
|
|
* short type;
|
|
* @Field(order = 4, type = Type.U8, padding = 6)
|
|
* short code;
|
|
* }
|
|
*
|
|
* - Parse the target message and refer the members.
|
|
* final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
|
|
* buf.order(ByteOrder.nativeOrder());
|
|
* final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
|
|
* assertEquals(10, nduserHdrMsg.family);
|
|
*/
|
|
public class Struct {
|
|
public enum Type {
|
|
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
|
|
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
|
|
}
|
|
|
|
/**
|
|
* Indicate that the field marked with this annotation will automatically be managed by this
|
|
* class (e.g., will be parsed by #parse).
|
|
*
|
|
* order: The placeholder associated with each field, consecutive order starting from zero.
|
|
* type: The primitive data type listed in above Type enumeration.
|
|
* padding: Padding bytes appear after the field for alignment.
|
|
* 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 absent,
|
|
* then default value 0 will be applied.
|
|
*/
|
|
@Retention(RetentionPolicy.RUNTIME)
|
|
@Target(ElementType.FIELD)
|
|
public @interface Field {
|
|
int order();
|
|
Type type();
|
|
int padding() default 0;
|
|
int arraysize() default 0;
|
|
}
|
|
|
|
private static class FieldInfo {
|
|
@NonNull
|
|
public final Field annotation;
|
|
@NonNull
|
|
public final java.lang.reflect.Field field;
|
|
|
|
FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
|
|
this.annotation = annotation;
|
|
this.field = field;
|
|
}
|
|
}
|
|
private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
|
|
|
|
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 UBE16:
|
|
if (fieldType == Integer.TYPE) return;
|
|
break;
|
|
case U32:
|
|
case U63:
|
|
case S64:
|
|
case UBE32:
|
|
case UBE63:
|
|
if (fieldType == Long.TYPE) return;
|
|
break;
|
|
case U64:
|
|
case UBE64:
|
|
if (fieldType == BigInteger.class) return;
|
|
break;
|
|
case S8:
|
|
if (fieldType == Byte.TYPE) return;
|
|
break;
|
|
case ByteArray:
|
|
if (fieldType != byte[].class) break;
|
|
if (annotation.arraysize() <= 0) {
|
|
throw new IllegalArgumentException("Invalid ByteArray size: "
|
|
+ annotation.arraysize());
|
|
}
|
|
return;
|
|
default:
|
|
throw new IllegalArgumentException("Unknown type" + annotation.type());
|
|
}
|
|
throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
|
|
+ " 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) {
|
|
return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
|
|
}
|
|
|
|
private static int getAnnotationFieldCount(final Class clazz) {
|
|
int count = 0;
|
|
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
|
|
if (field.isAnnotationPresent(Field.class)) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) {
|
|
for (FieldInfo fi : fields) {
|
|
if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
|
|
return !allFieldsFinal(fields, true /* immutable */)
|
|
&& !allFieldsFinal(fields, false /* mutable */);
|
|
}
|
|
|
|
private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
|
|
final Class[] paramTypes = cons.getParameterTypes();
|
|
if (paramTypes.length != fields.length) return false;
|
|
for (int i = 0; i < paramTypes.length; i++) {
|
|
if (!paramTypes[i].equals(fields[i].field.getType())) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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[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, fieldInfo.field.getType());
|
|
switch (fieldInfo.annotation.type()) {
|
|
case U8:
|
|
value = (short) (buf.get() & 0xFF);
|
|
break;
|
|
case U16:
|
|
value = (int) (buf.getShort() & 0xFFFF);
|
|
break;
|
|
case U32:
|
|
value = (long) (buf.getInt() & 0xFFFFFFFFL);
|
|
break;
|
|
case U64:
|
|
value = readBigInteger(buf, Type.U64);
|
|
break;
|
|
case S8:
|
|
value = buf.get();
|
|
break;
|
|
case S16:
|
|
value = buf.getShort();
|
|
break;
|
|
case S32:
|
|
value = buf.getInt();
|
|
break;
|
|
case U63:
|
|
case S64:
|
|
value = buf.getLong();
|
|
break;
|
|
case UBE16:
|
|
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
|
value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
|
|
} else {
|
|
value = (int) (buf.getShort() & 0xFFFF);
|
|
}
|
|
break;
|
|
case UBE32:
|
|
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
|
value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
|
|
} else {
|
|
value = (long) (buf.getInt() & 0xFFFFFFFFL);
|
|
}
|
|
break;
|
|
case UBE63:
|
|
if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
|
|
value = Long.reverseBytes(buf.getLong());
|
|
} else {
|
|
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);
|
|
value = array;
|
|
break;
|
|
default:
|
|
throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
|
|
}
|
|
|
|
// Skip the padding data for alignment if any.
|
|
if (fieldInfo.annotation.padding() > 0) {
|
|
buf.position(buf.position() + fieldInfo.annotation.padding());
|
|
}
|
|
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 "
|
|
+ Struct.class.getName());
|
|
}
|
|
|
|
final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
|
|
if (cachedAnnotationFields != null) {
|
|
return cachedAnnotationFields;
|
|
}
|
|
|
|
// Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
|
|
// of field appeared in the class, that is a problem when parsing raw data read from
|
|
// ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
|
|
final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
|
|
for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
|
|
if (Modifier.isStatic(field.getModifiers())) continue;
|
|
|
|
final Field annotation = field.getAnnotation(Field.class);
|
|
if (annotation == null) {
|
|
throw new IllegalArgumentException("Field " + field.getName()
|
|
+ " is missing the " + Field.class.getSimpleName()
|
|
+ " annotation");
|
|
}
|
|
if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
|
|
throw new IllegalArgumentException("Annotation order: " + annotation.order()
|
|
+ " is negative or non-consecutive");
|
|
}
|
|
if (annotationFields[annotation.order()] != null) {
|
|
throw new IllegalArgumentException("Duplicated annotation order: "
|
|
+ annotation.order());
|
|
}
|
|
annotationFields[annotation.order()] = new FieldInfo(annotation, field);
|
|
}
|
|
sFieldCache.putIfAbsent(clazz, annotationFields);
|
|
return annotationFields;
|
|
}
|
|
|
|
/**
|
|
* Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
|
|
* the type-variable object which is subclass of Struct class.
|
|
*
|
|
* TODO:
|
|
* 1. Support subclass inheritance.
|
|
* 2. Introduce annotation processor to enforce the subclass naming schema.
|
|
*/
|
|
public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
|
|
try {
|
|
final FieldInfo[] foundFields = getClassFieldInfo(clazz);
|
|
if (hasBothMutableAndImmutableFields(foundFields)) {
|
|
throw new IllegalArgumentException("Class has both final and non-final fields");
|
|
}
|
|
|
|
Constructor<?> constructor = null;
|
|
Constructor<?> defaultConstructor = null;
|
|
final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
|
|
for (Constructor cons : constructors) {
|
|
if (matchConstructor(cons, foundFields)) constructor = cons;
|
|
if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
|
|
}
|
|
|
|
if (constructor == null && defaultConstructor == null) {
|
|
throw new IllegalArgumentException("Fail to find available constructor");
|
|
}
|
|
if (constructor != null) {
|
|
final Object[] args = new Object[foundFields.length];
|
|
for (int i = 0; i < args.length; i++) {
|
|
args[i] = getFieldValue(buf, foundFields[i]);
|
|
}
|
|
return (T) constructor.newInstance(args);
|
|
}
|
|
|
|
final Object instance = defaultConstructor.newInstance();
|
|
for (FieldInfo fi : foundFields) {
|
|
fi.field.set(instance, getFieldValue(buf, fi));
|
|
}
|
|
return (T) instance;
|
|
} 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;
|
|
}
|
|
}
|