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. */
getHandle(void * opaque)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
Java_org_brotli_wrapper_dec_DecoderJNI_nativeCreate(JNIEnv * env,jobject,jlongArray ctx)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[3];
49 env->GetLongArrayRegion(ctx, 0, 3, context);
50 size_t input_size = context[1];
51 context[0] = 0;
52 context[2] = 0;
53 handle = new (std::nothrow) DecoderHandle();
54 ok = !!handle;
55
56 if (ok) {
57 handle->input_offset = 0;
58 handle->input_length = 0;
59 handle->input_start = nullptr;
60
61 if (input_size == 0) {
62 ok = false;
63 } else {
64 handle->input_start = new (std::nothrow) uint8_t[input_size];
65 ok = !!handle->input_start;
66 }
67 }
68
69 if (ok) {
70 handle->state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
71 ok = !!handle->state;
72 }
73
74 if (ok) {
75 /* TODO: future versions (e.g. when 128-bit architecture comes)
76 might require thread-safe cookie<->handle mapping. */
77 context[0] = reinterpret_cast<jlong>(handle);
78 } else if (!!handle) {
79 if (!!handle->input_start) delete[] handle->input_start;
80 delete handle;
81 }
82
83 env->SetLongArrayRegion(ctx, 0, 3, context);
84
85 if (!ok) {
86 return nullptr;
87 }
88
89 return env->NewDirectByteBuffer(handle->input_start, input_size);
90 }
91
92 /**
93 * Push data to decoder.
94 *
95 * status codes:
96 * - 0 error happened
97 * - 1 stream is finished, no more input / output expected
98 * - 2 needs more input to process further
99 * - 3 needs more output to process further
100 * - 4 ok, can proceed further without additional input
101 *
102 * @param ctx {in_cookie, out_status} tuple
103 * @param input_length number of bytes provided in input or direct input;
104 * 0 to process further previous input
105 */
106 JNIEXPORT void JNICALL
Java_org_brotli_wrapper_dec_DecoderJNI_nativePush(JNIEnv * env,jobject,jlongArray ctx,jint input_length)107 Java_org_brotli_wrapper_dec_DecoderJNI_nativePush(
108 JNIEnv* env, jobject /*jobj*/, jlongArray ctx, jint input_length) {
109 jlong context[3];
110 env->GetLongArrayRegion(ctx, 0, 3, context);
111 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0]));
112 context[1] = 0; /* ERROR */
113 context[2] = 0;
114 env->SetLongArrayRegion(ctx, 0, 3, context);
115
116 if (input_length != 0) {
117 /* Still have unconsumed data. Workflow is broken. */
118 if (handle->input_offset < handle->input_length) {
119 return;
120 }
121 handle->input_offset = 0;
122 handle->input_length = input_length;
123 }
124
125 /* Actual decompression. */
126 const uint8_t* in = handle->input_start + handle->input_offset;
127 size_t in_size = handle->input_length - handle->input_offset;
128 size_t out_size = 0;
129 BrotliDecoderResult status = BrotliDecoderDecompressStream(
130 handle->state, &in_size, &in, &out_size, nullptr, nullptr);
131 handle->input_offset = handle->input_length - in_size;
132 switch (status) {
133 case BROTLI_DECODER_RESULT_SUCCESS:
134 /* Bytes after stream end are not allowed. */
135 context[1] = (handle->input_offset == handle->input_length) ? 1 : 0;
136 break;
137
138 case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
139 context[1] = 2;
140 break;
141
142 case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
143 context[1] = 3;
144 break;
145
146 default:
147 context[1] = 0;
148 break;
149 }
150 context[2] = BrotliDecoderHasMoreOutput(handle->state) ? 1 : 0;
151 env->SetLongArrayRegion(ctx, 0, 3, context);
152 }
153
154 /**
155 * Pull decompressed data from decoder.
156 *
157 * @param ctx {in_cookie, out_status} tuple
158 * @returns direct ByteBuffer; all the produced data MUST be consumed before
159 * any further invocation; null in case of error
160 */
161 JNIEXPORT jobject JNICALL
Java_org_brotli_wrapper_dec_DecoderJNI_nativePull(JNIEnv * env,jobject,jlongArray ctx)162 Java_org_brotli_wrapper_dec_DecoderJNI_nativePull(
163 JNIEnv* env, jobject /*jobj*/, jlongArray ctx) {
164 jlong context[3];
165 env->GetLongArrayRegion(ctx, 0, 3, context);
166 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0]));
167 size_t data_length = 0;
168 const uint8_t* data = BrotliDecoderTakeOutput(handle->state, &data_length);
169 bool hasMoreOutput = !!BrotliDecoderHasMoreOutput(handle->state);
170 if (hasMoreOutput) {
171 context[1] = 3;
172 } else if (BrotliDecoderIsFinished(handle->state)) {
173 /* Bytes after stream end are not allowed. */
174 context[1] = (handle->input_offset == handle->input_length) ? 1 : 0;
175 } else {
176 /* Can proceed, or more data is required? */
177 context[1] = (handle->input_offset == handle->input_length) ? 2 : 4;
178 }
179 context[2] = hasMoreOutput ? 1 : 0;
180 env->SetLongArrayRegion(ctx, 0, 3, context);
181 return env->NewDirectByteBuffer(const_cast<uint8_t*>(data), data_length);
182 }
183
184 /**
185 * Releases all used resources.
186 *
187 * @param ctx {in_cookie} tuple
188 */
189 JNIEXPORT void JNICALL
Java_org_brotli_wrapper_dec_DecoderJNI_nativeDestroy(JNIEnv * env,jobject,jlongArray ctx)190 Java_org_brotli_wrapper_dec_DecoderJNI_nativeDestroy(
191 JNIEnv* env, jobject /*jobj*/, jlongArray ctx) {
192 jlong context[3];
193 env->GetLongArrayRegion(ctx, 0, 3, context);
194 DecoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0]));
195 BrotliDecoderDestroyInstance(handle->state);
196 delete[] handle->input_start;
197 delete handle;
198 }
199
200 #ifdef __cplusplus
201 }
202 #endif
203