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