#include #include #include #include #include "library/common/api/c_types.h" #include "library/common/extensions/filters/http/platform_bridge/c_types.h" #include "library/common/jni/jni_utility.h" #include "library/common/jni/jni_version.h" #include "library/common/main_interface.h" // NOLINT(namespace-envoy) JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = nullptr; if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION) != JNI_OK) { return -1; } set_vm(vm); return JNI_VERSION; } // JniLibrary extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_initEngine( JNIEnv* env, jclass // class ) { return init_engine(); } static void jvm_on_engine_running(void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_engine_running"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); jclass jcls_JvmonEngineRunningContext = env->GetObjectClass(j_context); jmethodID jmid_onEngineRunning = env->GetMethodID( jcls_JvmonEngineRunningContext, "invokeOnEngineRunning", "()Ljava/lang/Object;"); env->CallObjectMethod(j_context, jmid_onEngineRunning); env->DeleteLocalRef(jcls_JvmonEngineRunningContext); // TODO(goaway): This isn't re-used by other engine callbacks, so it's safe to delete here. // This will need to be updated for https://github.com/lyft/envoy-mobile/issues/332 env->DeleteGlobalRef(j_context); } static void jvm_on_exit(void*) { __android_log_write(ANDROID_LOG_INFO, "[Envoy]", "library is exiting"); // Note that this is not dispatched because the thread that // needs to be detached is the engine thread. // This function is called from the context of the engine's // thread due to it being posted to the engine's event dispatcher. jvm_detach_thread(); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_runEngine( JNIEnv* env, jclass, jlong engine, jstring config, jstring log_level, jobject context) { jobject retained_context = env->NewGlobalRef(context); // Required to keep context in memory envoy_engine_callbacks native_callbacks = {jvm_on_engine_running, jvm_on_exit, retained_context}; return run_engine(engine, native_callbacks, env->GetStringUTFChars(config, nullptr), env->GetStringUTFChars(log_level, nullptr)); } extern "C" JNIEXPORT jstring JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_templateString(JNIEnv* env, jclass // class ) { jstring result = env->NewStringUTF(config_template); return result; } extern "C" JNIEXPORT jstring JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_platformFilterTemplateString(JNIEnv* env, jclass // class ) { jstring result = env->NewStringUTF(platform_filter_template); return result; } extern "C" JNIEXPORT jstring JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_nativeFilterTemplateString(JNIEnv* env, jclass // class ) { jstring result = env->NewStringUTF(native_filter_template); return result; } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordCounterInc( JNIEnv* env, jclass, // class jlong engine, jstring elements, jint count) { return record_counter_inc(engine, env->GetStringUTFChars(elements, nullptr), count); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordGaugeSet( JNIEnv* env, jclass, // class jlong engine, jstring elements, jint value) { return record_gauge_set(engine, env->GetStringUTFChars(elements, nullptr), value); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordGaugeAdd( JNIEnv* env, jclass, // class jlong engine, jstring elements, jint amount) { return record_gauge_add(engine, env->GetStringUTFChars(elements, nullptr), amount); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordGaugeSub( JNIEnv* env, jclass, // class jlong engine, jstring elements, jint amount) { return record_gauge_sub(engine, env->GetStringUTFChars(elements, nullptr), amount); } // JvmCallbackContext static void pass_headers(const char* method, envoy_headers headers, jobject j_context) { JNIEnv* env = get_env(); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); jmethodID jmid_passHeader = env->GetMethodID(jcls_JvmCallbackContext, method, "([B[BZ)V"); env->PushLocalFrame(headers.length * 2); jboolean start_headers = JNI_TRUE; for (envoy_header_size_t i = 0; i < headers.length; i++) { // Note this is just an initial implementation, and we will pass a more optimized structure in // the future. // Note the JNI function NewStringUTF would appear to be an appealing option here, except it // requires a null-terminated *modified* UTF-8 string. // Create platform byte array for header key jbyteArray key = env->NewByteArray(headers.headers[i].key.length); // TODO: check if copied via isCopy. // TODO: check for NULL. // https://github.com/lyft/envoy-mobile/issues/758 void* critical_key = env->GetPrimitiveArrayCritical(key, nullptr); memcpy(critical_key, headers.headers[i].key.bytes, headers.headers[i].key.length); // Here '0' (for which there is no named constant) indicates we want to commit the changes back // to the JVM and free the c array, where applicable. env->ReleasePrimitiveArrayCritical(key, critical_key, 0); // Create platform byte array for header value jbyteArray value = env->NewByteArray(headers.headers[i].value.length); // TODO: check for NULL. void* critical_value = env->GetPrimitiveArrayCritical(value, nullptr); memcpy(critical_value, headers.headers[i].value.bytes, headers.headers[i].value.length); env->ReleasePrimitiveArrayCritical(value, critical_value, 0); // Pass this header pair to the platform env->CallVoidMethod(j_context, jmid_passHeader, key, value, start_headers); // We don't release local refs currently because we've pushed a large enough frame, but we could // consider this and/or periodically popping the frame. start_headers = JNI_FALSE; } env->PopLocalFrame(nullptr); env->DeleteLocalRef(jcls_JvmCallbackContext); release_envoy_headers(headers); } // Platform callback implementation // These methods call jvm methods which means the local references created will not be // released automatically. Manual bookkeeping is required for these methods. static void* jvm_on_headers(const char* method, envoy_headers headers, bool end_stream, void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_headers"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); pass_headers("passHeader", headers, j_context); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); jmethodID jmid_onHeaders = env->GetMethodID(jcls_JvmCallbackContext, method, "(JZ)Ljava/lang/Object;"); // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. // TODO: make this cast safer. jobject result = env->CallObjectMethod(j_context, jmid_onHeaders, (jlong)headers.length, end_stream ? JNI_TRUE : JNI_FALSE); env->DeleteLocalRef(jcls_JvmCallbackContext); return result; } static void* jvm_on_response_headers(envoy_headers headers, bool end_stream, void* context) { return jvm_on_headers("onResponseHeaders", headers, end_stream, context); } static envoy_filter_headers_status jvm_http_filter_on_request_headers(envoy_headers headers, bool end_stream, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_headers("onRequestHeaders", headers, end_stream, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_headers native_headers = to_native_headers(env, j_headers); env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_headers); return (envoy_filter_headers_status){/*status*/ unboxed_status, /*headers*/ native_headers}; } static envoy_filter_headers_status jvm_http_filter_on_response_headers(envoy_headers headers, bool end_stream, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_headers("onResponseHeaders", headers, end_stream, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_headers native_headers = to_native_headers(env, j_headers); env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_headers); return (envoy_filter_headers_status){/*status*/ unboxed_status, /*headers*/ native_headers}; } static void* jvm_on_data(const char* method, envoy_data data, bool end_stream, void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_data"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); jmethodID jmid_onData = env->GetMethodID(jcls_JvmCallbackContext, method, "([BZ)Ljava/lang/Object;"); jbyteArray j_data = env->NewByteArray(data.length); // TODO: check if copied via isCopy. // TODO: check for NULL. // https://github.com/lyft/envoy-mobile/issues/758 void* critical_data = env->GetPrimitiveArrayCritical(j_data, nullptr); memcpy(critical_data, data.bytes, data.length); // Here '0' (for which there is no named constant) indicates we want to commit the changes back // to the JVM and free the c array, where applicable. env->ReleasePrimitiveArrayCritical(j_data, critical_data, 0); jobject result = env->CallObjectMethod(j_context, jmid_onData, j_data, end_stream ? JNI_TRUE : JNI_FALSE); data.release(data.context); env->DeleteLocalRef(j_data); env->DeleteLocalRef(jcls_JvmCallbackContext); return result; } static void* jvm_on_response_data(envoy_data data, bool end_stream, void* context) { return jvm_on_data("onResponseData", data, end_stream, context); } static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, bool end_stream, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_data("onRequestData", data, end_stream, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobject j_data = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_data native_data = buffer_to_native_data(env, j_data); envoy_headers* pending_headers = nullptr; // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterDataStatusResumeIteration) { jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 2)); pending_headers = to_native_headers_ptr(env, j_headers); env->DeleteLocalRef(j_headers); } env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_data); return (envoy_filter_data_status){/*status*/ unboxed_status, /*data*/ native_data, /*pending_headers*/ pending_headers}; } static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data, bool end_stream, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_data("onResponseData", data, end_stream, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobject j_data = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_data native_data = buffer_to_native_data(env, j_data); envoy_headers* pending_headers = nullptr; // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterDataStatusResumeIteration) { jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 2)); pending_headers = to_native_headers_ptr(env, j_headers); env->DeleteLocalRef(j_headers); } env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_data); return (envoy_filter_data_status){/*status*/ unboxed_status, /*data*/ native_data, /*pending_headers*/ pending_headers}; } static void* jvm_on_metadata(envoy_headers metadata, void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_metadata"); __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", std::to_string(metadata.length).c_str()); return NULL; } static void* jvm_on_trailers(const char* method, envoy_headers trailers, void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_trailers"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); pass_headers("passHeader", trailers, j_context); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); jmethodID jmid_onTrailers = env->GetMethodID(jcls_JvmCallbackContext, method, "(J)Ljava/lang/Object;"); // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. // TODO: make this cast safer. jobject result = env->CallObjectMethod(j_context, jmid_onTrailers, (jlong)trailers.length); env->DeleteLocalRef(jcls_JvmCallbackContext); return result; } static void* jvm_on_response_trailers(envoy_headers trailers, void* context) { return jvm_on_trailers("onResponseTrailers", trailers, context); } static envoy_filter_trailers_status jvm_http_filter_on_request_trailers(envoy_headers trailers, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_trailers("onRequestTrailers", trailers, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobjectArray j_trailers = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_headers native_trailers = to_native_headers(env, j_trailers); envoy_headers* pending_headers = nullptr; envoy_data* pending_data = nullptr; // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterTrailersStatusResumeIteration) { jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 2)); pending_headers = to_native_headers_ptr(env, j_headers); env->DeleteLocalRef(j_headers); jobject j_data = static_cast(env->GetObjectArrayElement(result, 3)); pending_data = buffer_to_native_data_ptr(env, j_data); env->DeleteLocalRef(j_data); } env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_trailers); return (envoy_filter_trailers_status){/*status*/ unboxed_status, /*trailers*/ native_trailers, /*pending_headers*/ pending_headers, /*pending_data*/ pending_data}; } static envoy_filter_trailers_status jvm_http_filter_on_response_trailers(envoy_headers trailers, const void* context) { JNIEnv* env = get_env(); jobjectArray result = static_cast( jvm_on_trailers("onResponseTrailers", trailers, const_cast(context))); jobject status = env->GetObjectArrayElement(result, 0); jobjectArray j_trailers = static_cast(env->GetObjectArrayElement(result, 1)); int unboxed_status = unbox_integer(env, status); envoy_headers native_trailers = to_native_headers(env, j_trailers); envoy_headers* pending_headers = nullptr; envoy_data* pending_data = nullptr; // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterTrailersStatusResumeIteration) { jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 2)); pending_headers = to_native_headers_ptr(env, j_headers); env->DeleteLocalRef(j_headers); jobject j_data = static_cast(env->GetObjectArrayElement(result, 3)); pending_data = buffer_to_native_data_ptr(env, j_data); env->DeleteLocalRef(j_data); } env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_trailers); return (envoy_filter_trailers_status){/*status*/ unboxed_status, /*trailers*/ native_trailers, /*pending_headers*/ pending_headers, /*pending_data*/ pending_data}; } static void jvm_http_filter_set_request_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_http_filter_set_request_callbacks"); JNIEnv* env = get_env(); jobject j_context = static_cast(const_cast(context)); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); envoy_http_filter_callbacks* on_heap_callbacks = static_cast(safe_malloc(sizeof(envoy_http_filter_callbacks))); *on_heap_callbacks = callbacks; jlong callback_handle = reinterpret_cast(on_heap_callbacks); jmethodID jmid_setRequestFilterCallbacks = env->GetMethodID(jcls_JvmCallbackContext, "setRequestFilterCallbacks", "(J)V"); env->CallVoidMethod(j_context, jmid_setRequestFilterCallbacks, callback_handle); env->DeleteLocalRef(jcls_JvmCallbackContext); } static void jvm_http_filter_set_response_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_http_filter_set_response_callbacks"); JNIEnv* env = get_env(); jobject j_context = static_cast(const_cast(context)); jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); envoy_http_filter_callbacks* on_heap_callbacks = static_cast(safe_malloc(sizeof(envoy_http_filter_callbacks))); *on_heap_callbacks = callbacks; jlong callback_handle = reinterpret_cast(on_heap_callbacks); jmethodID jmid_setResponseFilterCallbacks = env->GetMethodID(jcls_JvmCallbackContext, "setResponseFilterCallbacks", "(J)V"); env->CallVoidMethod(j_context, jmid_setResponseFilterCallbacks, callback_handle); env->DeleteLocalRef(jcls_JvmCallbackContext); } static envoy_filter_resume_status jvm_http_filter_on_resume(const char* method, envoy_headers* headers, envoy_data* data, envoy_headers* trailers, bool end_stream, const void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_resume"); JNIEnv* env = get_env(); jobject j_context = static_cast(const_cast(context)); jlong headers_length = -1; if (headers) { headers_length = (jlong)headers->length; pass_headers("passHeader", *headers, j_context); } jbyteArray j_in_data = NULL; if (data) { j_in_data = env->NewByteArray(data->length); // TODO: check if copied via isCopy. // TODO: check for NULL. // https://github.com/lyft/envoy-mobile/issues/758 void* critical_data = env->GetPrimitiveArrayCritical(j_in_data, nullptr); memcpy(critical_data, data->bytes, data->length); // Here '0' (for which there is no named constant) indicates we want to commit the changes back // to the JVM and free the c array, where applicable. env->ReleasePrimitiveArrayCritical(j_in_data, critical_data, 0); } jlong trailers_length = -1; if (trailers) { trailers_length = (jlong)trailers->length; pass_headers("passTrailer", *trailers, j_context); } jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); jmethodID jmid_onResume = env->GetMethodID(jcls_JvmCallbackContext, method, "(J[BJZ)Ljava/lang/Object;"); // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. // TODO: make this cast safer. jobjectArray result = static_cast( env->CallObjectMethod(j_context, jmid_onResume, headers_length, j_in_data, trailers_length, end_stream ? JNI_TRUE : JNI_FALSE)); env->DeleteLocalRef(jcls_JvmCallbackContext); jobject status = env->GetObjectArrayElement(result, 0); jobjectArray j_headers = static_cast(env->GetObjectArrayElement(result, 1)); jobject j_data = static_cast(env->GetObjectArrayElement(result, 2)); jobjectArray j_trailers = static_cast(env->GetObjectArrayElement(result, 3)); int unboxed_status = unbox_integer(env, status); envoy_headers* pending_headers = to_native_headers_ptr(env, j_headers); envoy_data* pending_data = buffer_to_native_data_ptr(env, j_data); envoy_headers* pending_trailers = to_native_headers_ptr(env, j_trailers); env->DeleteLocalRef(result); env->DeleteLocalRef(status); env->DeleteLocalRef(j_headers); env->DeleteLocalRef(j_data); env->DeleteLocalRef(j_trailers); return (envoy_filter_resume_status){/*status*/ unboxed_status, /*pending_headers*/ pending_headers, /*pending_data*/ pending_data, /*pending_trailers*/ pending_trailers}; } static envoy_filter_resume_status jvm_http_filter_on_resume_request(envoy_headers* headers, envoy_data* data, envoy_headers* trailers, bool end_stream, const void* context) { return jvm_http_filter_on_resume("onResumeRequest", headers, data, trailers, end_stream, context); } static envoy_filter_resume_status jvm_http_filter_on_resume_response(envoy_headers* headers, envoy_data* data, envoy_headers* trailers, bool end_stream, const void* context) { return jvm_http_filter_on_resume("onResumeResponse", headers, data, trailers, end_stream, context); } static void* jvm_on_complete(void* context) { jni_delete_global_ref(context); return NULL; } static void* call_jvm_on_error(envoy_error error, void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_error"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); jclass jcls_JvmObserverContext = env->GetObjectClass(j_context); jmethodID jmid_onError = env->GetMethodID(jcls_JvmObserverContext, "onError", "(I[BI)Ljava/lang/Object;"); jbyteArray j_error_message = env->NewByteArray(error.message.length); // TODO: check if copied via isCopy. // TODO: check for NULL. // https://github.com/lyft/envoy-mobile/issues/758 void* critical_error_message = env->GetPrimitiveArrayCritical(j_error_message, nullptr); memcpy(critical_error_message, error.message.bytes, error.message.length); // Here '0' (for which there is no named constant) indicates we want to commit the changes back // to the JVM and free the c array, where applicable. env->ReleasePrimitiveArrayCritical(j_error_message, critical_error_message, 0); jobject result = env->CallObjectMethod(j_context, jmid_onError, error.error_code, j_error_message, error.attempt_count); error.message.release(error.message.context); env->DeleteLocalRef(jcls_JvmObserverContext); env->DeleteLocalRef(j_error_message); return result; } static void* jvm_on_error(envoy_error error, void* context) { void* result = call_jvm_on_error(error, context); jni_delete_global_ref(context); return result; } static void* call_jvm_on_cancel(void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_on_cancel"); JNIEnv* env = get_env(); jobject j_context = static_cast(context); jclass jcls_JvmObserverContext = env->GetObjectClass(j_context); jmethodID jmid_onCancel = env->GetMethodID(jcls_JvmObserverContext, "onCancel", "()Ljava/lang/Object;"); jobject result = env->CallObjectMethod(j_context, jmid_onCancel); env->DeleteLocalRef(jcls_JvmObserverContext); return result; } static void* jvm_on_cancel(void* context) { void* result = call_jvm_on_cancel(context); jni_delete_global_ref(context); return result; } static void jvm_http_filter_on_error(envoy_error error, const void* context) { call_jvm_on_error(error, const_cast(context)); } static void jvm_http_filter_on_cancel(const void* context) { call_jvm_on_cancel(const_cast(context)); } // JvmFilterFactoryContext static const void* jvm_http_filter_init(const void* context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "jvm_filter_init"); JNIEnv* env = get_env(); jobject j_context = static_cast(const_cast(context)); __android_log_print(ANDROID_LOG_VERBOSE, "[Envoy]", "j_context: %p", j_context); jclass jcls_JvmFilterFactoryContext = env->GetObjectClass(j_context); jmethodID jmid_create = env->GetMethodID(jcls_JvmFilterFactoryContext, "create", "()Lio/envoyproxy/envoymobile/engine/JvmFilterContext;"); jobject j_filter = env->CallObjectMethod(j_context, jmid_create); __android_log_print(ANDROID_LOG_VERBOSE, "[Envoy]", "j_filter: %p", j_filter); jobject retained_filter = env->NewGlobalRef(j_filter); env->DeleteLocalRef(jcls_JvmFilterFactoryContext); env->DeleteLocalRef(j_filter); return retained_filter; } // EnvoyStringAccessor static envoy_data jvm_get_string(void* context) { JNIEnv* env = get_env(); jobject j_context = static_cast(context); jclass jcls_JvmStringAccessorContext = env->GetObjectClass(j_context); jmethodID jmid_getString = env->GetMethodID(jcls_JvmStringAccessorContext, "getString", "()Ljava/nio/ByteBuffer;"); // Passed as a java.nio.ByteBuffer. jobject j_data = env->CallObjectMethod(j_context, jmid_getString); envoy_data native_data = buffer_to_native_data(env, j_data); env->DeleteLocalRef(jcls_JvmStringAccessorContext); env->DeleteLocalRef(j_data); return native_data; } // EnvoyHTTPStream extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_initStream( JNIEnv* env, jclass, jlong engine_handle) { return init_stream(static_cast(engine_handle)); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_startStream( JNIEnv* env, jclass, jlong stream_handle, jobject j_context) { jclass jcls_JvmCallbackContext = env->GetObjectClass(j_context); // TODO: To be truly safe we may need stronger guarantees of operation ordering on this ref. jobject retained_context = env->NewGlobalRef(j_context); envoy_http_callbacks native_callbacks = {jvm_on_response_headers, jvm_on_response_data, jvm_on_metadata, jvm_on_response_trailers, jvm_on_error, jvm_on_complete, jvm_on_cancel, retained_context}; envoy_status_t result = start_stream(static_cast(stream_handle), native_callbacks); if (result != ENVOY_SUCCESS) { env->DeleteGlobalRef(retained_context); // No callbacks are fired and we need to release } env->DeleteLocalRef(jcls_JvmCallbackContext); return result; } // EnvoyHTTPFilter extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerFilterFactory(JNIEnv* env, jclass, jstring filter_name, jobject j_context) { // TODO(goaway): Everything here leaks, but it's all be tied to the life of the engine. // This will need to be updated for https://github.com/lyft/envoy-mobile/issues/332 __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "registerFilterFactory"); __android_log_print(ANDROID_LOG_VERBOSE, "[Envoy]", "j_context: %p", j_context); jclass jcls_JvmFilterFactoryContext = env->GetObjectClass(j_context); jobject retained_context = env->NewGlobalRef(j_context); __android_log_print(ANDROID_LOG_VERBOSE, "[Envoy]", "retained_context: %p", retained_context); envoy_http_filter* api = (envoy_http_filter*)safe_malloc(sizeof(envoy_http_filter)); api->init_filter = jvm_http_filter_init; api->on_request_headers = jvm_http_filter_on_request_headers; api->on_request_data = jvm_http_filter_on_request_data; api->on_request_trailers = jvm_http_filter_on_request_trailers; api->on_response_headers = jvm_http_filter_on_response_headers; api->on_response_data = jvm_http_filter_on_response_data; api->on_response_trailers = jvm_http_filter_on_response_trailers; api->set_request_callbacks = jvm_http_filter_set_request_callbacks; api->on_resume_request = jvm_http_filter_on_resume_request; api->set_response_callbacks = jvm_http_filter_set_response_callbacks; api->on_resume_response = jvm_http_filter_on_resume_response; api->on_cancel = jvm_http_filter_on_cancel; api->on_error = jvm_http_filter_on_error; api->release_filter = jni_delete_const_global_ref; api->static_context = retained_context; api->instance_context = NULL; envoy_status_t result = register_platform_api(env->GetStringUTFChars(filter_name, nullptr), api); env->DeleteLocalRef(jcls_JvmFilterFactoryContext); return result; } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callResumeIteration( JNIEnv* env, jclass, jlong callback_handle, jobject j_context) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "callResumeIteration"); // Context is only passed here to ensure it's not inadvertently gc'd during execution of this // function. To be extra safe, do an explicit retain with a GlobalRef. jobject retained_context = env->NewGlobalRef(j_context); envoy_http_filter_callbacks* callbacks = reinterpret_cast(callback_handle); callbacks->resume_iteration(callbacks->callback_context); env->DeleteGlobalRef(retained_context); } extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_engine_EnvoyHTTPFilterCallbacksImpl_callReleaseCallbacks( JNIEnv* env, jclass, jlong callback_handle) { __android_log_write(ANDROID_LOG_VERBOSE, "[Envoy]", "callReleaseCallbacks"); envoy_http_filter_callbacks* callbacks = reinterpret_cast(callback_handle); callbacks->release_callbacks(callbacks->callback_context); free(callbacks); } // EnvoyHTTPStream // Note: JLjava_nio_ByteBuffer_2Z is the mangled signature of the java method. // https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendData__JLjava_nio_ByteBuffer_2Z( JNIEnv* env, jclass, jlong stream_handle, jobject data, jboolean end_stream) { // TODO: check for null pointer in envoy_data.bytes - we could copy or raise an exception. return send_data(static_cast(stream_handle), buffer_to_native_data(env, data), end_stream); } // Note: J_3BZ is the mangled signature of the java method. // https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendData__J_3BZ( JNIEnv* env, jclass, jlong stream_handle, jbyteArray data, jboolean end_stream) { // TODO: check for null pointer in envoy_data.bytes - we could copy or raise an exception. return send_data(static_cast(stream_handle), array_to_native_data(env, data), end_stream); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendHeaders( JNIEnv* env, jclass, jlong stream_handle, jobjectArray headers, jboolean end_stream) { return send_headers(static_cast(stream_handle), to_native_headers(env, headers), end_stream); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendTrailers( JNIEnv* env, jclass, jlong stream_handle, jobjectArray trailers) { return send_trailers(static_cast(stream_handle), to_native_headers(env, trailers)); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_resetStream( JNIEnv* env, jclass, jlong stream_handle) { return reset_stream(static_cast(stream_handle)); } // EnvoyStringAccessor extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_registerStringAccessor(JNIEnv* env, jclass, jstring accessor_name, jobject j_context) { // TODO(goaway): The retained_context leaks, but it's tied to the life of the engine. // This will need to be updated for https://github.com/lyft/envoy-mobile/issues/332. jclass jcls_JvmStringAccessorContext = env->GetObjectClass(j_context); jobject retained_context = env->NewGlobalRef(j_context); envoy_string_accessor* string_accessor = (envoy_string_accessor*)safe_malloc(sizeof(envoy_string_accessor)); string_accessor->get_string = jvm_get_string; string_accessor->context = retained_context; envoy_status_t result = register_platform_api(env->GetStringUTFChars(accessor_name, nullptr), string_accessor); env->DeleteLocalRef(jcls_JvmStringAccessorContext); return result; }