Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions mobile/library/common/jni/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ cc_library(
deps = [
"//library/common/jni/import:jni_import_lib",
"//library/common/types:c_types_lib",
"//library/common/types:managed_types_lib",
"@envoy//source/common/common:assert_lib",
],
)
Expand Down Expand Up @@ -48,6 +49,7 @@ cc_library(
":ndk_jni_support",
"//library/common:envoy_main_interface_lib",
"//library/common/api:c_types",
"//library/common/types:managed_types_lib",
],
# We need this to ensure that we link this into the .so even though there are no code references.
alwayslink = True,
Expand Down Expand Up @@ -115,6 +117,7 @@ cc_binary(
"//library/common:envoy_main_interface_lib",
"//library/common/api:c_types",
"//library/common/jni/import:jni_import_lib",
"//library/common/types:managed_types_lib",
],
)

Expand Down Expand Up @@ -169,6 +172,7 @@ cc_library(
"//library/common/jni/import:jni_import_lib",
"//library/common:envoy_main_interface_lib",
"//library/common/types:c_types_lib",
"//library/common/types:managed_types_lib",
"@envoy//source/common/common:assert_lib",
] + TEST_EXTENSIONS,
)
Expand Down
62 changes: 48 additions & 14 deletions mobile/library/common/jni/jni_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "library/common/jni/jni_utility.h"
#include "library/common/jni/jni_version.h"
#include "library/common/main_interface.h"
#include "library/common/types/managed_envoy_headers.h"

// NOLINT(namespace-envoy)

