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