1 2 /* 3 * Copyright 2024, Google LLC 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * Neither the name of Google LLC nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package com.android.tools.smali.util; 33 34 import static java.lang.Math.max; 35 import static java.lang.Math.min; 36 37 import java.io.DataInput; 38 import java.io.EOFException; 39 import java.io.IOException; 40 import java.io.InputStream; 41 import java.io.OutputStream; 42 import java.util.ArrayDeque; 43 import java.util.Arrays; 44 import java.util.Queue; 45 46 import javax.annotation.Nonnull; 47 48 /** 49 * Utility methods for working with {@link InputStream}. Based on guava ByteStreams. 50 */ 51 public final class InputStreamUtil { 52 53 private static final int BUFFER_SIZE = 8192; 54 55 /** Max array length on JVM. */ 56 private static final int MAX_ARRAY_LEN = Integer.MAX_VALUE - 8; 57 58 /** Large enough to never need to expand, given the geometric progression of buffer sizes. */ 59 private static final int TO_BYTE_ARRAY_DEQUE_SIZE = 20; 60 61 /** 62 * Reads all bytes from an input stream into a byte array. Does not close the stream. 63 * 64 * @param in the input stream to read from 65 * @return a byte array containing all the bytes from the stream 66 * @throws IOException if an I/O error occurs 67 */ toByteArray(InputStream in)68 public static byte[] toByteArray(InputStream in) throws IOException { 69 int totalLen = 0; 70 ArrayDeque<byte[]> bufs = new ArrayDeque<byte[]>(TO_BYTE_ARRAY_DEQUE_SIZE); 71 72 // Roughly size to match what has been read already. Some file systems, such as procfs, 73 // return 0 74 // as their length. These files are very small, so it's wasteful to allocate an 8KB buffer. 75 int initialBufferSize = min(BUFFER_SIZE, max(128, Integer.highestOneBit(totalLen) * 2)); 76 // Starting with an 8k buffer, double the size of each successive buffer. Smaller buffers 77 // quadruple in size until they reach 8k, to minimize the number of small reads for longer 78 // streams. Buffers are retained in a deque so that there's no copying between buffers while 79 // reading and so all of the bytes in each new allocated buffer are available for reading 80 // from 81 // the stream. 82 for (int bufSize = initialBufferSize; totalLen < MAX_ARRAY_LEN; bufSize = 83 saturatedMultiply(bufSize, bufSize < 4096 ? 4 : 2)) { 84 byte[] buf = new byte[min(bufSize, MAX_ARRAY_LEN - totalLen)]; 85 bufs.add(buf); 86 int off = 0; 87 while (off < buf.length) { 88 // always OK to fill buf; its size plus the rest of bufs is never more than 89 // MAX_ARRAY_LEN 90 int r = in.read(buf, off, buf.length - off); 91 if (r == -1) { 92 return combineBuffers(bufs, totalLen); 93 } 94 off += r; 95 totalLen += r; 96 } 97 } 98 99 // read MAX_ARRAY_LEN bytes without seeing end of stream 100 if (in.read() == -1) { 101 // oh, there's the end of the stream 102 return combineBuffers(bufs, MAX_ARRAY_LEN); 103 } else { 104 throw new OutOfMemoryError("input is too large to fit in a byte array"); 105 } 106 } 107 saturatedMultiply(int a, int b)108 private static int saturatedMultiply(int a, int b) { 109 long value = (long) a * b; 110 if (value > Integer.MAX_VALUE) { 111 return Integer.MAX_VALUE; 112 } 113 if (value < Integer.MIN_VALUE) { 114 return Integer.MIN_VALUE; 115 } 116 return (int) value; 117 } 118 combineBuffers(Queue<byte[]> bufs, int totalLen)119 private static byte[] combineBuffers(Queue<byte[]> bufs, int totalLen) { 120 if (bufs.isEmpty()) { 121 return new byte[0]; 122 } 123 byte[] result = bufs.remove(); 124 if (result.length == totalLen) { 125 return result; 126 } 127 int remaining = totalLen - result.length; 128 result = Arrays.copyOf(result, totalLen); 129 while (remaining > 0) { 130 byte[] buf = bufs.remove(); 131 int bytesToCopy = min(remaining, buf.length); 132 int resultOffset = totalLen - remaining; 133 System.arraycopy(buf, 0, result, resultOffset, bytesToCopy); 134 remaining -= bytesToCopy; 135 } 136 return result; 137 } 138 139 /** 140 * Discards {@code n} bytes of data from the input stream. This method will block until the full 141 * amount has been skipped. Does not close the stream. 142 * 143 * @param in the input stream to read from 144 * @param n the number of bytes to skip 145 * @throws EOFException if this stream reaches the end before skipping all the bytes 146 * @throws IOException if an I/O error occurs, or the stream does not support skipping 147 */ skipFully(InputStream in, long n)148 public static void skipFully(InputStream in, long n) throws IOException { 149 long skipped = skipUpTo(in, n); 150 if (skipped < n) { 151 throw new EOFException( 152 "reached end of stream after skipping " + skipped + " bytes; " + n 153 + " bytes expected"); 154 } 155 } 156 157 /** 158 * Discards up to {@code n} bytes of data from the input stream. This method will block until 159 * either the full amount has been skipped or until the end of the stream is reached, whichever 160 * happens first. Returns the total number of bytes skipped. 161 */ skipUpTo(InputStream in, long n)162 static long skipUpTo(InputStream in, long n) throws IOException { 163 long totalSkipped = 0; 164 // A buffer is allocated if skipSafely does not skip any bytes. 165 byte[] buf = null; 166 167 while (totalSkipped < n) { 168 long remaining = n - totalSkipped; 169 long skipped = skipSafely(in, remaining); 170 171 if (skipped == 0) { 172 // Do a buffered read since skipSafely could return 0 repeatedly, for example if 173 // in.available() always returns 0 (the default). 174 int skip = (int) Math.min(remaining, BUFFER_SIZE); 175 if (buf == null) { 176 // Allocate a buffer bounded by the maximum size that can be requested, for 177 // example an array of BUFFER_SIZE is unnecessary when the value of remaining 178 // is smaller. 179 buf = new byte[skip]; 180 } 181 if ((skipped = in.read(buf, 0, skip)) == -1) { 182 // Reached EOF 183 break; 184 } 185 } 186 187 totalSkipped += skipped; 188 } 189 190 return totalSkipped; 191 } 192 193 /** 194 * Attempts to skip up to {@code n} bytes from the given input stream, but not more than {@code 195 * in.available()} bytes. This prevents {@code FileInputStream} from skipping more bytes than 196 * actually remain in the file, something that it {@linkplain java.io.FileInputStream#skip(long) 197 * specifies} it can do in its Javadoc despite the fact that it is violating the contract of 198 * {@code InputStream.skip()}. 199 */ skipSafely(InputStream in, long n)200 private static long skipSafely(InputStream in, long n) throws IOException { 201 int available = in.available(); 202 return available == 0 ? 0 : in.skip(Math.min(available, n)); 203 } 204 205 /** 206 * Attempts to read enough bytes from the stream to fill the given byte array, with the same 207 * behavior as {@link DataInput#readFully(byte[])}. Does not close the stream. 208 * 209 * @param in the input stream to read from. 210 * @param b the buffer into which the data is read. 211 * @throws EOFException if this stream reaches the end before reading all the bytes. 212 * @throws IOException if an I/O error occurs. 213 */ readFully(@onnull InputStream in, @Nonnull byte[] b)214 public static void readFully(@Nonnull InputStream in, @Nonnull byte[] b) throws IOException { 215 int read = read(in, b, 0, b.length); 216 if (read != b.length) { 217 throw new EOFException( 218 "reached end of stream after reading " + read + " bytes; " + b.length 219 + " bytes expected"); 220 } 221 } 222 223 /** 224 * Reads some bytes from an input stream and stores them into the buffer array {@code b}. This 225 * method blocks until {@code len} bytes of input data have been read into the array, or end of 226 * file is detected. The number of bytes read is returned, possibly zero. Does not close the 227 * stream. 228 * <p> 229 * A caller can detect EOF if the number of bytes read is less than {@code len}. All subsequent 230 * calls on the same stream will return zero. 231 * <p> 232 * If {@code b} is null, a {@code NullPointerException} is thrown. If {@code off} is negative, 233 * or {@code len} is negative, or {@code off+len} is greater than the length of the array {@code 234 * b}, then an {@code IndexOutOfBoundsException} is thrown. If {@code len} is zero, then no 235 * bytes are read. Otherwise, the first byte read is stored into element {@code b[off]}, the 236 * next one into {@code b[off+1]}, and so on. The number of bytes read is, at most, equal to 237 * {@code len}. 238 * 239 * @param in the input stream to read from 240 * @param b the buffer into which the data is read 241 * @param off an int specifying the offset into the data 242 * @param len an int specifying the number of bytes to read 243 * @return the number of bytes read 244 * @throws IOException if an I/O error occurs 245 * @throws IndexOutOfBoundsException if {@code off} is negative, if {@code len} is negative, or 246 * if {@code off + len} is greater than {@code b.length} 247 */ read(@onnull InputStream in, @Nonnull byte[] b, int off, int len)248 public static int read(@Nonnull InputStream in, @Nonnull byte[] b, int off, int len) throws IOException { 249 if (off < 0 || len < 0 || off + len > b.length) { 250 throw new IndexOutOfBoundsException("trying to read invalid offset/length range"); 251 } 252 253 int total = 0; 254 while (total < len) { 255 int result = in.read(b, off + total, len - total); 256 if (result == -1) { 257 break; 258 } 259 total += result; 260 } 261 return total; 262 } 263 264 /** 265 * Copies all bytes from the input stream to the output stream. Does not close or flush either 266 * stream. 267 * 268 * @param from the input stream to read from 269 * @param to the output stream to write to 270 * @return the number of bytes copied 271 * @throws IOException if an I/O error occurs 272 */ copy(InputStream from, OutputStream to)273 public static long copy(InputStream from, OutputStream to) throws IOException { 274 if (from == null || to == null) { 275 throw new NullPointerException(); 276 } 277 byte[] buf = new byte[BUFFER_SIZE]; 278 long total = 0; 279 while (true) { 280 int r = from.read(buf); 281 if (r == -1) { 282 break; 283 } 284 to.write(buf, 0, r); 285 total += r; 286 } 287 return total; 288 } 289 } 290