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