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 FileChannel} for {@link RandomAccessFile} access. 29 */ 30 public class FileChannelDataSource implements DataSource { 31 32 private static final int MAX_READ_CHUNK_SIZE = 1024 * 1024; 33 34 private final FileChannel mChannel; 35 private final long mOffset; 36 private final long mSize; 37 38 /** 39 * Constructs a new {@code FileChannelDataSource} based on the data contained in the 40 * whole file. Changes to the contents of the file, including the size of the file, 41 * will be visible in this data source. 42 */ FileChannelDataSource(FileChannel channel)43 public FileChannelDataSource(FileChannel channel) { 44 mChannel = channel; 45 mOffset = 0; 46 mSize = -1; 47 } 48 49 /** 50 * Constructs a new {@code FileChannelDataSource} 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 */ FileChannelDataSource(FileChannel channel, long offset, long size)56 public FileChannelDataSource(FileChannel channel, 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 mChannel = channel; 64 mOffset = offset; 65 mSize = size; 66 } 67 68 @Override size()69 public long size() { 70 if (mSize == -1) { 71 try { 72 return mChannel.size(); 73 } catch (IOException e) { 74 return 0; 75 } 76 } else { 77 return mSize; 78 } 79 } 80 81 @Override slice(long offset, long size)82 public FileChannelDataSource 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 FileChannelDataSource(mChannel, mOffset + offset, size); 90 } 91 92 @Override feed(long offset, long size, DataSink sink)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 ByteBuffer buf = ByteBuffer.allocateDirect((int) Math.min(remaining, MAX_READ_CHUNK_SIZE)); 103 104 while (remaining > 0) { 105 int chunkSize = (int) Math.min(remaining, buf.capacity()); 106 int chunkRemaining = chunkSize; 107 buf.limit(chunkSize); 108 synchronized (mChannel) { 109 mChannel.position(chunkOffsetInFile); 110 while (chunkRemaining > 0) { 111 int read = mChannel.read(buf); 112 if (read < 0) { 113 throw new IOException("Unexpected EOF encountered"); 114 } 115 chunkRemaining -= read; 116 } 117 } 118 buf.flip(); 119 sink.consume(buf); 120 buf.clear(); 121 chunkOffsetInFile += chunkSize; 122 remaining -= chunkSize; 123 } 124 } 125 126 @Override copyTo(long offset, int size, ByteBuffer dest)127 public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 128 long sourceSize = size(); 129 checkChunkValid(offset, size, sourceSize); 130 if (size == 0) { 131 return; 132 } 133 if (size > dest.remaining()) { 134 throw new BufferOverflowException(); 135 } 136 137 long offsetInFile = mOffset + offset; 138 int remaining = size; 139 int prevLimit = dest.limit(); 140 try { 141 // FileChannel.read(ByteBuffer) reads up to dest.remaining(). Thus, we need to adjust 142 // the buffer's limit to avoid reading more than size bytes. 143 dest.limit(dest.position() + size); 144 while (remaining > 0) { 145 int chunkSize; 146 synchronized (mChannel) { 147 mChannel.position(offsetInFile); 148 chunkSize = mChannel.read(dest); 149 } 150 offsetInFile += chunkSize; 151 remaining -= chunkSize; 152 } 153 } finally { 154 dest.limit(prevLimit); 155 } 156 } 157 158 @Override getByteBuffer(long offset, int size)159 public ByteBuffer getByteBuffer(long offset, int size) throws IOException { 160 if (size < 0) { 161 throw new IndexOutOfBoundsException("size: " + size); 162 } 163 ByteBuffer result = ByteBuffer.allocate(size); 164 copyTo(offset, size, result); 165 result.flip(); 166 return result; 167 } 168 checkChunkValid(long offset, long size, long sourceSize)169 private static void checkChunkValid(long offset, long size, long sourceSize) { 170 if (offset < 0) { 171 throw new IndexOutOfBoundsException("offset: " + offset); 172 } 173 if (size < 0) { 174 throw new IndexOutOfBoundsException("size: " + size); 175 } 176 if (offset > sourceSize) { 177 throw new IndexOutOfBoundsException( 178 "offset (" + offset + ") > source size (" + sourceSize + ")"); 179 } 180 long endOffset = offset + size; 181 if (endOffset < offset) { 182 throw new IndexOutOfBoundsException( 183 "offset (" + offset + ") + size (" + size + ") overflow"); 184 } 185 if (endOffset > sourceSize) { 186 throw new IndexOutOfBoundsException( 187 "offset (" + offset + ") + size (" + size 188 + ") > source size (" + sourceSize +")"); 189 } 190 } 191 } 192