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