Merge "Move util classes to their destination package"
diff --git a/staticlibs/device/android/net/util/Struct.java b/staticlibs/device/android/net/util/Struct.java
deleted file mode 100644
index 03f5f22..0000000
--- a/staticlibs/device/android/net/util/Struct.java
+++ /dev/null
@@ -1,384 +0,0 @@
-/*
- * 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
-        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
-        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 absence,
-     * 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 Type type, final Class fieldType) {
-        switch (type) {
-            case U8:
-            case S16:
-                if (fieldType == Short.TYPE) return;
-                break;
-            case U16:
-            case S32:
-            case BE16:
-                if (fieldType == Integer.TYPE) return;
-                break;
-            case U32:
-            case S64:
-            case BE32:
-                if (fieldType == Long.TYPE) return;
-                break;
-            case U64:
-            case BE64:
-                if (fieldType == BigInteger.class || fieldType == Long.TYPE) return;
-                break;
-            case S8:
-                if (fieldType == Byte.TYPE) return;
-                break;
-            case ByteArray:
-                if (fieldType == byte[].class) return;
-                break;
-            default:
-                throw new IllegalArgumentException("Unknown type" + type);
-        }
-        throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
-                + "for annotation type: " + type);
-    }
-
-    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 matchModifier(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 !matchModifier(fields, true /* immutable */)
-                && !matchModifier(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;
-    }
-
-    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.
-        final byte[] input = new byte[8];
-        for (int i = 0; i < 8; i++) {
-            input[(buf.order() == ByteOrder.LITTLE_ENDIAN ? input.length - 1 - i : i)] = buf.get();
-        }
-        return new BigInteger(1, input);
-    }
-
-    private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
-            throws BufferUnderflowException {
-        final Object value;
-        checkAnnotationType(fieldInfo.annotation.type(), 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:
-                if (fieldInfo.field.getType() == BigInteger.class) {
-                    value = readBigInteger(buf);
-                } else {
-                    value = buf.getLong();
-                }
-                break;
-            case S8:
-                value = buf.get();
-                break;
-            case S16:
-                value = buf.getShort();
-                break;
-            case S32:
-                value = buf.getInt();
-                break;
-            case S64:
-                value = buf.getLong();
-                break;
-            case BE16:
-                if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
-                    value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
-                } else {
-                    value = (int) (buf.getShort() & 0xFFFF);
-                }
-                break;
-            case BE32:
-                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);
-                } else {
-                    if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
-                        value = Long.reverseBytes(buf.getLong());
-                    } else {
-                        value = buf.getLong();
-                    }
-                }
-                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 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 immutable and mutable 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);
-        }
-    }
-}
diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java
new file mode 100644
index 0000000..bc8761a
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/Struct.java
@@ -0,0 +1,588 @@
+/*
+ * 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 com.android.net.module.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 U64 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;
+    }
+}
diff --git a/staticlibs/tests/unit/src/android/net/util/StructTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
similarity index 63%
rename from staticlibs/tests/unit/src/android/net/util/StructTest.java
rename to staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
index 5ca278a..1b03e75 100644
--- a/staticlibs/tests/unit/src/android/net/util/StructTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/StructTest.java
@@ -14,27 +14,29 @@
  * limitations under the License.
  */
 
-package android.net.util;
+package com.android.net.module.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;
 
 import android.net.IpPrefix;
-import android.net.util.Struct.Field;
-import android.net.util.Struct.Type;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
 
 import org.junit.Test;
 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 @@
     // 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 @@
     // 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 @@
         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 @@
     }
 
     @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 @@
     }
 
     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 @@
         @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 @@
     @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 @@
 
     @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 @@
     }
 
     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 @@
         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 @@
     @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 @@
     @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));
     }