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.internal.util;
18 
19 import android.annotation.NonNull;
20 import android.util.CharsetUtils;
21 
22 import dalvik.system.VMRuntime;
23 
24 import java.io.BufferedInputStream;
25 import java.io.Closeable;
26 import java.io.DataInput;
27 import java.io.DataInputStream;
28 import java.io.EOFException;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.Arrays;
32 import java.util.Objects;
33 
34 /**
35  * Optimized implementation of {@link DataInput} which buffers data in memory
36  * from the underlying {@link InputStream}.
37  * <p>
38  * Benchmarks have demonstrated this class is 3x more efficient than using a
39  * {@link DataInputStream} with a {@link BufferedInputStream}.
40  */
41 public class FastDataInput implements DataInput, Closeable {
42     private static final int MAX_UNSIGNED_SHORT = 65_535;
43 
44     private final VMRuntime mRuntime;
45     private final InputStream mIn;
46 
47     private final byte[] mBuffer;
48     private final long mBufferPtr;
49     private final int mBufferCap;
50 
51     private int mBufferPos;
52     private int mBufferLim;
53 
54     /**
55      * Values that have been "interned" by {@link #readInternedUTF()}.
56      */
57     private int mStringRefCount = 0;
58     private String[] mStringRefs = new String[32];
59 
FastDataInput(@onNull InputStream in, int bufferSize)60     public FastDataInput(@NonNull InputStream in, int bufferSize) {
61         mRuntime = VMRuntime.getRuntime();
62         mIn = Objects.requireNonNull(in);
63         if (bufferSize < 8) {
64             throw new IllegalArgumentException();
65         }
66 
67         mBuffer = (byte[]) mRuntime.newNonMovableArray(byte.class, bufferSize);
68         mBufferPtr = mRuntime.addressOf(mBuffer);
69         mBufferCap = mBuffer.length;
70     }
71 
fill(int need)72     private void fill(int need) throws IOException {
73         final int remain = mBufferLim - mBufferPos;
74         System.arraycopy(mBuffer, mBufferPos, mBuffer, 0, remain);
75         mBufferPos = 0;
76         mBufferLim = remain;
77         need -= remain;
78 
79         while (need > 0) {
80             int c = mIn.read(mBuffer, mBufferLim, mBufferCap - mBufferLim);
81             if (c == -1) {
82                 throw new EOFException();
83             } else {
84                 mBufferLim += c;
85                 need -= c;
86             }
87         }
88     }
89 
90     @Override
close()91     public void close() throws IOException {
92         mIn.close();
93     }
94 
95     @Override
readFully(byte[] b)96     public void readFully(byte[] b) throws IOException {
97         readFully(b, 0, b.length);
98     }
99 
100     @Override
readFully(byte[] b, int off, int len)101     public void readFully(byte[] b, int off, int len) throws IOException {
102         // Attempt to read directly from buffer space if there's enough room,
103         // otherwise fall back to chunking into place
104         if (mBufferCap >= len) {
105             if (mBufferLim - mBufferPos < len) fill(len);
106             System.arraycopy(mBuffer, mBufferPos, b, off, len);
107             mBufferPos += len;
108         } else {
109             final int remain = mBufferLim - mBufferPos;
110             System.arraycopy(mBuffer, mBufferPos, b, off, remain);
111             mBufferPos += remain;
112             off += remain;
113             len -= remain;
114 
115             while (len > 0) {
116                 int c = mIn.read(b, off, len);
117                 if (c == -1) {
118                     throw new EOFException();
119                 } else {
120                     off += c;
121                     len -= c;
122                 }
123             }
124         }
125     }
126 
127     @Override
readUTF()128     public String readUTF() throws IOException {
129         // Attempt to read directly from buffer space if there's enough room,
130         // otherwise fall back to chunking into place
131         final int len = readUnsignedShort();
132         if (mBufferCap > len) {
133             if (mBufferLim - mBufferPos < len) fill(len);
134             final String res = CharsetUtils.fromModifiedUtf8Bytes(mBufferPtr, mBufferPos, len);
135             mBufferPos += len;
136             return res;
137         } else {
138             final byte[] tmp = (byte[]) mRuntime.newNonMovableArray(byte.class, len + 1);
139             readFully(tmp, 0, len);
140             return CharsetUtils.fromModifiedUtf8Bytes(mRuntime.addressOf(tmp), 0, len);
141         }
142     }
143 
144     /**
145      * Read a {@link String} value with the additional signal that the given
146      * value is a candidate for being canonicalized, similar to
147      * {@link String#intern()}.
148      * <p>
149      * Canonicalization is implemented by writing each unique string value once
150      * the first time it appears, and then writing a lightweight {@code short}
151      * reference when that string is written again in the future.
152      *
153      * @see FastDataOutput#writeInternedUTF(String)
154      */
readInternedUTF()155     public @NonNull String readInternedUTF() throws IOException {
156         final int ref = readUnsignedShort();
157         if (ref == MAX_UNSIGNED_SHORT) {
158             final String s = readUTF();
159 
160             // We can only safely intern when we have remaining values; if we're
161             // full we at least sent the string value above
162             if (mStringRefCount < MAX_UNSIGNED_SHORT) {
163                 if (mStringRefCount == mStringRefs.length) {
164                     mStringRefs = Arrays.copyOf(mStringRefs,
165                             mStringRefCount + (mStringRefCount >> 1));
166                 }
167                 mStringRefs[mStringRefCount++] = s;
168             }
169 
170             return s;
171         } else {
172             return mStringRefs[ref];
173         }
174     }
175 
176     @Override
readBoolean()177     public boolean readBoolean() throws IOException {
178         return readByte() != 0;
179     }
180 
181     /**
182      * Returns the same decoded value as {@link #readByte()} but without
183      * actually consuming the underlying data.
184      */
peekByte()185     public byte peekByte() throws IOException {
186         if (mBufferLim - mBufferPos < 1) fill(1);
187         return mBuffer[mBufferPos];
188     }
189 
190     @Override
readByte()191     public byte readByte() throws IOException {
192         if (mBufferLim - mBufferPos < 1) fill(1);
193         return mBuffer[mBufferPos++];
194     }
195 
196     @Override
readUnsignedByte()197     public int readUnsignedByte() throws IOException {
198         return Byte.toUnsignedInt(readByte());
199     }
200 
201     @Override
readShort()202     public short readShort() throws IOException {
203         if (mBufferLim - mBufferPos < 2) fill(2);
204         return (short) (((mBuffer[mBufferPos++] & 0xff) <<  8) |
205                         ((mBuffer[mBufferPos++] & 0xff) <<  0));
206     }
207 
208     @Override
readUnsignedShort()209     public int readUnsignedShort() throws IOException {
210         return Short.toUnsignedInt((short) readShort());
211     }
212 
213     @Override
readChar()214     public char readChar() throws IOException {
215         return (char) readShort();
216     }
217 
218     @Override
readInt()219     public int readInt() throws IOException {
220         if (mBufferLim - mBufferPos < 4) fill(4);
221         return (((mBuffer[mBufferPos++] & 0xff) << 24) |
222                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
223                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
224                 ((mBuffer[mBufferPos++] & 0xff) <<  0));
225     }
226 
227     @Override
readLong()228     public long readLong() throws IOException {
229         if (mBufferLim - mBufferPos < 8) fill(8);
230         int h = ((mBuffer[mBufferPos++] & 0xff) << 24) |
231                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
232                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
233                 ((mBuffer[mBufferPos++] & 0xff) <<  0);
234         int l = ((mBuffer[mBufferPos++] & 0xff) << 24) |
235                 ((mBuffer[mBufferPos++] & 0xff) << 16) |
236                 ((mBuffer[mBufferPos++] & 0xff) <<  8) |
237                 ((mBuffer[mBufferPos++] & 0xff) <<  0);
238         return (((long) h) << 32L) | ((long) l) & 0xffffffffL;
239     }
240 
241     @Override
readFloat()242     public float readFloat() throws IOException {
243         return Float.intBitsToFloat(readInt());
244     }
245 
246     @Override
readDouble()247     public double readDouble() throws IOException {
248         return Double.longBitsToDouble(readLong());
249     }
250 
251     @Override
skipBytes(int n)252     public int skipBytes(int n) throws IOException {
253         // Callers should read data piecemeal
254         throw new UnsupportedOperationException();
255     }
256 
257     @Override
readLine()258     public String readLine() throws IOException {
259         // Callers should read data piecemeal
260         throw new UnsupportedOperationException();
261     }
262 }
263