Home | History | Annotate | Download | only in native
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 #define LOG_TAG "Inflater"
     19 
     20 #include "JniConstants.h"
     21 #include "ScopedPrimitiveArray.h"
     22 #include "zip.h"
     23 #include <errno.h>
     24 
     25 static struct {
     26     jfieldID inRead;
     27     jfieldID finished;
     28     jfieldID needsDictionary;
     29 } gCachedFields;
     30 
     31 /* Create a new stream . This stream cannot be used until it has been properly initialized. */
     32 static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) {
     33     UniquePtr<NativeZipStream> jstream(new NativeZipStream);
     34     if (jstream.get() == NULL) {
     35         jniThrowOutOfMemoryError(env, NULL);
     36         return -1;
     37     }
     38     jstream->stream.adler = 1;
     39 
     40     /*
     41      * In the range 8..15 for checked, or -8..-15 for unchecked inflate. Unchecked
     42      * is appropriate for formats like zip that do their own validity checking.
     43      */
     44     /* Window bits to use. 15 is fastest but consumes the most memory */
     45     int wbits = 15;               /*Use MAX for fastest */
     46     if (noHeader) {
     47         wbits = wbits / -1;
     48     }
     49     int err = inflateInit2(&jstream->stream, wbits);
     50     if (err != Z_OK) {
     51         throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err);
     52         return -1;
     53     }
     54     return reinterpret_cast<uintptr_t>(jstream.release());
     55 }
     56 
     57 static void Inflater_setInputImpl(JNIEnv* env, jobject, jbyteArray buf, jint off, jint len, jlong handle) {
     58     toNativeZipStream(handle)->setInput(env, buf, off, len);
     59 }
     60 
     61 static jint Inflater_setFileInputImpl(JNIEnv* env, jobject, jobject javaFileDescriptor, jlong off, jint len, jlong handle) {
     62     NativeZipStream* stream = toNativeZipStream(handle);
     63 
     64     // We reuse the existing native buffer if it's large enough.
     65     // TODO: benchmark.
     66     if (stream->inCap < len) {
     67         stream->setInput(env, NULL, 0, len);
     68     } else {
     69         stream->stream.next_in = reinterpret_cast<Bytef*>(&stream->input[0]);
     70         stream->stream.avail_in = len;
     71     }
     72 
     73     // As an Android-specific optimization, we read directly onto the native heap.
     74     // The original code used Java to read onto the Java heap and then called setInput(byte[]).
     75     // TODO: benchmark.
     76     int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor);
     77     int rc = TEMP_FAILURE_RETRY(lseek(fd, off, SEEK_SET));
     78     if (rc == -1) {
     79         jniThrowIOException(env, errno);
     80         return 0;
     81     }
     82     jint totalByteCount = 0;
     83     Bytef* dst = reinterpret_cast<Bytef*>(&stream->input[0]);
     84     ssize_t byteCount;
     85     while ((byteCount = TEMP_FAILURE_RETRY(read(fd, dst, len))) > 0) {
     86         dst += byteCount;
     87         len -= byteCount;
     88         totalByteCount += byteCount;
     89     }
     90     if (byteCount == -1) {
     91         jniThrowIOException(env, errno);
     92         return 0;
     93     }
     94     return totalByteCount;
     95 }
     96 
     97 static jint Inflater_inflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int off, int len, jlong handle) {
     98     jfieldID fid2 = 0;
     99 
    100     /* We need to get the number of bytes already read */
    101     jfieldID fid = gCachedFields.inRead;
    102     jint inBytes = env->GetIntField(recv, fid);
    103 
    104     NativeZipStream* stream = toNativeZipStream(handle);
    105     stream->stream.avail_out = len;
    106     jint sin = stream->stream.total_in;
    107     jint sout = stream->stream.total_out;
    108     ScopedByteArrayRW out(env, buf);
    109     if (out.get() == NULL) {
    110         return -1;
    111     }
    112     stream->stream.next_out = reinterpret_cast<Bytef*>(out.get() + off);
    113     int err = inflate(&stream->stream, Z_SYNC_FLUSH);
    114     if (err != Z_OK) {
    115         if (err == Z_STREAM_ERROR) {
    116             return 0;
    117         }
    118         if (err == Z_STREAM_END || err == Z_NEED_DICT) {
    119             env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes);
    120             if (err == Z_STREAM_END) {
    121                 fid2 = gCachedFields.finished;
    122             } else {
    123                 fid2 = gCachedFields.needsDictionary;
    124             }
    125             env->SetBooleanField(recv, fid2, JNI_TRUE);
    126             return stream->stream.total_out - sout;
    127         } else {
    128             throwExceptionForZlibError(env, "java/util/zip/DataFormatException", err);
    129             return -1;
    130         }
    131     }
    132 
    133     /* Need to update the number of input bytes read. Is there a better way
    134      * (Maybe global the fid then delete when end is called)?
    135      */
    136     env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes);
    137     return stream->stream.total_out - sout;
    138 }
    139 
    140 static jint Inflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) {
    141     return toNativeZipStream(handle)->stream.adler;
    142 }
    143 
    144 static void Inflater_endImpl(JNIEnv*, jobject, jlong handle) {
    145     NativeZipStream* stream = toNativeZipStream(handle);
    146     inflateEnd(&stream->stream);
    147     delete stream;
    148 }
    149 
    150 static void Inflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, int off, int len, jlong handle) {
    151     toNativeZipStream(handle)->setDictionary(env, dict, off, len, true);
    152 }
    153 
    154 static void Inflater_resetImpl(JNIEnv* env, jobject, jlong handle) {
    155     int err = inflateReset(&toNativeZipStream(handle)->stream);
    156     if (err != Z_OK) {
    157         throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err);
    158     }
    159 }
    160 
    161 static jlong Inflater_getTotalOutImpl(JNIEnv*, jobject, jlong handle) {
    162     return toNativeZipStream(handle)->stream.total_out;
    163 }
    164 
    165 static jlong Inflater_getTotalInImpl(JNIEnv*, jobject, jlong handle) {
    166     return toNativeZipStream(handle)->stream.total_in;
    167 }
    168 
    169 static JNINativeMethod gMethods[] = {
    170     NATIVE_METHOD(Inflater, createStream, "(Z)J"),
    171     NATIVE_METHOD(Inflater, endImpl, "(J)V"),
    172     NATIVE_METHOD(Inflater, getAdlerImpl, "(J)I"),
    173     NATIVE_METHOD(Inflater, getTotalInImpl, "(J)J"),
    174     NATIVE_METHOD(Inflater, getTotalOutImpl, "(J)J"),
    175     NATIVE_METHOD(Inflater, inflateImpl, "([BIIJ)I"),
    176     NATIVE_METHOD(Inflater, resetImpl, "(J)V"),
    177     NATIVE_METHOD(Inflater, setDictionaryImpl, "([BIIJ)V"),
    178     NATIVE_METHOD(Inflater, setFileInputImpl, "(Ljava/io/FileDescriptor;JIJ)I"),
    179     NATIVE_METHOD(Inflater, setInputImpl, "([BIIJ)V"),
    180 };
    181 int register_java_util_zip_Inflater(JNIEnv* env) {
    182     gCachedFields.finished = env->GetFieldID(JniConstants::inflaterClass, "finished", "Z");
    183     gCachedFields.inRead = env->GetFieldID(JniConstants::inflaterClass, "inRead", "I");
    184     gCachedFields.needsDictionary = env->GetFieldID(JniConstants::inflaterClass, "needsDictionary", "Z");
    185     return jniRegisterNativeMethods(env, "java/util/zip/Inflater", gMethods, NELEM(gMethods));
    186 }
    187