1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.net.module.util;
18 
19 import android.net.MacAddress;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 
24 import java.lang.annotation.ElementType;
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.lang.annotation.Target;
28 import java.lang.reflect.Constructor;
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Modifier;
31 import java.math.BigInteger;
32 import java.net.Inet4Address;
33 import java.net.Inet6Address;
34 import java.net.InetAddress;
35 import java.net.UnknownHostException;
36 import java.nio.BufferUnderflowException;
37 import java.nio.ByteBuffer;
38 import java.nio.ByteOrder;
39 import java.util.Arrays;
40 import java.util.Objects;
41 import java.util.concurrent.ConcurrentHashMap;
42 
43 /**
44  * Define a generic class that helps to parse the structured message.
45  *
46  * Example usage:
47  *
48  *    // C-style NduserOption message header definition in the kernel:
49  *    struct nduseroptmsg {
50  *        unsigned char nduseropt_family;
51  *        unsigned char nduseropt_pad1;
52  *        unsigned short nduseropt_opts_len;
53  *        int nduseropt_ifindex;
54  *        __u8 nduseropt_icmp_type;
55  *        __u8 nduseropt_icmp_code;
56  *        unsigned short nduseropt_pad2;
57  *        unsigned int nduseropt_pad3;
58  *    }
59  *
60  *    - Declare a subclass with explicit constructor or not which extends from this class to parse
61  *      NduserOption header from raw bytes array.
62  *
63  *    - Option w/ explicit constructor:
64  *      static class NduserOptHeaderMessage extends Struct {
65  *          @Field(order = 0, type = Type.U8, padding = 1)
66  *          final short family;
67  *          @Field(order = 1, type = Type.U16)
68  *          final int len;
69  *          @Field(order = 2, type = Type.S32)
70  *          final int ifindex;
71  *          @Field(order = 3, type = Type.U8)
72  *          final short type;
73  *          @Field(order = 4, type = Type.U8, padding = 6)
74  *          final short code;
75  *
76  *          NduserOptHeaderMessage(final short family, final int len, final int ifindex,
77  *                  final short type, final short code) {
78  *              this.family = family;
79  *              this.len = len;
80  *              this.ifindex = ifindex;
81  *              this.type = type;
82  *              this.code = code;
83  *          }
84  *      }
85  *
86  *      - Option w/o explicit constructor:
87  *        static class NduserOptHeaderMessage extends Struct {
88  *            @Field(order = 0, type = Type.U8, padding = 1)
89  *            short family;
90  *            @Field(order = 1, type = Type.U16)
91  *            int len;
92  *            @Field(order = 2, type = Type.S32)
93  *            int ifindex;
94  *            @Field(order = 3, type = Type.U8)
95  *            short type;
96  *            @Field(order = 4, type = Type.U8, padding = 6)
97  *            short code;
98  *        }
99  *
100  *    - Parse the target message and refer the members.
101  *      final ByteBuffer buf = ByteBuffer.wrap(RAW_BYTES_ARRAY);
102  *      buf.order(ByteOrder.nativeOrder());
103  *      final NduserOptHeaderMessage nduserHdrMsg = Struct.parse(NduserOptHeaderMessage.class, buf);
104  *      assertEquals(10, nduserHdrMsg.family);
105  */
106 public class Struct {
107     public enum Type {
108         U8,          // unsigned byte,  size = 1 byte
109         U16,         // unsigned short, size = 2 bytes
110         U32,         // unsigned int,   size = 4 bytes
111         U63,         // unsigned long(MSB: 0), size = 8 bytes
112         U64,         // unsigned long,  size = 8 bytes
113         S8,          // signed byte,    size = 1 byte
114         S16,         // signed short,   size = 2 bytes
115         S32,         // signed int,     size = 4 bytes
116         S64,         // signed long,    size = 8 bytes
117         UBE16,       // unsigned short in network order, size = 2 bytes
118         UBE32,       // unsigned int in network order,   size = 4 bytes
119         UBE63,       // unsigned long(MSB: 0) in network order, size = 8 bytes
120         UBE64,       // unsigned long in network order,  size = 8 bytes
121         ByteArray,   // byte array with predefined length
122         EUI48,       // IEEE Extended Unique Identifier, a 48-bits long MAC address in network order
123         Ipv4Address, // IPv4 address in network order
124         Ipv6Address, // IPv6 address in network order
125     }
126 
127     /**
128      * Indicate that the field marked with this annotation will automatically be managed by this
129      * class (e.g., will be parsed by #parse).
130      *
131      * order:     The placeholder associated with each field, consecutive order starting from zero.
132      * type:      The primitive data type listed in above Type enumeration.
133      * padding:   Padding bytes appear after the field for alignment.
134      * arraysize: The length of byte array.
135      *
136      * Annotation associated with field MUST have order and type properties at least, padding
137      * and arraysize properties depend on the specific usage, if these properties are absent,
138      * then default value 0 will be applied.
139      */
140     @Retention(RetentionPolicy.RUNTIME)
141     @Target(ElementType.FIELD)
142     public @interface Field {
order()143         int order();
type()144         Type type();
padding()145         int padding() default 0;
arraysize()146         int arraysize() default 0;
147     }
148 
149     /**
150      * Indicates that this field contains a computed value and is ignored for the purposes of Struct
151      * parsing.
152      */
153     @Retention(RetentionPolicy.RUNTIME)
154     @Target(ElementType.FIELD)
155     public @interface Computed {}
156 
157     private static class FieldInfo {
158         @NonNull
159         public final Field annotation;
160         @NonNull
161         public final java.lang.reflect.Field field;
162 
FieldInfo(final Field annotation, final java.lang.reflect.Field field)163         FieldInfo(final Field annotation, final java.lang.reflect.Field field) {
164             this.annotation = annotation;
165             this.field = field;
166         }
167     }
168     private static ConcurrentHashMap<Class, FieldInfo[]> sFieldCache = new ConcurrentHashMap();
169 
checkAnnotationType(final Field annotation, final Class fieldType)170     private static void checkAnnotationType(final Field annotation, final Class fieldType) {
171         switch (annotation.type()) {
172             case U8:
173             case S16:
174                 if (fieldType == Short.TYPE) return;
175                 break;
176             case U16:
177             case S32:
178             case UBE16:
179                 if (fieldType == Integer.TYPE) return;
180                 break;
181             case U32:
182             case U63:
183             case S64:
184             case UBE32:
185             case UBE63:
186                 if (fieldType == Long.TYPE) return;
187                 break;
188             case U64:
189             case UBE64:
190                 if (fieldType == BigInteger.class) return;
191                 break;
192             case S8:
193                 if (fieldType == Byte.TYPE) return;
194                 break;
195             case ByteArray:
196                 if (fieldType != byte[].class) break;
197                 if (annotation.arraysize() <= 0) {
198                     throw new IllegalArgumentException("Invalid ByteArray size: "
199                             + annotation.arraysize());
200                 }
201                 return;
202             case EUI48:
203                 if (fieldType == MacAddress.class) return;
204                 break;
205             case Ipv4Address:
206                 if (fieldType == Inet4Address.class) return;
207                 break;
208             case Ipv6Address:
209                 if (fieldType == Inet6Address.class) return;
210                 break;
211             default:
212                 throw new IllegalArgumentException("Unknown type" + annotation.type());
213         }
214         throw new IllegalArgumentException("Invalid primitive data type: " + fieldType
215                 + " for annotation type: " + annotation.type());
216     }
217 
getFieldLength(final Field annotation)218     private static int getFieldLength(final Field annotation) {
219         int length = 0;
220         switch (annotation.type()) {
221             case U8:
222             case S8:
223                 length = 1;
224                 break;
225             case U16:
226             case S16:
227             case UBE16:
228                 length = 2;
229                 break;
230             case U32:
231             case S32:
232             case UBE32:
233                 length = 4;
234                 break;
235             case U63:
236             case U64:
237             case S64:
238             case UBE63:
239             case UBE64:
240                 length = 8;
241                 break;
242             case ByteArray:
243                 length = annotation.arraysize();
244                 break;
245             case EUI48:
246                 length = 6;
247                 break;
248             case Ipv4Address:
249                 length = 4;
250                 break;
251             case Ipv6Address:
252                 length = 16;
253                 break;
254             default:
255                 throw new IllegalArgumentException("Unknown type" + annotation.type());
256         }
257         return length + annotation.padding();
258     }
259 
isStructSubclass(final Class clazz)260     private static boolean isStructSubclass(final Class clazz) {
261         return clazz != null && Struct.class.isAssignableFrom(clazz) && Struct.class != clazz;
262     }
263 
getAnnotationFieldCount(final Class clazz)264     private static int getAnnotationFieldCount(final Class clazz) {
265         int count = 0;
266         for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
267             if (field.isAnnotationPresent(Field.class)) count++;
268         }
269         return count;
270     }
271 
allFieldsFinal(final FieldInfo[] fields, boolean immutable)272     private static boolean allFieldsFinal(final FieldInfo[] fields, boolean immutable) {
273         for (FieldInfo fi : fields) {
274             if (Modifier.isFinal(fi.field.getModifiers()) != immutable) return false;
275         }
276         return true;
277     }
278 
hasBothMutableAndImmutableFields(final FieldInfo[] fields)279     private static boolean hasBothMutableAndImmutableFields(final FieldInfo[] fields) {
280         return !allFieldsFinal(fields, true /* immutable */)
281                 && !allFieldsFinal(fields, false /* mutable */);
282     }
283 
matchConstructor(final Constructor cons, final FieldInfo[] fields)284     private static boolean matchConstructor(final Constructor cons, final FieldInfo[] fields) {
285         final Class[] paramTypes = cons.getParameterTypes();
286         if (paramTypes.length != fields.length) return false;
287         for (int i = 0; i < paramTypes.length; i++) {
288             if (!paramTypes[i].equals(fields[i].field.getType())) return false;
289         }
290         return true;
291     }
292 
293     /**
294      * Read U64/UBE64 type data from ByteBuffer and output a BigInteger instance.
295      *
296      * @param buf The byte buffer to read.
297      * @param type The annotation type.
298      *
299      * The magnitude argument of BigInteger constructor is a byte array in big-endian order.
300      * If BigInteger data is read from the byte buffer in little-endian, reverse the order of
301      * the bytes is required; if BigInteger data is read from the byte buffer in big-endian,
302      * then just keep it as-is.
303      */
readBigInteger(final ByteBuffer buf, final Type type)304     private static BigInteger readBigInteger(final ByteBuffer buf, final Type type) {
305         final byte[] input = new byte[8];
306         boolean reverseBytes = (type == Type.U64 && buf.order() == ByteOrder.LITTLE_ENDIAN);
307         for (int i = 0; i < 8; i++) {
308             input[reverseBytes ? input.length - 1 - i : i] = buf.get();
309         }
310         return new BigInteger(1, input);
311     }
312 
313     /**
314      * Get the last 8 bytes of a byte array. If there are less than 8 bytes,
315      * the first bytes are replaced with zeroes.
316      */
getLast8Bytes(final byte[] input)317     private static byte[] getLast8Bytes(final byte[] input) {
318         final byte[] tmp = new byte[8];
319         System.arraycopy(
320                 input,
321                 Math.max(0, input.length - 8), // srcPos: read at most last 8 bytes
322                 tmp,
323                 Math.max(0, 8 - input.length), // dstPos: pad output with that many zeroes
324                 Math.min(8, input.length));    // length
325         return tmp;
326     }
327 
328     /**
329      * Convert U64/UBE64 type data interpreted by BigInteger class to bytes array, output are
330      * always 8 bytes.
331      *
332      * @param bigInteger The number to convert.
333      * @param order Indicate ByteBuffer is read as little-endian or big-endian.
334      * @param type The annotation U64 type.
335      *
336      * BigInteger#toByteArray returns a byte array containing the 2's complement representation
337      * of this BigInteger, in big-endian. If annotation type is U64 and ByteBuffer is read as
338      * little-endian, then reversing the order of the bytes is required.
339      */
bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order, final Type type)340     private static byte[] bigIntegerToU64Bytes(final BigInteger bigInteger, final ByteOrder order,
341             final Type type) {
342         final byte[] bigIntegerBytes = bigInteger.toByteArray();
343         final byte[] output = getLast8Bytes(bigIntegerBytes);
344 
345         if (type == Type.U64 && order == ByteOrder.LITTLE_ENDIAN) {
346             for (int i = 0; i < 4; i++) {
347                 byte tmp = output[i];
348                 output[i] = output[7 - i];
349                 output[7 - i] = tmp;
350             }
351         }
352         return output;
353     }
354 
getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)355     private static Object getFieldValue(final ByteBuffer buf, final FieldInfo fieldInfo)
356             throws BufferUnderflowException {
357         final Object value;
358         checkAnnotationType(fieldInfo.annotation, fieldInfo.field.getType());
359         switch (fieldInfo.annotation.type()) {
360             case U8:
361                 value = (short) (buf.get() & 0xFF);
362                 break;
363             case U16:
364                 value = (int) (buf.getShort() & 0xFFFF);
365                 break;
366             case U32:
367                 value = (long) (buf.getInt() & 0xFFFFFFFFL);
368                 break;
369             case U64:
370                 value = readBigInteger(buf, Type.U64);
371                 break;
372             case S8:
373                 value = buf.get();
374                 break;
375             case S16:
376                 value = buf.getShort();
377                 break;
378             case S32:
379                 value = buf.getInt();
380                 break;
381             case U63:
382             case S64:
383                 value = buf.getLong();
384                 break;
385             case UBE16:
386                 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
387                     value = (int) (Short.reverseBytes(buf.getShort()) & 0xFFFF);
388                 } else {
389                     value = (int) (buf.getShort() & 0xFFFF);
390                 }
391                 break;
392             case UBE32:
393                 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
394                     value = (long) (Integer.reverseBytes(buf.getInt()) & 0xFFFFFFFFL);
395                 } else {
396                     value = (long) (buf.getInt() & 0xFFFFFFFFL);
397                 }
398                 break;
399             case UBE63:
400                 if (buf.order() == ByteOrder.LITTLE_ENDIAN) {
401                     value = Long.reverseBytes(buf.getLong());
402                 } else {
403                     value = buf.getLong();
404                 }
405                 break;
406             case UBE64:
407                 value = readBigInteger(buf, Type.UBE64);
408                 break;
409             case ByteArray:
410                 final byte[] array = new byte[fieldInfo.annotation.arraysize()];
411                 buf.get(array);
412                 value = array;
413                 break;
414             case EUI48:
415                 final byte[] macAddress = new byte[6];
416                 buf.get(macAddress);
417                 value = MacAddress.fromBytes(macAddress);
418                 break;
419             case Ipv4Address:
420             case Ipv6Address:
421                 final boolean isIpv6 = (fieldInfo.annotation.type() == Type.Ipv6Address);
422                 final byte[] address = new byte[isIpv6 ? 16 : 4];
423                 buf.get(address);
424                 try {
425                     if (isIpv6) {
426                         // Using Inet6Address.getByAddress since InetAddress.getByAddress converts
427                         // v4-mapped v6 address to v4 address internally and returns Inet4Address.
428                         value = Inet6Address.getByAddress(
429                                 null /* host */, address, -1 /* scope_id */);
430                     } else {
431                         value = InetAddress.getByAddress(address);
432                     }
433                 } catch (UnknownHostException e) {
434                     throw new IllegalArgumentException("illegal length of IP address", e);
435                 }
436                 break;
437             default:
438                 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
439         }
440 
441         // Skip the padding data for alignment if any.
442         if (fieldInfo.annotation.padding() > 0) {
443             buf.position(buf.position() + fieldInfo.annotation.padding());
444         }
445         return value;
446     }
447 
448     @Nullable
getFieldValue(@onNull java.lang.reflect.Field field)449     private Object getFieldValue(@NonNull java.lang.reflect.Field field) {
450         try {
451             return field.get(this);
452         } catch (IllegalAccessException e) {
453             throw new IllegalStateException("Cannot access field: " + field, e);
454         }
455     }
456 
putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo, final Object value)457     private static void putFieldValue(final ByteBuffer output, final FieldInfo fieldInfo,
458             final Object value) throws BufferUnderflowException {
459         switch (fieldInfo.annotation.type()) {
460             case U8:
461                 output.put((byte) (((short) value) & 0xFF));
462                 break;
463             case U16:
464                 output.putShort((short) (((int) value) & 0xFFFF));
465                 break;
466             case U32:
467                 output.putInt((int) (((long) value) & 0xFFFFFFFFL));
468                 break;
469             case U63:
470                 output.putLong((long) value);
471                 break;
472             case U64:
473                 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.U64));
474                 break;
475             case S8:
476                 output.put((byte) value);
477                 break;
478             case S16:
479                 output.putShort((short) value);
480                 break;
481             case S32:
482                 output.putInt((int) value);
483                 break;
484             case S64:
485                 output.putLong((long) value);
486                 break;
487             case UBE16:
488                 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
489                     output.putShort(Short.reverseBytes((short) (((int) value) & 0xFFFF)));
490                 } else {
491                     output.putShort((short) (((int) value) & 0xFFFF));
492                 }
493                 break;
494             case UBE32:
495                 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
496                     output.putInt(Integer.reverseBytes(
497                             (int) (((long) value) & 0xFFFFFFFFL)));
498                 } else {
499                     output.putInt((int) (((long) value) & 0xFFFFFFFFL));
500                 }
501                 break;
502             case UBE63:
503                 if (output.order() == ByteOrder.LITTLE_ENDIAN) {
504                     output.putLong(Long.reverseBytes((long) value));
505                 } else {
506                     output.putLong((long) value);
507                 }
508                 break;
509             case UBE64:
510                 output.put(bigIntegerToU64Bytes((BigInteger) value, output.order(), Type.UBE64));
511                 break;
512             case ByteArray:
513                 checkByteArraySize((byte[]) value, fieldInfo);
514                 output.put((byte[]) value);
515                 break;
516             case EUI48:
517                 final byte[] macAddress = ((MacAddress) value).toByteArray();
518                 output.put(macAddress);
519                 break;
520             case Ipv4Address:
521             case Ipv6Address:
522                 final byte[] address = ((InetAddress) value).getAddress();
523                 output.put(address);
524                 break;
525             default:
526                 throw new IllegalArgumentException("Unknown type:" + fieldInfo.annotation.type());
527         }
528 
529         // padding zero after field value for alignment.
530         for (int i = 0; i < fieldInfo.annotation.padding(); i++) output.put((byte) 0);
531     }
532 
getClassFieldInfo(final Class clazz)533     private static FieldInfo[] getClassFieldInfo(final Class clazz) {
534         if (!isStructSubclass(clazz)) {
535             throw new IllegalArgumentException(clazz.getName() + " is not a subclass of "
536                     + Struct.class.getName() + ", its superclass is "
537                     + clazz.getSuperclass().getName());
538         }
539 
540         final FieldInfo[] cachedAnnotationFields = sFieldCache.get(clazz);
541         if (cachedAnnotationFields != null) {
542             return cachedAnnotationFields;
543         }
544 
545         // Since array returned from Class#getDeclaredFields doesn't guarantee the actual order
546         // of field appeared in the class, that is a problem when parsing raw data read from
547         // ByteBuffer. Store the fields appeared by the order() defined in the Field annotation.
548         final FieldInfo[] annotationFields = new FieldInfo[getAnnotationFieldCount(clazz)];
549         for (java.lang.reflect.Field field : clazz.getDeclaredFields()) {
550             if (Modifier.isStatic(field.getModifiers())) continue;
551             if (field.getAnnotation(Computed.class) != null) continue;
552 
553             final Field annotation = field.getAnnotation(Field.class);
554             if (annotation == null) {
555                 throw new IllegalArgumentException("Field " + field.getName()
556                         + " is missing the " + Field.class.getSimpleName()
557                         + " annotation");
558             }
559             if (annotation.order() < 0 || annotation.order() >= annotationFields.length) {
560                 throw new IllegalArgumentException("Annotation order: " + annotation.order()
561                         + " is negative or non-consecutive");
562             }
563             if (annotationFields[annotation.order()] != null) {
564                 throw new IllegalArgumentException("Duplicated annotation order: "
565                         + annotation.order());
566             }
567             annotationFields[annotation.order()] = new FieldInfo(annotation, field);
568         }
569         sFieldCache.putIfAbsent(clazz, annotationFields);
570         return annotationFields;
571     }
572 
573     /**
574      * Parse raw data from ByteBuffer according to the pre-defined annotation rule and return
575      * the type-variable object which is subclass of Struct class.
576      *
577      * TODO:
578      * 1. Support subclass inheritance.
579      * 2. Introduce annotation processor to enforce the subclass naming schema.
580      */
parse(final Class<T> clazz, final ByteBuffer buf)581     public static <T> T parse(final Class<T> clazz, final ByteBuffer buf) {
582         try {
583             final FieldInfo[] foundFields = getClassFieldInfo(clazz);
584             if (hasBothMutableAndImmutableFields(foundFields)) {
585                 throw new IllegalArgumentException("Class has both final and non-final fields");
586             }
587 
588             Constructor<?> constructor = null;
589             Constructor<?> defaultConstructor = null;
590             final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
591             for (Constructor cons : constructors) {
592                 if (matchConstructor(cons, foundFields)) constructor = cons;
593                 if (cons.getParameterTypes().length == 0) defaultConstructor = cons;
594             }
595 
596             if (constructor == null && defaultConstructor == null) {
597                 throw new IllegalArgumentException("Fail to find available constructor");
598             }
599             if (constructor != null) {
600                 final Object[] args = new Object[foundFields.length];
601                 for (int i = 0; i < args.length; i++) {
602                     args[i] = getFieldValue(buf, foundFields[i]);
603                 }
604                 return (T) constructor.newInstance(args);
605             }
606 
607             final Object instance = defaultConstructor.newInstance();
608             for (FieldInfo fi : foundFields) {
609                 fi.field.set(instance, getFieldValue(buf, fi));
610             }
611             return (T) instance;
612         } catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
613             throw new IllegalArgumentException("Fail to create a instance from constructor", e);
614         } catch (BufferUnderflowException e) {
615             throw new IllegalArgumentException("Fail to read raw data from ByteBuffer", e);
616         }
617     }
618 
getSizeInternal(final FieldInfo[] fieldInfos)619     private static int getSizeInternal(final FieldInfo[] fieldInfos) {
620         int size = 0;
621         for (FieldInfo fi : fieldInfos) {
622             size += getFieldLength(fi.annotation);
623         }
624         return size;
625     }
626 
627     // Check whether the actual size of byte array matches the array size declared in
628     // annotation. For other annotation types, the actual length of field could be always
629     // deduced from annotation correctly.
checkByteArraySize(@ullable final byte[] array, @NonNull final FieldInfo fieldInfo)630     private static void checkByteArraySize(@Nullable final byte[] array,
631             @NonNull final FieldInfo fieldInfo) {
632         Objects.requireNonNull(array, "null byte array for field " + fieldInfo.field.getName());
633         int annotationArraySize = fieldInfo.annotation.arraysize();
634         if (array.length == annotationArraySize) return;
635         throw new IllegalStateException("byte array actual length: "
636                 + array.length + " doesn't match the declared array size: " + annotationArraySize);
637     }
638 
writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos)639     private void writeToByteBufferInternal(final ByteBuffer output, final FieldInfo[] fieldInfos) {
640         for (FieldInfo fi : fieldInfos) {
641             final Object value = getFieldValue(fi.field);
642             try {
643                 putFieldValue(output, fi, value);
644             } catch (BufferUnderflowException e) {
645                 throw new IllegalArgumentException("Fail to fill raw data to ByteBuffer", e);
646             }
647         }
648     }
649 
650     /**
651      * Get the size of Struct subclass object.
652      */
getSize(final Class<T> clazz)653     public static <T extends Struct> int getSize(final Class<T> clazz) {
654         final FieldInfo[] fieldInfos = getClassFieldInfo(clazz);
655         return getSizeInternal(fieldInfos);
656     }
657 
658     /**
659      * Convert the parsed Struct subclass object to ByteBuffer.
660      *
661      * @param output ByteBuffer passed-in from the caller.
662      */
writeToByteBuffer(final ByteBuffer output)663     public final void writeToByteBuffer(final ByteBuffer output) {
664         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
665         writeToByteBufferInternal(output, fieldInfos);
666     }
667 
668     /**
669      * Convert the parsed Struct subclass object to byte array.
670      *
671      * @param order indicate ByteBuffer is outputted as little-endian or big-endian.
672      */
writeToBytes(final ByteOrder order)673     public final byte[] writeToBytes(final ByteOrder order) {
674         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
675         final byte[] output = new byte[getSizeInternal(fieldInfos)];
676         final ByteBuffer buffer = ByteBuffer.wrap(output);
677         buffer.order(order);
678         writeToByteBufferInternal(buffer, fieldInfos);
679         return output;
680     }
681 
682     /** Convert the parsed Struct subclass object to byte array with native order. */
writeToBytes()683     public final byte[] writeToBytes() {
684         return writeToBytes(ByteOrder.nativeOrder());
685     }
686 
687     @Override
equals(Object obj)688     public boolean equals(Object obj) {
689         if (obj == null || this.getClass() != obj.getClass()) return false;
690 
691         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
692         for (int i = 0; i < fieldInfos.length; i++) {
693             try {
694                 final Object value = fieldInfos[i].field.get(this);
695                 final Object otherValue = fieldInfos[i].field.get(obj);
696 
697                 // Use Objects#deepEquals because the equals method on arrays does not check the
698                 // contents of the array. The only difference between Objects#deepEquals and
699                 // Objects#equals is that the former will call Arrays#deepEquals when comparing
700                 // arrays. In turn, the only difference between Arrays#deepEquals is that it
701                 // supports nested arrays. Struct does not currently support these, and if it did,
702                 // Objects#deepEquals might be more correct.
703                 if (!Objects.deepEquals(value, otherValue)) return false;
704             } catch (IllegalAccessException e) {
705                 throw new IllegalStateException("Cannot access field: " + fieldInfos[i].field, e);
706             }
707         }
708         return true;
709     }
710 
711     @Override
hashCode()712     public int hashCode() {
713         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
714         final Object[] values = new Object[fieldInfos.length];
715         for (int i = 0; i < fieldInfos.length; i++) {
716             final Object value = getFieldValue(fieldInfos[i].field);
717             // For byte array field, put the hash code generated based on the array content into
718             // the Object array instead of the reference to byte array, which might change and cause
719             // to get a different hash code even with the exact same elements.
720             if (fieldInfos[i].field.getType() == byte[].class) {
721                 values[i] = Arrays.hashCode((byte[]) value);
722             } else {
723                 values[i] = value;
724             }
725         }
726         return Objects.hash(values);
727     }
728 
729     @Override
toString()730     public String toString() {
731         final StringBuilder sb = new StringBuilder();
732         final FieldInfo[] fieldInfos = getClassFieldInfo(this.getClass());
733         for (int i = 0; i < fieldInfos.length; i++) {
734             sb.append(fieldInfos[i].field.getName()).append(": ");
735             final Object value = getFieldValue(fieldInfos[i].field);
736             if (value == null) {
737                 sb.append("null");
738             } else if (fieldInfos[i].annotation.type() == Type.ByteArray) {
739                 sb.append("0x").append(HexDump.toHexString((byte[]) value));
740             } else if (fieldInfos[i].annotation.type() == Type.Ipv4Address
741                     || fieldInfos[i].annotation.type() == Type.Ipv6Address) {
742                 sb.append(((InetAddress) value).getHostAddress());
743             } else {
744                 sb.append(value.toString());
745             }
746             if (i != fieldInfos.length - 1) sb.append(", ");
747         }
748         return sb.toString();
749     }
750 
751     /** A simple Struct which only contains a u8 field. */
752     public static class U8 extends Struct {
753         @Struct.Field(order = 0, type = Struct.Type.U8)
754         public final short val;
755 
U8(final short val)756         public U8(final short val) {
757             this.val = val;
758         }
759     }
760 
761     /** A simple Struct which only contains an s32 field. */
762     public static class S32 extends Struct {
763         @Struct.Field(order = 0, type = Struct.Type.S32)
764         public final int val;
765 
S32(final int val)766         public S32(final int val) {
767             this.val = val;
768         }
769     }
770 
771     /** A simple Struct which only contains a u32 field. */
772     public static class U32 extends Struct {
773         @Struct.Field(order = 0, type = Struct.Type.U32)
774         public final long val;
775 
U32(final long val)776         public U32(final long val) {
777             this.val = val;
778         }
779     }
780 
781     /** A simple Struct which only contains an s64 field. */
782     public static class S64 extends Struct {
783         @Struct.Field(order = 0, type = Struct.Type.S64)
784         public final long val;
785 
S64(final long val)786         public S64(final long val) {
787             this.val = val;
788         }
789     }
790 }
791