/* 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 #include #include 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(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[5]; env->GetLongArrayRegion(ctx, 0, 5, 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(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[5]; env->GetLongArrayRegion(ctx, 0, 5, context); EncoderHandle* handle = getHandle(reinterpret_cast(context[0])); int operation = context[1]; context[1] = 0; /* ERROR */ env->SetLongArrayRegion(ctx, 0, 5, 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; context[4] = BrotliEncoderIsFinished(handle->state) ? 1 : 0; } env->SetLongArrayRegion(ctx, 0, 5, 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[5]; env->GetLongArrayRegion(ctx, 0, 5, context); EncoderHandle* handle = getHandle(reinterpret_cast(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; context[4] = BrotliEncoderIsFinished(handle->state) ? 1 : 0; env->SetLongArrayRegion(ctx, 0, 5, context); return env->NewDirectByteBuffer(const_cast(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(context[0])); BrotliEncoderDestroyInstance(handle->state); delete[] handle->input_start; delete handle; } #ifdef __cplusplus } #endif