• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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