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