| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.apksig.internal.util; |
| |
| import com.android.apksig.util.DataSink; |
| import com.android.apksig.util.DataSource; |
| import com.android.apksig.util.ReadableDataSink; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| |
| /** |
| * Growable byte array which can be appended to via {@link DataSink} interface and read from via |
| * {@link DataSource} interface. |
| */ |
| public class ByteArrayDataSink implements ReadableDataSink { |
| |
| private static final int MAX_READ_CHUNK_SIZE = 65536; |
| |
| private byte[] mArray; |
| private int mSize; |
| |
| public ByteArrayDataSink() { |
| this(65536); |
| } |
| |
| public ByteArrayDataSink(int initialCapacity) { |
| if (initialCapacity < 0) { |
| throw new IllegalArgumentException("initial capacity: " + initialCapacity); |
| } |
| mArray = new byte[initialCapacity]; |
| } |
| |
| @Override |
| public void consume(byte[] buf, int offset, int length) throws IOException { |
| if (offset < 0) { |
| // Must perform this check because System.arraycopy below doesn't perform it when |
| // length == 0 |
| throw new IndexOutOfBoundsException("offset: " + offset); |
| } |
| if (offset > buf.length) { |
| // Must perform this check because System.arraycopy below doesn't perform it when |
| // length == 0 |
| throw new IndexOutOfBoundsException( |
| "offset: " + offset + ", buf.length: " + buf.length); |
| } |
| if (length == 0) { |
| return; |
| } |
| |
| ensureAvailable(length); |
| System.arraycopy(buf, offset, mArray, mSize, length); |
| mSize += length; |
| } |
| |
| @Override |
| public void consume(ByteBuffer buf) throws IOException { |
| if (!buf.hasRemaining()) { |
| return; |
| } |
| |
| if (buf.hasArray()) { |
| consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining()); |
| buf.position(buf.limit()); |
| return; |
| } |
| |
| ensureAvailable(buf.remaining()); |
| byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)]; |
| while (buf.hasRemaining()) { |
| int chunkSize = Math.min(buf.remaining(), tmp.length); |
| buf.get(tmp, 0, chunkSize); |
| System.arraycopy(tmp, 0, mArray, mSize, chunkSize); |
| mSize += chunkSize; |
| } |
| } |
| |
| private void ensureAvailable(int minAvailable) throws IOException { |
| if (minAvailable <= 0) { |
| return; |
| } |
| |
| long minCapacity = ((long) mSize) + minAvailable; |
| if (minCapacity <= mArray.length) { |
| return; |
| } |
| if (minCapacity > Integer.MAX_VALUE) { |
| throw new IOException( |
| "Required capacity too large: " + minCapacity + ", max: " + Integer.MAX_VALUE); |
| } |
| int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE); |
| int newSize = (int) Math.max(minCapacity, doubleCurrentSize); |
| mArray = Arrays.copyOf(mArray, newSize); |
| } |
| |
| @Override |
| public long size() { |
| return mSize; |
| } |
| |
| @Override |
| public ByteBuffer getByteBuffer(long offset, int size) { |
| checkChunkValid(offset, size); |
| |
| // checkChunkValid ensures that it's OK to cast offset to int. |
| return ByteBuffer.wrap(mArray, (int) offset, size).slice(); |
| } |
| |
| @Override |
| public void feed(long offset, long size, DataSink sink) throws IOException { |
| checkChunkValid(offset, size); |
| |
| // checkChunkValid ensures that it's OK to cast offset and size to int. |
| sink.consume(mArray, (int) offset, (int) size); |
| } |
| |
| @Override |
| public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { |
| checkChunkValid(offset, size); |
| |
| // checkChunkValid ensures that it's OK to cast offset to int. |
| dest.put(mArray, (int) offset, size); |
| } |
| |
| private void checkChunkValid(long offset, long size) { |
| if (offset < 0) { |
| throw new IndexOutOfBoundsException("offset: " + offset); |
| } |
| if (size < 0) { |
| throw new IndexOutOfBoundsException("size: " + size); |
| } |
| if (offset > mSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") > source size (" + mSize + ")"); |
| } |
| long endOffset = offset + size; |
| if (endOffset < offset) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size + ") overflow"); |
| } |
| if (endOffset > mSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")"); |
| } |
| } |
| |
| @Override |
| public DataSource slice(long offset, long size) { |
| checkChunkValid(offset, size); |
| // checkChunkValid ensures that it's OK to cast offset and size to int. |
| return new SliceDataSource((int) offset, (int) size); |
| } |
| |
| /** |
| * Slice of the growable byte array. The slice's offset and size in the array are fixed. |
| */ |
| private class SliceDataSource implements DataSource { |
| private final int mSliceOffset; |
| private final int mSliceSize; |
| |
| private SliceDataSource(int offset, int size) { |
| mSliceOffset = offset; |
| mSliceSize = size; |
| } |
| |
| @Override |
| public long size() { |
| return mSliceSize; |
| } |
| |
| @Override |
| public void feed(long offset, long size, DataSink sink) throws IOException { |
| checkChunkValid(offset, size); |
| // checkChunkValid combined with the way instances of this class are constructed ensures |
| // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. |
| sink.consume(mArray, (int) (mSliceOffset + offset), (int) size); |
| } |
| |
| @Override |
| public ByteBuffer getByteBuffer(long offset, int size) throws IOException { |
| checkChunkValid(offset, size); |
| // checkChunkValid combined with the way instances of this class are constructed ensures |
| // that mSliceOffset + offset does not overflow. |
| return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice(); |
| } |
| |
| @Override |
| public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { |
| checkChunkValid(offset, size); |
| // checkChunkValid combined with the way instances of this class are constructed ensures |
| // that mSliceOffset + offset does not overflow. |
| dest.put(mArray, (int) (mSliceOffset + offset), size); |
| } |
| |
| @Override |
| public DataSource slice(long offset, long size) { |
| checkChunkValid(offset, size); |
| // checkChunkValid combined with the way instances of this class are constructed ensures |
| // that mSliceOffset + offset does not overflow and that it's fine to cast size to int. |
| return new SliceDataSource((int) (mSliceOffset + offset), (int) size); |
| } |
| |
| private void checkChunkValid(long offset, long size) { |
| if (offset < 0) { |
| throw new IndexOutOfBoundsException("offset: " + offset); |
| } |
| if (size < 0) { |
| throw new IndexOutOfBoundsException("size: " + size); |
| } |
| if (offset > mSliceSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") > source size (" + mSliceSize + ")"); |
| } |
| long endOffset = offset + size; |
| if (endOffset < offset) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size + ") overflow"); |
| } |
| if (endOffset > mSliceSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize |
| + ")"); |
| } |
| } |
| } |
| } |