/* * 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 (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); } @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 IllegalArgumentException("offset: " + offset); } if (size < 0) { throw new IllegalArgumentException("size: " + size); } if (offset > mSize) { throw new IllegalArgumentException( "offset (" + offset + ") > source size (" + mSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IllegalArgumentException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > mSize) { throw new IllegalArgumentException( "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); } @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 IllegalArgumentException("offset: " + offset); } if (size < 0) { throw new IllegalArgumentException("size: " + size); } if (offset > mSliceSize) { throw new IllegalArgumentException( "offset (" + offset + ") > source size (" + mSliceSize + ")"); } long endOffset = offset + size; if (endOffset < offset) { throw new IllegalArgumentException( "offset (" + offset + ") + size (" + size + ") overflow"); } if (endOffset > mSliceSize) { throw new IllegalArgumentException( "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize + ")"); } } } }