| /* Copyright 2017 Google Inc. All Rights Reserved. |
| |
| Distributed under MIT license. |
| See file LICENSE for detail or copy at https://opensource.org/licenses/MIT |
| */ |
| |
| #include <jni.h> |
| |
| #include <new> |
| |
| #include <brotli/encode.h> |
| |
| namespace { |
| /* A structure used to persist the encoder's state in between calls. */ |
| typedef struct EncoderHandle { |
| BrotliEncoderState* state; |
| |
| uint8_t* input_start; |
| size_t input_offset; |
| size_t input_last; |
| } EncoderHandle; |
| |
| /* Obtain handle from opaque pointer. */ |
| EncoderHandle* getHandle(void* opaque) { |
| return static_cast<EncoderHandle*>(opaque); |
| } |
| |
| } /* namespace */ |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| /** |
| * Creates a new Encoder. |
| * |
| * Cookie to address created encoder is stored in out_cookie. In case of failure |
| * cookie is 0. |
| * |
| * @param ctx {out_cookie, in_directBufferSize, in_quality, in_lgwin} tuple |
| * @returns direct ByteBuffer if directBufferSize is not 0; otherwise null |
| */ |
| JNIEXPORT jobject JNICALL |
| Java_org_brotli_wrapper_enc_EncoderJNI_nativeCreate( |
| JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { |
| bool ok = true; |
| EncoderHandle* handle = nullptr; |
| jlong context[4]; |
| env->GetLongArrayRegion(ctx, 0, 4, context); |
| size_t input_size = context[1]; |
| context[0] = 0; |
| handle = new (std::nothrow) EncoderHandle(); |
| ok = !!handle; |
| |
| if (ok) { |
| handle->input_offset = 0; |
| handle->input_last = 0; |
| handle->input_start = nullptr; |
| |
| if (input_size == 0) { |
| ok = false; |
| } else { |
| handle->input_start = new (std::nothrow) uint8_t[input_size]; |
| ok = !!handle->input_start; |
| } |
| } |
| |
| if (ok) { |
| handle->state = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); |
| ok = !!handle->state; |
| } |
| |
| if (ok) { |
| int quality = context[2]; |
| if (quality >= 0) { |
| BrotliEncoderSetParameter(handle->state, BROTLI_PARAM_QUALITY, quality); |
| } |
| int lgwin = context[3]; |
| if (lgwin >= 0) { |
| BrotliEncoderSetParameter(handle->state, BROTLI_PARAM_LGWIN, lgwin); |
| } |
| } |
| |
| if (ok) { |
| /* TODO: future versions (e.g. when 128-bit architecture comes) |
| might require thread-safe cookie<->handle mapping. */ |
| context[0] = reinterpret_cast<jlong>(handle); |
| } else if (!!handle) { |
| if (!!handle->input_start) delete[] handle->input_start; |
| delete handle; |
| } |
| |
| env->SetLongArrayRegion(ctx, 0, 1, context); |
| |
| if (!ok) { |
| return nullptr; |
| } |
| |
| return env->NewDirectByteBuffer(handle->input_start, input_size); |
| } |
| |
| /** |
| * Push data to encoder. |
| * |
| * @param ctx {in_cookie, in_operation_out_success, out_has_more_output, |
| * out_has_remaining_input} tuple |
| * @param input_length number of bytes provided in input or direct input; |
| * 0 to process further previous input |
| */ |
| JNIEXPORT void JNICALL |
| Java_org_brotli_wrapper_enc_EncoderJNI_nativePush( |
| JNIEnv* env, jobject /*jobj*/, jlongArray ctx, jint input_length) { |
| jlong context[4]; |
| env->GetLongArrayRegion(ctx, 0, 4, context); |
| EncoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); |
| int operation = context[1]; |
| context[1] = 0; /* ERROR */ |
| env->SetLongArrayRegion(ctx, 0, 4, context); |
| |
| BrotliEncoderOperation op; |
| switch (operation) { |
| case 0: op = BROTLI_OPERATION_PROCESS; break; |
| case 1: op = BROTLI_OPERATION_FLUSH; break; |
| case 2: op = BROTLI_OPERATION_FINISH; break; |
| default: return; /* ERROR */ |
| } |
| |
| if (input_length != 0) { |
| /* Still have unconsumed data. Workflow is broken. */ |
| if (handle->input_offset < handle->input_last) { |
| return; |
| } |
| handle->input_offset = 0; |
| handle->input_last = input_length; |
| } |
| |
| /* Actual compression. */ |
| const uint8_t* in = handle->input_start + handle->input_offset; |
| size_t in_size = handle->input_last - handle->input_offset; |
| size_t out_size = 0; |
| BROTLI_BOOL status = BrotliEncoderCompressStream( |
| handle->state, op, &in_size, &in, &out_size, nullptr, nullptr); |
| handle->input_offset = handle->input_last - in_size; |
| if (!!status) { |
| context[1] = 1; |
| context[2] = BrotliEncoderHasMoreOutput(handle->state) ? 1 : 0; |
| context[3] = (handle->input_offset != handle->input_last) ? 1 : 0; |
| } |
| env->SetLongArrayRegion(ctx, 0, 4, context); |
| } |
| |
| /** |
| * Pull decompressed data from encoder. |
| * |
| * @param ctx {in_cookie, out_success, out_has_more_output, |
| * out_has_remaining_input} tuple |
| * @returns direct ByteBuffer; all the produced data MUST be consumed before |
| * any further invocation; null in case of error |
| */ |
| JNIEXPORT jobject JNICALL |
| Java_org_brotli_wrapper_enc_EncoderJNI_nativePull( |
| JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { |
| jlong context[4]; |
| env->GetLongArrayRegion(ctx, 0, 4, context); |
| EncoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); |
| size_t data_length = 0; |
| const uint8_t* data = BrotliEncoderTakeOutput(handle->state, &data_length); |
| context[1] = 1; |
| context[2] = BrotliEncoderHasMoreOutput(handle->state) ? 1 : 0; |
| context[3] = (handle->input_offset != handle->input_last) ? 1 : 0; |
| env->SetLongArrayRegion(ctx, 0, 4, context); |
| return env->NewDirectByteBuffer(const_cast<uint8_t*>(data), data_length); |
| } |
| |
| /** |
| * Releases all used resources. |
| * |
| * @param ctx {in_cookie} tuple |
| */ |
| JNIEXPORT void JNICALL |
| Java_org_brotli_wrapper_enc_EncoderJNI_nativeDestroy( |
| JNIEnv* env, jobject /*jobj*/, jlongArray ctx) { |
| jlong context[2]; |
| env->GetLongArrayRegion(ctx, 0, 2, context); |
| EncoderHandle* handle = getHandle(reinterpret_cast<void*>(context[0])); |
| BrotliEncoderDestroyInstance(handle->state); |
| delete[] handle->input_start; |
| delete handle; |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |