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