blob: 3492399a506f603df49da1b5222cf9b11fc75b5e [file] [log] [blame]
/*
* 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 +")");
}
}
}