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