1 /* 2 * Copyright (C) 2016 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.apksig.internal.util; 18 19 import com.android.apksig.util.DataSink; 20 import com.android.apksig.util.DataSource; 21 import com.android.apksig.util.ReadableDataSink; 22 import java.io.IOException; 23 import java.nio.ByteBuffer; 24 import java.util.Arrays; 25 26 /** 27 * Growable byte array which can be appended to via {@link DataSink} interface and read from via 28 * {@link DataSource} interface. 29 */ 30 public class ByteArrayDataSink implements ReadableDataSink { 31 32 private static final int MAX_READ_CHUNK_SIZE = 65536; 33 34 private byte[] mArray; 35 private int mSize; 36 37 public ByteArrayDataSink() { 38 this(65536); 39 } 40 41 public ByteArrayDataSink(int initialCapacity) { 42 if (initialCapacity < 0) { 43 throw new IllegalArgumentException("initial capacity: " + initialCapacity); 44 } 45 mArray = new byte[initialCapacity]; 46 } 47 48 @Override 49 public void consume(byte[] buf, int offset, int length) throws IOException { 50 if (offset < 0) { 51 // Must perform this check because System.arraycopy below doesn't perform it when 52 // length == 0 53 throw new IndexOutOfBoundsException("offset: " + offset); 54 } 55 if (offset > buf.length) { 56 // Must perform this check because System.arraycopy below doesn't perform it when 57 // length == 0 58 throw new IndexOutOfBoundsException( 59 "offset: " + offset + ", buf.length: " + buf.length); 60 } 61 if (length == 0) { 62 return; 63 } 64 65 ensureAvailable(length); 66 System.arraycopy(buf, offset, mArray, mSize, length); 67 mSize += length; 68 } 69 70 @Override 71 public void consume(ByteBuffer buf) throws IOException { 72 if (!buf.hasRemaining()) { 73 return; 74 } 75 76 if (buf.hasArray()) { 77 consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); 78 buf.position(buf.limit()); 79 return; 80 } 81 82 ensureAvailable(buf.remaining()); 83 byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)]; 84 while (buf.hasRemaining()) { 85 int chunkSize = Math.min(buf.remaining(), tmp.length); 86 buf.get(tmp, 0, chunkSize); 87 System.arraycopy(tmp, 0, mArray, mSize, chunkSize); 88 mSize += chunkSize; 89 } 90 } 91 92 private void ensureAvailable(int minAvailable) throws IOException { 93 if (minAvailable <= 0) { 94 return; 95 } 96 97 long minCapacity = ((long) mSize) + minAvailable; 98 if (minCapacity <= mArray.length) { 99 return; 100 } 101 if (minCapacity > Integer.MAX_VALUE) { 102 throw new IOException( 103 "Required capacity too large: " + minCapacity + ", max: " + Integer.MAX_VALUE); 104 } 105 int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE); 106 int newSize = (int) Math.max(minCapacity, doubleCurrentSize); 107 mArray = Arrays.copyOf(mArray, newSize); 108 } 109 110 @Override 111 public long size() { 112 return mSize; 113 } 114 115 @Override 116 public ByteBuffer getByteBuffer(long offset, int size) { 117 checkChunkValid(offset, size); 118 119 // checkChunkValid ensures that it's OK to cast offset to int. 120 return ByteBuffer.wrap(mArray, (int) offset, size).slice(); 121 } 122 123 @Override 124 public void feed(long offset, long size, DataSink sink) throws IOException { 125 checkChunkValid(offset, size); 126 127 // checkChunkValid ensures that it's OK to cast offset and size to int. 128 sink.consume(mArray, (int) offset, (int) size); 129 } 130 131 @Override 132 public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 133 checkChunkValid(offset, size); 134 135 // checkChunkValid ensures that it's OK to cast offset to int. 136 dest.put(mArray, (int) offset, size); 137 } 138 139 private void checkChunkValid(long offset, long size) { 140 if (offset < 0) { 141 throw new IndexOutOfBoundsException("offset: " + offset); 142 } 143 if (size < 0) { 144 throw new IndexOutOfBoundsException("size: " + size); 145 } 146 if (offset > mSize) { 147 throw new IndexOutOfBoundsException( 148 "offset (" + offset + ") > source size (" + mSize + ")"); 149 } 150 long endOffset = offset + size; 151 if (endOffset < offset) { 152 throw new IndexOutOfBoundsException( 153 "offset (" + offset + ") + size (" + size + ") overflow"); 154 } 155 if (endOffset > mSize) { 156 throw new IndexOutOfBoundsException( 157 "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")"); 158 } 159 } 160 161 @Override 162 public DataSource slice(long offset, long size) { 163 checkChunkValid(offset, size); 164 // checkChunkValid ensures that it's OK to cast offset and size to int. 165 return new SliceDataSource((int) offset, (int) size); 166 } 167 168 /** 169 * Slice of the growable byte array. The slice's offset and size in the array are fixed. 170 */ 171 private class SliceDataSource implements DataSource { 172 private final int mSliceOffset; 173 private final int mSliceSize; 174 175 private SliceDataSource(int offset, int size) { 176 mSliceOffset = offset; 177 mSliceSize = size; 178 } 179 180 @Override 181 public long size() { 182 return mSliceSize; 183 } 184 185 @Override 186 public void feed(long offset, long size, DataSink sink) throws IOException { 187 checkChunkValid(offset, size); 188 // checkChunkValid combined with the way instances of this class are constructed ensures 189 // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. 190 sink.consume(mArray, (int) (mSliceOffset + offset), (int) size); 191 } 192 193 @Override 194 public ByteBuffer getByteBuffer(long offset, int size) throws IOException { 195 checkChunkValid(offset, size); 196 // checkChunkValid combined with the way instances of this class are constructed ensures 197 // that mSliceOffset + offset does not overflow. 198 return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice(); 199 } 200 201 @Override 202 public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 203 checkChunkValid(offset, size); 204 // checkChunkValid combined with the way instances of this class are constructed ensures 205 // that mSliceOffset + offset does not overflow. 206 dest.put(mArray, (int) (mSliceOffset + offset), size); 207 } 208 209 @Override 210 public DataSource slice(long offset, long size) { 211 checkChunkValid(offset, size); 212 // checkChunkValid combined with the way instances of this class are constructed ensures 213 // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. 214 return new SliceDataSource((int) (mSliceOffset + offset), (int) size); 215 } 216 217 private void checkChunkValid(long offset, long size) { 218 if (offset < 0) { 219 throw new IndexOutOfBoundsException("offset: " + offset); 220 } 221 if (size < 0) { 222 throw new IndexOutOfBoundsException("size: " + size); 223 } 224 if (offset > mSliceSize) { 225 throw new IndexOutOfBoundsException( 226 "offset (" + offset + ") > source size (" + mSliceSize + ")"); 227 } 228 long endOffset = offset + size; 229 if (endOffset < offset) { 230 throw new IndexOutOfBoundsException( 231 "offset (" + offset + ") + size (" + size + ") overflow"); 232 } 233 if (endOffset > mSliceSize) { 234 throw new IndexOutOfBoundsException( 235 "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize 236 + ")"); 237 } 238 } 239 } 240 } 241