1 /* Copyright 2017 Google Inc. All Rights Reserved. 2 3 Distributed under MIT license. 4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT 5 */ 6 7 #include <jni.h> 8 9 #include <new> 10 11 #include <brotli/decode.h> 12 13 namespace { 14 /* A structure used to persist the decoder's state in between calls. */ 15 typedef struct DecoderHandle { 16 BrotliDecoderState* state; 17 18 uint8_t* input_start; 19 size_t input_offset; 20 size_t input_length; 21 } DecoderHandle; 22 23 /* Obtain handle from opaque pointer. */ 24 DecoderHandle* getHandle(void* opaque) { 25 return static_cast<DecoderHandle*>(opaque); 26 } 27 28 } /* namespace */ 29 30 #ifdef __cplusplus 31 extern "C" { 32 #endif 33 34 /** 35 * Creates a new Decoder. 36 * 37 * Cookie to address created decoder is stored in out_cookie. In case of failure 38 * cookie is 0. 39 * 40 * @param ctx {out_cookie, in_directBufferSize} tuple 41 * @returns direct ByteBuffer if directBufferSize is not 0; otherwise null 42 */ 43 JNIEXPORT jobject JNICALL 44 Java_org_brotli_wrapper_dec_DecoderJNI_nativeCreate( 45 JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { 46 bool ok = true; 47 DecoderHandle* handle = nullptr; 48 jlong context[2]; 49 env->GetLongArrayRegion(ctx, 0, 2, context); 50 size_t input_size = context[1]; 51 context[0] = 0; 52 handle = new (std::nothrow) DecoderHandle(); 53 ok = !!handle; 54 55 if (ok) { 56 handle->input_offset = 0; 57 handle->input_length = 0; 58 handle->input_start = nullptr; 59 60 if (input_size == 0) { 61 ok = false; 62 } else { 63 handle->input_start = new (std::nothrow) uint8_t[input_size]; 64 ok = !!handle->input_start; 65 } 66 } 67 68 if (ok) { 69 handle->state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); 70 ok = !!handle->state; 71 } 72 73 if (ok) { 74 /* TODO: future versions (e.g. when 128-bit architecture comes) 75 might require thread-safe cookie<->handle mapping. */ 76 context[0] = reinterpret_cast<jlong>(handle); 77 } else if (!!handle) { 78 if (!!handle->input_start) delete[] handle->input_start; 79 delete handle; 80 } 81 82 env->SetLongArrayRegion(ctx, 0, 2, context); 83 84 if (!ok) { 85 return nullptr; 86 } 87 88 return env->NewDirectByteBuffer(handle->input_start, input_size); 89 } 90 91 /** 92 * Push data to decoder. 93 * 94 * status codes: 95 * - 0 error happened 96 * - 1 stream is finished, no more input / output expected 97 * - 2 needs more input to process further 98 * - 3 needs more output to process further 99 * - 4 ok, can proceed further without additional input 100 * 101 * @param ctx {in_cookie, out_status} tuple 102 * @param input_length number of bytes provided in input or direct input; 103 * 0 to process further previous input 104 */ 105 JNIEXPORT void JNICALL 106 Java_org_brotli_wrapper_dec_DecoderJNI_nativePush( 107 JNIEnv* env, jobject /*jobj*/, jlongArray ctx, jint input_length) { 108 jlong context[2]; 109 env->GetLongArrayRegion(ctx, 0, 2, context); 110 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); 111 context[1] = 0; /* ERROR */ 112 env->SetLongArrayRegion(ctx, 0, 2, context); 113 114 if (input_length != 0) { 115 /* Still have unconsumed data. Workflow is broken. */ 116 if (handle->input_offset < handle->input_length) { 117 return; 118 } 119 handle->input_offset = 0; 120 handle->input_length = input_length; 121 } 122 123 /* Actual decompression. */ 124 const uint8_t* in = handle->input_start + handle->input_offset; 125 size_t in_size = handle->input_length - handle->input_offset; 126 size_t out_size = 0; 127 BrotliDecoderResult status = BrotliDecoderDecompressStream( 128 handle->state, &in_size, &in, &out_size, nullptr, nullptr); 129 handle->input_offset = handle->input_length - in_size; 130 switch (status) { 131 case BROTLI_DECODER_RESULT_SUCCESS: 132 /* Bytes after stream end are not allowed. */ 133 context[1] = (handle->input_offset == handle->input_length) ? 1 : 0; 134 break; 135 136 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT: 137 context[1] = 2; 138 break; 139 140 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT: 141 context[1] = 3; 142 break; 143 144 default: 145 context[1] = 0; 146 break; 147 } 148 env->SetLongArrayRegion(ctx, 0, 2, context); 149 } 150 151 /** 152 * Pull decompressed data from decoder. 153 * 154 * @param ctx {in_cookie, out_status} tuple 155 * @returns direct ByteBuffer; all the produced data MUST be consumed before 156 * any further invocation; null in case of error 157 */ 158 JNIEXPORT jobject JNICALL 159 Java_org_brotli_wrapper_dec_DecoderJNI_nativePull( 160 JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { 161 jlong context[2]; 162 env->GetLongArrayRegion(ctx, 0, 2, context); 163 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); 164 size_t data_length = 0; 165 const uint8_t* data = BrotliDecoderTakeOutput(handle->state, &data_length); 166 if (BrotliDecoderHasMoreOutput(handle->state)) { 167 context[1] = 3; 168 } else if (BrotliDecoderIsFinished(handle->state)) { 169 /* Bytes after stream end are not allowed. */ 170 context[1] = (handle->input_offset == handle->input_length) ? 1 : 0; 171 } else { 172 /* Can proceed, or more data is required? */ 173 context[1] = (handle->input_offset == handle->input_length) ? 2 : 4; 174 } 175 env->SetLongArrayRegion(ctx, 0, 2, context); 176 return env->NewDirectByteBuffer(const_cast<uint8_t*>(data), data_length); 177 } 178 179 /** 180 * Releases all used resources. 181 * 182 * @param ctx {in_cookie} tuple 183 */ 184 JNIEXPORT void JNICALL 185 Java_org_brotli_wrapper_dec_DecoderJNI_nativeDestroy( 186 JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { 187 jlong context[2]; 188 env->GetLongArrayRegion(ctx, 0, 2, context); 189 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); 190 BrotliDecoderDestroyInstance(handle->state); 191 delete[] handle->input_start; 192 delete handle; 193 } 194 195 #ifdef __cplusplus 196 } 197 #endif 198