Home | History | Annotate | Download | only in util
      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 com.android.apksig.util.ReadableDataSink;
     22 import java.io.IOException;
     23 import java.nio.ByteBuffer;
     24 import java.util.Arrays;
     25 
     26 /**
     27  * Growable byte array which can be appended to via {@link DataSink} interface and read from via
     28  * {@link DataSource} interface.
     29  */
     30 public class ByteArrayDataSink implements ReadableDataSink {
     31 
     32     private static final int MAX_READ_CHUNK_SIZE = 65536;
     33 
     34     private byte[] mArray;
     35     private int mSize;
     36 
     37     public ByteArrayDataSink() {
     38         this(65536);
     39     }
     40 
     41     public ByteArrayDataSink(int initialCapacity) {
     42         if (initialCapacity < 0) {
     43             throw new IllegalArgumentException("initial capacity: " + initialCapacity);
     44         }
     45         mArray = new byte[initialCapacity];
     46     }
     47 
     48     @Override
     49     public void consume(byte[] buf, int offset, int length) throws IOException {
     50         if (offset < 0) {
     51             // Must perform this check because System.arraycopy below doesn't perform it when
     52             // length == 0
     53             throw new IndexOutOfBoundsException("offset: " + offset);
     54         }
     55         if (offset > buf.length) {
     56             // Must perform this check because System.arraycopy below doesn't perform it when
     57             // length == 0
     58             throw new IndexOutOfBoundsException(
     59                     "offset: " + offset + ", buf.length: " + buf.length);
     60         }
     61         if (length == 0) {
     62             return;
     63         }
     64 
     65         ensureAvailable(length);
     66         System.arraycopy(buf, offset, mArray, mSize, length);
     67         mSize += length;
     68     }
     69 
     70     @Override
     71     public void consume(ByteBuffer buf) throws IOException {
     72         if (!buf.hasRemaining()) {
     73             return;
     74         }
     75 
     76         if (buf.hasArray()) {
     77             consume(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining());
     78             buf.position(buf.limit());
     79             return;
     80         }
     81 
     82         ensureAvailable(buf.remaining());
     83         byte[] tmp = new byte[Math.min(buf.remaining(), MAX_READ_CHUNK_SIZE)];
     84         while (buf.hasRemaining()) {
     85             int chunkSize = Math.min(buf.remaining(), tmp.length);
     86             buf.get(tmp, 0, chunkSize);
     87             System.arraycopy(tmp, 0, mArray, mSize, chunkSize);
     88             mSize += chunkSize;
     89         }
     90     }
     91 
     92     private void ensureAvailable(int minAvailable) throws IOException {
     93         if (minAvailable <= 0) {
     94             return;
     95         }
     96 
     97         long minCapacity = ((long) mSize) + minAvailable;
     98         if (minCapacity <= mArray.length) {
     99             return;
    100         }
    101         if (minCapacity > Integer.MAX_VALUE) {
    102             throw new IOException(
    103                     "Required capacity too large: " + minCapacity + ", max: " + Integer.MAX_VALUE);
    104         }
    105         int doubleCurrentSize = (int) Math.min(mArray.length * 2L, Integer.MAX_VALUE);
    106         int newSize = (int) Math.max(minCapacity, doubleCurrentSize);
    107         mArray = Arrays.copyOf(mArray, newSize);
    108     }
    109 
    110     @Override
    111     public long size() {
    112         return mSize;
    113     }
    114 
    115     @Override
    116     public ByteBuffer getByteBuffer(long offset, int size) {
    117         checkChunkValid(offset, size);
    118 
    119         // checkChunkValid ensures that it's OK to cast offset to int.
    120         return ByteBuffer.wrap(mArray, (int) offset, size).slice();
    121     }
    122 
    123     @Override
    124     public void feed(long offset, long size, DataSink sink) throws IOException {
    125         checkChunkValid(offset, size);
    126 
    127         // checkChunkValid ensures that it's OK to cast offset and size to int.
    128         sink.consume(mArray, (int) offset, (int) size);
    129     }
    130 
    131     @Override
    132     public void copyTo(long offset, int size, ByteBuffer dest) throws IOException {
    133         checkChunkValid(offset, size);
    134 
    135         // checkChunkValid ensures that it's OK to cast offset to int.
    136         dest.put(mArray, (int) offset, size);
    137     }
    138 
    139     private void checkChunkValid(long offset, long size) {
    140         if (offset < 0) {
    141             throw new IndexOutOfBoundsException("offset: " + offset);
    142         }
    143         if (size < 0) {
    144             throw new IndexOutOfBoundsException("size: " + size);
    145         }
    146         if (offset > mSize) {
    147             throw new IndexOutOfBoundsException(
    148                     "offset (" + offset + ") > source size (" + mSize + ")");
    149         }
    150         long endOffset = offset + size;
    151         if (endOffset < offset) {
    152             throw new IndexOutOfBoundsException(
    153                     "offset (" + offset + ") + size (" + size + ") overflow");
    154         }
    155         if (endOffset > mSize) {
    156             throw new IndexOutOfBoundsException(
    157                     "offset (" + offset + ") + size (" + size + ") > source size (" + mSize + ")");
    158         }
    159     }
    160 
    161     @Override
    162     public DataSource slice(long offset, long size) {
    163         checkChunkValid(offset, size);
    164         // checkChunkValid ensures that it's OK to cast offset and size to int.
    165         return new SliceDataSource((int) offset, (int) size);
    166     }
    167 
    168     /**
    169      * Slice of the growable byte array. The slice's offset and size in the array are fixed.
    170      */
    171     private class SliceDataSource implements DataSource {
    172         private final int mSliceOffset;
    173         private final int mSliceSize;
    174 
    175         private SliceDataSource(int offset, int size) {
    176             mSliceOffset = offset;
    177             mSliceSize = size;
    178         }
    179 
    180         @Override
    181         public long size() {
    182             return mSliceSize;
    183         }
    184 
    185         @Override
    186         public void feed(long offset, long size, DataSink sink) throws IOException {
    187             checkChunkValid(offset, size);
    188             // checkChunkValid combined with the way instances of this class are constructed ensures
    189             // that mSliceOffset + offset does not overflow and that it's fine to cast size to int.
    190             sink.consume(mArray, (int) (mSliceOffset + offset), (int) size);
    191         }
    192 
    193         @Override
    194         public ByteBuffer getByteBuffer(long offset, int size) throws IOException {
    195             checkChunkValid(offset, size);
    196             // checkChunkValid combined with the way instances of this class are constructed ensures
    197             // that mSliceOffset + offset does not overflow.
    198             return ByteBuffer.wrap(mArray, (int) (mSliceOffset + offset), size).slice();
    199         }
    200 
    201         @Override
    202         public void copyTo(long offset, int size, ByteBuffer dest) throws IOException {
    203             checkChunkValid(offset, size);
    204             // checkChunkValid combined with the way instances of this class are constructed ensures
    205             // that mSliceOffset + offset does not overflow.
    206             dest.put(mArray, (int) (mSliceOffset + offset), size);
    207         }
    208 
    209         @Override
    210         public DataSource slice(long offset, long size) {
    211             checkChunkValid(offset, size);
    212             // checkChunkValid combined with the way instances of this class are constructed ensures
    213             // that mSliceOffset + offset does not overflow and that it's fine to cast size to int.
    214             return new SliceDataSource((int) (mSliceOffset + offset), (int) size);
    215         }
    216 
    217         private void checkChunkValid(long offset, long size) {
    218             if (offset < 0) {
    219                 throw new IndexOutOfBoundsException("offset: " + offset);
    220             }
    221             if (size < 0) {
    222                 throw new IndexOutOfBoundsException("size: " + size);
    223             }
    224             if (offset > mSliceSize) {
    225                 throw new IndexOutOfBoundsException(
    226                         "offset (" + offset + ") > source size (" + mSliceSize + ")");
    227             }
    228             long endOffset = offset + size;
    229             if (endOffset < offset) {
    230                 throw new IndexOutOfBoundsException(
    231                         "offset (" + offset + ") + size (" + size + ") overflow");
    232             }
    233             if (endOffset > mSliceSize) {
    234                 throw new IndexOutOfBoundsException(
    235                         "offset (" + offset + ") + size (" + size + ") > source size (" + mSliceSize
    236                                 + ")");
    237             }
    238         }
    239     }
    240 }
    241