1 /* 2 * Copyright (C) 2017 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.nio.ByteBuffer; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 26 /** Pseudo {@link DataSource} that chains the given {@link DataSource} as a continuous one. */ 27 public class ChainedDataSource implements DataSource { 28 29 private final DataSource[] mSources; 30 private final long mTotalSize; 31 32 public ChainedDataSource(DataSource... sources) { 33 mSources = sources; 34 mTotalSize = Arrays.stream(sources).mapToLong(src -> src.size()).sum(); 35 } 36 37 @Override 38 public long size() { 39 return mTotalSize; 40 } 41 42 @Override 43 public void feed(long offset, long size, DataSink sink) throws IOException { 44 if (offset + size > mTotalSize) { 45 throw new IndexOutOfBoundsException("Requested more than available"); 46 } 47 48 for (DataSource src : mSources) { 49 // Offset is beyond the current source. Skip. 50 if (offset >= src.size()) { 51 offset -= src.size(); 52 continue; 53 } 54 55 // If the remaining is enough, finish it. 56 long remaining = src.size() - offset; 57 if (remaining >= size) { 58 src.feed(offset, size, sink); 59 break; 60 } 61 62 // If the remaining is not enough, consume all. 63 src.feed(offset, remaining, sink); 64 size -= remaining; 65 offset = 0; 66 } 67 } 68 69 @Override 70 public ByteBuffer getByteBuffer(long offset, int size) throws IOException { 71 if (offset + size > mTotalSize) { 72 throw new IndexOutOfBoundsException("Requested more than available"); 73 } 74 75 // Skip to the first DataSource we need. 76 Pair<Integer, Long> firstSource = locateDataSource(offset); 77 int i = firstSource.getFirst(); 78 offset = firstSource.getSecond(); 79 80 // Return the current source's ByteBuffer if it fits. 81 if (offset + size <= mSources[i].size()) { 82 return mSources[i].getByteBuffer(offset, size); 83 } 84 85 // Otherwise, read into a new buffer. 86 ByteBuffer buffer = ByteBuffer.allocate(size); 87 for (; i < mSources.length && buffer.hasRemaining(); i++) { 88 long sizeToCopy = Math.min(mSources[i].size() - offset, buffer.remaining()); 89 mSources[i].copyTo(offset, Math.toIntExact(sizeToCopy), buffer); 90 offset = 0; // may not be zero for the first source, but reset after that. 91 } 92 buffer.rewind(); 93 return buffer; 94 } 95 96 @Override 97 public void copyTo(long offset, int size, ByteBuffer dest) throws IOException { 98 feed(offset, size, new ByteBufferSink(dest)); 99 } 100 101 @Override 102 public DataSource slice(long offset, long size) { 103 // Find the first slice. 104 Pair<Integer, Long> firstSource = locateDataSource(offset); 105 int beginIndex = firstSource.getFirst(); 106 long beginLocalOffset = firstSource.getSecond(); 107 DataSource beginSource = mSources[beginIndex]; 108 109 if (beginLocalOffset + size <= beginSource.size()) { 110 return beginSource.slice(beginLocalOffset, size); 111 } 112 113 // Add the first slice to chaining, followed by the middle full slices, then the last. 114 ArrayList<DataSource> sources = new ArrayList<>(); 115 sources.add(beginSource.slice( 116 beginLocalOffset, beginSource.size() - beginLocalOffset)); 117 118 Pair<Integer, Long> lastSource = locateDataSource(offset + size); 119 int endIndex = lastSource.getFirst(); 120 long endLocalOffset = lastSource.getSecond(); 121 122 for (int i = beginIndex + 1; i < endIndex - 1; i++) { 123 sources.add(mSources[i]); 124 } 125 126 sources.add(mSources[endIndex].slice(0, endLocalOffset)); 127 return new ChainedDataSource(sources.toArray(new DataSource[0])); 128 } 129 130 /** 131 * Find the index of DataSource that offset is at. 132 * @return Pair of DataSource index and the local offset in the DataSource. 133 */ 134 private Pair<Integer, Long> locateDataSource(long offset) { 135 long localOffset = offset; 136 for (int i = 0; i < mSources.length; i++) { 137 if (localOffset < mSources[i].size()) { 138 return Pair.of(i, localOffset); 139 } 140 localOffset -= mSources[i].size(); 141 } 142 throw new IndexOutOfBoundsException("Access is out of bound, offset: " + offset + 143 ", totalSize: " + mTotalSize); 144 } 145 } 146