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