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 java.io.IOException; 22 import java.io.RandomAccessFile; 23 import java.nio.BufferOverflowException; 24 import java.nio.ByteBuffer; 25 import java.nio.channels.FileChannel; 26 27 /** 28 * {@link DataSource} backed by a {@link RandomAccessFile}. 29 */ 30 public class RandomAccessFileDataSource implements DataSource { 31 32 private static final int MAX_READ_CHUNK_SIZE = 65536; 33 34 private final RandomAccessFile mFile; 35 private final long mOffset; 36 private final long mSize; 37 38 /** 39 * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the 40 * specified the whole file. Changes to the contents of the file, including the size of the 41 * file, will be visible in this data source. 42 */ 43 public RandomAccessFileDataSource(RandomAccessFile file) { 44 mFile = file; 45 mOffset = 0; 46 mSize = -1; 47 } 48 49 /** 50 * Constructs a new {@code RandomAccessFileDataSource} based on the data contained in the 51 * specified region of the provided file. Changes to the contents of the file will be visible in 52 * this data source. 53 * 54 * @throws IndexOutOfBoundsException if {@code offset} or {@code size} is negative. 55 */ 56 public RandomAccessFileDataSource(RandomAccessFile file, long offset, long size) { 57 if (offset < 0) { 58 throw new IndexOutOfBoundsException("offset: " + size); 59 } 60 if (size < 0) { 61 throw new IndexOutOfBoundsException("size: " + size); 62 } 63 mFile = file; 64 mOffset = offset; 65 mSize = size; 66 } 67 68 @Override 69 public long size() { 70 if (mSize == -1) { 71 try { 72 return mFile.length(); 73 } catch (IOException e) { 74 return 0; 75 } 76 } else { 77 return mSize; 78 } 79 } 80 81 @Override 82 public RandomAccessFileDataSource slice(long offset, long size) { 83 long sourceSize = size(); 84 checkChunkValid(offset, size, sourceSize); 85 if ((offset == 0) && (size == sourceSize)) { 86 return this; 87 } 88 89 return new RandomAccessFileDataSource(mFile, mOffset + offset, size); 90 } 91 92 @Override 93 public void feed(long offset, long size, DataSink sink) throws IOException { 94 long sourceSize = size(); 95 checkChunkValid(offset, size, sourceSize); 96 if (size == 0) { 97 return; 98 } 99 100 long chunkOffsetInFile = mOffset + offset; 101 long remaining = size; 102 byte[] buf = new byte[(int) Math.min(remaining, MAX_READ_CHUNK_SIZE)]; 103 while (remaining > 0) { 104 int chunkSize = (int) Math.min(remaining, buf.length); 105 synchronized (mFile) { 106 mFile.seek(chunkOffsetInFile); 107 mFile.readFully(buf, 0, chunkSize); 108 } 109 sink.consume(buf, 0, chunkSize); 110 chunkOffsetInFile += chunkSize; 111 remaining -= chunkSize; 112 } 113 } 114 115 @Override 116 public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 117 long sourceSize = size(); 118 checkChunkValid(offset, size, sourceSize); 119 if (size == 0) { 120 return; 121 } 122 if (size > dest.remaining()) { 123 throw new BufferOverflowException(); 124 } 125 126 long offsetInFile = mOffset + offset; 127 int remaining = size; 128 int prevLimit = dest.limit(); 129 try { 130 // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust 131 // the buffer's limit to avoid reading more than size bytes. 132 dest.limit(dest.position() + size); 133 FileChannel fileChannel = mFile.getChannel(); 134 while (remaining > 0) { 135 int chunkSize; 136 synchronized (mFile) { 137 fileChannel.position(offsetInFile); 138 chunkSize = fileChannel.read(dest); 139 } 140 offsetInFile += chunkSize; 141 remaining -= chunkSize; 142 } 143 } finally { 144 dest.limit(prevLimit); 145 } 146 } 147 148 @Override 149 public ByteBuffer getByteBuffer(long offset, int size) throws IOException { 150 if (size < 0) { 151 throw new IndexOutOfBoundsException("size: " + size); 152 } 153 ByteBuffer result = ByteBuffer.allocate(size); 154 copyTo(offset, size, result); 155 result.flip(); 156 return result; 157 } 158 159 private static void checkChunkValid(long offset, long size, long sourceSize) { 160 if (offset < 0) { 161 throw new IndexOutOfBoundsException("offset: " + offset); 162 } 163 if (size < 0) { 164 throw new IndexOutOfBoundsException("size: " + size); 165 } 166 if (offset > sourceSize) { 167 throw new IndexOutOfBoundsException( 168 "offset (" + offset + ") > source size (" + sourceSize + ")"); 169 } 170 long endOffset = offset + size; 171 if (endOffset < offset) { 172 throw new IndexOutOfBoundsException( 173 "offset (" + offset + ") + size (" + size + ") overflow"); 174 } 175 if (endOffset > sourceSize) { 176 throw new IndexOutOfBoundsException( 177 "offset (" + offset + ") + size (" + size 178 + ") > source size (" + sourceSize +")"); 179 } 180 } 181 } 182