Expand Down Expand Up @@ -278,22 +279,23 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_recordHistogramValue(JNIEnv* en

// JvmCallbackContext

static void pass_headers(const char* method, envoy_headers headers, jobject j_context) {
static void pass_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought these all had to be simple (C) types. can you clue me in on why it's OK use ManagedEnvoyHeaders?

edit: nm I think I see why. if this isn't called from Java layers can we use standard passHeaders naming to make that clear?

@Augustyniak Augustyniak Dec 6, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought these all had to be simple (C) types. can you clue me in on why it's OK use ManagedEnvoyHeaders?

the usage of c types is a self-imposed limitations in many places in our code. Decided to use c++ type in here to make mem management simpler.

edit: nm I think I see why. if this isn't called from Java layers can we use standard passHeaders naming to make that clear?

OK

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");
jboolean start_headers = JNI_TRUE;

for (envoy_map_size_t i = 0; i < headers.length; i++) {
for (envoy_map_size_t i = 0; i < headers.get().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 j_key = native_data_to_array(env, headers.entries[i].key);
jbyteArray j_key = native_data_to_array(env, headers.get().entries[i].key);
// Create platform byte array for header value
jbyteArray j_value = native_data_to_array(env, headers.entries[i].value);
jbyteArray j_value = native_data_to_array(env, headers.get().entries[i].value);

// Pass this header pair to the platform
env->CallVoidMethod(j_context, jmid_passHeader, j_key, j_value, start_headers);
Expand All @@ -306,15 +308,14 @@ static void pass_headers(const char* method, envoy_headers headers, jobject j_co
}

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,
envoy_stream_intel stream_intel, void* context) {
static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one isn't called by jvm directly either. optional nit onHeadersFromJvm or something

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's calling a method from the java world tho which I think is why it has a jvm prefix (pass_headers - now renamed to passHeaders- method did not havejvmprefix as it was/is not calling intojvm`).

I do not have a strong opinion in here but will keep the things they are until we figure out new naming patterns for this file - ideally in a separate PR.

bool end_stream, envoy_stream_intel stream_intel, void* context) {
jni_log("[Envoy]", "jvm_on_headers");
JNIEnv* env = get_env();
jobject j_context = static_cast<jobject>(context);
Expand All @@ -327,24 +328,50 @@ static void* jvm_on_headers(const char* method, envoy_headers headers, bool end_
jlongArray j_stream_intel = native_stream_intel_to_array(env, stream_intel);
// 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,
jobject result = env->CallObjectMethod(j_context, jmid_onHeaders, (jlong)headers.get().length,
end_stream ? JNI_TRUE : JNI_FALSE, j_stream_intel);
// TODO(Augustyniak): Pass the name of the filter in here so that we can instrument the origin of
// the JNI exception better.
if (exception_check(env)) {
env->DeleteLocalRef(j_stream_intel);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the DeleteLocalRefs be moved outside of the exception check? alternately if the exception check needs to happen before deletion
bool saw_exception = exception_check(env)
deletelocalRef
deletelocalRef
if (saw_exception) {...

env->DeleteLocalRef(jcls_JvmCallbackContext);

env->DeleteLocalRef(j_stream_intel);
env->DeleteLocalRef(jcls_JvmCallbackContext);
jclass jcls_object_array = env->FindClass("java/lang/Object");
jobjectArray result = env->NewObjectArray(2, jcls_object_array, NULL);

return result;
jclass jcls_int = env->FindClass("java/lang/Integer");
jmethodID jmid_intInit = env->GetMethodID(jcls_int, "<init>", "(I)V");
jobject status = env->NewObject(jcls_int, jmid_intInit, 0);
// Set status to "0" (FilterHeadersStatus::Continue). Signal that the intent
// is to continue the iteration of the filter chain.
env->SetObjectArrayElement(result, 0, status);

// Since the "on headers" call threw an exception set input headers as output headers.
env->SetObjectArrayElement(result, 1, ToJavaArrayOfObjectArray(env, headers));

env->DeleteLocalRef(jcls_object_array);
env->DeleteLocalRef(jcls_int);

return result;
} else {
env->DeleteLocalRef(j_stream_intel);
env->DeleteLocalRef(jcls_JvmCallbackContext);

return result;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to proxy Ryan, let's do this first, and do
if (!exception) {
return result
}
// handle exception

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you @RyanTheOptimist :)

}
}

static void* jvm_on_response_headers(envoy_headers headers, bool end_stream,
envoy_stream_intel stream_intel, void* context) {
return jvm_on_headers("onResponseHeaders", headers, end_stream, stream_intel, context);
const auto managed_headers = Envoy::Types::ManagedEnvoyHeaders(headers);
return jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context);
}

static envoy_filter_headers_status
jvm_http_filter_on_request_headers(envoy_headers headers, bool end_stream,
jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream,
envoy_stream_intel stream_intel, const void* context) {
JNIEnv* env = get_env();
const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers);
jobjectArray result = static_cast<jobjectArray>(jvm_on_headers(
"onRequestHeaders", headers, end_stream, stream_intel, const_cast<void*>(context)));

Expand All @@ -363,9 +390,10 @@ jvm_http_filter_on_request_headers(envoy_headers headers, bool end_stream,
}

static envoy_filter_headers_status
jvm_http_filter_on_response_headers(envoy_headers headers, bool end_stream,
jvm_http_filter_on_response_headers(envoy_headers input_headers, bool end_stream,
envoy_stream_intel stream_intel, const void* context) {
JNIEnv* env = get_env();
const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers);
jobjectArray result = static_cast<jobjectArray>(jvm_on_headers(
"onResponseHeaders", headers, end_stream, stream_intel, const_cast<void*>(context)));

Expand Down Expand Up @@ -493,6 +521,7 @@ static void* jvm_on_trailers(const char* method, envoy_headers trailers,
jlongArray j_stream_intel = native_stream_intel_to_array(env, stream_intel);
// Note: be careful of JVM types. Before we casted to jlong we were getting integer problems.
// TODO: make this cast safer.
// TODO(Augustyniak): check for pending exceptions after returning from JNI call.
jobject result =
env->CallObjectMethod(j_context, jmid_onTrailers, (jlong)trailers.length, j_stream_intel);

Expand Down Expand Up @@ -714,6 +743,8 @@ static void* call_jvm_on_complete(envoy_stream_intel stream_intel,
jobject result =
env->CallObjectMethod(j_context, jmid_onComplete, j_stream_intel, j_final_stream_intel);

exception_check(env);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment on why it's Ok to clear without handling?


env->DeleteLocalRef(j_stream_intel);
env->DeleteLocalRef(j_final_stream_intel);
env->DeleteLocalRef(jcls_JvmObserverContext);
Expand All @@ -737,6 +768,8 @@ static void* call_jvm_on_error(envoy_error error, envoy_stream_intel stream_inte
jobject result = env->CallObjectMethod(j_context, jmid_onError, error.error_code, j_error_message,
error.attempt_count, j_stream_intel, j_final_stream_intel);

exception_check(env);

env->DeleteLocalRef(j_stream_intel);
env->DeleteLocalRef(j_final_stream_intel);
env->DeleteLocalRef(j_error_message);
Expand Down Expand Up @@ -768,6 +801,7 @@ static void* call_jvm_on_cancel(envoy_stream_intel stream_intel,

jobject result =
env->CallObjectMethod(j_context, jmid_onCancel, j_stream_intel, j_final_stream_intel);
exception_check(env);

env->DeleteLocalRef(j_stream_intel);
env->DeleteLocalRef(j_final_stream_intel);
Expand Down
25 changes: 25 additions & 0 deletions mobile/library/common/jni/jni_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ void jni_delete_const_global_ref(const void* context) {
jni_delete_global_ref(const_cast<void*>(context));
}

bool exception_check(JNIEnv* env) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be inclined to ABSL_MUST_USE_RESULT for this. or at least make it clear we're clearing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not think that it always have to be handled explicitly. Per ur suggestion, will change method's name to imply that it clears any pending exceptions

if (env->ExceptionCheck() == JNI_TRUE) {
env->ExceptionClear();
// TODO(Augustyniak): Log exception details.
return true;
} else {
return false;
}
}

int unbox_integer(JNIEnv* env, jobject boxedInteger) {
jclass jcls_Integer = env->FindClass("java/lang/Integer");
jmethodID jmid_intValue = env->GetMethodID(jcls_Integer, "intValue", "()I");
Expand Down Expand Up @@ -278,6 +288,21 @@ envoy_map to_native_map(JNIEnv* env, jobjectArray entries) {
return native_map;
}

jobjectArray ToJavaArrayOfObjectArray(JNIEnv* env, const Envoy::Types::ManagedEnvoyHeaders& map) {
jclass jcls_byte_array = env->FindClass("java/lang/Object");
jobjectArray javaArray = env->NewObjectArray(2 * map.get().length, jcls_byte_array, nullptr);

for (envoy_map_size_t i = 0; i < map.get().length; i++) {
jbyteArray key = native_data_to_array(env, map.get().entries[i].key);
jbyteArray value = native_data_to_array(env, map.get().entries[i].value);

env->SetObjectArrayElement(javaArray, 2 * i, key);
env->SetObjectArrayElement(javaArray, 2 * i + 1, value);
}

return javaArray;
}

jobjectArray ToJavaArrayOfByteArray(JNIEnv* env, const std::vector<std::string>& v) {
jclass jcls_byte_array = env->FindClass("[B");
jobjectArray joa = env->NewObjectArray(v.size(), jcls_byte_array, nullptr);
Expand Down
12 changes: 12 additions & 0 deletions mobile/library/common/jni/jni_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "library/common/jni/import/jni_import.h"
#include "library/common/types/c_types.h"
#include "library/common/types/managed_envoy_headers.h"

// NOLINT(namespace-envoy)

Expand Down Expand Up @@ -43,6 +44,15 @@ void jni_delete_global_ref(void* context);

void jni_delete_const_global_ref(const void* context);

/**
* Checks whether there is a pending JNI exception and clear it if necessary.
*
* @param env, the JNI env pointer.
*
* @return Whether there was a pending JNI exception.
*/
bool exception_check(JNIEnv* env);

int unbox_integer(JNIEnv* env, jobject boxedInteger);

envoy_data array_to_native_data(JNIEnv* env, jbyteArray j_data);
Expand Down Expand Up @@ -100,6 +110,8 @@ jbyteArray ToJavaByteArray(JNIEnv* env, const uint8_t* bytes, size_t len);

jbyteArray ToJavaByteArray(JNIEnv* env, const std::string& str);

jobjectArray ToJavaArrayOfObjectArray(JNIEnv* env, const Envoy::Types::ManagedEnvoyHeaders& map);

void JavaArrayOfByteArrayToStringVector(JNIEnv* env, jobjectArray array,
std::vector<std::string>* out);

Expand Down
12 changes: 12 additions & 0 deletions mobile/library/common/types/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ envoy_cc_library(
"@envoy//source/common/common:assert_lib",
],
)

envoy_cc_library(
name = "managed_types_lib",
srcs = [
"managed_envoy_headers.h",
],
repository = "@envoy",
visibility = ["//visibility:public"],
deps = [
"//library/common/types:c_types_lib",
],
)
32 changes: 32 additions & 0 deletions mobile/library/common/types/managed_envoy_headers.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include "library/common/types/c_types.h"

namespace Envoy {
namespace Types {

/**
* A wrapper around envoy_headers that's responsible for freeing
* the underlying headers when they are not needed anymore.
*/
class ManagedEnvoyHeaders {
public:
/**
* Initialize a new instance of the receiver using a given instance of envoy headers.
*
* @param headers, that should be wrapped by the receiver. The wrapper will hold onto
* the passed headers and free them once the receiver is not used anymore.
*/
ManagedEnvoyHeaders(envoy_headers headers) : headers_(headers){};
~ManagedEnvoyHeaders() { release_envoy_headers(headers_); }
const envoy_headers& get() const { return headers_; }

private:
envoy_headers headers_;
// Make copy and assignment operators private to prevent copying of the receiver.
ManagedEnvoyHeaders(const ManagedEnvoyHeaders&);
ManagedEnvoyHeaders& operator=(const ManagedEnvoyHeaders&);
};

} // namespace Types
} // namespace Envoy
23 changes: 22 additions & 1 deletion mobile/test/kotlin/integration/BUILD
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_jni_kt_test")
load("@envoy_mobile//bazel:kotlin_test.bzl", "envoy_mobile_android_test", "envoy_mobile_jni_kt_test")
load("@envoy_mobile//bazel:kotlin_lib.bzl", "envoy_mobile_kt_library")

envoy_mobile_jni_kt_test(
name = "engine_start_test",
Expand Down Expand Up @@ -200,3 +201,23 @@ envoy_mobile_jni_kt_test(
"//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib",
],
)

envoy_mobile_android_test(
name = "filter_throwing_exception_test",
srcs = [
"FilterThrowingExceptionTest.kt",
],
exec_properties = {
# TODO(lfpino): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses.
"sandboxNetwork": "standard",
"dockerNetwork": "standard",
},
native_deps = [
"//library/common/jni:libndk_envoy_jni.so",
"//library/common/jni:libndk_envoy_jni.jnilib",
],
deps = [
"//library/kotlin/io/envoyproxy/envoymobile:envoy_interfaces_lib",
"//library/kotlin/io/envoyproxy/envoymobile:envoy_lib",
],
)
Loading