Check the ByteArray actual size before writing it to ByteBuffer.
ByteArray is the unique type whose actual size might be different
with the size delcared in the annotation, e.g, user initializes an
object manually, or mutates the byte array after parsing from
ByteBuffer. Therefore, checking the length of byte array is required
before writing the array value to output bytes array or ByteBuffer.
Bug: 163492391
Test: atest NetworkStaticLibTests
Change-Id: I28936a2afbe60dd2dadac803d08062df153b2588
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
index f11a5ac..d95c303 100644
--- a/staticlibs/device/com/android/net/module/util/Struct.java
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -17,6 +17,7 @@
package com.android.net.module.util;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.MacAddress;
import java.lang.annotation.ElementType;
@@ -428,6 +429,15 @@
return value;
}
+ @Nullable
+ private Object getFieldValue(@NonNull java.lang.reflect.Field field) {
+ try {
+ return field.get(this);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException("Cannot access field: " + field, e);
+ }
+ }
+
private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
final Object value) throws BufferUnderflowException {
switch (fieldInfo.annotation.type()) {
@@ -484,6 +494,7 @@
output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
break;
case ByteArray:
+ checkByteArraySize((byte[]) value, fieldInfo);
output.put((byte[]) value);
break;
case EUI48:
@@ -595,12 +606,23 @@
return size;
}
+ // Check whether the actual size of byte array matches the array size declared in
+ // annotation. For other annotation types, the actual length of field could be always
+ // deduced from annotation correctly.
+ private static void checkByteArraySize(@Nullable final byte[] array,
+ @NonNull final FieldInfo fieldInfo) {
+ Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName());
+ int annotationArraySize = fieldInfo.annotation.arraysize();
+ if (array.length == annotationArraySize) return;
+ throw new IllegalStateException("byte array actual length: "
+ + array.length + " doesn't match the declared array size: " + annotationArraySize);
+ }
+
private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
for (FieldInfo fi : fieldInfos) {
+ final Object value = getFieldValue(fi.field);
try {
- putFieldValue(output, fi, fi.field.get(this));
- } catch (IllegalAccessException e) {
- throw new IllegalArgumentException("Fail to get the field value from instance", e);
+ putFieldValue(output, fi, value);
} catch (BufferUnderflowException e) {
throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
}
@@ -673,12 +695,7 @@
final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
final Object[] values = new Object[fieldInfos.length];
for (int i = 0; i < fieldInfos.length; i++) {
- final Object value;
- try {
- value = fieldInfos[i].field.get(this);
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
- }
+ final Object value = getFieldValue(fieldInfos[i].field);
// For byte array field, put the hash code generated based on the array content into
// the Object array instead of the reference to byte array, which might change and cause
// to get a different hash code even with the exact same elements.
@@ -697,12 +714,7 @@
final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
for (int i = 0; i < fieldInfos.length; i++) {
sb.append(fieldInfos[i].field.getName()).append(": ");
- final Object value;
- try {
- value = fieldInfos[i].field.get(this);
- } catch (IllegalAccessException e) {
- throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
- }
+ final Object value = getFieldValue(fieldInfos[i].field);
if (value == null) {
sb.append("null");
} else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index b172e21..df74398 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -1027,4 +1027,48 @@
assertTrue(msg.equals(msg1));
assertEquals(msg.hashCode(), msg1.hashCode());
}
+
+ static class InvalidByteArray extends Struct {
+ @Field(order = 0, type = Type.ByteArray, arraysize = 12) public byte[] bytes;
+ }
+
+ @Test
+ public void testStructClass_WrongByteArraySize() {
+ final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+ InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+ // Actual byte array size doesn't match the size declared in the annotation.
+ msg.bytes = new byte[16];
+ assertThrows(IllegalStateException.class, () -> msg.writeToBytes());
+
+ final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+ output.order(ByteOrder.LITTLE_ENDIAN);
+ assertThrows(IllegalStateException.class, () -> msg.writeToByteBuffer(output));
+ }
+
+ @Test
+ public void testStructClass_NullByteArray() {
+ final InvalidByteArray msg = doParsingMessageTest("20010db80003000400050006",
+ InvalidByteArray.class, ByteOrder.BIG_ENDIAN);
+
+ msg.bytes = null;
+ assertThrows(NullPointerException.class, () -> msg.writeToBytes());
+
+ final ByteBuffer output = ByteBuffer.allocate(Struct.getSize(InvalidByteArray.class));
+ output.order(ByteOrder.LITTLE_ENDIAN);
+ assertThrows(NullPointerException.class, () -> msg.writeToByteBuffer(output));
+ }
+
+ @Test
+ public void testStructClass_ParsingByteArrayAfterInitialization() {
+ InvalidByteArray msg = new InvalidByteArray();
+ msg.bytes = new byte[]{(byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03};
+
+ // Although bytes member has been initialized with the length different with
+ // annotation size, parsing from ByteBuffer will get bytes member have the
+ // reference to byte array with correct size.
+ msg = doParsingMessageTest("20010db80003000400050006", InvalidByteArray.class,
+ ByteOrder.BIG_ENDIAN);
+ assertArrayEquals(TEST_PREFIX64, msg.bytes);
+ }
}