| /* |
| * 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 java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.nio.BufferOverflowException; |
| import java.nio.ByteBuffer; |
| import java.nio.channels.FileChannel; |
| |
| /** |
| * {@link DataSource} backed by a {@link RandomAccessFile}. |
| */ |
| public class RandomAccessFileDataSource implements DataSource { |
| |
| private static final int MAX_READ_CHUNK_SIZE = 65536; |
| |
| private final RandomAccessFile mFile; |
| private final long mOffset; |
| private final long mSize; |
| |
| /** |
| * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the |
| * specified the whole file. Changes to the contents of the file, including the size of the |
| * file, will be visible in this data source. |
| */ |
| public RandomAccessFileDataSource(RandomAccessFile file) { |
| mFile = file; |
| mOffset = 0; |
| mSize = -1; |
| } |
| |
| /** |
| * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the |
| * specified region of the provided file. Changes to the contents of the file will be visible in |
| * this data source. |
| * |
| * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative. |
| */ |
| public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) { |
| if (offset < 0) { |
| throw new IndexOutOfBoundsException("offset: " + size); |
| } |
| if (size < 0) { |
| throw new IndexOutOfBoundsException("size: " + size); |
| } |
| mFile = file; |
| mOffset = offset; |
| mSize = size; |
| } |
| |
| @Override |
| public long size() { |
| if (mSize == -1) { |
| try { |
| return mFile.length(); |
| } catch (IOException e) { |
| return 0; |
| } |
| } else { |
| return mSize; |
| } |
| } |
| |
| @Override |
| public RandomAccessFileDataSource slice(long offset, long size) { |
| long sourceSize = size(); |
| checkChunkValid(offset, size, sourceSize); |
| if ((offset == 0) && (size == sourceSize)) { |
| return this; |
| } |
| |
| return new RandomAccessFileDataSource(mFile, mOffset + offset, size); |
| } |
| |
| @Override |
| public void feed(long offset, long size, DataSink sink) throws IOException { |
| long sourceSize = size(); |
| checkChunkValid(offset, size, sourceSize); |
| if (size == 0) { |
| return; |
| } |
| |
| long chunkOffsetInFile = mOffset + offset; |
| long remaining = size; |
| byte[] buf = new byte[(int) Math.min(remaining, MAX_READ_CHUNK_SIZE)]; |
| while (remaining > 0) { |
| int chunkSize = (int) Math.min(remaining, buf.length); |
| synchronized (mFile) { |
| mFile.seek(chunkOffsetInFile); |
| mFile.readFully(buf, 0, chunkSize); |
| } |
| sink.consume(buf, 0, chunkSize); |
| chunkOffsetInFile += chunkSize; |
| remaining -= chunkSize; |
| } |
| } |
| |
| @Override |
| public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { |
| long sourceSize = size(); |
| checkChunkValid(offset, size, sourceSize); |
| if (size == 0) { |
| return; |
| } |
| if (size > dest.remaining()) { |
| throw new BufferOverflowException(); |
| } |
| |
| long offsetInFile = mOffset + offset; |
| int remaining = size; |
| int prevLimit = dest.limit(); |
| try { |
| // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust |
| // the buffer's limit to avoid reading more than size bytes. |
| dest.limit(dest.position() + size); |
| FileChannel fileChannel = mFile.getChannel(); |
| while (remaining > 0) { |
| int chunkSize; |
| synchronized (mFile) { |
| fileChannel.position(offsetInFile); |
| chunkSize = fileChannel.read(dest); |
| } |
| offsetInFile += chunkSize; |
| remaining -= chunkSize; |
| } |
| } finally { |
| dest.limit(prevLimit); |
| } |
| } |
| |
| @Override |
| public ByteBuffer getByteBuffer(long offset, int size) throws IOException { |
| if (size < 0) { |
| throw new IndexOutOfBoundsException("size: " + size); |
| } |
| ByteBuffer result = ByteBuffer.allocate(size); |
| copyTo(offset, size, result); |
| result.flip(); |
| return result; |
| } |
| |
| private static void checkChunkValid(long offset, long size, long sourceSize) { |
| if (offset < 0) { |
| throw new IndexOutOfBoundsException("offset: " + offset); |
| } |
| if (size < 0) { |
| throw new IndexOutOfBoundsException("size: " + size); |
| } |
| if (offset > sourceSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") > source size (" + sourceSize + ")"); |
| } |
| long endOffset = offset + size; |
| if (endOffset < offset) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size + ") overflow"); |
| } |
| if (endOffset > sourceSize) { |
| throw new IndexOutOfBoundsException( |
| "offset (" + offset + ") + size (" + size |
| + ") > source size (" + sourceSize +")"); |
| } |
| } |
| } |