1 /* 2 * Copyright (C) 2019 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 android.util; 18 19 import static java.nio.charset.StandardCharsets.UTF_8; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.os.SystemClock; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 /** 29 * StatsEvent builds and stores the buffer sent over the statsd socket. 30 * This class defines and encapsulates the socket protocol. 31 * 32 * <p>Usage:</p> 33 * <pre> 34 * // Pushed event 35 * StatsEvent statsEvent = StatsEvent.newBuilder() 36 * .setAtomId(atomId) 37 * .writeBoolean(false) 38 * .writeString("annotated String field") 39 * .addBooleanAnnotation(annotationId, true) 40 * .usePooledBuffer() 41 * .build(); 42 * StatsLog.write(statsEvent); 43 * 44 * // Pulled event 45 * StatsEvent statsEvent = StatsEvent.newBuilder() 46 * .setAtomId(atomId) 47 * .writeBoolean(false) 48 * .writeString("annotated String field") 49 * .addBooleanAnnotation(annotationId, true) 50 * .build(); 51 * </pre> 52 * @hide 53 **/ 54 public final class StatsEvent { 55 // Type Ids. 56 /** 57 * @hide 58 **/ 59 @VisibleForTesting 60 public static final byte TYPE_INT = 0x00; 61 62 /** 63 * @hide 64 **/ 65 @VisibleForTesting 66 public static final byte TYPE_LONG = 0x01; 67 68 /** 69 * @hide 70 **/ 71 @VisibleForTesting 72 public static final byte TYPE_STRING = 0x02; 73 74 /** 75 * @hide 76 **/ 77 @VisibleForTesting 78 public static final byte TYPE_LIST = 0x03; 79 80 /** 81 * @hide 82 **/ 83 @VisibleForTesting 84 public static final byte TYPE_FLOAT = 0x04; 85 86 /** 87 * @hide 88 **/ 89 @VisibleForTesting 90 public static final byte TYPE_BOOLEAN = 0x05; 91 92 /** 93 * @hide 94 **/ 95 @VisibleForTesting 96 public static final byte TYPE_BYTE_ARRAY = 0x06; 97 98 /** 99 * @hide 100 **/ 101 @VisibleForTesting 102 public static final byte TYPE_OBJECT = 0x07; 103 104 /** 105 * @hide 106 **/ 107 @VisibleForTesting 108 public static final byte TYPE_KEY_VALUE_PAIRS = 0x08; 109 110 /** 111 * @hide 112 **/ 113 @VisibleForTesting 114 public static final byte TYPE_ATTRIBUTION_CHAIN = 0x09; 115 116 /** 117 * @hide 118 **/ 119 @VisibleForTesting 120 public static final byte TYPE_ERRORS = 0x0F; 121 122 // Error flags. 123 /** 124 * @hide 125 **/ 126 @VisibleForTesting 127 public static final int ERROR_NO_TIMESTAMP = 0x1; 128 129 /** 130 * @hide 131 **/ 132 @VisibleForTesting 133 public static final int ERROR_NO_ATOM_ID = 0x2; 134 135 /** 136 * @hide 137 **/ 138 @VisibleForTesting 139 public static final int ERROR_OVERFLOW = 0x4; 140 141 /** 142 * @hide 143 **/ 144 @VisibleForTesting 145 public static final int ERROR_ATTRIBUTION_CHAIN_TOO_LONG = 0x8; 146 147 /** 148 * @hide 149 **/ 150 @VisibleForTesting 151 public static final int ERROR_TOO_MANY_KEY_VALUE_PAIRS = 0x10; 152 153 /** 154 * @hide 155 **/ 156 @VisibleForTesting 157 public static final int ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD = 0x20; 158 159 /** 160 * @hide 161 **/ 162 @VisibleForTesting 163 public static final int ERROR_INVALID_ANNOTATION_ID = 0x40; 164 165 /** 166 * @hide 167 **/ 168 @VisibleForTesting 169 public static final int ERROR_ANNOTATION_ID_TOO_LARGE = 0x80; 170 171 /** 172 * @hide 173 **/ 174 @VisibleForTesting 175 public static final int ERROR_TOO_MANY_ANNOTATIONS = 0x100; 176 177 /** 178 * @hide 179 **/ 180 @VisibleForTesting 181 public static final int ERROR_TOO_MANY_FIELDS = 0x200; 182 183 /** 184 * @hide 185 **/ 186 @VisibleForTesting 187 public static final int ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL = 0x1000; 188 189 // Size limits. 190 191 /** 192 * @hide 193 **/ 194 @VisibleForTesting 195 public static final int MAX_ANNOTATION_COUNT = 15; 196 197 /** 198 * @hide 199 **/ 200 @VisibleForTesting 201 public static final int MAX_ATTRIBUTION_NODES = 127; 202 203 /** 204 * @hide 205 **/ 206 @VisibleForTesting 207 public static final int MAX_NUM_ELEMENTS = 127; 208 209 /** 210 * @hide 211 **/ 212 @VisibleForTesting 213 public static final int MAX_KEY_VALUE_PAIRS = 127; 214 215 private static final int LOGGER_ENTRY_MAX_PAYLOAD = 4068; 216 217 // Max payload size is 4 bytes less as 4 bytes are reserved for statsEventTag. 218 // See android_util_StatsLog.cpp. 219 private static final int MAX_PAYLOAD_SIZE = LOGGER_ENTRY_MAX_PAYLOAD - 4; 220 221 private final int mAtomId; 222 private final byte[] mPayload; 223 private Buffer mBuffer; 224 private final int mNumBytes; 225 StatsEvent(final int atomId, @Nullable final Buffer buffer, @NonNull final byte[] payload, final int numBytes)226 private StatsEvent(final int atomId, @Nullable final Buffer buffer, 227 @NonNull final byte[] payload, final int numBytes) { 228 mAtomId = atomId; 229 mBuffer = buffer; 230 mPayload = payload; 231 mNumBytes = numBytes; 232 } 233 234 /** 235 * Returns a new StatsEvent.Builder for building StatsEvent object. 236 **/ 237 @NonNull newBuilder()238 public static StatsEvent.Builder newBuilder() { 239 return new StatsEvent.Builder(Buffer.obtain()); 240 } 241 242 /** 243 * Get the atom Id of the atom encoded in this StatsEvent object. 244 * 245 * @hide 246 **/ getAtomId()247 public int getAtomId() { 248 return mAtomId; 249 } 250 251 /** 252 * Get the byte array that contains the encoded payload that can be sent to statsd. 253 * 254 * @hide 255 **/ 256 @NonNull getBytes()257 public byte[] getBytes() { 258 return mPayload; 259 } 260 261 /** 262 * Get the number of bytes used to encode the StatsEvent payload. 263 * 264 * @hide 265 **/ getNumBytes()266 public int getNumBytes() { 267 return mNumBytes; 268 } 269 270 /** 271 * Recycle resources used by this StatsEvent object. 272 * No actions should be taken on this StatsEvent after release() is called. 273 **/ release()274 public void release() { 275 if (mBuffer != null) { 276 mBuffer.release(); 277 mBuffer = null; 278 } 279 } 280 281 /** 282 * Builder for constructing a StatsEvent object. 283 * 284 * <p>This class defines and encapsulates the socket encoding for the buffer. 285 * The write methods must be called in the same order as the order of fields in the 286 * atom definition.</p> 287 * 288 * <p>setAtomId() can be called anytime before build().</p> 289 * 290 * <p>Example:</p> 291 * <pre> 292 * // Atom definition. 293 * message MyAtom { 294 * optional int32 field1 = 1; 295 * optional int64 field2 = 2; 296 * optional string field3 = 3 [(annotation1) = true]; 297 * } 298 * 299 * // StatsEvent construction for pushed event. 300 * StatsEvent.newBuilder() 301 * StatsEvent statsEvent = StatsEvent.newBuilder() 302 * .setAtomId(atomId) 303 * .writeInt(3) // field1 304 * .writeLong(8L) // field2 305 * .writeString("foo") // field 3 306 * .addBooleanAnnotation(annotation1Id, true) 307 * .usePooledBuffer() 308 * .build(); 309 * 310 * // StatsEvent construction for pulled event. 311 * StatsEvent.newBuilder() 312 * StatsEvent statsEvent = StatsEvent.newBuilder() 313 * .setAtomId(atomId) 314 * .writeInt(3) // field1 315 * .writeLong(8L) // field2 316 * .writeString("foo") // field 3 317 * .addBooleanAnnotation(annotation1Id, true) 318 * .build(); 319 * </pre> 320 **/ 321 public static final class Builder { 322 // Fixed positions. 323 private static final int POS_NUM_ELEMENTS = 1; 324 private static final int POS_TIMESTAMP_NS = POS_NUM_ELEMENTS + Byte.BYTES; 325 private static final int POS_ATOM_ID = POS_TIMESTAMP_NS + Byte.BYTES + Long.BYTES; 326 327 private final Buffer mBuffer; 328 private long mTimestampNs; 329 private int mAtomId; 330 private byte mCurrentAnnotationCount; 331 private int mPos; 332 private int mPosLastField; 333 private byte mLastType; 334 private int mNumElements; 335 private int mErrorMask; 336 private boolean mUsePooledBuffer = false; 337 Builder(final Buffer buffer)338 private Builder(final Buffer buffer) { 339 mBuffer = buffer; 340 mCurrentAnnotationCount = 0; 341 mAtomId = 0; 342 mTimestampNs = SystemClock.elapsedRealtimeNanos(); 343 mNumElements = 0; 344 345 // Set mPos to 0 for writing TYPE_OBJECT at 0th position. 346 mPos = 0; 347 writeTypeId(TYPE_OBJECT); 348 349 // Set mPos to after atom id's location in the buffer. 350 // First 2 elements in the buffer are event timestamp followed by the atom id. 351 mPos = POS_ATOM_ID + Byte.BYTES + Integer.BYTES; 352 mPosLastField = 0; 353 mLastType = 0; 354 } 355 356 /** 357 * Sets the atom id for this StatsEvent. 358 **/ 359 @NonNull setAtomId(final int atomId)360 public Builder setAtomId(final int atomId) { 361 mAtomId = atomId; 362 return this; 363 } 364 365 /** 366 * Sets the timestamp in nanos for this StatsEvent. 367 **/ 368 @VisibleForTesting 369 @NonNull setTimestampNs(final long timestampNs)370 public Builder setTimestampNs(final long timestampNs) { 371 mTimestampNs = timestampNs; 372 return this; 373 } 374 375 /** 376 * Write a boolean field to this StatsEvent. 377 **/ 378 @NonNull writeBoolean(final boolean value)379 public Builder writeBoolean(final boolean value) { 380 // Write boolean typeId byte followed by boolean byte representation. 381 writeTypeId(TYPE_BOOLEAN); 382 mPos += mBuffer.putBoolean(mPos, value); 383 mNumElements++; 384 return this; 385 } 386 387 /** 388 * Write an integer field to this StatsEvent. 389 **/ 390 @NonNull writeInt(final int value)391 public Builder writeInt(final int value) { 392 // Write integer typeId byte followed by 4-byte representation of value. 393 writeTypeId(TYPE_INT); 394 mPos += mBuffer.putInt(mPos, value); 395 mNumElements++; 396 return this; 397 } 398 399 /** 400 * Write a long field to this StatsEvent. 401 **/ 402 @NonNull writeLong(final long value)403 public Builder writeLong(final long value) { 404 // Write long typeId byte followed by 8-byte representation of value. 405 writeTypeId(TYPE_LONG); 406 mPos += mBuffer.putLong(mPos, value); 407 mNumElements++; 408 return this; 409 } 410 411 /** 412 * Write a float field to this StatsEvent. 413 **/ 414 @NonNull writeFloat(final float value)415 public Builder writeFloat(final float value) { 416 // Write float typeId byte followed by 4-byte representation of value. 417 writeTypeId(TYPE_FLOAT); 418 mPos += mBuffer.putFloat(mPos, value); 419 mNumElements++; 420 return this; 421 } 422 423 /** 424 * Write a String field to this StatsEvent. 425 **/ 426 @NonNull writeString(@onNull final String value)427 public Builder writeString(@NonNull final String value) { 428 // Write String typeId byte, followed by 4-byte representation of number of bytes 429 // in the UTF-8 encoding, followed by the actual UTF-8 byte encoding of value. 430 final byte[] valueBytes = stringToBytes(value); 431 writeByteArray(valueBytes, TYPE_STRING); 432 return this; 433 } 434 435 /** 436 * Write a byte array field to this StatsEvent. 437 **/ 438 @NonNull writeByteArray(@onNull final byte[] value)439 public Builder writeByteArray(@NonNull final byte[] value) { 440 // Write byte array typeId byte, followed by 4-byte representation of number of bytes 441 // in value, followed by the actual byte array. 442 writeByteArray(value, TYPE_BYTE_ARRAY); 443 return this; 444 } 445 writeByteArray(@onNull final byte[] value, final byte typeId)446 private void writeByteArray(@NonNull final byte[] value, final byte typeId) { 447 writeTypeId(typeId); 448 final int numBytes = value.length; 449 mPos += mBuffer.putInt(mPos, numBytes); 450 mPos += mBuffer.putByteArray(mPos, value); 451 mNumElements++; 452 } 453 454 /** 455 * Write an attribution chain field to this StatsEvent. 456 * 457 * The sizes of uids and tags must be equal. The AttributionNode at position i is 458 * made up of uids[i] and tags[i]. 459 * 460 * @param uids array of uids in the attribution nodes. 461 * @param tags array of tags in the attribution nodes. 462 **/ 463 @NonNull writeAttributionChain( @onNull final int[] uids, @NonNull final String[] tags)464 public Builder writeAttributionChain( 465 @NonNull final int[] uids, @NonNull final String[] tags) { 466 final byte numUids = (byte) uids.length; 467 final byte numTags = (byte) tags.length; 468 469 if (numUids != numTags) { 470 mErrorMask |= ERROR_ATTRIBUTION_UIDS_TAGS_SIZES_NOT_EQUAL; 471 } else if (numUids > MAX_ATTRIBUTION_NODES) { 472 mErrorMask |= ERROR_ATTRIBUTION_CHAIN_TOO_LONG; 473 } else { 474 // Write attribution chain typeId byte, followed by 1-byte representation of 475 // number of attribution nodes, followed by encoding of each attribution node. 476 writeTypeId(TYPE_ATTRIBUTION_CHAIN); 477 mPos += mBuffer.putByte(mPos, numUids); 478 for (int i = 0; i < numUids; i++) { 479 // Each uid is encoded as 4-byte representation of its int value. 480 mPos += mBuffer.putInt(mPos, uids[i]); 481 482 // Each tag is encoded as 4-byte representation of number of bytes in its 483 // UTF-8 encoding, followed by the actual UTF-8 bytes. 484 final byte[] tagBytes = stringToBytes(tags[i]); 485 mPos += mBuffer.putInt(mPos, tagBytes.length); 486 mPos += mBuffer.putByteArray(mPos, tagBytes); 487 } 488 mNumElements++; 489 } 490 return this; 491 } 492 493 /** 494 * Write KeyValuePairsAtom entries to this StatsEvent. 495 * 496 * @param intMap Integer key-value pairs. 497 * @param longMap Long key-value pairs. 498 * @param stringMap String key-value pairs. 499 * @param floatMap Float key-value pairs. 500 **/ 501 @NonNull writeKeyValuePairs( @onNull final SparseIntArray intMap, @NonNull final SparseLongArray longMap, @NonNull final SparseArray<String> stringMap, @NonNull final SparseArray<Float> floatMap)502 public Builder writeKeyValuePairs( 503 @NonNull final SparseIntArray intMap, 504 @NonNull final SparseLongArray longMap, 505 @NonNull final SparseArray<String> stringMap, 506 @NonNull final SparseArray<Float> floatMap) { 507 final int intMapSize = intMap.size(); 508 final int longMapSize = longMap.size(); 509 final int stringMapSize = stringMap.size(); 510 final int floatMapSize = floatMap.size(); 511 final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize; 512 513 if (totalCount > MAX_KEY_VALUE_PAIRS) { 514 mErrorMask |= ERROR_TOO_MANY_KEY_VALUE_PAIRS; 515 } else { 516 writeTypeId(TYPE_KEY_VALUE_PAIRS); 517 mPos += mBuffer.putByte(mPos, (byte) totalCount); 518 519 for (int i = 0; i < intMapSize; i++) { 520 final int key = intMap.keyAt(i); 521 final int value = intMap.valueAt(i); 522 mPos += mBuffer.putInt(mPos, key); 523 writeTypeId(TYPE_INT); 524 mPos += mBuffer.putInt(mPos, value); 525 } 526 527 for (int i = 0; i < longMapSize; i++) { 528 final int key = longMap.keyAt(i); 529 final long value = longMap.valueAt(i); 530 mPos += mBuffer.putInt(mPos, key); 531 writeTypeId(TYPE_LONG); 532 mPos += mBuffer.putLong(mPos, value); 533 } 534 535 for (int i = 0; i < stringMapSize; i++) { 536 final int key = stringMap.keyAt(i); 537 final String value = stringMap.valueAt(i); 538 mPos += mBuffer.putInt(mPos, key); 539 writeTypeId(TYPE_STRING); 540 final byte[] valueBytes = stringToBytes(value); 541 mPos += mBuffer.putInt(mPos, valueBytes.length); 542 mPos += mBuffer.putByteArray(mPos, valueBytes); 543 } 544 545 for (int i = 0; i < floatMapSize; i++) { 546 final int key = floatMap.keyAt(i); 547 final float value = floatMap.valueAt(i); 548 mPos += mBuffer.putInt(mPos, key); 549 writeTypeId(TYPE_FLOAT); 550 mPos += mBuffer.putFloat(mPos, value); 551 } 552 553 mNumElements++; 554 } 555 556 return this; 557 } 558 559 /** 560 * Write a boolean annotation for the last field written. 561 **/ 562 @NonNull addBooleanAnnotation( final byte annotationId, final boolean value)563 public Builder addBooleanAnnotation( 564 final byte annotationId, final boolean value) { 565 // Ensure there's a field written to annotate. 566 if (0 == mPosLastField) { 567 mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; 568 } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { 569 mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; 570 } else { 571 mPos += mBuffer.putByte(mPos, annotationId); 572 mPos += mBuffer.putByte(mPos, TYPE_BOOLEAN); 573 mPos += mBuffer.putBoolean(mPos, value); 574 mCurrentAnnotationCount++; 575 writeAnnotationCount(); 576 } 577 return this; 578 } 579 580 /** 581 * Write an integer annotation for the last field written. 582 **/ 583 @NonNull addIntAnnotation(final byte annotationId, final int value)584 public Builder addIntAnnotation(final byte annotationId, final int value) { 585 if (0 == mPosLastField) { 586 mErrorMask |= ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD; 587 } else if (mCurrentAnnotationCount >= MAX_ANNOTATION_COUNT) { 588 mErrorMask |= ERROR_TOO_MANY_ANNOTATIONS; 589 } else { 590 mPos += mBuffer.putByte(mPos, annotationId); 591 mPos += mBuffer.putByte(mPos, TYPE_INT); 592 mPos += mBuffer.putInt(mPos, value); 593 mCurrentAnnotationCount++; 594 writeAnnotationCount(); 595 } 596 return this; 597 } 598 599 /** 600 * Indicates to reuse Buffer's byte array as the underlying payload in StatsEvent. 601 * This should be called for pushed events to reduce memory allocations and garbage 602 * collections. 603 **/ 604 @NonNull usePooledBuffer()605 public Builder usePooledBuffer() { 606 mUsePooledBuffer = true; 607 return this; 608 } 609 610 /** 611 * Builds a StatsEvent object with values entered in this Builder. 612 **/ 613 @NonNull build()614 public StatsEvent build() { 615 if (0L == mTimestampNs) { 616 mErrorMask |= ERROR_NO_TIMESTAMP; 617 } 618 if (0 == mAtomId) { 619 mErrorMask |= ERROR_NO_ATOM_ID; 620 } 621 if (mBuffer.hasOverflowed()) { 622 mErrorMask |= ERROR_OVERFLOW; 623 } 624 if (mNumElements > MAX_NUM_ELEMENTS) { 625 mErrorMask |= ERROR_TOO_MANY_FIELDS; 626 } 627 628 int size = mPos; 629 mPos = POS_TIMESTAMP_NS; 630 writeLong(mTimestampNs); 631 writeInt(mAtomId); 632 if (0 == mErrorMask) { 633 mBuffer.putByte(POS_NUM_ELEMENTS, (byte) mNumElements); 634 } else { 635 mPos += mBuffer.putByte(mPos, TYPE_ERRORS); 636 mPos += mBuffer.putInt(mPos, mErrorMask); 637 mBuffer.putByte(POS_NUM_ELEMENTS, (byte) 3); 638 size = mPos; 639 } 640 641 if (mUsePooledBuffer) { 642 return new StatsEvent(mAtomId, mBuffer, mBuffer.getBytes(), size); 643 } else { 644 // Create a copy of the buffer with the required number of bytes. 645 final byte[] payload = new byte[size]; 646 System.arraycopy(mBuffer.getBytes(), 0, payload, 0, size); 647 648 // Return Buffer instance to the pool. 649 mBuffer.release(); 650 651 return new StatsEvent(mAtomId, null, payload, size); 652 } 653 } 654 writeTypeId(final byte typeId)655 private void writeTypeId(final byte typeId) { 656 mPosLastField = mPos; 657 mLastType = typeId; 658 mCurrentAnnotationCount = 0; 659 final byte encodedId = (byte) (typeId & 0x0F); 660 mPos += mBuffer.putByte(mPos, encodedId); 661 } 662 writeAnnotationCount()663 private void writeAnnotationCount() { 664 // Use first 4 bits for annotation count and last 4 bits for typeId. 665 final byte encodedId = (byte) ((mCurrentAnnotationCount << 4) | (mLastType & 0x0F)); 666 mBuffer.putByte(mPosLastField, encodedId); 667 } 668 669 @NonNull stringToBytes(@ullable final String value)670 private static byte[] stringToBytes(@Nullable final String value) { 671 return (null == value ? "" : value).getBytes(UTF_8); 672 } 673 } 674 675 private static final class Buffer { 676 private static Object sLock = new Object(); 677 678 @GuardedBy("sLock") 679 private static Buffer sPool; 680 681 private final byte[] mBytes = new byte[MAX_PAYLOAD_SIZE]; 682 private boolean mOverflow = false; 683 684 @NonNull obtain()685 private static Buffer obtain() { 686 final Buffer buffer; 687 synchronized (sLock) { 688 buffer = null == sPool ? new Buffer() : sPool; 689 sPool = null; 690 } 691 buffer.reset(); 692 return buffer; 693 } 694 Buffer()695 private Buffer() { 696 } 697 698 @NonNull getBytes()699 private byte[] getBytes() { 700 return mBytes; 701 } 702 release()703 private void release() { 704 synchronized (sLock) { 705 if (null == sPool) { 706 sPool = this; 707 } 708 } 709 } 710 reset()711 private void reset() { 712 mOverflow = false; 713 } 714 hasOverflowed()715 private boolean hasOverflowed() { 716 return mOverflow; 717 } 718 719 /** 720 * Checks for available space in the byte array. 721 * 722 * @param index starting position in the buffer to start the check. 723 * @param numBytes number of bytes to check from index. 724 * @return true if space is available, false otherwise. 725 **/ hasEnoughSpace(final int index, final int numBytes)726 private boolean hasEnoughSpace(final int index, final int numBytes) { 727 final boolean result = index + numBytes < MAX_PAYLOAD_SIZE; 728 if (!result) { 729 mOverflow = true; 730 } 731 return result; 732 } 733 734 /** 735 * Writes a byte into the buffer. 736 * 737 * @param index position in the buffer where the byte is written. 738 * @param value the byte to write. 739 * @return number of bytes written to buffer from this write operation. 740 **/ 741 private int putByte(final int index, final byte value) { 742 if (hasEnoughSpace(index, Byte.BYTES)) { 743 mBytes[index] = (byte) (value); 744 return Byte.BYTES; 745 } 746 return 0; 747 } 748 749 /** 750 * Writes a boolean into the buffer. 751 * 752 * @param index position in the buffer where the boolean is written. 753 * @param value the boolean to write. 754 * @return number of bytes written to buffer from this write operation. 755 **/ 756 private int putBoolean(final int index, final boolean value) { 757 return putByte(index, (byte) (value ? 1 : 0)); 758 } 759 760 /** 761 * Writes an integer into the buffer. 762 * 763 * @param index position in the buffer where the integer is written. 764 * @param value the integer to write. 765 * @return number of bytes written to buffer from this write operation. 766 **/ 767 private int putInt(final int index, final int value) { 768 if (hasEnoughSpace(index, Integer.BYTES)) { 769 // Use little endian byte order. 770 mBytes[index] = (byte) (value); 771 mBytes[index + 1] = (byte) (value >> 8); 772 mBytes[index + 2] = (byte) (value >> 16); 773 mBytes[index + 3] = (byte) (value >> 24); 774 return Integer.BYTES; 775 } 776 return 0; 777 } 778 779 /** 780 * Writes a long into the buffer. 781 * 782 * @param index position in the buffer where the long is written. 783 * @param value the long to write. 784 * @return number of bytes written to buffer from this write operation. 785 **/ putLong(final int index, final long value)786 private int putLong(final int index, final long value) { 787 if (hasEnoughSpace(index, Long.BYTES)) { 788 // Use little endian byte order. 789 mBytes[index] = (byte) (value); 790 mBytes[index + 1] = (byte) (value >> 8); 791 mBytes[index + 2] = (byte) (value >> 16); 792 mBytes[index + 3] = (byte) (value >> 24); 793 mBytes[index + 4] = (byte) (value >> 32); 794 mBytes[index + 5] = (byte) (value >> 40); 795 mBytes[index + 6] = (byte) (value >> 48); 796 mBytes[index + 7] = (byte) (value >> 56); 797 return Long.BYTES; 798 } 799 return 0; 800 } 801 802 /** 803 * Writes a float into the buffer. 804 * 805 * @param index position in the buffer where the float is written. 806 * @param value the float to write. 807 * @return number of bytes written to buffer from this write operation. 808 **/ putFloat(final int index, final float value)809 private int putFloat(final int index, final float value) { 810 return putInt(index, Float.floatToIntBits(value)); 811 } 812 813 /** 814 * Copies a byte array into the buffer. 815 * 816 * @param index position in the buffer where the byte array is copied. 817 * @param value the byte array to copy. 818 * @return number of bytes written to buffer from this write operation. 819 **/ putByteArray(final int index, @NonNull final byte[] value)820 private int putByteArray(final int index, @NonNull final byte[] value) { 821 final int numBytes = value.length; 822 if (hasEnoughSpace(index, numBytes)) { 823 System.arraycopy(value, 0, mBytes, index, numBytes); 824 return numBytes; 825 } 826 return 0; 827 } 828 } 829 } 830