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