From 38b2f8166798d076dd7b1cb3e55a39e2861207e8 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 13:36:29 +0100 Subject: [PATCH 01/22] deps/api: Bump `com_github_bufbuild_buf` -> 1.32.1 (#34348) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- api/bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index e48dd32951d3..f4769e34bb36 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -131,11 +131,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "buf", project_desc = "A new way of working with Protocol Buffers.", # Used for breaking change detection in API protobufs project_url = "https://buf.build", - version = "1.32.0", - sha256 = "305ca72cdd874deab5d803580ea2a930a37df0a7a81813a0d0b5f3ef5384f735", + version = "1.32.1", + sha256 = "ca09415a6f0b86d9c38bde25a678dcc31b8e75492e68379e36b6c9ccd1755190", strip_prefix = "buf", urls = ["https://github.com/bufbuild/buf/releases/download/v{version}/buf-Linux-x86_64.tar.gz"], - release_date = "2024-05-16", + release_date = "2024-05-21", use_category = ["api"], license = "Apache-2.0", license_url = "https://github.com/bufbuild/buf/blob/v{version}/LICENSE", From 63b3e8d14a83fe62f49bd0f2ca04cdeed4860d47 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 13:36:43 +0100 Subject: [PATCH 02/22] deps: Bump `com_github_bazelbuild_buildtools` -> 7.1.2 (#34346) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 3cc6f6b5a074..c90178726a5f 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -72,9 +72,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Bazel build tools", project_desc = "Developer tools for working with Google's bazel buildtool.", project_url = "https://github.com/bazelbuild/buildtools", - version = "7.1.1", - sha256 = "60a9025072ae237f325d0e7b661e1685f34922c29883888c2d06f5789462b939", - release_date = "2024-04-17", + version = "7.1.2", + sha256 = "39c59cb5352892292cbe3174055aac187edcb5324c9b4e2d96cb6e40bd753877", + release_date = "2024-05-23", strip_prefix = "buildtools-{version}", urls = ["https://github.com/bazelbuild/buildtools/archive/v{version}.tar.gz"], use_category = ["test_only"], From 0ef4c59fce05d41c5a6021143ac28fd3fb408532 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 13:36:54 +0100 Subject: [PATCH 03/22] deps: Bump `aspect_bazel_lib` -> 2.7.6 (#34347) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index c90178726a5f..d33e481a7422 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -148,12 +148,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Aspect Bazel helpers", project_desc = "Base Starlark libraries and basic Bazel rules which are useful for constructing rulesets and BUILD files", project_url = "https://github.com/aspect-build/bazel-lib", - version = "2.7.3", - sha256 = "87ab4ec479ebeb00d286266aca2068caeef1bb0b1765e8f71c7b6cfee6af4226", + version = "2.7.6", + sha256 = "3a702a082560c94c2f1a9b34996a2f1364aeb979641cece34a7868508bae552e", strip_prefix = "bazel-lib-{version}", urls = ["https://github.com/aspect-build/bazel-lib/archive/v{version}.tar.gz"], use_category = ["build"], - release_date = "2024-05-08", + release_date = "2024-05-23", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/aspect-build/bazel-lib/blob/v{version}/LICENSE", From a1c5de9500a96553186935a288efaeef47ddbaf0 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 13:37:19 +0100 Subject: [PATCH 04/22] deps: Bump `com_github_google_benchmark` -> 1.8.4 (#34345) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d33e481a7422..65d139a2d581 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -636,12 +636,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Benchmark", project_desc = "Library to benchmark code snippets", project_url = "https://github.com/google/benchmark", - version = "1.8.3", - sha256 = "6bc180a57d23d4d9515519f92b0c83d61b05b5bab188961f36ac7b06b0d9e9ce", + version = "1.8.4", + sha256 = "3e7059b6b11fb1bbe28e33e02519398ca94c1818874ebed18e504dc6f709be45", strip_prefix = "benchmark-{version}", urls = ["https://github.com/google/benchmark/archive/v{version}.tar.gz"], use_category = ["test_only"], - release_date = "2023-08-31", + release_date = "2024-05-23", license = "Apache-2.0", license_url = "https://github.com/google/benchmark/blob/v{version}/LICENSE", ), From 1e3386177a134f7e0ea24d681a12c4ab69a3e779 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 09:04:09 -0500 Subject: [PATCH 05/22] mobile: Fix the READMEs about running local Android test apps (#34333) Risk Level: n/a Testing: n/a Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Fredy Wijaya --- mobile/examples/java/hello_world/README.md | 6 ++++-- mobile/examples/kotlin/hello_world/README.md | 6 ++++-- mobile/test/kotlin/apps/baseline/README md | 6 ++++-- mobile/test/kotlin/apps/experimental/README.md | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/mobile/examples/java/hello_world/README.md b/mobile/examples/java/hello_world/README.md index bcac020fe5c6..df7de7b55d55 100644 --- a/mobile/examples/java/hello_world/README.md +++ b/mobile/examples/java/hello_world/README.md @@ -1,6 +1,8 @@ # How to Run the App in an Emulator +Run these scripts from the `mobile` root directory. + ``` -$ ./start_emulator.sh # Wait until the emulator is fully booted up. -$ ./start_app.sh +$ examples/java/hello_world/start_emulator.sh # Wait until the emulator is fully booted up. +$ examples/java/hello_world/start_app.sh ``` diff --git a/mobile/examples/kotlin/hello_world/README.md b/mobile/examples/kotlin/hello_world/README.md index bcac020fe5c6..7d22735ec174 100644 --- a/mobile/examples/kotlin/hello_world/README.md +++ b/mobile/examples/kotlin/hello_world/README.md @@ -1,6 +1,8 @@ # How to Run the App in an Emulator +Run these scripts from the `mobile` root directory. + ``` -$ ./start_emulator.sh # Wait until the emulator is fully booted up. -$ ./start_app.sh +$ examples/kotlin/hello_world/start_emulator.sh # Wait until the emulator is fully booted up. +$ examples/kotlin/hello_world/start_app.sh ``` diff --git a/mobile/test/kotlin/apps/baseline/README md b/mobile/test/kotlin/apps/baseline/README md index bcac020fe5c6..96340a88f56c 100644 --- a/mobile/test/kotlin/apps/baseline/README md +++ b/mobile/test/kotlin/apps/baseline/README md @@ -1,6 +1,8 @@ # How to Run the App in an Emulator +Run these scripts from the `mobile` root directory. + ``` -$ ./start_emulator.sh # Wait until the emulator is fully booted up. -$ ./start_app.sh +$ test/kotlin/apps/baseline/start_emulator.sh # Wait until the emulator is fully booted up. +$ test/kotlin/apps/baseline/start_app.sh ``` diff --git a/mobile/test/kotlin/apps/experimental/README.md b/mobile/test/kotlin/apps/experimental/README.md index bcac020fe5c6..53bea112abb7 100644 --- a/mobile/test/kotlin/apps/experimental/README.md +++ b/mobile/test/kotlin/apps/experimental/README.md @@ -1,6 +1,8 @@ # How to Run the App in an Emulator +Run these scripts from the `mobile` root directory. + ``` -$ ./start_emulator.sh # Wait until the emulator is fully booted up. -$ ./start_app.sh +$ test/kotlin/apps/experimental/start_emulator.sh # Wait until the emulator is fully booted up. +$ test/kotlin/apps/experimental/start_app.sh ``` From 0e69ac60a4872b17b4ddc021af891c4900dcb572 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 10:37:34 -0500 Subject: [PATCH 06/22] mobile: Fix clang-tidy issue in jni_impl.cc (#34351) This fixes this clang-tidy issue. Clang-Tidy: 'event_tracker_global_ref' declared with a const-qualified typedef; results in the type being '_jobject *const' instead of 'const _jobject * Risk Level: low Testing: unit tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Fredy Wijaya --- mobile/library/jni/jni_impl.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 434ed4c83602..9a499ddfd70f 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -61,7 +61,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr //================================================================================================ std::unique_ptr logger = std::make_unique(); if (envoy_logger != nullptr) { - const jobject envoy_logger_global_ref = env->NewGlobalRef(envoy_logger); + jobject envoy_logger_global_ref = env->NewGlobalRef(envoy_logger); logger->on_log_ = [envoy_logger_global_ref](Envoy::Logger::Logger::Levels level, const std::string& message) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); @@ -85,7 +85,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr std::unique_ptr event_tracker = std::make_unique(); if (envoy_event_tracker != nullptr) { - const jobject event_tracker_global_ref = env->NewGlobalRef(envoy_event_tracker); + jobject event_tracker_global_ref = env->NewGlobalRef(envoy_event_tracker); event_tracker->on_track_ = [event_tracker_global_ref]( const absl::flat_hash_map& events) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); From 266f9a35ab9d698ec807999c7d5bfd226bdeb8bf Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 10:40:51 -0500 Subject: [PATCH 07/22] mobile: Fix Kotlin warning (#34350) This fixes the following warning. library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt:7:28: warning: '@JvmOverloads' annotation has no effect for methods without default arguments class AndroidEngineBuilder @JvmOverloads constructor(context: Context) : EngineBuilder() { Risk Level: low Testing: bazel build Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Fredy Wijaya --- .../kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt index 03996bcc05be..4ed76ae3ace9 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/AndroidEngineBuilder.kt @@ -4,7 +4,7 @@ import android.content.Context import io.envoyproxy.envoymobile.engine.AndroidEngineImpl /** The engine builder to use to create Envoy engine on Android. */ -class AndroidEngineBuilder @JvmOverloads constructor(context: Context) : EngineBuilder() { +class AndroidEngineBuilder(context: Context) : EngineBuilder() { init { addEngineType { AndroidEngineImpl( From e92fdccbf17a96b842da85d295cd147136ba7f72 Mon Sep 17 00:00:00 2001 From: birenroy Date: Fri, 24 May 2024 12:35:53 -0400 Subject: [PATCH 08/22] http2: migrates DATA frame payloads to the visitor API (#34278) This slightly simplifies the flow of DATA frame payloads; after this change, the StreamDataFrameSource class can be removed. Http2Visitor::OnReadyToSendDataForStream() is substantially the same implementation as StreamDataFrameSource::SelectPayloadLength(). Http2Visitor::SendDataFrame() is substantially the same as StreamDataFrameSource::Send(). Commit Message: http2: migrates DATA frame payloads to the visitor API Additional Description: Risk Level: low Testing: ran unit and integration tests locally Docs Changes: Release Notes: added a note with the runtime feature to disable in case of problems Runtime guard: envoy_reloadable_features_http2_use_visitor_for_data Signed-off-by: Biren Roy --- changelogs/current.yaml | 4 ++ source/common/http/http2/codec_impl.cc | 77 ++++++++++++++++++++--- source/common/http/http2/codec_impl.h | 6 ++ source/common/runtime/runtime_features.cc | 1 + test/common/http/http2/codec_impl_test.cc | 64 +++++++++++++++++++ test/common/http/http2/http2_frame.cc | 13 ++++ test/common/http/http2/http2_frame.h | 2 + tools/spelling/spelling_dictionary.txt | 1 + 8 files changed, 159 insertions(+), 9 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 54f77ee15a56..7d4b99887c30 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -16,6 +16,10 @@ behavior_changes: change: | Changes the default value of ``envoy.reloadable_features.http2_use_oghttp2`` to true. This changes the codec used for HTTP/2 requests and responses. This behavior can be reverted by setting the feature to false. +- area: http2 + change: | + Passes HTTP/2 DATA frames through a different codec API. This behavior can be temporarily disabled by setting the runtime + feature ``envoy.reloadable_features.http2_use_visitor_for_data`` to false. - area: proxy_protocol change: | Populate typed metadata by default in proxy protocol listener. Typed metadata can be consumed as diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index f2ac14ba1531..eaaa006bfad7 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -368,7 +368,7 @@ void ConnectionImpl::StreamImpl::processBufferedData() { // We only buffer the onStreamClose if we had no errors. if (Status status = parent_.onStreamClose(this, 0); !status.ok()) { ENVOY_CONN_LOG(debug, "error invoking onStreamClose: {}", parent_.connection_, - status.message()); // GCOV_EXCL_LINE + status.message()); // LCOV_EXCL_LINE } } } @@ -663,8 +663,12 @@ bool ConnectionImpl::StreamDataFrameSource::Send(absl::string_view frame_header, void ConnectionImpl::ClientStreamImpl::submitHeaders(const HeaderMap& headers, bool end_stream) { ASSERT(stream_id_ == -1); + const bool skip_frame_source = + end_stream || + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_use_visitor_for_data"); stream_id_ = parent_.adapter_->SubmitRequest( - buildHeaders(headers), end_stream ? nullptr : std::make_unique(*this), + buildHeaders(headers), + skip_frame_source ? nullptr : std::make_unique(*this), end_stream, base()); ASSERT(stream_id_ > 0); } @@ -685,9 +689,12 @@ void ConnectionImpl::ClientStreamImpl::advanceHeadersState() { void ConnectionImpl::ServerStreamImpl::submitHeaders(const HeaderMap& headers, bool end_stream) { ASSERT(stream_id_ != -1); - parent_.adapter_->SubmitResponse(stream_id_, buildHeaders(headers), - end_stream ? nullptr - : std::make_unique(*this)); + const bool skip_frame_source = + end_stream || + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.http2_use_visitor_for_data"); + parent_.adapter_->SubmitResponse( + stream_id_, buildHeaders(headers), + skip_frame_source ? nullptr : std::make_unique(*this), end_stream); } Status ConnectionImpl::ServerStreamImpl::onBeginHeaders() { @@ -780,7 +787,7 @@ void ConnectionImpl::StreamImpl::resetStream(StreamResetReason reason) { // its stream close. if (Status status = parent_.onStreamClose(this, 0); !status.ok()) { ENVOY_CONN_LOG(debug, "error invoking onStreamClose: {}", parent_.connection_, - status.message()); // GCOV_EXCL_LINE + status.message()); // LCOV_EXCL_LINE } return; } @@ -978,7 +985,7 @@ Http::Status ConnectionImpl::dispatch(Buffer::Instance& data) { // protections that Envoy's codec does not have. if (rc == NGHTTP2_ERR_FLOODED) { return bufferFloodError( - "Flooding was detected in this HTTP/2 session, and it must be closed"); // GCOV_EXCL_LINE + "Flooding was detected in this HTTP/2 session, and it must be closed"); // LCOV_EXCL_LINE } if (rc != static_cast(slice.len_)) { return codecProtocolError(nghttp2_strerror(rc)); @@ -1181,7 +1188,7 @@ Status ConnectionImpl::onHeaders(int32_t stream_id, size_t length, uint8_t flags default: // We do not currently support push. - ENVOY_BUG(false, "push not supported"); // GCOV_EXCL_LINE + ENVOY_BUG(false, "push not supported"); // LCOV_EXCL_LINE } stream->advanceHeadersState(); @@ -1305,7 +1312,7 @@ int ConnectionImpl::onInvalidFrame(int32_t stream_id, int error_code) { default: // Unknown error conditions. Trigger ENVOY_BUG and connection close. - ENVOY_BUG(false, absl::StrCat("Unexpected error_code: ", error_code)); // GCOV_EXCL_LINE + ENVOY_BUG(false, absl::StrCat("Unexpected error_code: ", error_code)); // LCOV_EXCL_LINE break; } @@ -1692,6 +1699,58 @@ int64_t ConnectionImpl::Http2Visitor::OnReadyToSend(absl::string_view serialized serialized.size()); } +ConnectionImpl::Http2Visitor::DataFrameHeaderInfo +ConnectionImpl::Http2Visitor::OnReadyToSendDataForStream(Http2StreamId stream_id, + size_t max_length) { + StreamImpl* stream = connection_->getStream(stream_id); + if (stream == nullptr) { + return {/*payload_length=*/-1, /*end_data=*/false, /*end_stream=*/false}; + } + if (stream->pending_send_data_->length() == 0 && !stream->local_end_stream_) { + stream->data_deferred_ = true; + return {/*payload_length=*/0, /*end_data=*/false, /*end_stream=*/false}; + } + const size_t length = std::min(max_length, stream->pending_send_data_->length()); + bool end_data = false; + bool end_stream = false; + if (stream->local_end_stream_ && length == stream->pending_send_data_->length()) { + end_data = true; + if (stream->pending_trailers_to_encode_) { + stream->submitTrailers(*stream->pending_trailers_to_encode_); + stream->pending_trailers_to_encode_.reset(); + } else { + end_stream = true; + } + } + return {static_cast(length), end_data, end_stream}; +} + +bool ConnectionImpl::Http2Visitor::SendDataFrame(Http2StreamId stream_id, + absl::string_view frame_header, + size_t payload_length) { + connection_->protocol_constraints_.incrementOutboundDataFrameCount(); + + StreamImpl* stream = connection_->getStream(stream_id); + if (stream == nullptr) { + ENVOY_CONN_LOG(error, "error sending data frame: stream {} not found", connection_->connection_, + stream_id); + return false; + } + Buffer::OwnedImpl output; + connection_->addOutboundFrameFragment( + output, reinterpret_cast(frame_header.data()), frame_header.size()); + if (!connection_->protocol_constraints_.checkOutboundFrameLimits().ok()) { + ENVOY_CONN_LOG(debug, "error sending data frame: Too many frames in the outbound queue", + connection_->connection_); + stream->setDetails(Http2ResponseCodeDetails::get().outbound_frame_flood); + } + + connection_->stats_.pending_send_bytes_.sub(payload_length); + output.move(*stream->pending_send_data_, payload_length); + connection_->connection_.write(output, false); + return true; +} + bool ConnectionImpl::Http2Visitor::OnFrameHeader(Http2StreamId stream_id, size_t length, uint8_t type, uint8_t flags) { ENVOY_CONN_LOG(debug, "Http2Visitor::OnFrameHeader({}, {}, {}, {})", connection_->connection_, diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 4d0284f891ab..a72ae97a1e32 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -176,6 +176,10 @@ class ConnectionImpl : public virtual Connection, stream_close_listener_ = std::move(f); } int64_t OnReadyToSend(absl::string_view serialized) override; + DataFrameHeaderInfo OnReadyToSendDataForStream(Http2StreamId stream_id, + size_t max_length) override; + bool SendDataFrame(Http2StreamId stream_id, absl::string_view frame_header, + size_t payload_bytes) override; void OnConnectionError(ConnectionError /*error*/) override {} bool OnFrameHeader(Http2StreamId stream_id, size_t length, uint8_t type, uint8_t flags) override; @@ -466,6 +470,8 @@ class ConnectionImpl : public virtual Connection, }; // Encapsulates the logic for sending DATA frames on a given stream. + // Deprecated. Remove when removing + // `envoy_reloadable_features_http2_use_visitor_for_data`. class StreamDataFrameSource : public http2::adapter::DataFrameSource { public: explicit StreamDataFrameSource(StreamImpl& stream) : stream_(stream) {} diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 4cac40375d2b..fb02d63268df 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -59,6 +59,7 @@ RUNTIME_GUARD(envoy_reloadable_features_http2_decode_metadata_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http2_discard_host_header); // Ignore the automated "remove this flag" issue: we should keep this for 1 year. RUNTIME_GUARD(envoy_reloadable_features_http2_use_oghttp2); +RUNTIME_GUARD(envoy_reloadable_features_http2_use_visitor_for_data); RUNTIME_GUARD(envoy_reloadable_features_http2_validate_authority_with_quiche); RUNTIME_GUARD(envoy_reloadable_features_http_allow_partial_urls_in_referer); RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply); diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index 6f25b2b886fb..d9f09aee9e00 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -597,6 +597,51 @@ TEST_P(Http2CodecImplTest, SimpleRequestResponse) { } } +TEST_P(Http2CodecImplTest, SimpleRequestResponseOldApi) { + scoped_runtime_.mergeValues({{"envoy.reloadable_features.http2_use_visitor_for_data", "false"}}); + initialize(); + + InSequence s; + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + request_headers.setMethod("POST"); + + // Encode request headers. + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + + // Queue request body. + Buffer::OwnedImpl request_body(std::string(1024, 'a')); + request_encoder_->encodeData(request_body, true); + + // Flush request body. + EXPECT_CALL(request_decoder_, decodeData(_, true)).Times(AtLeast(1)); + driveToCompletion(); + + TestResponseHeaderMapImpl response_headers{{":status", "200"}}; + + // Encode response headers. + EXPECT_CALL(response_decoder_, decodeHeaders_(_, false)); + response_encoder_->encodeHeaders(response_headers, false); + + // Queue response body. + Buffer::OwnedImpl response_body(std::string(1024, 'b')); + response_encoder_->encodeData(response_body, true); + + // Flush response body. + EXPECT_CALL(response_decoder_, decodeData(_, true)).Times(AtLeast(1)); + driveToCompletion(); + + EXPECT_TRUE(client_wrapper_->status_.ok()); + EXPECT_TRUE(server_wrapper_->status_.ok()); + + if (http2_implementation_ == Http2Impl::Nghttp2) { + // Regression test for issue #19761. + EXPECT_EQ(0, getClientDataSourcesSize()); + EXPECT_EQ(0, getServerDataSourcesSize()); + } +} + TEST_P(Http2CodecImplTest, ShutdownNotice) { initialize(); EXPECT_EQ(absl::nullopt, request_encoder_->http1StreamEncoderOptions()); @@ -3477,6 +3522,25 @@ TEST_P(Http2CodecImplTest, WindowUpdateFloodOverride) { EXPECT_NO_THROW(driveToCompletion()); } +TEST_P(Http2CodecImplTest, DataFrameWithPadding) { + initialize(); + TestRequestHeaderMapImpl request_headers; + HttpTestUtility::addDefaultHeaders(request_headers); + request_headers.setMethod("POST"); + EXPECT_CALL(request_decoder_, decodeHeaders_(_, false)); + EXPECT_TRUE(request_encoder_->encodeHeaders(request_headers, false).ok()); + driveToCompletion(); + Http2Frame dataFrame = Http2Frame::makeDataFrameWithPadding(Http2Frame::makeClientStreamId(0), + "some data with padding", 193); + Buffer::OwnedImpl data; + data.add(dataFrame.data(), dataFrame.size()); + server_wrapper_->buffer_.add(std::move(data)); + EXPECT_CALL(request_decoder_, decodeData(_, false)); + driveToCompletion(); + const Http::Status& status = server_wrapper_->status_; + EXPECT_TRUE(status.ok()); +} + TEST_P(Http2CodecImplTest, EmptyDataFlood) { expect_buffered_data_on_teardown_ = true; Buffer::OwnedImpl data; diff --git a/test/common/http/http2/http2_frame.cc b/test/common/http/http2/http2_frame.cc index 319b3fc87380..fe4229fd3aa5 100644 --- a/test/common/http/http2/http2_frame.cc +++ b/test/common/http/http2/http2_frame.cc @@ -423,6 +423,19 @@ Http2Frame Http2Frame::makeDataFrame(uint32_t stream_index, absl::string_view da return frame; } +Http2Frame Http2Frame::makeDataFrameWithPadding(uint32_t stream_index, absl::string_view data, + uint8_t padding_size) { + ASSERT(padding_size > 0); + Http2Frame frame; + frame.buildHeader(Type::Data, 0, 0x08, makeNetworkOrderStreamId(stream_index)); + frame.appendData({static_cast(padding_size - 1u)}); + frame.appendData(data); + std::vector padding(padding_size - 1); + frame.appendData(padding); + frame.adjustPayloadSize(); + return frame; +} + } // namespace Http2 } // namespace Http } // namespace Envoy diff --git a/test/common/http/http2/http2_frame.h b/test/common/http/http2/http2_frame.h index 2844c6bcebde..5e83cfb06310 100644 --- a/test/common/http/http2/http2_frame.h +++ b/test/common/http/http2/http2_frame.h @@ -184,6 +184,8 @@ class Http2Frame { const std::vector
extra_headers); static Http2Frame makeDataFrame(uint32_t stream_index, absl::string_view data, DataFlags flags = DataFlags::None); + static Http2Frame makeDataFrameWithPadding(uint32_t stream_index, absl::string_view data, + uint8_t padding_size); /** * Creates a frame with the given contents. This frame can be diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 21123f3dfabf..de4bc0f26c16 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -258,6 +258,7 @@ KiB Kille LBs LC +LCOV LDS LEDS LEV From c38670b1b3a8ea3b761a99a1407acca4571448ea Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 13:16:15 -0500 Subject: [PATCH 09/22] mobile: Rewrite Java/JNI stream callbacks (#34234) This PR rewrites the Java/JNI implementation of the stream callbacks by removing a lot of indirection. This should reduce the number of translation layers, copies, as well as making the code easier to read and maintain. Prior to this PR, the filter and callback implementation was somewhat shared, but it makes the code confusing to read because there's some logic that's only relevant for the filters and vice versa. With the new implementation, the stream callback implementation is separate from the filter implementation, but some common helpers have been written so that the code can still be shared between the stream callback and filter implementations. In the next PR, I will update the filter implementation, so that we can finally get rid of the C wrapper types. Risk Level: medium Testing: unit & integration tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: mobile Signed-off-by: Fredy Wijaya --- mobile/library/common/types/c_types.h | 1 + .../io/envoyproxy/envoymobile/engine/BUILD | 3 - .../engine/EnvoyFinalStreamIntelImpl.java | 113 ---------- .../envoymobile/engine/EnvoyHTTPStream.java | 6 +- .../engine/EnvoyStreamIntelImpl.java | 37 ---- .../envoymobile/engine/JniLibrary.java | 20 +- .../engine/JvmCallbackContext.java | 151 ------------- .../envoymobile/engine/JvmFilterContext.java | 29 +-- .../engine/types/EnvoyFinalStreamIntel.java | 177 ++++++++++++--- .../engine/types/EnvoyHTTPCallbacks.java | 4 - .../engine/types/EnvoyStreamIntel.java | 50 ++++- .../net/impl/CronvoyBidirectionalStream.java | 13 -- .../chromium/net/impl/CronvoyUrlRequest.java | 26 +-- mobile/library/jni/jni_helper.cc | 19 ++ mobile/library/jni/jni_helper.h | 27 ++- mobile/library/jni/jni_impl.cc | 202 +++++++++--------- mobile/library/jni/jni_utility.cc | 163 ++++++++++++-- mobile/library/jni/jni_utility.h | 45 +++- .../envoyproxy/envoymobile/StreamCallbacks.kt | 75 +++++-- .../envoyproxy/envoymobile/StreamPrototype.kt | 5 +- .../envoymobile/grpc/GRPCStreamPrototype.kt | 3 +- mobile/library/proguard.txt | 16 +- .../java/io/envoyproxy/envoymobile/jni/BUILD | 1 + .../envoymobile/jni/JniUtilityTest.java | 38 ++++ mobile/test/java/org/chromium/net/impl/BUILD | 1 + .../org/chromium/net/impl/ErrorsTest.java | 23 +- mobile/test/jni/jni_utility_test.cc | 28 ++- .../FilterThrowingExceptionTest.kt | 8 +- .../envoyproxy/envoymobile/GRPCStreamTest.kt | 23 +- .../envoymobile/mocks/MockStream.kt | 84 +------- .../envoymobile/mocks/MockStreamPrototype.kt | 2 +- 31 files changed, 725 insertions(+), 668 deletions(-) delete mode 100644 mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyFinalStreamIntelImpl.java delete mode 100644 mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyStreamIntelImpl.java delete mode 100644 mobile/library/java/io/envoyproxy/envoymobile/engine/JvmCallbackContext.java diff --git a/mobile/library/common/types/c_types.h b/mobile/library/common/types/c_types.h index df1ce2052ba0..40ef2f0f488e 100644 --- a/mobile/library/common/types/c_types.h +++ b/mobile/library/common/types/c_types.h @@ -188,6 +188,7 @@ typedef struct { int64_t response_start_ms; // The time when the stream reached a final state: Error, Cancel, Success. int64_t stream_end_ms; + // TODO(fredyw): This should be a bool instead. // True if the upstream socket had been used previously. uint64_t socket_reused; // The number of bytes sent upstream. diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/BUILD b/mobile/library/java/io/envoyproxy/envoymobile/engine/BUILD index 9478da98d433..e73d3f5045ee 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/BUILD +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/BUILD @@ -34,18 +34,15 @@ java_library( "EnvoyConfiguration.java", "EnvoyEngine.java", "EnvoyEngineImpl.java", - "EnvoyFinalStreamIntelImpl.java", "EnvoyHTTPFilterCallbacksImpl.java", "EnvoyHTTPStream.java", "EnvoyNativeFilterConfig.java", "EnvoyNativeResourceRegistry.java", "EnvoyNativeResourceReleaser.java", "EnvoyNativeResourceWrapper.java", - "EnvoyStreamIntelImpl.java", "HeaderMatchConfig.java", "JniBridgeUtility.java", "JniLibrary.java", - "JvmCallbackContext.java", "JvmFilterContext.java", "JvmFilterFactoryContext.java", "JvmKeyValueStoreContext.java", diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyFinalStreamIntelImpl.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyFinalStreamIntelImpl.java deleted file mode 100644 index bba2961384a4..000000000000 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyFinalStreamIntelImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -package io.envoyproxy.envoymobile.engine; - -import io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel; -import androidx.annotation.VisibleForTesting; - -// This class is made public for access in tests. -public class EnvoyFinalStreamIntelImpl implements EnvoyFinalStreamIntel { - private final long streamStartMs; - private final long dnsStartMs; - private final long dnsEndMs; - private final long connectStartMs; - private final long connectEndMs; - private final long sslStartMs; - private final long sslEndMs; - private final long sendingStartMs; - private final long sendingEndMs; - private final long responseStartMs; - private final long streamEndMs; - private final boolean socketReused; - private final long sentByteCount; - private final long receivedByteCount; - private final long responseFlags; - private final long upstreamProtocol; - - EnvoyFinalStreamIntelImpl(long[] values) { - streamStartMs = values[0]; - dnsStartMs = values[1]; - dnsEndMs = values[2]; - connectStartMs = values[3]; - connectEndMs = values[4]; - sslStartMs = values[5]; - sslEndMs = values[6]; - sendingStartMs = values[7]; - sendingEndMs = values[8]; - responseStartMs = values[9]; - streamEndMs = values[10]; - socketReused = values[11] != 0; - sentByteCount = values[12]; - receivedByteCount = values[13]; - responseFlags = values[14]; - upstreamProtocol = values[15]; - } - - @VisibleForTesting - public static EnvoyFinalStreamIntelImpl createForTesting(long[] values) { - return new EnvoyFinalStreamIntelImpl(values); - } - - @Override - public long getStreamStartMs() { - return streamStartMs; - } - @Override - public long getDnsStartMs() { - return dnsStartMs; - } - @Override - public long getDnsEndMs() { - return dnsEndMs; - } - @Override - public long getConnectStartMs() { - return connectStartMs; - } - @Override - public long getConnectEndMs() { - return connectEndMs; - } - @Override - public long getSslStartMs() { - return sslStartMs; - } - @Override - public long getSslEndMs() { - return sslEndMs; - } - @Override - public long getSendingStartMs() { - return sendingStartMs; - } - @Override - public long getSendingEndMs() { - return sendingEndMs; - } - @Override - public long getResponseStartMs() { - return responseStartMs; - } - @Override - public long getStreamEndMs() { - return streamEndMs; - } - @Override - public boolean getSocketReused() { - return socketReused; - } - @Override - public long getSentByteCount() { - return sentByteCount; - } - @Override - public long getReceivedByteCount() { - return receivedByteCount; - } - @Override - public long getResponseFlags() { - return responseFlags; - } - @Override - public long getUpstreamProtocol() { - return upstreamProtocol; - } -} diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java index c8f0f0d6f87f..c3c1d9481e7e 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java @@ -10,13 +10,13 @@ public class EnvoyHTTPStream { private final long engineHandle; private final long streamHandle; private final boolean explicitFlowControl; - private final JvmCallbackContext callbacksContext; + private final EnvoyHTTPCallbacks callbacks; /** * Start the stream via the JNI library. */ void start() { - JniLibrary.startStream(engineHandle, streamHandle, callbacksContext, explicitFlowControl); + JniLibrary.startStream(engineHandle, streamHandle, callbacks, explicitFlowControl); } /** @@ -31,7 +31,7 @@ public EnvoyHTTPStream(long engineHandle, long streamHandle, EnvoyHTTPCallbacks this.engineHandle = engineHandle; this.streamHandle = streamHandle; this.explicitFlowControl = explicitFlowControl; - callbacksContext = new JvmCallbackContext(callbacks); + this.callbacks = callbacks; } /** diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyStreamIntelImpl.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyStreamIntelImpl.java deleted file mode 100644 index 23d1c5f779f6..000000000000 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyStreamIntelImpl.java +++ /dev/null @@ -1,37 +0,0 @@ -package io.envoyproxy.envoymobile.engine; - -import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel; - -class EnvoyStreamIntelImpl implements EnvoyStreamIntel { - private final long streamId; - private final long connectionId; - private final long attemptCount; - private final long consumedBytesFromResponse; - - EnvoyStreamIntelImpl(long[] values) { - streamId = values[0]; - connectionId = values[1]; - attemptCount = values[2]; - consumedBytesFromResponse = values[3]; - } - - @Override - public long getStreamId() { - return streamId; - } - - @Override - public long getConnectionId() { - return connectionId; - } - - @Override - public long getAttemptCount() { - return attemptCount; - } - - @Override - public long getConsumedBytesFromResponse() { - return consumedBytesFromResponse; - } -} diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index e3ac65ce54d6..7c8e71c7eefd 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -1,6 +1,7 @@ package io.envoyproxy.envoymobile.engine; import io.envoyproxy.envoymobile.engine.types.EnvoyEventTracker; +import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks; import io.envoyproxy.envoymobile.engine.types.EnvoyLogger; import io.envoyproxy.envoymobile.engine.types.EnvoyOnEngineRunning; import java.nio.ByteBuffer; @@ -58,16 +59,13 @@ private static class JavaLoader { * Open an underlying HTTP stream. Note: Streams must be started before other * other interaction can can occur. * - * @param engine, handle to the stream's associated engine. - * @param stream, handle to the stream to be started. - * @param context, context that contains dispatch logic to fire callbacks - * callbacks. - * @param explicitFlowControl, whether explicit flow control should be enabled - * for the stream. - * @return envoy_stream, with a stream handle and a success status, or a failure - * status. + * @param engine handle to the stream's associated engine. + * @param stream handle to the stream to be started. + * @param callbacks context that contains dispatch logic to fire callbacks. + * @param explicitFlowControl whether explicit flow control should be enabled for the stream. + * @return a stream handle and a success status, or a failure status. */ - protected static native int startStream(long engine, long stream, JvmCallbackContext context, + protected static native int startStream(long engine, long stream, EnvoyHTTPCallbacks callbacks, boolean explicitFlowControl); /** @@ -263,7 +261,7 @@ public static native Object callCertificateVerificationFromNative(byte[][] certC * Mimic a call to AndroidNetworkLibrary#addTestRootCertificate from native code. * To be used for testing only. * - * @param rootCert DER encoded bytes of the certificate. + * @param cert DER encoded bytes of the certificate. */ public static native void callAddTestRootCertificateFromNative(byte[] cert); @@ -274,7 +272,7 @@ public static native Object callCertificateVerificationFromNative(byte[][] certC */ public static native void callClearTestRootCertificateFromNative(); - /* + /** * Given a filter name, create the proto config for adding the native filter * * @param filterName the name of the native filter diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmCallbackContext.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmCallbackContext.java deleted file mode 100644 index cdfafb01f1ae..000000000000 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmCallbackContext.java +++ /dev/null @@ -1,151 +0,0 @@ -package io.envoyproxy.envoymobile.engine; - -import java.nio.ByteBuffer; -import java.util.List; -import java.util.Map; - -import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks; - -class JvmCallbackContext { - private final JvmBridgeUtility bridgeUtility; - private final EnvoyHTTPCallbacks callbacks; - - public JvmCallbackContext(EnvoyHTTPCallbacks callbacks) { - bridgeUtility = new JvmBridgeUtility(); - this.callbacks = callbacks; - } - - /** - * Delegates header retrieval to the bridge utility. - * - * @param key, the name of the HTTP header. - * @param value, the value of the HTTP header. - * @param start, indicates this is the first header pair of the block. - */ - void passHeader(byte[] key, byte[] value, boolean start) { - bridgeUtility.passHeader(key, value, start); - } - - /** - * Invokes onHeaders callback using headers passed via passHeaders. - * - * @param headerCount, the total number of headers included in this header block. - * @param endStream, whether this header block is the final remote frame. - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onResponseHeaders(long headerCount, boolean endStream, long[] streamIntel) { - assert bridgeUtility.validateCount(headerCount); - final Map> headers = bridgeUtility.retrieveHeaders(); - - callbacks.getExecutor().execute( - () -> callbacks.onHeaders(headers, endStream, new EnvoyStreamIntelImpl(streamIntel))); - - return null; - } - - /** - * Invokes onTrailers callback using trailers passed via passHeaders. - * - * @param trailerCount, the total number of trailers included in this header block. - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onResponseTrailers(long trailerCount, long[] streamIntel) { - assert bridgeUtility.validateCount(trailerCount); - final Map> trailers = bridgeUtility.retrieveHeaders(); - - callbacks.getExecutor().execute( - () -> callbacks.onTrailers(trailers, new EnvoyStreamIntelImpl(streamIntel))); - - return null; - } - - /** - * Dispatches data received from the JNI layer up to the platform. - * - * @param data, chunk of body data from the HTTP response. - * @param endStream, indicates this is the last remote frame of the stream. - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onResponseData(ByteBuffer data, boolean endStream, long[] streamIntel) { - // Create a copy of the `data` because the `data` uses direct `ByteBuffer` and the `data` will - // be destroyed after calling this callback. - ByteBuffer copiedData = ByteBuffers.copy(data); - callbacks.getExecutor().execute( - () -> callbacks.onData(copiedData, endStream, new EnvoyStreamIntelImpl(streamIntel))); - return null; - } - - /** - * Dispatches error received from the JNI layer up to the platform. - * - * @param errorCode, the error code. - * @param message, the error message. - * @param attemptCount, the number of times an operation was attempted before firing this - * error. - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @param finalStreamIntel, final internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onError(int errorCode, byte[] message, int attemptCount, long[] streamIntel, - long[] finalStreamIntel) { - callbacks.getExecutor().execute(() -> { - String errorMessage = new String(message); - callbacks.onError(errorCode, errorMessage, attemptCount, - new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); - }); - - return null; - } - - /** - * Dispatches cancellation notice up to the platform - * - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @param finalStreamIntel, final internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onCancel(long[] streamIntel, long[] finalStreamIntel) { - callbacks.getExecutor().execute(() -> { - // This call is atomically gated at the call-site and will only happen once. - callbacks.onCancel(new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); - }); - - return null; - } - - /** - * Dispatches onSendWindowAvailable notice up to the platform - * - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @return Object, not used for response callbacks. - */ - public Object onSendWindowAvailable(long[] streamIntel) { - callbacks.getExecutor().execute(() -> { - // This call is atomically gated at the call-site and will only happen once. - callbacks.onSendWindowAvailable(new EnvoyStreamIntelImpl(streamIntel)); - }); - - return null; - } - /** - * Called with all stream metrics after the final headers/data/trailers call. - * - * @param streamIntel, internal HTTP stream metrics, context, and other details. - * @param finalStreamIntel, final internal HTTP stream metrics for the end of stream. - * @return Object, not used for response callbacks. - */ - public Object onComplete(long[] streamIntel, long[] finalStreamIntel) { - callbacks.getExecutor().execute(() -> { - // This call is atomically gated at the call-site and will only happen once. - callbacks.onComplete(new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); - }); - - return null; - } -} diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmFilterContext.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmFilterContext.java index 251de4cb070e..59822278408f 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmFilterContext.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JvmFilterContext.java @@ -4,7 +4,9 @@ import java.util.List; import java.util.Map; +import io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel; import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPFilter; +import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel; /** * Wrapper class for EnvoyHTTPFilter for receiving JNI calls. @@ -54,7 +56,7 @@ public Object onRequestHeaders(long headerCount, boolean endStream, long[] strea assert headerUtility.validateCount(headerCount); final Map> headers = headerUtility.retrieveHeaders(); return toJniFilterHeadersStatus( - filter.onRequestHeaders(headers, endStream, new EnvoyStreamIntelImpl(streamIntel))); + filter.onRequestHeaders(headers, endStream, new EnvoyStreamIntel(streamIntel))); } /** @@ -70,7 +72,7 @@ public Object onRequestData(ByteBuffer data, boolean endStream, long[] streamInt // be destroyed after calling this callback. ByteBuffer copiedData = ByteBuffers.copy(data); return toJniFilterDataStatus( - filter.onRequestData(copiedData, endStream, new EnvoyStreamIntelImpl(streamIntel))); + filter.onRequestData(copiedData, endStream, new EnvoyStreamIntel(streamIntel))); } /** @@ -84,7 +86,7 @@ public Object onRequestTrailers(long trailerCount, long[] streamIntel) { assert headerUtility.validateCount(trailerCount); final Map> trailers = headerUtility.retrieveHeaders(); return toJniFilterTrailersStatus( - filter.onRequestTrailers(trailers, new EnvoyStreamIntelImpl(streamIntel))); + filter.onRequestTrailers(trailers, new EnvoyStreamIntel(streamIntel))); } /** @@ -99,7 +101,7 @@ public Object onResponseHeaders(long headerCount, boolean endStream, long[] stre assert headerUtility.validateCount(headerCount); final Map> headers = headerUtility.retrieveHeaders(); return toJniFilterHeadersStatus( - filter.onResponseHeaders(headers, endStream, new EnvoyStreamIntelImpl(streamIntel))); + filter.onResponseHeaders(headers, endStream, new EnvoyStreamIntel(streamIntel))); } /** @@ -115,7 +117,7 @@ public Object onResponseData(ByteBuffer data, boolean endStream, long[] streamIn // be destroyed after calling this callback. ByteBuffer copiedData = ByteBuffers.copy(data); return toJniFilterDataStatus( - filter.onResponseData(copiedData, endStream, new EnvoyStreamIntelImpl(streamIntel))); + filter.onResponseData(copiedData, endStream, new EnvoyStreamIntel(streamIntel))); } /** @@ -129,7 +131,7 @@ public Object onResponseTrailers(long trailerCount, long[] streamIntel) { assert headerUtility.validateCount(trailerCount); final Map> trailers = headerUtility.retrieveHeaders(); return toJniFilterTrailersStatus( - filter.onResponseTrailers(trailers, new EnvoyStreamIntelImpl(streamIntel))); + filter.onResponseTrailers(trailers, new EnvoyStreamIntel(streamIntel))); } /** @@ -160,7 +162,7 @@ public Object onResumeRequest(long headerCount, ByteBuffer data, long trailerCou trailers = trailerUtility.retrieveHeaders(); } return toJniFilterResumeStatus(filter.onResumeRequest(headers, copiedData, trailers, endStream, - new EnvoyStreamIntelImpl(streamIntel))); + new EnvoyStreamIntel(streamIntel))); } /** @@ -191,7 +193,7 @@ public Object onResumeResponse(long headerCount, ByteBuffer data, long trailerCo trailers = trailerUtility.retrieveHeaders(); } return toJniFilterResumeStatus(filter.onResumeResponse(headers, copiedData, trailers, endStream, - new EnvoyStreamIntelImpl(streamIntel))); + new EnvoyStreamIntel(streamIntel))); } /** @@ -225,8 +227,8 @@ public void setResponseFilterCallbacks(long callbackHandle) { public Object onError(int errorCode, byte[] message, int attemptCount, long[] streamIntel, long[] finalStreamIntel) { String errorMessage = new String(message); - filter.onError(errorCode, errorMessage, attemptCount, new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); + filter.onError(errorCode, errorMessage, attemptCount, new EnvoyStreamIntel(streamIntel), + new EnvoyFinalStreamIntel(finalStreamIntel)); return null; } @@ -238,8 +240,7 @@ public Object onError(int errorCode, byte[] message, int attemptCount, long[] st * @return Object, not used in HTTP filters. */ public Object onCancel(long[] streamIntel, long[] finalStreamIntel) { - filter.onCancel(new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); + filter.onCancel(new EnvoyStreamIntel(streamIntel), new EnvoyFinalStreamIntel(finalStreamIntel)); return null; } @@ -251,8 +252,8 @@ public Object onCancel(long[] streamIntel, long[] finalStreamIntel) { * @return Object, not used in HTTP filters. */ public Object onComplete(long[] streamIntel, long[] finalStreamIntel) { - filter.onComplete(new EnvoyStreamIntelImpl(streamIntel), - new EnvoyFinalStreamIntelImpl(finalStreamIntel)); + filter.onComplete(new EnvoyStreamIntel(streamIntel), + new EnvoyFinalStreamIntel(finalStreamIntel)); return null; } diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel.java index 1ab43786cb32..8cc30eff168f 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel.java @@ -1,79 +1,190 @@ package io.envoyproxy.envoymobile.engine.types; +import java.util.Objects; + /** * Exposes internal HTTP stream metrics, context, and other details sent once on stream end. * * Note: a value of -1 means "not present" for any field where the name is suffixed with "Ms". */ -public interface EnvoyFinalStreamIntel { - /* +public class EnvoyFinalStreamIntel { + private final long streamStartMs; + private final long dnsStartMs; + private final long dnsEndMs; + private final long connectStartMs; + private final long connectEndMs; + private final long sslStartMs; + private final long sslEndMs; + private final long sendingStartMs; + private final long sendingEndMs; + private final long responseStartMs; + private final long streamEndMs; + private final boolean socketReused; + private final long sentByteCount; + private final long receivedByteCount; + private final long responseFlags; + private final long upstreamProtocol; + + public EnvoyFinalStreamIntel(long streamStartMs, long dnsStartMs, long dnsEndMs, + long connectStartMs, long connectEndMs, long sslStartMs, + long sslEndMs, long sendingStartMs, long sendingEndMs, + long responseStartMs, long streamEndMs, boolean socketReused, + long sentByteCount, long receivedByteCount, long responseFlags, + long upstreamProtocol) { + this.streamStartMs = streamStartMs; + this.dnsStartMs = dnsStartMs; + this.dnsEndMs = dnsEndMs; + this.connectStartMs = connectStartMs; + this.connectEndMs = connectEndMs; + this.sslStartMs = sslStartMs; + this.sslEndMs = sslEndMs; + this.sendingStartMs = sendingStartMs; + this.sendingEndMs = sendingEndMs; + this.responseStartMs = responseStartMs; + this.streamEndMs = streamEndMs; + this.socketReused = socketReused; + this.sentByteCount = sentByteCount; + this.receivedByteCount = receivedByteCount; + this.responseFlags = responseFlags; + this.upstreamProtocol = upstreamProtocol; + } + + public EnvoyFinalStreamIntel(long[] values) { + streamStartMs = values[0]; + dnsStartMs = values[1]; + dnsEndMs = values[2]; + connectStartMs = values[3]; + connectEndMs = values[4]; + sslStartMs = values[5]; + sslEndMs = values[6]; + sendingStartMs = values[7]; + sendingEndMs = values[8]; + responseStartMs = values[9]; + streamEndMs = values[10]; + socketReused = values[11] != 0; + sentByteCount = values[12]; + receivedByteCount = values[13]; + responseFlags = values[14]; + upstreamProtocol = values[15]; + } + + /** * The time the stream started (a.k.a request started), in ms since the epoch. */ - long getStreamStartMs(); - /* + public long getStreamStartMs() { return streamStartMs; } + + /** * The time the DNS resolution for this request started, in ms since the epoch. */ - long getDnsStartMs(); - /* + public long getDnsStartMs() { return dnsStartMs; } + + /** * The time the DNS resolution for this request completed, in ms since the epoch. */ - long getDnsEndMs(); - /* + public long getDnsEndMs() { return dnsEndMs; } + + /** * The time the upstream connection started, in ms since the epoch. * This may not be set if socket_reused is false. */ - long getConnectStartMs(); - /* + public long getConnectStartMs() { return connectStartMs; } + + /** * The time the upstream connection completed, in ms since the epoch. * This may not be set if socket_reused is false. */ - long getConnectEndMs(); - /* + public long getConnectEndMs() { return connectEndMs; } + + /** * The time the SSL handshake started, in ms since the epoch. * This may not be set if socket_reused is false. */ - long getSslStartMs(); - /* + public long getSslStartMs() { return sslStartMs; } + + /** * The time the SSL handshake completed, in ms since the epoch. * This may not be set if socket_reused is false. */ - long getSslEndMs(); - /* + public long getSslEndMs() { return sslEndMs; } + + /** * The time the first byte of the request was sent upstream, in ms since the epoch. */ - long getSendingStartMs(); - /* + public long getSendingStartMs() { return sendingStartMs; } + + /** * The time the last byte of the request was sent upstream, in ms since the epoch. */ - long getSendingEndMs(); - /* + public long getSendingEndMs() { return sendingEndMs; } + + /** * The time the first byte of the response was received, in ms since the epoch. */ - long getResponseStartMs(); - /* + public long getResponseStartMs() { return responseStartMs; } + + /** * The time when the stream reached a final state (Error, Cancel, Success), in ms since the epoch. */ - long getStreamEndMs(); - /* + public long getStreamEndMs() { return streamEndMs; } + + /** * True if the upstream socket had been used previously. */ - boolean getSocketReused(); - /* + public boolean getSocketReused() { return socketReused; } + + /** * The number of bytes sent upstream. */ - long getSentByteCount(); - /* + public long getSentByteCount() { return sentByteCount; } + + /** * The number of bytes received from upstream. */ - long getReceivedByteCount(); + public long getReceivedByteCount() { return receivedByteCount; } + /* * The response flags for the stream. See * https://github.com/envoyproxy/envoy/blob/main/envoy/stream_info/stream_info.h#L39 * for values. */ - long getResponseFlags(); + public long getResponseFlags() { return responseFlags; } + + /** + * The protocol for the upstream stream, if one was established, else -1 See + * https://github.com/envoyproxy/envoy/blob/main/envoy/http/protocol.h#L39 for values. + */ + public long getUpstreamProtocol() { return upstreamProtocol; } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + EnvoyFinalStreamIntel finalStreamIntel = (EnvoyFinalStreamIntel)object; + return streamStartMs == finalStreamIntel.streamStartMs && + dnsStartMs == finalStreamIntel.dnsStartMs && dnsEndMs == finalStreamIntel.dnsEndMs && + connectStartMs == finalStreamIntel.connectStartMs && + connectEndMs == finalStreamIntel.connectEndMs && + sslStartMs == finalStreamIntel.sslStartMs && sslEndMs == finalStreamIntel.sslEndMs && + sendingStartMs == finalStreamIntel.sendingStartMs && + sendingEndMs == finalStreamIntel.sendingEndMs && + responseStartMs == finalStreamIntel.responseStartMs && + streamEndMs == finalStreamIntel.streamEndMs && + socketReused == finalStreamIntel.socketReused && + sentByteCount == finalStreamIntel.sentByteCount && + receivedByteCount == finalStreamIntel.receivedByteCount && + responseFlags == finalStreamIntel.responseFlags && + upstreamProtocol == finalStreamIntel.upstreamProtocol; + } - /* The protocol for the upstream stream, if one was established, else -1 See - * https://github.com/envoyproxy/envoy/blob/main/envoy/http/protocol.h#L39 for values. */ - long getUpstreamProtocol(); + @Override + public int hashCode() { + return Objects.hash(streamStartMs, dnsStartMs, dnsEndMs, connectStartMs, connectEndMs, + sslStartMs, sslEndMs, sendingStartMs, sendingEndMs, responseStartMs, + streamEndMs, socketReused, sentByteCount, receivedByteCount, responseFlags, + upstreamProtocol); + } } diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyHTTPCallbacks.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyHTTPCallbacks.java index 890615aea700..5ebea6b49567 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyHTTPCallbacks.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyHTTPCallbacks.java @@ -1,14 +1,10 @@ package io.envoyproxy.envoymobile.engine.types; import java.nio.ByteBuffer; -import java.util.concurrent.Executor; import java.util.List; import java.util.Map; public interface EnvoyHTTPCallbacks { - - Executor getExecutor(); - /** * Called when all headers get received on the async HTTP stream. * diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel.java index a7eee04a1933..4b0f88f2f61d 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel.java @@ -1,24 +1,45 @@ package io.envoyproxy.envoymobile.engine.types; +import java.util.Objects; + /** * Exposes internal HTTP stream metrics, context, and other details. */ -public interface EnvoyStreamIntel { +public class EnvoyStreamIntel { + private final long streamId; + private final long connectionId; + private final long attemptCount; + private final long consumedBytesFromResponse; + + public EnvoyStreamIntel(long streamId, long connectionId, long attemptCount, + long consumedBytesFromResponse) { + this.streamId = streamId; + this.connectionId = connectionId; + this.attemptCount = attemptCount; + this.consumedBytesFromResponse = consumedBytesFromResponse; + } + + public EnvoyStreamIntel(long[] values) { + streamId = values[0]; + connectionId = values[1]; + attemptCount = values[2]; + consumedBytesFromResponse = values[3]; + } /** * An internal identifier for the stream. */ - long getStreamId(); + public long getStreamId() { return streamId; } /** * An internal identifier for the connection carrying the stream. */ - long getConnectionId(); + public long getConnectionId() { return connectionId; } /** * The number of internal attempts to carry out a request/operation. */ - long getAttemptCount(); + public long getAttemptCount() { return attemptCount; } /** * The number of bytes consumed by the non terminal callbacks, from the response. @@ -28,5 +49,24 @@ public interface EnvoyStreamIntel { * number of bytes received before decompression. getConsumedBytesFromResponse() omits the number * number of bytes related to the Status Line, and is after decompression. */ - long getConsumedBytesFromResponse(); + public long getConsumedBytesFromResponse() { return consumedBytesFromResponse; } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + EnvoyStreamIntel streamIntel = (EnvoyStreamIntel)object; + return streamId == streamIntel.streamId && connectionId == streamIntel.connectionId && + attemptCount == streamIntel.attemptCount && + consumedBytesFromResponse == streamIntel.consumedBytesFromResponse; + } + + @Override + public int hashCode() { + return Objects.hash(streamId, connectionId, attemptCount, consumedBytesFromResponse); + } } diff --git a/mobile/library/java/org/chromium/net/impl/CronvoyBidirectionalStream.java b/mobile/library/java/org/chromium/net/impl/CronvoyBidirectionalStream.java index 33b2a4a448bd..e7896a311498 100644 --- a/mobile/library/java/org/chromium/net/impl/CronvoyBidirectionalStream.java +++ b/mobile/library/java/org/chromium/net/impl/CronvoyBidirectionalStream.java @@ -116,7 +116,6 @@ public final class CronvoyBidirectionalStream private static final String X_ENVOY = "x-envoy"; private static final String X_ENVOY_SELECTED_TRANSPORT = "x-envoy-upstream-alpn"; private static final String USER_AGENT = "User-Agent"; - private static final Executor DIRECT_EXECUTOR = new DirectExecutor(); private final CronvoyUrlRequestContext mRequestContext; private final Executor mExecutor; @@ -936,11 +935,6 @@ private static boolean isValidHeaderName(String header) { return headers; } - @Override - public Executor getExecutor() { - return DIRECT_EXECUTOR; - } - @Override public void onSendWindowAvailable(EnvoyStreamIntel streamIntel) { switch (mState.nextAction(Event.ON_SEND_WINDOW_AVAILABLE)) { @@ -1112,11 +1106,4 @@ private static class ReadBuffer { this.mInitialLimit = mByteBuffer.limit(); } } - - private static class DirectExecutor implements Executor { - @Override - public void execute(Runnable runnable) { - runnable.run(); - } - } } diff --git a/mobile/library/java/org/chromium/net/impl/CronvoyUrlRequest.java b/mobile/library/java/org/chromium/net/impl/CronvoyUrlRequest.java index d14a5a07508c..b651a9ccae10 100644 --- a/mobile/library/java/org/chromium/net/impl/CronvoyUrlRequest.java +++ b/mobile/library/java/org/chromium/net/impl/CronvoyUrlRequest.java @@ -103,8 +103,6 @@ public final class CronvoyUrlRequest extends CronvoyUrlRequestBase { private static final String TAG = CronvoyUrlRequest.class.getSimpleName(); private static final String USER_AGENT = "User-Agent"; private static final String CONTENT_TYPE = "Content-Type"; - private static final Executor DIRECT_EXECUTOR = new DirectExecutor(); - private final String mUserAgent; private final HeadersList mRequestHeaders = new HeadersList(); private final Collection mRequestAnnotations; @@ -766,23 +764,11 @@ private static int determineNextState(boolean endStream, @State int original, private static class HeadersList extends ArrayList> {} - private static class DirectExecutor implements Executor { - @Override - public void execute(Runnable runnable) { - runnable.run(); - } - } - private class CronvoyHttpCallbacks implements EnvoyHTTPCallbacks { private final AtomicInteger mCancelState = new AtomicInteger(CancelState.READY); private volatile boolean mEndStream = false; // Accessed by different Threads - @Override - public Executor getExecutor() { - return DIRECT_EXECUTOR; - } - @Override public void onHeaders(Map> headers, boolean endStream, EnvoyStreamIntel streamIntel) { @@ -880,15 +866,19 @@ public void onData(ByteBuffer data, boolean endStream, EnvoyStreamIntel streamIn return; } + ByteBuffer userBuffer = mUserCurrentReadBuffer; + mUserCurrentReadBuffer = null; // Avoid the reference to a potentially large buffer. + int dataRead = data.remaining(); + // It is important to copy the `data` into the `userBuffer` outside the thread execution + // because the `data` is backed by a direct `ByteBuffer` and it will be destroyed once + // the `onData` completes. + userBuffer.put(data); // NPE ==> BUG, BufferOverflowException ==> User not behaving. Runnable task = new Runnable() { @Override public void run() { checkCallingThread(); try { - ByteBuffer userBuffer = mUserCurrentReadBuffer; - mUserCurrentReadBuffer = null; // Avoid the reference to a potentially large buffer. - int dataRead = data.remaining(); - userBuffer.put(data); // NPE ==> BUG, BufferOverflowException ==> User not behaving. + if (dataRead > 0 || !endStream) { mWaitingOnRead.set(true); mCallback.onReadCompleted(CronvoyUrlRequest.this, mUrlResponseInfo, userBuffer); diff --git a/mobile/library/jni/jni_helper.cc b/mobile/library/jni/jni_helper.cc index 0ce0040b051f..dbfe2c1d66c8 100644 --- a/mobile/library/jni/jni_helper.cc +++ b/mobile/library/jni/jni_helper.cc @@ -2,6 +2,8 @@ #include "source/common/common/assert.h" +#include "absl/strings/string_view.h" + namespace Envoy { namespace JNI { namespace { @@ -10,6 +12,7 @@ constexpr jint JNI_VERSION = JNI_VERSION_1_6; constexpr const char* THREAD_NAME = "EnvoyMain"; std::atomic java_vm_cache_; thread_local JNIEnv* jni_env_cache_ = nullptr; +absl::flat_hash_map JCLASS_CACHES; } // namespace @@ -19,6 +22,14 @@ void JniHelper::initialize(JavaVM* java_vm) { java_vm_cache_.store(java_vm, std::memory_order_release); } +void JniHelper::addClassToCache(const char* class_name) { + JNIEnv* env; + jint result = getJavaVm()->GetEnv(reinterpret_cast(&env), getVersion()); + ASSERT(result == JNI_OK, "Unable to get JNIEnv from the JavaVM."); + jclass java_class = reinterpret_cast(env->NewGlobalRef(env->FindClass(class_name))); + JCLASS_CACHES.emplace(class_name, java_class); +} + JavaVM* JniHelper::getJavaVm() { return java_vm_cache_.load(std::memory_order_acquire); } void JniHelper::detachCurrentThread() { @@ -84,6 +95,14 @@ LocalRefUniquePtr JniHelper::findClass(const char* class_name) { return result; } +jclass JniHelper::findClassFromCache(const char* class_name) { + if (auto i = JCLASS_CACHES.find(class_name); i != JCLASS_CACHES.end()) { + return i->second; + } + ASSERT(false, absl::StrFormat("Unable to find class '%s'.", class_name)); + return nullptr; +} + LocalRefUniquePtr JniHelper::getObjectClass(jobject object) { return {env_->GetObjectClass(object), LocalRefDeleter(env_)}; } diff --git a/mobile/library/jni/jni_helper.h b/mobile/library/jni/jni_helper.h index e70074c5ccae..8fa22b2661a9 100644 --- a/mobile/library/jni/jni_helper.h +++ b/mobile/library/jni/jni_helper.h @@ -141,9 +141,27 @@ class JniHelper { /** Gets the JNI version supported. */ static jint getVersion(); - /** Initializes the `JavaVM`. This is typically set in `JNI_OnLoad`. */ + /** Initializes the `JavaVM`. This function is typically called inside `JNI_OnLoad`. */ static void initialize(JavaVM* java_vm); + /** + * Adds the `jclass` object into a cache. This function is typically called inside `JNI_OnLoad`. + * + * Caching the `jclass` can be useful for performance. + * See https://developer.android.com/training/articles/perf-jni#jclass,-jmethodid,-and-jfieldid + * + * Another reason for caching the `jclass` object is to able to find a non-built-in class when the + * native code creates a thread and then attaches it with `AttachCurrentThread`, i.e. calling + * `getThreadLocalEnv()->getEnv()->FindClass`. This is because there are no stack frames from the + * application. When calling `FindClass` from the thread, the `JavaVM` will start in the "system" + * class loader instead of the one associated with the application, so attempts to find + * app-specific classes will fail. + * + * See + * https://developer.android.com/training/articles/perf-jni#faq:-why-didnt-findclass-find-my-class + */ + static void addClassToCache(const char* class_name); + /** Gets the `JavaVM`. The `initialize(JavaVM*) must be called first. */ static JavaVM* getJavaVm(); @@ -218,6 +236,13 @@ class JniHelper { */ [[nodiscard]] LocalRefUniquePtr findClass(const char* class_name); + /** + * Finds the given `class_name` from the cache. + * + * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#findclass + */ + [[nodiscard]] jclass findClassFromCache(const char* class_name); + /** * Returns the class of a given `object`. * diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 9a499ddfd70f..10752af2a148 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -21,6 +21,9 @@ using Envoy::Platform::EngineBuilder; extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); + Envoy::JNI::JniHelper::addClassToCache( + "io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); return Envoy::JNI::JniHelper::getVersion(); } @@ -247,12 +250,6 @@ jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& head return noopResult; } -static void jvm_on_response_headers(envoy_headers headers, bool end_stream, - envoy_stream_intel stream_intel, void* context) { - const auto managed_headers = Envoy::Types::ManagedEnvoyHeaders(headers); - jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context); -} - static envoy_filter_headers_status jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream, envoy_stream_intel stream_intel, const void* context) { @@ -328,11 +325,6 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_data(const char* metho return result; } -static void jvm_on_response_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, - void* context) { - jvm_on_data("onResponseData", data, end_stream, stream_intel, context); -} - static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, const void* context) { @@ -424,11 +416,6 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* m return result; } -static void jvm_on_response_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, - void* context) { - jvm_on_trailers("onResponseTrailers", trailers, stream_intel, context); -} - static envoy_filter_trailers_status jvm_http_filter_on_request_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, const void* context) { @@ -619,24 +606,6 @@ jvm_http_filter_on_resume_response(envoy_headers* headers, envoy_data* data, stream_intel, context); } -static void call_jvm_on_complete(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); - jobject j_context = static_cast(context); - - Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = - jni_helper.getObjectClass(j_context); - jmethodID jmid_onComplete = jni_helper.getMethodId(jcls_JvmObserverContext.get(), "onComplete", - "([J[J)Ljava/lang/Object;"); - - Envoy::JNI::LocalRefUniquePtr j_stream_intel = - Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); - Envoy::JNI::LocalRefUniquePtr j_final_stream_intel = - Envoy::JNI::envoyFinalStreamIntelToJavaLongArray(jni_helper, final_stream_intel); - Envoy::JNI::LocalRefUniquePtr unused = jni_helper.callObjectMethod( - j_context, jmid_onComplete, j_stream_intel.get(), j_final_stream_intel.get()); -} - static void call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); @@ -661,12 +630,6 @@ static void call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel release_envoy_error(error); } -static void jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - call_jvm_on_error(error, stream_intel, final_stream_intel, context); - Envoy::JNI::jniDeleteGlobalRef(context); -} - static void call_jvm_on_cancel(envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); @@ -686,18 +649,6 @@ static void call_jvm_on_cancel(envoy_stream_intel stream_intel, j_context, jmid_onCancel, j_stream_intel.get(), j_final_stream_intel.get()); } -static void jvm_on_complete(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - call_jvm_on_complete(stream_intel, final_stream_intel, context); - Envoy::JNI::jniDeleteGlobalRef(context); -} - -static void jvm_on_cancel(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - call_jvm_on_cancel(stream_intel, final_stream_intel, context); - Envoy::JNI::jniDeleteGlobalRef(context); -} - static void jvm_http_filter_on_error(envoy_error error, envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, const void* context) { @@ -710,22 +661,6 @@ static void jvm_http_filter_on_cancel(envoy_stream_intel stream_intel, call_jvm_on_cancel(stream_intel, final_stream_intel, const_cast(context)); } -static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); - jobject j_context = static_cast(context); - - Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = - jni_helper.getObjectClass(j_context); - jmethodID jmid_onSendWindowAvailable = jni_helper.getMethodId( - jcls_JvmObserverContext.get(), "onSendWindowAvailable", "([J)Ljava/lang/Object;"); - - Envoy::JNI::LocalRefUniquePtr j_stream_intel = - Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); - - Envoy::JNI::LocalRefUniquePtr unused = - jni_helper.callObjectMethod(j_context, jmid_onSendWindowAvailable, j_stream_intel.get()); -} - // JvmKeyValueStoreContext static envoy_data jvm_kv_store_read(envoy_data key, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); @@ -828,46 +763,113 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra auto java_stream_callbacks_global_ref = jni_helper.newGlobalRef(java_stream_callbacks).release(); auto engine = reinterpret_cast(engine_handle); Envoy::EnvoyStreamCallbacks stream_callbacks; - // TODO(fredyw): Rewrite the whole implementation to not use the bridge data structures. - stream_callbacks.on_headers_ = - [java_stream_callbacks_global_ref](const Envoy::Http::ResponseHeaderMap& headers, - bool end_stream, envoy_stream_intel stream_intel) { - envoy_headers bridge_headers = Envoy::Http::Utility::toBridgeHeaders(headers); - jvm_on_response_headers(bridge_headers, end_stream, stream_intel, - java_stream_callbacks_global_ref); - }; + stream_callbacks.on_headers_ = [java_stream_callbacks_global_ref]( + const Envoy::Http::ResponseHeaderMap& headers, bool end_stream, + envoy_stream_intel stream_intel) { + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_headers = Envoy::JNI::cppHeadersToJavaHeaders(jni_helper, headers); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_headers_method_id = jni_helper.getMethodId( + java_stream_callbacks_class.get(), "onHeaders", + "(Ljava/util/Map;ZLio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_headers_method_id, + java_headers.get(), static_cast(end_stream), + java_stream_intel.get()); + }; stream_callbacks.on_data_ = [java_stream_callbacks_global_ref]( const Envoy::Buffer::Instance& buffer, uint64_t length, bool end_stream, envoy_stream_intel stream_intel) { - envoy_data bridge_data = Envoy::Bridge::Utility::toBridgeDataNoDrain(buffer, length); - jvm_on_response_data(bridge_data, end_stream, stream_intel, java_stream_callbacks_global_ref); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_byte_buffer = + Envoy::JNI::cppBufferInstanceToJavaDirectByteBuffer(jni_helper, buffer, length); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_on_data_method_id = jni_helper.getMethodId( + java_stream_callbacks_class.get(), "onData", + "(Ljava/nio/ByteBuffer;ZLio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_data_method_id, + java_byte_buffer.get(), static_cast(end_stream), + java_stream_intel.get()); + }; + stream_callbacks.on_trailers_ = [java_stream_callbacks_global_ref]( + const Envoy::Http::ResponseTrailerMap& trailers, + envoy_stream_intel stream_intel) { + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_trailers = Envoy::JNI::cppHeadersToJavaHeaders(jni_helper, trailers); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_trailers_method_id = jni_helper.getMethodId( + java_stream_callbacks_class.get(), "onTrailers", + "(Ljava/util/Map;Lio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_trailers_method_id, + java_trailers.get(), java_stream_intel.get()); + }; + stream_callbacks.on_complete_ = [java_stream_callbacks_global_ref]( + envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel) { + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_final_stream_intel = + Envoy::JNI::cppFinalStreamIntelToJavaFinalStreamIntel(jni_helper, final_stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_complete_method_id = + jni_helper.getMethodId(java_stream_callbacks_class.get(), "onComplete", + "(Lio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;" + "Lio/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_complete_method_id, + java_stream_intel.get(), java_final_stream_intel.get()); + // on_complete_ is a terminal callback, delete the java_stream_callbacks_global_ref. + jni_helper.getEnv()->DeleteGlobalRef(java_stream_callbacks_global_ref); }; - stream_callbacks.on_trailers_ = - [java_stream_callbacks_global_ref](const Envoy::Http::ResponseTrailerMap& trailers, - envoy_stream_intel stream_intel) { - envoy_headers bridge_trailers = Envoy::Http::Utility::toBridgeHeaders(trailers); - jvm_on_response_trailers(bridge_trailers, stream_intel, java_stream_callbacks_global_ref); - }; - stream_callbacks.on_complete_ = - [java_stream_callbacks_global_ref](envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel) { - jvm_on_complete(stream_intel, final_stream_intel, java_stream_callbacks_global_ref); - }; stream_callbacks.on_error_ = [java_stream_callbacks_global_ref]( Envoy::EnvoyError error, envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel) { - envoy_error bridge_error = Envoy::Bridge::Utility::toBridgeError(error); - jvm_on_error(bridge_error, stream_intel, final_stream_intel, java_stream_callbacks_global_ref); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_final_stream_intel = + Envoy::JNI::cppFinalStreamIntelToJavaFinalStreamIntel(jni_helper, final_stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_error_method_id = jni_helper.getMethodId( + java_stream_callbacks_class.get(), "onError", + "(ILjava/lang/String;ILio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;" + "Lio/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel;)V"); + auto java_error_message = Envoy::JNI::cppStringToJavaString(jni_helper, error.message); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_error_method_id, + static_cast(error.error_code), java_error_message.get(), + error.attempt_count.value_or(-1), java_stream_intel.get(), + java_final_stream_intel.get()); + // on_error_ is a terminal callback, delete the java_stream_callbacks_global_ref. + jni_helper.getEnv()->DeleteGlobalRef(java_stream_callbacks_global_ref); + }; + stream_callbacks.on_cancel_ = [java_stream_callbacks_global_ref]( + envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel) { + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_final_stream_intel = + Envoy::JNI::cppFinalStreamIntelToJavaFinalStreamIntel(jni_helper, final_stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_cancel_method_id = + jni_helper.getMethodId(java_stream_callbacks_class.get(), "onCancel", + "(Lio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;" + "Lio/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, java_on_cancel_method_id, + java_stream_intel.get(), java_final_stream_intel.get()); + // on_cancel_ is a terminal callback, delete the java_stream_callbacks_global_ref. + jni_helper.getEnv()->DeleteGlobalRef(java_stream_callbacks_global_ref); + }; + stream_callbacks.on_send_window_available_ = [java_stream_callbacks_global_ref]( + envoy_stream_intel stream_intel) { + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); + auto java_stream_intel = Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, stream_intel); + auto java_stream_callbacks_class = jni_helper.getObjectClass(java_stream_callbacks_global_ref); + auto java_on_send_window_available_method_id = + jni_helper.getMethodId(java_stream_callbacks_class.get(), "onSendWindowAvailable", + "(Lio/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel;)V"); + jni_helper.callVoidMethod(java_stream_callbacks_global_ref, + java_on_send_window_available_method_id, java_stream_intel.get()); }; - stream_callbacks.on_cancel_ = - [java_stream_callbacks_global_ref](envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel) { - jvm_on_cancel(stream_intel, final_stream_intel, java_stream_callbacks_global_ref); - }; - stream_callbacks.on_send_window_available_ = - [java_stream_callbacks_global_ref](envoy_stream_intel stream_intel) { - jvm_on_send_window_available(stream_intel, java_stream_callbacks_global_ref); - }; envoy_status_t result = engine->startStream(static_cast(stream_handle), std::move(stream_callbacks), explicit_flow_control); diff --git a/mobile/library/jni/jni_utility.cc b/mobile/library/jni/jni_utility.cc index 79757a30c026..80d16aa87ffe 100644 --- a/mobile/library/jni/jni_utility.cc +++ b/mobile/library/jni/jni_utility.cc @@ -402,7 +402,8 @@ absl::flat_hash_map javaMapToCppMap(JniHelper& jni_hel LocalRefUniquePtr cppHeadersToJavaHeaders(JniHelper& jni_helper, const Http::HeaderMap& cpp_headers) { - auto java_map_class = jni_helper.findClass("java/util/HashMap"); + // Use LinkedHashMap to preserve the insertion order. + auto java_map_class = jni_helper.findClass("java/util/LinkedHashMap"); auto java_map_init_method_id = jni_helper.getMethodId(java_map_class.get(), "", "()V"); auto java_map_put_method_id = jni_helper.getMethodId( java_map_class.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); @@ -511,8 +512,7 @@ bool isJavaDirectByteBuffer(JniHelper& jni_helper, jobject java_byte_buffer) { Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, jobject java_byte_buffer, jlong length) { - RELEASE_ASSERT(java_byte_buffer != nullptr, - "The ByteBuffer argument is not a direct ByteBuffer."); + ASSERT(java_byte_buffer != nullptr, "The ByteBuffer argument is not a direct ByteBuffer."); // Because the direct ByteBuffer is allocated in the JVM, we need to tell the JVM to not garbage // collect it by wrapping with a GlobalRef. auto java_byte_buffer_global_ref = jni_helper.newGlobalRef(java_byte_buffer).release(); @@ -529,13 +529,12 @@ Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helpe return cpp_buffer_instance; } -LocalRefUniquePtr -cppBufferInstanceToJavaDirectByteBuffer(JniHelper& jni_helper, - const Buffer::Instance& cpp_buffer_instance) { - // The JNI implementation guarantees that there is only going to be a single slice. - Buffer::RawSlice raw_slice = cpp_buffer_instance.frontSlice(); +LocalRefUniquePtr cppBufferInstanceToJavaDirectByteBuffer( + JniHelper& jni_helper, const Buffer::Instance& cpp_buffer_instance, uint64_t length) { + void* data = + const_cast(cpp_buffer_instance).linearize(static_cast(length)); LocalRefUniquePtr java_byte_buffer = - jni_helper.newDirectByteBuffer(raw_slice.mem_, static_cast(raw_slice.len_)); + jni_helper.newDirectByteBuffer(data, static_cast(length)); return java_byte_buffer; } @@ -547,8 +546,7 @@ Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_he jni_helper.getMethodId(java_byte_buffer_class.get(), "array", "()[B"); auto java_byte_array = jni_helper.callObjectMethod(java_byte_buffer, java_byte_buffer_array_method_id); - RELEASE_ASSERT(java_byte_array != nullptr, - "The ByteBuffer argument is not a non-direct ByteBuffer."); + ASSERT(java_byte_array != nullptr, "The ByteBuffer argument is not a non-direct ByteBuffer."); auto java_byte_array_elements = jni_helper.getByteArrayElements(java_byte_array.get(), nullptr); Buffer::InstancePtr cpp_buffer_instance = std::make_unique(); cpp_buffer_instance->add(static_cast(java_byte_array_elements.get()), @@ -556,16 +554,14 @@ Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_he return cpp_buffer_instance; } -LocalRefUniquePtr -cppBufferInstanceToJavaNonDirectByteBuffer(JniHelper& jni_helper, - const Buffer::Instance& cpp_buffer_instance) { +LocalRefUniquePtr cppBufferInstanceToJavaNonDirectByteBuffer( + JniHelper& jni_helper, const Buffer::Instance& cpp_buffer_instance, uint64_t length) { auto java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); auto java_byte_buffer_wrap_method_id = jni_helper.getStaticMethodId( java_byte_buffer_class.get(), "wrap", "([B)Ljava/nio/ByteBuffer;"); auto java_byte_array = jni_helper.newByteArray(static_cast(cpp_buffer_instance.length())); auto java_byte_array_elements = jni_helper.getByteArrayElements(java_byte_array.get(), nullptr); - cpp_buffer_instance.copyOut(0, cpp_buffer_instance.length(), - static_cast(java_byte_array_elements.get())); + cpp_buffer_instance.copyOut(0, length, static_cast(java_byte_array_elements.get())); return jni_helper.callStaticObjectMethod(java_byte_buffer_class.get(), java_byte_buffer_wrap_method_id, java_byte_array.get()); } @@ -579,5 +575,140 @@ std::string getJavaExceptionMessage(JniHelper& jni_helper, jthrowable throwable) return javaStringToCppString(jni_helper, java_exception_message.get()); } +envoy_stream_intel javaStreamIntelToCppStreamIntel(JniHelper& jni_helper, + jobject java_stream_intel) { + auto java_stream_intel_class = jni_helper.getObjectClass(java_stream_intel); + jlong java_stream_id = jni_helper.callLongMethod( + java_stream_intel, + jni_helper.getMethodId(java_stream_intel_class.get(), "getStreamId", "()J")); + jlong java_connection_id = jni_helper.callLongMethod( + java_stream_intel, + jni_helper.getMethodId(java_stream_intel_class.get(), "getConnectionId", "()J")); + jlong java_attempt_count = jni_helper.callLongMethod( + java_stream_intel, + jni_helper.getMethodId(java_stream_intel_class.get(), "getAttemptCount", "()J")); + jlong java_consumed_bytes_from_response = jni_helper.callLongMethod( + java_stream_intel, + jni_helper.getMethodId(java_stream_intel_class.get(), "getConsumedBytesFromResponse", "()J")); + + return { + /* stream_id= */ static_cast(java_stream_id), + /* connection_id= */ static_cast(java_connection_id), + /* attempt_count= */ static_cast(java_attempt_count), + /* consumed_bytes_from_response= */ static_cast(java_consumed_bytes_from_response), + }; +} + +LocalRefUniquePtr cppStreamIntelToJavaStreamIntel(JniHelper& jni_helper, + const envoy_stream_intel& stream_intel) { + auto java_stream_intel_class = + jni_helper.findClassFromCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); + auto java_stream_intel_init_method_id = + jni_helper.getMethodId(java_stream_intel_class, "", "(JJJJ)V"); + return jni_helper.newObject(java_stream_intel_class, java_stream_intel_init_method_id, + static_cast(stream_intel.stream_id), + static_cast(stream_intel.connection_id), + static_cast(stream_intel.attempt_count), + static_cast(stream_intel.consumed_bytes_from_response)); +} + +envoy_final_stream_intel +javaFinalStreamIntelToCppFinalStreamIntel(JniHelper& jni_helper, jobject java_final_stream_intel) { + auto java_final_stream_intel_class = jni_helper.getObjectClass(java_final_stream_intel); + jlong java_stream_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getStreamStartMs", "()J")); + jlong java_dns_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getDnsStartMs", "()J")); + jlong java_dns_end_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getDnsEndMs", "()J")); + jlong java_connect_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getConnectStartMs", "()J")); + jlong java_connect_end_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getConnectEndMs", "()J")); + jlong java_ssl_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSslStartMs", "()J")); + jlong java_ssl_end_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSslEndMs", "()J")); + jlong java_sending_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSendingStartMs", "()J")); + jlong java_sending_end_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSendingEndMs", "()J")); + jlong java_response_start_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getResponseStartMs", "()J")); + jlong java_stream_end_ms = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getStreamEndMs", "()J")); + jboolean java_socket_reused = jni_helper.callBooleanMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSocketReused", "()Z")); + jlong java_sent_byte_count = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getSentByteCount", "()J")); + jlong java_received_byte_count = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getReceivedByteCount", "()J")); + jlong java_response_flags = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getResponseFlags", "()J")); + jlong java_upstream_protocol = jni_helper.callLongMethod( + java_final_stream_intel, + jni_helper.getMethodId(java_final_stream_intel_class.get(), "getUpstreamProtocol", "()J")); + + return { + /* stream_start_ms= */ static_cast(java_stream_start_ms), + /* dns_start_ms= */ static_cast(java_dns_start_ms), + /* dns_end_ms= */ static_cast(java_dns_end_ms), + /* connect_start_ms= */ static_cast(java_connect_start_ms), + /* connect_end_ms= */ static_cast(java_connect_end_ms), + /* ssl_start_ms= */ static_cast(java_ssl_start_ms), + /* ssl_end_ms= */ static_cast(java_ssl_end_ms), + /* sending_start_ms= */ static_cast(java_sending_start_ms), + /* sending_end_ms= */ static_cast(java_sending_end_ms), + /* response_start_ms= */ static_cast(java_response_start_ms), + /* stream_end_ms= */ static_cast(java_stream_end_ms), + /* socket_reused= */ static_cast((java_socket_reused == JNI_TRUE) ? 1 : 0), + /* sent_byte_count= */ static_cast(java_sent_byte_count), + /* received_byte_count= */ static_cast(java_received_byte_count), + /* response_flags= */ static_cast(java_response_flags), + /* upstream_protocol= */ static_cast(java_upstream_protocol), + }; +} + +LocalRefUniquePtr +cppFinalStreamIntelToJavaFinalStreamIntel(JniHelper& jni_helper, + const envoy_final_stream_intel& final_stream_intel) { + auto java_final_stream_intel_class = + jni_helper.findClassFromCache("io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); + auto java_final_stream_intel_init_method_id = + jni_helper.getMethodId(java_final_stream_intel_class, "", "(JJJJJJJJJJJZJJJJ)V"); + return jni_helper.newObject(java_final_stream_intel_class, java_final_stream_intel_init_method_id, + static_cast(final_stream_intel.stream_start_ms), + static_cast(final_stream_intel.dns_start_ms), + static_cast(final_stream_intel.dns_end_ms), + static_cast(final_stream_intel.connect_start_ms), + static_cast(final_stream_intel.connect_end_ms), + static_cast(final_stream_intel.ssl_start_ms), + static_cast(final_stream_intel.ssl_end_ms), + static_cast(final_stream_intel.sending_start_ms), + static_cast(final_stream_intel.sending_end_ms), + static_cast(final_stream_intel.response_start_ms), + static_cast(final_stream_intel.stream_end_ms), + static_cast(final_stream_intel.socket_reused), + static_cast(final_stream_intel.sent_byte_count), + static_cast(final_stream_intel.received_byte_count), + static_cast(final_stream_intel.response_flags), + static_cast(final_stream_intel.upstream_protocol)); +} + } // namespace JNI } // namespace Envoy diff --git a/mobile/library/jni/jni_utility.h b/mobile/library/jni/jni_utility.h index 9694591d52b8..0643562eaa32 100644 --- a/mobile/library/jni/jni_utility.h +++ b/mobile/library/jni/jni_utility.h @@ -190,13 +190,13 @@ Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helpe jobject java_byte_buffer, jlong length); /** - * Converts from `Envoy::Buffer::Instance` to Java direct `ByteBuffer` (off the JVM heap). + * Converts from `Envoy::Buffer::Instance` to Java direct `ByteBuffer` (off the JVM heap) up to the + * specified length. * * The function will avoid copying the data from `Envoy::Buffer::Instance` into the `ByteBuffer`. */ -LocalRefUniquePtr -cppBufferInstanceToJavaDirectByteBuffer(JniHelper& jni_helper, - const Buffer::Instance& cpp_buffer_instance); +LocalRefUniquePtr cppBufferInstanceToJavaDirectByteBuffer( + JniHelper& jni_helper, const Buffer::Instance& cpp_buffer_instance, uint64_t length); /** * Converts from Java non-direct `ByteBuffer` (on the JVM heap) to `Envoy::Buffer::Instance` up @@ -209,16 +209,45 @@ Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_he jlong length); /** - * Converts from `Envoy::Buffer::Instance` to Java non-direct `ByteBuffer` (off the JVM heap). + * Converts from `Envoy::Buffer::Instance` to Java non-direct `ByteBuffer` (off the JVM heap) up to + * the specified length. * * The function will copy the data from `Envoy::Buffer::Instance` into the `ByteBuffer`. */ -LocalRefUniquePtr -cppBufferInstanceToJavaNonDirectByteBuffer(JniHelper& jni_helper, - const Buffer::Instance& cpp_buffer_instance); +LocalRefUniquePtr cppBufferInstanceToJavaNonDirectByteBuffer( + JniHelper& jni_helper, const Buffer::Instance& cpp_buffer_instance, uint64_t length); /** Gets the Java exception message from the `throwable`. */ std::string getJavaExceptionMessage(JniHelper& jni_helper, jthrowable throwable); +/** + * Converts from Java `io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel` to C++ + * `envoy_stream_intel`. + */ +envoy_stream_intel javaStreamIntelToCppStreamIntel(JniHelper& jni_helper, + jobject java_stream_intel); + +/** + * Converts from C++ `envoy_stream_intel` to Java to + * `io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel`. + */ +LocalRefUniquePtr cppStreamIntelToJavaStreamIntel(JniHelper& jni_helper, + const envoy_stream_intel& stream_intel); + +/** + * Converts from Java `io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel` to C++ + * `envoy_final_stream_intel`. + */ +envoy_final_stream_intel javaFinalStreamIntelToCppFinalStreamIntel(JniHelper& jni_helper, + jobject java_final_stream_intel); + +/** + * Converts from C++ `envoy_final_stream_intel` to Java to + * `io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel`. + */ +LocalRefUniquePtr +cppFinalStreamIntelToJavaFinalStreamIntel(JniHelper& jni_helper, + const envoy_final_stream_intel& final_stream_intel); + } // namespace JNI } // namespace Envoy diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt index e5668eb28b49..0a965a10b8af 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamCallbacks.kt @@ -1,5 +1,6 @@ package io.envoyproxy.envoymobile +import io.envoyproxy.envoymobile.engine.ByteBuffers import io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel import io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel @@ -29,27 +30,46 @@ internal class StreamCallbacks { * `EnvoyHTTPCallbacks`. */ internal class EnvoyHTTPCallbacksAdapter( - private val executor: Executor, + private val executor: Executor?, private val callbacks: StreamCallbacks ) : EnvoyHTTPCallbacks { - override fun getExecutor(): Executor { - return executor - } - override fun onHeaders( headers: Map>, endStream: Boolean, streamIntel: EnvoyStreamIntel ) { - callbacks.onHeaders?.invoke(ResponseHeaders(headers), endStream, StreamIntel(streamIntel)) + if (executor != null) { + executor.execute { + callbacks.onHeaders?.invoke(ResponseHeaders(headers), endStream, StreamIntel(streamIntel)) + } + } else { + callbacks.onHeaders?.invoke(ResponseHeaders(headers), endStream, StreamIntel(streamIntel)) + } } override fun onData(byteBuffer: ByteBuffer, endStream: Boolean, streamIntel: EnvoyStreamIntel) { - callbacks.onData?.invoke(byteBuffer, endStream, StreamIntel(streamIntel)) + if (executor != null) { + // The `ByteBuffer` passed into `onData` is a direct `ByteBuffer` managed by the native code + // and it will be destroyed upon completing the `onData` call. We need to copy the + // `ByteBuffer` when executing the `onData` in a separate thread to avoid a dangling + // reference. + val copiedBuffer = ByteBuffers.copy(byteBuffer) + executor.execute { + callbacks.onData?.invoke(copiedBuffer, endStream, StreamIntel(streamIntel)) + } + } else { + callbacks.onData?.invoke(byteBuffer, endStream, StreamIntel(streamIntel)) + } } override fun onTrailers(trailers: Map>, streamIntel: EnvoyStreamIntel) { - callbacks.onTrailers?.invoke(ResponseTrailers((trailers)), StreamIntel(streamIntel)) + if (executor != null) { + executor.execute { + callbacks.onTrailers?.invoke(ResponseTrailers((trailers)), StreamIntel(streamIntel)) + } + } else { + callbacks.onTrailers?.invoke(ResponseTrailers((trailers)), StreamIntel(streamIntel)) + } } override fun onError( @@ -59,21 +79,46 @@ internal class EnvoyHTTPCallbacksAdapter( streamIntel: EnvoyStreamIntel, finalStreamIntel: EnvoyFinalStreamIntel ) { - callbacks.onError?.invoke( - EnvoyError(errorCode, message, attemptCount), - FinalStreamIntel(streamIntel, finalStreamIntel) - ) + if (executor != null) { + executor.execute { + callbacks.onError?.invoke( + EnvoyError(errorCode, message, attemptCount), + FinalStreamIntel(streamIntel, finalStreamIntel) + ) + } + } else { + callbacks.onError?.invoke( + EnvoyError(errorCode, message, attemptCount), + FinalStreamIntel(streamIntel, finalStreamIntel) + ) + } } override fun onCancel(streamIntel: EnvoyStreamIntel, finalStreamIntel: EnvoyFinalStreamIntel) { - callbacks.onCancel?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + if (executor != null) { + executor.execute { + callbacks.onCancel?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + } + } else { + callbacks.onCancel?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + } } override fun onSendWindowAvailable(streamIntel: EnvoyStreamIntel) { - callbacks.onSendWindowAvailable?.invoke(StreamIntel(streamIntel)) + if (executor != null) { + executor.execute { callbacks.onSendWindowAvailable?.invoke(StreamIntel(streamIntel)) } + } else { + callbacks.onSendWindowAvailable?.invoke(StreamIntel(streamIntel)) + } } override fun onComplete(streamIntel: EnvoyStreamIntel, finalStreamIntel: EnvoyFinalStreamIntel) { - callbacks.onComplete?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + if (executor != null) { + executor.execute { + callbacks.onComplete?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + } + } else { + callbacks.onComplete?.invoke(FinalStreamIntel(streamIntel, finalStreamIntel)) + } } } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt index 887c21cede41..df379cb116d8 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/StreamPrototype.kt @@ -3,7 +3,6 @@ package io.envoyproxy.envoymobile import io.envoyproxy.envoymobile.engine.EnvoyEngine import java.nio.ByteBuffer import java.util.concurrent.Executor -import java.util.concurrent.Executors /** * A type representing a stream that has not yet been started. @@ -24,7 +23,7 @@ open class StreamPrototype(private val engine: EnvoyEngine) { * @param executor Executor on which to receive callback events. * @return The new stream. */ - open fun start(executor: Executor = Executors.newSingleThreadExecutor()): Stream { + open fun start(executor: Executor? = null): Stream { val engineStream = engine.startStream(createCallbacks(executor), explicitFlowControl) return Stream(engineStream, useByteBufferPosition) } @@ -157,7 +156,7 @@ open class StreamPrototype(private val engine: EnvoyEngine) { * @param executor Executor on which to receive callback events. * @return A new set of engine callbacks. */ - internal fun createCallbacks(executor: Executor): EnvoyHTTPCallbacksAdapter { + internal fun createCallbacks(executor: Executor?): EnvoyHTTPCallbacksAdapter { return EnvoyHTTPCallbacksAdapter(executor, callbacks) } } diff --git a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt index cb8f94390304..e3c0fcecc19f 100644 --- a/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt +++ b/mobile/library/kotlin/io/envoyproxy/envoymobile/grpc/GRPCStreamPrototype.kt @@ -4,7 +4,6 @@ import java.io.ByteArrayOutputStream import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.concurrent.Executor -import java.util.concurrent.Executors /** * A type representing a gRPC stream that has not yet been started. @@ -19,7 +18,7 @@ class GRPCStreamPrototype(private val underlyingStream: StreamPrototype) { * @param executor Executor on which to receive callback events. * @return The new gRPC stream. */ - fun start(executor: Executor = Executors.newSingleThreadExecutor()): GRPCStream { + fun start(executor: Executor? = null): GRPCStream { val stream = underlyingStream.start(executor) return GRPCStream(stream) } diff --git a/mobile/library/proguard.txt b/mobile/library/proguard.txt index 8ab6ef8decf3..21ac86a15691 100644 --- a/mobile/library/proguard.txt +++ b/mobile/library/proguard.txt @@ -35,10 +35,6 @@ ; } --keep, includedescriptorclasses class io.envoyproxy.envoymobile.engine.JvmCallbackContext { - ; -} - -keep, includedescriptorclasses class io.envoyproxy.envoymobile.engine.JvmFilterContext { ; } @@ -58,3 +54,15 @@ -keep class io.envoyproxy.envoymobile.engine.types.EnvoyLogger { ; } + +-keep class io.envoyproxy.envoymobile.engine.types.EnvoyHTTPCallbacks { + ; +} + +-keep class io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel { + ; +} + +-keep class io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel { + ; +} diff --git a/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD b/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD index c88259eb6cd7..00d488778572 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD +++ b/mobile/test/java/io/envoyproxy/envoymobile/jni/BUILD @@ -26,6 +26,7 @@ envoy_mobile_android_test( ], native_lib_name = "envoy_jni_utility_test", deps = [ + "//library/java/io/envoyproxy/envoymobile/engine/types:envoy_c_types_lib", "@maven//:com_google_protobuf_protobuf_javalite", ], ) diff --git a/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java index 696f95c74531..e99ebfe24ba3 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java +++ b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java @@ -14,6 +14,9 @@ import java.util.List; import java.util.Map; +import io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel; +import io.envoyproxy.envoymobile.engine.types.EnvoyStreamIntel; + @RunWith(RobolectricTestRunner.class) public class JniUtilityTest { public JniUtilityTest() { System.loadLibrary("envoy_jni_utility_test"); } @@ -32,6 +35,9 @@ public static native ByteBuffer javaCppDirectByteBufferConversion(ByteBuffer byt public static native ByteBuffer javaCppNonDirectByteBufferConversion(ByteBuffer byteBuffer, long length); public static native String getJavaExceptionMessage(Throwable throwable); + public static native EnvoyStreamIntel javaCppStreamIntelConversion(EnvoyStreamIntel streamIntel); + public static native EnvoyFinalStreamIntel + javaCppFinalStreamIntelConversion(EnvoyFinalStreamIntel finalStreamIntel); @Test public void testProtoJavaByteArrayConversion() throws Exception { @@ -150,4 +156,36 @@ public void testGetJavaExceptionMessage() { assertThat(getJavaExceptionMessage(new RuntimeException("Test exception"))) .isEqualTo("Test exception"); } + + @Test + public void testJavaCppStreamIntelConversion() { + EnvoyStreamIntel streamIntel = new EnvoyStreamIntel( + /* streamId= */ 1, + /* connectionId= */ 2, + /* attemptCount= */ 3, + /* consumedBytesFromResponse= */ 4); + assertThat(javaCppStreamIntelConversion(streamIntel)).isEqualTo(streamIntel); + } + + @Test + public void testJavaCppFinalStreamIntelConversion() { + EnvoyFinalStreamIntel finalStreamIntel = new EnvoyFinalStreamIntel( + /* streamStartMs= */ 1, + /* dnsStartMs= */ 2, + /* dnsEndMs= */ 3, + /* connectStartMs= */ 4, + /* connectEndMs= */ 5, + /* sslStartMs= */ 6, + /* sslEndMs= */ 7, + /* sendingStartMs= */ 8, + /* sendingEndMs= */ 9, + /* responseStartMs= */ 10, + /* streamEndMs= */ 11, + /* socketReused= */ true, + /* sentByteCount= */ 13, + /* receivedByteCount= */ 14, + /* responseFlags= */ 15, + /* upstreamProtocol= */ 16); + assertThat(javaCppFinalStreamIntelConversion(finalStreamIntel)).isEqualTo(finalStreamIntel); + } } diff --git a/mobile/test/java/org/chromium/net/impl/BUILD b/mobile/test/java/org/chromium/net/impl/BUILD index 08b28b4ad877..8bde99407885 100644 --- a/mobile/test/java/org/chromium/net/impl/BUILD +++ b/mobile/test/java/org/chromium/net/impl/BUILD @@ -32,6 +32,7 @@ envoy_mobile_android_test( deps = [ "//library/java/io/envoyproxy/envoymobile/engine:envoy_base_engine_lib", "//library/java/io/envoyproxy/envoymobile/engine:envoy_engine_lib", + "//library/java/io/envoyproxy/envoymobile/engine/types:envoy_c_types_lib", "//library/java/io/envoyproxy/envoymobile/utilities", "//library/java/org/chromium/net", "//library/java/org/chromium/net/impl:cronvoy", diff --git a/mobile/test/java/org/chromium/net/impl/ErrorsTest.java b/mobile/test/java/org/chromium/net/impl/ErrorsTest.java index 8145c3b5cc09..566f14374408 100644 --- a/mobile/test/java/org/chromium/net/impl/ErrorsTest.java +++ b/mobile/test/java/org/chromium/net/impl/ErrorsTest.java @@ -4,8 +4,10 @@ import static org.junit.Assert.assertEquals; import androidx.test.ext.junit.runners.AndroidJUnit4; -import io.envoyproxy.envoymobile.engine.EnvoyFinalStreamIntelImpl; + import io.envoyproxy.envoymobile.engine.UpstreamHttpProtocol; +import io.envoyproxy.envoymobile.engine.types.EnvoyFinalStreamIntel; + import org.chromium.net.impl.Errors.NetError; import org.junit.runner.RunWith; import org.junit.Test; @@ -18,8 +20,7 @@ public void testMapEnvoyMobileErrorToNetErrorHttp3() throws Exception { // 8 corresponds to NoRouteFound in StreamInfo::CoreResponseFlag: // https://github.com/envoyproxy/envoy/blob/410e9a77bd6b74abb3e1545b4fd077e734d0fce3/envoy/stream_info/stream_info.h#L39 long responseFlags = 1L << 8; - EnvoyFinalStreamIntelImpl intel = - constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP3); + EnvoyFinalStreamIntel intel = constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP3); NetError error = mapEnvoyMobileErrorToNetError(intel); // It's an HTTP3 error, so it becomes a QUIC protocol error regardless of the response flag. assertEquals(NetError.ERR_QUIC_PROTOCOL_ERROR, error); @@ -30,8 +31,7 @@ public void testMapEnvoyMobileErrorToNetErrorFoundInMap() throws Exception { // 4 corresponds to UpstreamRemoteReset in StreamInfo::CoreResponseFlag: // https://github.com/envoyproxy/envoy/blob/410e9a77bd6b74abb3e1545b4fd077e734d0fce3/envoy/stream_info/stream_info.h#L39 long responseFlags = 1L << 4; - EnvoyFinalStreamIntelImpl intel = - constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); + EnvoyFinalStreamIntel intel = constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); NetError error = mapEnvoyMobileErrorToNetError(intel); assertEquals(NetError.ERR_CONNECTION_RESET, error); } @@ -43,8 +43,7 @@ public void testMapEnvoyMobileErrorToNetErrorMultipleResponseFlags() throws Exce // https://github.com/envoyproxy/envoy/blob/410e9a77bd6b74abb3e1545b4fd077e734d0fce3/envoy/stream_info/stream_info.h#L39 long responseFlags = 1L << 4; responseFlags |= (1L << 16); - EnvoyFinalStreamIntelImpl intel = - constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); + EnvoyFinalStreamIntel intel = constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); NetError error = mapEnvoyMobileErrorToNetError(intel); // STREAM_IDLE_TIMEOUT is first in the map's entries, so ERR_TIMED_OUT should be chosen over // ERR_CONNECTION_RESET. @@ -56,8 +55,7 @@ public void testMapEnvoyMobileErrorToNetErrorNotFoundInMap() throws Exception { // 1 corresponds to NoHealthyUpstream in StreamInfo::CoreResponseFlag: // https://github.com/envoyproxy/envoy/blob/410e9a77bd6b74abb3e1545b4fd077e734d0fce3/envoy/stream_info/stream_info.h#L39 long responseFlags = 1L << 1; - EnvoyFinalStreamIntelImpl intel = - constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); + EnvoyFinalStreamIntel intel = constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); NetError error = mapEnvoyMobileErrorToNetError(intel); // There is no NetError mapping from NoHealthyUpstream, so the default is ERR_OTHER. assertEquals(NetError.ERR_OTHER, error); @@ -67,14 +65,13 @@ public void testMapEnvoyMobileErrorToNetErrorNotFoundInMap() throws Exception { public void testMapEnvoyMobileErrorToNetErrorEmptyResponseFlags() throws Exception { // 0 means no response flags are set on the bitmap. long responseFlags = 0; - EnvoyFinalStreamIntelImpl intel = - constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); + EnvoyFinalStreamIntel intel = constructStreamIntel(responseFlags, UpstreamHttpProtocol.HTTP2); NetError error = mapEnvoyMobileErrorToNetError(intel); // The default is ERR_OTHER. assertEquals(NetError.ERR_OTHER, error); } - private EnvoyFinalStreamIntelImpl constructStreamIntel(long responseFlags, long protocol) { + private EnvoyFinalStreamIntel constructStreamIntel(long responseFlags, long protocol) { long[] values = new long[16]; values[0] = 0; values[1] = 0; @@ -94,6 +91,6 @@ private EnvoyFinalStreamIntelImpl constructStreamIntel(long responseFlags, long values[14] = responseFlags; values[15] = protocol; - return EnvoyFinalStreamIntelImpl.createForTesting(values); + return new EnvoyFinalStreamIntel(values); } } diff --git a/mobile/test/jni/jni_utility_test.cc b/mobile/test/jni/jni_utility_test.cc index c163390421f8..2a96d4a82784 100644 --- a/mobile/test/jni/jni_utility_test.cc +++ b/mobile/test/jni/jni_utility_test.cc @@ -13,6 +13,9 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); + Envoy::JNI::JniHelper::addClassToCache( + "io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); return Envoy::JNI::JniHelper::getVersion(); } @@ -63,7 +66,8 @@ Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppDirectByteBufferConvers Envoy::JNI::JniHelper jni_helper(env); auto cpp_buffer_instance = Envoy::JNI::javaDirectByteBufferToCppBufferInstance(jni_helper, java_byte_buffer, length); - return Envoy::JNI::cppBufferInstanceToJavaDirectByteBuffer(jni_helper, *cpp_buffer_instance) + return Envoy::JNI::cppBufferInstanceToJavaDirectByteBuffer(jni_helper, *cpp_buffer_instance, + length) .release(); } @@ -73,7 +77,8 @@ Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppNonDirectByteBufferConv Envoy::JNI::JniHelper jni_helper(env); auto cpp_buffer_instance = Envoy::JNI::javaNonDirectByteBufferToCppBufferInstance(jni_helper, java_byte_buffer, length); - return Envoy::JNI::cppBufferInstanceToJavaNonDirectByteBuffer(jni_helper, *cpp_buffer_instance) + return Envoy::JNI::cppBufferInstanceToJavaNonDirectByteBuffer(jni_helper, *cpp_buffer_instance, + length) .release(); } @@ -84,3 +89,22 @@ Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_getJavaExceptionMessage(JNIEnv std::string exception_message = Envoy::JNI::getJavaExceptionMessage(jni_helper, throwble); return Envoy::JNI::cppStringToJavaString(jni_helper, exception_message).release(); } + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppStreamIntelConversion( + JNIEnv* env, jclass, jobject java_stream_intel) { + Envoy::JNI::JniHelper jni_helper(env); + auto cpp_stream_intel = + Envoy::JNI::javaStreamIntelToCppStreamIntel(jni_helper, java_stream_intel); + return Envoy::JNI::cppStreamIntelToJavaStreamIntel(jni_helper, cpp_stream_intel).release(); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppFinalStreamIntelConversion( + JNIEnv* env, jclass, jobject java_final_stream_intel) { + Envoy::JNI::JniHelper jni_helper(env); + auto cpp_final_stream_intel = + Envoy::JNI::javaFinalStreamIntelToCppFinalStreamIntel(jni_helper, java_final_stream_intel); + return Envoy::JNI::cppFinalStreamIntelToJavaFinalStreamIntel(jni_helper, cpp_final_stream_intel) + .release(); +} diff --git a/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt b/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt index 1c28cc59439b..76e5d2123fc7 100644 --- a/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt +++ b/mobile/test/kotlin/integration/FilterThrowingExceptionTest.kt @@ -91,7 +91,7 @@ class FilterThrowingExceptionTest { @Test fun `registers a filter that throws an exception and performs an HTTP request`() { val onEngineRunningLatch = CountDownLatch(1) - val onRespondeHeadersLatch = CountDownLatch(1) + val onResponseHeadersLatch = CountDownLatch(1) val onJNIExceptionEventLatch = CountDownLatch(2) var expectedMessages = @@ -133,13 +133,13 @@ class FilterThrowingExceptionTest { .setOnResponseHeaders { responseHeaders, _, _ -> val status = responseHeaders.httpStatus ?: 0L assertThat(status).isEqualTo(200) - onRespondeHeadersLatch.countDown() + onResponseHeadersLatch.countDown() } .start(Executors.newSingleThreadExecutor()) .sendHeaders(requestHeaders, true) - onRespondeHeadersLatch.await(15, TimeUnit.SECONDS) - assertThat(onRespondeHeadersLatch.count).isEqualTo(0) + onResponseHeadersLatch.await(15, TimeUnit.SECONDS) + assertThat(onResponseHeadersLatch.count).isEqualTo(0) onJNIExceptionEventLatch.await(15, TimeUnit.SECONDS) assertThat(onJNIExceptionEventLatch.count).isEqualTo(0) diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt index f7de3354d45c..4f5b937b90ac 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/GRPCStreamTest.kt @@ -7,7 +7,6 @@ import java.io.ByteArrayOutputStream import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor import java.util.concurrent.TimeUnit import org.junit.Test @@ -23,7 +22,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start().sendMessage(message1) assertThat(sentData.size()).isEqualTo(5 + message1.array().count()) } @@ -35,7 +34,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start().sendMessage(message1) assertThat(sentData.toByteArray()[0]).isEqualTo(0) } @@ -47,7 +46,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start().sendMessage(message1) val size = ByteBuffer.wrap(sentData.toByteArray().sliceArray(1 until 5)).order(ByteOrder.BIG_ENDIAN).int @@ -61,7 +60,7 @@ class GRPCStreamTest { stream.onRequestData = { data, _ -> sentData.write(data.array()) } } - GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).sendMessage(message1) + GRPCClient(streamClient).newGRPCStreamPrototype().start().sendMessage(message1) assertThat(sentData.toByteArray().sliceArray(5 until sentData.size())) .isEqualTo(message1.array()) @@ -74,7 +73,7 @@ class GRPCStreamTest { stream.onCancel = { countDownLatch.countDown() } } - GRPCClient(streamClient).newGRPCStreamPrototype().start(Executor {}).cancel() + GRPCClient(streamClient).newGRPCStreamPrototype().start().cancel() assertThat(countDownLatch.await(2000, TimeUnit.MILLISECONDS)).isTrue() } @@ -96,7 +95,7 @@ class GRPCStreamTest { assertThat(endStream).isTrue() countDownLatch.countDown() } - .start(Executor {}) + .start() stream?.receiveHeaders(expectedHeaders, true) countDownLatch.await() @@ -117,7 +116,7 @@ class GRPCStreamTest { .isEqualTo(expectedTrailers.caseSensitiveHeaders()) countDownLatch.countDown() } - .start(Executor {}) + .start() stream?.receiveTrailers(expectedTrailers) countDownLatch.await() @@ -135,7 +134,7 @@ class GRPCStreamTest { assertThat(message.array()).isEqualTo(message1.array()) countDownLatch.countDown() } - .start(Executor {}) + .start() val messageLength = message1.array().count() val data = ByteBuffer.allocate(5 + messageLength) @@ -189,7 +188,7 @@ class GRPCStreamTest { } countDownLatch.countDown() } - .start(Executor {}) + .start() stream?.receiveData(firstMessageBuffer, false) stream?.receiveData(secondMessageBufferPart1, false) @@ -209,7 +208,7 @@ class GRPCStreamTest { assertThat(message.array()).hasLength(0) countDownLatch.countDown() } - .start(Executor {}) + .start() val emptyMessage = ByteBuffer.wrap( @@ -265,7 +264,7 @@ class GRPCStreamTest { } countDownLatch.countDown() } - .start(Executor {}) + .start() stream?.receiveData(emptyMessageBuffer, false) stream?.receiveData(secondMessageBuffer, false) diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt index 0c80cc40218d..838ce7bb3ebf 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStream.kt @@ -18,91 +18,11 @@ class MockStream(underlyingStream: MockEnvoyHTTPStream) : Stream(underlyingStream, useByteBufferPosition = false) { private val mockStream: MockEnvoyHTTPStream = underlyingStream - private val mockStreamIntel = - object : EnvoyStreamIntel { - override fun getStreamId(): Long { - return 0 - } - - override fun getConnectionId(): Long { - return 0 - } - - override fun getAttemptCount(): Long { - return 0 - } - - override fun getConsumedBytesFromResponse(): Long { - return 0 - } - } + private val mockStreamIntel = EnvoyStreamIntel(0, 0, 0, 0) private val mockFinalStreamIntel = - object : EnvoyFinalStreamIntel { - override fun getStreamStartMs(): Long { - return 0 - } - - override fun getDnsStartMs(): Long { - return 0 - } - - override fun getDnsEndMs(): Long { - return 0 - } - - override fun getConnectStartMs(): Long { - return 0 - } - - override fun getConnectEndMs(): Long { - return 0 - } - - override fun getSslStartMs(): Long { - return 0 - } - - override fun getSslEndMs(): Long { - return 0 - } - - override fun getSendingStartMs(): Long { - return 0 - } - - override fun getSendingEndMs(): Long { - return 0 - } - - override fun getResponseStartMs(): Long { - return 0 - } - - override fun getStreamEndMs(): Long { - return 0 - } - - override fun getSocketReused(): Boolean { - return false - } - - override fun getSentByteCount(): Long { - return 0 - } - - override fun getReceivedByteCount(): Long { - return 0 - } - - override fun getResponseFlags(): Long { - return 0 - } + EnvoyFinalStreamIntel(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false, 0, 0, 0, 0) - override fun getUpstreamProtocol(): Long { - return 0 - } - } /** Closure that will be called when request headers are sent. */ var onRequestHeaders: ((headers: RequestHeaders, endStream: Boolean) -> Unit)? = null /** Closure that will be called when request data is sent. */ diff --git a/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt b/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt index 0eb1ee86be5f..b350d81d89d1 100644 --- a/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt +++ b/mobile/test/kotlin/io/envoyproxy/envoymobile/mocks/MockStreamPrototype.kt @@ -11,7 +11,7 @@ import java.util.concurrent.Executor */ class MockStreamPrototype(private val onStart: ((stream: MockStream) -> Unit)?) : StreamPrototype(MockEnvoyEngine()) { - override fun start(executor: Executor): Stream { + override fun start(executor: Executor?): Stream { val callbacks = createCallbacks(executor) val stream = MockStream(MockEnvoyHTTPStream(callbacks, false)) onStart?.invoke(stream) From 3fab851394f08d4f83c662815e99564b8b736d45 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 13:40:19 -0500 Subject: [PATCH 10/22] mobile: Remove homebrew caches and unused packages (#34352) This is an attempt to salvage some disk space since the iOS related tests are consistently running out of disk space. Risk Level: low Testing: CI Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: Fredy Wijaya --- mobile/ci/mac_ci_setup.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mobile/ci/mac_ci_setup.sh b/mobile/ci/mac_ci_setup.sh index 1fd4760ac10e..3bea5a2ed199 100755 --- a/mobile/ci/mac_ci_setup.sh +++ b/mobile/ci/mac_ci_setup.sh @@ -51,6 +51,11 @@ do is_installed "${DEP}" || install "${DEP}" done +# This is to save some disk space. +# https://mac.install.guide/homebrew/8 +brew autoremove +brew cleanup --prune=all + # https://github.com/actions/runner-images/blob/main/images/macos/macos-12-Readme.md#xcode sudo xcode-select --switch /Applications/Xcode_14.1.app From 7081e5637c2cb0ecc90f1d1949c8acf27d576979 Mon Sep 17 00:00:00 2001 From: Kuo-Chung Hsu Date: Fri, 24 May 2024 13:46:17 -0700 Subject: [PATCH 11/22] Thrift to Metadata filter proto design (#33919) For apache thrift compatible HTTP requests and responses, this filter parses the thrift metadata and put them into filter dynamic metadata for other filter usage. This is the initial proto design, which refers to other filters like json_to_metadata and payload_to_metadata. Risk Level: low Testing: build Docs Changes: yes #29371 Signed-off-by: kuochunghsu --- CODEOWNERS | 2 + api/BUILD | 1 + .../filters/http/thrift_to_metadata/v3/BUILD | 13 ++ .../v3/thrift_to_metadata.proto | 190 ++++++++++++++++++ api/versioning/BUILD | 1 + .../_include/thrift-to-metadata-filter.yaml | 99 +++++++++ .../http/http_filters/http_filters.rst | 1 + .../thrift_to_metadata_filter.rst | 50 +++++ source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../filters/http/thrift_to_metadata/BUILD | 34 ++++ .../filters/http/thrift_to_metadata/config.cc | 36 ++++ .../filters/http/thrift_to_metadata/config.h | 30 +++ .../filters/http/thrift_to_metadata/filter.cc | 23 +++ .../filters/http/thrift_to_metadata/filter.h | 66 ++++++ .../filters/http/thrift_to_metadata/BUILD | 34 ++++ .../http/thrift_to_metadata/config_test.cc | 66 ++++++ .../http/thrift_to_metadata/filter_test.cc | 87 ++++++++ 18 files changed, 741 insertions(+) create mode 100644 api/envoy/extensions/filters/http/thrift_to_metadata/v3/BUILD create mode 100644 api/envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.proto create mode 100644 docs/root/configuration/http/http_filters/_include/thrift-to-metadata-filter.yaml create mode 100644 docs/root/configuration/http/http_filters/thrift_to_metadata_filter.rst create mode 100644 source/extensions/filters/http/thrift_to_metadata/BUILD create mode 100644 source/extensions/filters/http/thrift_to_metadata/config.cc create mode 100644 source/extensions/filters/http/thrift_to_metadata/config.h create mode 100644 source/extensions/filters/http/thrift_to_metadata/filter.cc create mode 100644 source/extensions/filters/http/thrift_to_metadata/filter.h create mode 100644 test/extensions/filters/http/thrift_to_metadata/BUILD create mode 100644 test/extensions/filters/http/thrift_to_metadata/config_test.cc create mode 100644 test/extensions/filters/http/thrift_to_metadata/filter_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index 4f064d203fb2..e0d262de1d4d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -293,6 +293,8 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 # Thrift /*/extensions/filters/network/thrift_proxy @zuercher @JuniorHsu /*/extensions/health_checkers/thrift @zuercher @JuniorHsu +# Thrift to metadata +/*/extensions/filters/http/thrift_to_metadata @JuniorHsu @zuercher # IP tagging /*/extensions/filters/http/ip_tagging @alyssawilk @JuniorHsu # Header to metadata diff --git a/api/BUILD b/api/BUILD index 57bb807f2d0a..1ac8e46d467c 100644 --- a/api/BUILD +++ b/api/BUILD @@ -215,6 +215,7 @@ proto_library( "//envoy/extensions/filters/http/set_metadata/v3:pkg", "//envoy/extensions/filters/http/stateful_session/v3:pkg", "//envoy/extensions/filters/http/tap/v3:pkg", + "//envoy/extensions/filters/http/thrift_to_metadata/v3:pkg", "//envoy/extensions/filters/http/upstream_codec/v3:pkg", "//envoy/extensions/filters/http/wasm/v3:pkg", "//envoy/extensions/filters/listener/http_inspector/v3:pkg", diff --git a/api/envoy/extensions/filters/http/thrift_to_metadata/v3/BUILD b/api/envoy/extensions/filters/http/thrift_to_metadata/v3/BUILD new file mode 100644 index 000000000000..f592dc73ad98 --- /dev/null +++ b/api/envoy/extensions/filters/http/thrift_to_metadata/v3/BUILD @@ -0,0 +1,13 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/extensions/filters/network/thrift_proxy/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + "@com_github_cncf_xds//xds/annotations/v3:pkg", + ], +) diff --git a/api/envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.proto b/api/envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.proto new file mode 100644 index 000000000000..9450387aaee2 --- /dev/null +++ b/api/envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.proto @@ -0,0 +1,190 @@ +syntax = "proto3"; + +package envoy.extensions.filters.http.thrift_to_metadata.v3; + +import "envoy/extensions/filters/network/thrift_proxy/v3/thrift_proxy.proto"; + +import "google/protobuf/struct.proto"; + +import "xds/annotations/v3/status.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.filters.http.thrift_to_metadata.v3"; +option java_outer_classname = "ThriftToMetadataProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/thrift_to_metadata/v3;thrift_to_metadatav3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; +option (xds.annotations.v3.file_status).work_in_progress = true; + +// [#protodoc-title: Thrift-To-Metadata Filter] +// +// The Thrift to Metadata filter serves for thrift over HTTP traffic, expecting serialized +// Thrift request and response bodies in the HTTP payload. It extracts *thrift metadata* from the +// HTTP body and put them into the *filter metadata*. This is useful for matching load balancer +// subsets, logging, etc. +// +// Thrift to Metadata :ref:`configuration overview `. +// [#extension: envoy.filters.http.thrift_to_metadata] + +enum Field { + // The Thrift method name, string value. + METHOD_NAME = 0; + + // The Thrift protocol name, string value. Values are "binary", "binary/non-strict", and "compact", with "(auto)" suffix if + // :ref:`protocol ` + // is set to :ref:`AUTO_PROTOCOL` + PROTOCOL = 1; + + // The Thrift transport name, string value. Values are "framed", "header", and "unframed", with "(auto)" suffix if + // :ref:`transport ` + // is set to :ref:`AUTO_TRANSPORT` + TRANSPORT = 2; + + // The Thrift message type, singed 16-bit integer value. + HEADER_FLAGS = 3; + + // The Thrift sequence ID, singed 32-bit integer value. + SEQUENCE_ID = 4; + + // The Thrift message type, string value. Values in request are "call" and "oneway", and in response are "reply" and "exception". + MESSAGE_TYPE = 5; + + // The Thrift reply type, string value. This is only valid for response rules. Values are "success" and "error". + REPLY_TYPE = 6; +} + +message KeyValuePair { + // The namespace — if this is empty, the filter's namespace will be used. + string metadata_namespace = 1; + + // The key to use within the namespace. + string key = 2 [(validate.rules).string = {min_len: 1}]; + + // When used for on_present case, if value is non-empty it'll be used instead + // of the field value. + // + // When used for on_missing case, a non-empty value must be provided. + google.protobuf.Value value = 3; +} + +message FieldSelector { + option (xds.annotations.v3.message_status).work_in_progress = true; + + // field name to log + string name = 1 [(validate.rules).string = {min_len: 1}]; + + // field id to match + int32 id = 2 [(validate.rules).int32 = {lte: 32767 gte: -32768}]; + + // next node of the field selector + FieldSelector child = 3; +} + +// [#next-free-field: 6] +message Rule { + // The field to match on. If set, takes precedence over field_selector. + Field field = 1; + + // Specifies that a match will be performed on the value of a field in the thrift body. + // If set, the whole http body will be buffered to extract the field value, which + // may have performance implications. + // + // It's a thrift over http version of + // :ref:`field_selector`. + // + // See also `payload-to-metadata `_ + // for more reference. + // + // Example: + // + // .. code-block:: yaml + // + // method_name: foo + // field_selector: + // name: info + // id: 2 + // child: + // name: version + // id: 1 + // + // The above yaml will match on value of ``info.version`` in the below thrift schema as input of + // :ref:`on_present` or + // :ref:`on_missing` + // while we are processing ``foo`` method. This rule won't be applied to ``bar`` method. + // + // .. code-block:: thrift + // + // struct Info { + // 1: required string version; + // } + // service Server { + // bool foo(1: i32 id, 2: Info info); + // bool bar(1: i32 id, 2: Info info); + // } + // + FieldSelector field_selector = 2 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // If specified, :ref:`field_selector` + // will be used to extract the field value *only* on the thrift message with method name. + string method_name = 3 [(xds.annotations.v3.field_status).work_in_progress = true]; + + // The key-value pair to set in the *filter metadata* if the field is present + // in *thrift metadata*. + // + // If the value in the KeyValuePair is non-empty, it'll be used instead + // of field value. + KeyValuePair on_present = 4; + + // The key-value pair to set in the *filter metadata* if the field is missing + // in *thrift metadata*. + // + // The value in the KeyValuePair must be set, since it'll be used in lieu + // of the missing field value. + KeyValuePair on_missing = 5; +} + +// The configuration for transforming thrift metadata into filter metadata. +// +// [#next-free-field: 7] +message ThriftToMetadata { + // The list of rules to apply to http request body to extract thrift metadata. + repeated Rule request_rules = 1; + + // The list of rules to apply to http response body to extract thrift metadata. + repeated Rule response_rules = 2; + + // Supplies the type of transport that the Thrift proxy should use. Defaults to + // :ref:`AUTO_TRANSPORT`. + network.thrift_proxy.v3.TransportType transport = 3 + [(validate.rules).enum = {defined_only: true}]; + + // Supplies the type of protocol that the Thrift proxy should use. Defaults to + // :ref:`AUTO_PROTOCOL`. + // Note that :ref:`TWITTER` is + // not supported due to deprecation in envoy. + network.thrift_proxy.v3.ProtocolType protocol = 4 [(validate.rules).enum = {defined_only: true}]; + + // Allowed content-type for thrift payload to filter metadata transformation. + // Default to ``{"application/x-thrift"}``. + // + // Set ``allow_empty_content_type`` if empty/missing content-type header + // is allowed. + repeated string allow_content_types = 5 + [(validate.rules).repeated = {items {string {min_len: 1}}}]; + + // Allowed empty content-type for thrift payload to filter metadata transformation. + // Default to false. + bool allow_empty_content_type = 6; +} + +// Thrift to metadata configuration on a per-route basis, which overrides the global configuration for +// request rules and responses rules. +message ThriftToMetadataPerRoute { + // The list of rules to apply to http request body to extract thrift metadata. + repeated Rule request_rules = 1; + + // The list of rules to apply to http response body to extract thrift metadata. + repeated Rule response_rules = 2; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index dc5442bbf390..35831f251fd6 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -153,6 +153,7 @@ proto_library( "//envoy/extensions/filters/http/set_metadata/v3:pkg", "//envoy/extensions/filters/http/stateful_session/v3:pkg", "//envoy/extensions/filters/http/tap/v3:pkg", + "//envoy/extensions/filters/http/thrift_to_metadata/v3:pkg", "//envoy/extensions/filters/http/upstream_codec/v3:pkg", "//envoy/extensions/filters/http/wasm/v3:pkg", "//envoy/extensions/filters/listener/http_inspector/v3:pkg", diff --git a/docs/root/configuration/http/http_filters/_include/thrift-to-metadata-filter.yaml b/docs/root/configuration/http/http_filters/_include/thrift-to-metadata-filter.yaml new file mode 100644 index 000000000000..f8fa4b3178ab --- /dev/null +++ b/docs/root/configuration/http/http_filters/_include/thrift-to-metadata-filter.yaml @@ -0,0 +1,99 @@ +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 80 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: AUTO + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: app + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: versioned-cluster + http_filters: + - name: envoy.filters.http.thrift_to_metadata + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.thrift_to_metadata.v3.ThriftToMetadata + request_rules: + - field: PROTOCOL + on_present: + metadata_namespace: envoy.lb + key: protocol + on_missing: + metadata_namespace: envoy.lb + key: protocol + value: "unknown" + - field: TRANSPORT + on_present: + metadata_namespace: envoy.lb + key: transport + on_missing: + metadata_namespace: envoy.lb + key: transport + value: "unknown" + response_rules: + - field: MESSAGE_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + value: "exception" + - field: REPLY_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + value: "error" + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + + clusters: + - name: versioned-cluster + type: STRICT_DNS + lb_policy: ROUND_ROBIN + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: + - protocol + - transport + load_assignment: + cluster_name: versioned-cluster + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + metadata: + filter_metadata: + envoy.lb: + default: "true" + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8081 + metadata: + filter_metadata: + envoy.lb: + version: "1.0" diff --git a/docs/root/configuration/http/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst index cb40dd8b0019..d9f93e0262f6 100644 --- a/docs/root/configuration/http/http_filters/http_filters.rst +++ b/docs/root/configuration/http/http_filters/http_filters.rst @@ -62,5 +62,6 @@ HTTP filters stateful_session_filter sxg_filter tap_filter + thrift_to_metadata_filter upstream_codec_filter wasm_filter diff --git a/docs/root/configuration/http/http_filters/thrift_to_metadata_filter.rst b/docs/root/configuration/http/http_filters/thrift_to_metadata_filter.rst new file mode 100644 index 000000000000..497dab730f70 --- /dev/null +++ b/docs/root/configuration/http/http_filters/thrift_to_metadata_filter.rst @@ -0,0 +1,50 @@ +.. _config_http_filters_thrift_to_metadata: + +Envoy Thrift-To-Metadata Filter +=============================== +* This filter should be configured with the type URL ``type.googleapis.com/envoy.extensions.filters.http.thrift_to_metadata.v3.ThriftToMetadata``. +* :ref:`v3 API reference ` + +The Thrift to Metadata filter serves for thrift over HTTP traffic, expecting serialized Thrift request and response bodies +in the HTTP payload. This filter is configured with rules that will be matched against Apache thrift compatible requests and +responses in HTTP payload. The filter will parse the thrift body, extract *thrift metadata* or *thrift payload*, and add them to +*dynamic filter metadata* based on the configuration of the rule. + +The *filter metadata* can then be used for load balancing decisions, consumed from logs, etc. + +A typical use case for this filter is to dynamically match a specified thrift method of requests +with rate limit. For this, thrift method name is attached to the request as dynamic filter metadata which +would then be used to match a rate limit action on filter metadata. + +Example +------- + +A sample filter configuration to route traffic to endpoints based on the presence or +absence of a version attribute could be: + +.. literalinclude:: _include/thrift-to-metadata-filter.yaml + :language: yaml + :lines: 25-55 + :lineno-start: 25 + :linenos: + :caption: :download:`thrift-to-metadata-filter.yaml <_include/thrift-to-metadata-filter.yaml>` + +Statistics +---------- + +The thrift to metadata filter outputs statistics in the *http..thrift_to_metadata.* namespace. The :ref:`stat prefix +` +comes from the owning HTTP connection manager. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + rq_success, Counter, Total requests that succeed to parse the thrift body. + rq_mismatched_content_type, Counter, Total requests that mismatch the content type + rq_no_body, Counter, Total requests without content body + rq_invalid_thrift_body, Counter, Total requests with invalid thrift body + resp_success, Counter, Total responses that succeed to parse the thrift body. + resp_mismatched_content_type, Counter, Total responses that mismatch the content type + resp_no_body, Counter, Total responses without content body + resp_invalid_thrift_body, Counter, Total responses with invalid thrift body diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 3259d46a4b32..0c1a85fb040f 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -176,6 +176,7 @@ EXTENSIONS = { "envoy.filters.http.set_filter_state": "//source/extensions/filters/http/set_filter_state:config", "envoy.filters.http.set_metadata": "//source/extensions/filters/http/set_metadata:config", "envoy.filters.http.tap": "//source/extensions/filters/http/tap:config", + "envoy.filters.http.thrift_to_metadata": "//source/extensions/filters/http/thrift_to_metadata:config", "envoy.filters.http.wasm": "//source/extensions/filters/http/wasm:config", "envoy.filters.http.stateful_session": "//source/extensions/filters/http/stateful_session:config", "envoy.filters.http.header_mutation": "//source/extensions/filters/http/header_mutation:config", diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index fbf490fd2b76..cb9dc22929d6 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -544,6 +544,13 @@ envoy.filters.http.tap: status: alpha type_urls: - envoy.extensions.filters.http.tap.v3.Tap +envoy.filters.http.thrift_to_metadata: + categories: + - envoy.filters.http + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.filters.http.thrift_to_metadata.v3.ThriftToMetadata envoy.filters.http.wasm: categories: - envoy.filters.http diff --git a/source/extensions/filters/http/thrift_to_metadata/BUILD b/source/extensions/filters/http/thrift_to_metadata/BUILD new file mode 100644 index 000000000000..aeb25887da27 --- /dev/null +++ b/source/extensions/filters/http/thrift_to_metadata/BUILD @@ -0,0 +1,34 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "thrift_to_metadata_lib", + srcs = ["filter.cc"], + hdrs = ["filter.h"], + deps = [ + "//envoy/server:filter_config_interface", + "//source/common/http:header_utility_lib", + "//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy_api//envoy/extensions/filters/http/thrift_to_metadata/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":thrift_to_metadata_lib", + "//envoy/registry", + "//source/extensions/filters/http/common:factory_base_lib", + "@envoy_api//envoy/extensions/filters/http/thrift_to_metadata/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/filters/http/thrift_to_metadata/config.cc b/source/extensions/filters/http/thrift_to_metadata/config.cc new file mode 100644 index 000000000000..ae44278dba66 --- /dev/null +++ b/source/extensions/filters/http/thrift_to_metadata/config.cc @@ -0,0 +1,36 @@ +#include "source/extensions/filters/http/thrift_to_metadata/config.h" + +#include "envoy/http/header_map.h" +#include "envoy/registry/registry.h" + +#include "source/common/protobuf/utility.h" +#include "source/extensions/filters/http/thrift_to_metadata/filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +ThriftToMetadataConfig::ThriftToMetadataConfig() + : FactoryBase("envoy.filters.http.thrift_to_metadata") {} + +Http::FilterFactoryCb ThriftToMetadataConfig::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata& proto_config, + const std::string&, Server::Configuration::FactoryContext& context) { + std::shared_ptr config = + std::make_shared(proto_config, context.scope()); + + return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(config)); + }; +} + +/** + * Static registration for this filter. @see RegisterFactory. + */ +REGISTER_FACTORY(ThriftToMetadataConfig, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/thrift_to_metadata/config.h b/source/extensions/filters/http/thrift_to_metadata/config.h new file mode 100644 index 000000000000..60b2bf7dd2bb --- /dev/null +++ b/source/extensions/filters/http/thrift_to_metadata/config.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include "envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.pb.h" +#include "envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.pb.validate.h" + +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +class ThriftToMetadataConfig + : public Extensions::HttpFilters::Common::FactoryBase< + envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata> { +public: + ThriftToMetadataConfig(); + +private: + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata&, + const std::string&, Server::Configuration::FactoryContext&) override; +}; + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/thrift_to_metadata/filter.cc b/source/extensions/filters/http/thrift_to_metadata/filter.cc new file mode 100644 index 000000000000..878f63c3fd80 --- /dev/null +++ b/source/extensions/filters/http/thrift_to_metadata/filter.cc @@ -0,0 +1,23 @@ +#include "source/extensions/filters/http/thrift_to_metadata/filter.h" + +#include "source/common/http/header_map_impl.h" +#include "source/common/http/utility.h" + +#include "absl/strings/str_cat.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +FilterConfig::FilterConfig( + const envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata& proto_config, + Stats::Scope& scope) { + UNREFERENCED_PARAMETER(proto_config); + UNREFERENCED_PARAMETER(scope); +} + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/thrift_to_metadata/filter.h b/source/extensions/filters/http/thrift_to_metadata/filter.h new file mode 100644 index 000000000000..a2223391e855 --- /dev/null +++ b/source/extensions/filters/http/thrift_to_metadata/filter.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "envoy/extensions/filters/http/thrift_to_metadata/v3/thrift_to_metadata.pb.h" +#include "envoy/server/filter_config.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/matchers.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +/** + * All stats for the Thrift to Metadata filter. @see stats_macros.h + */ +#define ALL_THRIFT_TO_METADATA_FILTER_STATS(COUNTER) \ + COUNTER(success) \ + COUNTER(mismatched_content_type) \ + COUNTER(no_body) \ + COUNTER(invalid_thrift_body) + +/** + * Wrapper struct for Thrift to Metadata filter stats. @see stats_macros.h + */ +struct ThriftToMetadataStats { + ALL_THRIFT_TO_METADATA_FILTER_STATS(GENERATE_COUNTER_STRUCT) +}; + +using ProtoRule = envoy::extensions::filters::http::thrift_to_metadata::v3::Rule; +using KeyValuePair = envoy::extensions::filters::http::thrift_to_metadata::v3::KeyValuePair; + +/** + * Configuration for the Thrift to Metadata filter. + */ +class FilterConfig { +public: + FilterConfig(const envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata& + proto_config, + Stats::Scope& scope); +}; + +/** + * HTTP Thrift to Metadata Filter. + */ +class Filter : public Http::PassThroughFilter, Logger::Loggable { +public: + Filter(std::shared_ptr config) : config_(config){}; + +private: + std::shared_ptr config_; +}; + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/thrift_to_metadata/BUILD b/test/extensions/filters/http/thrift_to_metadata/BUILD new file mode 100644 index 000000000000..df3643ba5086 --- /dev/null +++ b/test/extensions/filters/http/thrift_to_metadata/BUILD @@ -0,0 +1,34 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "filter_test", + srcs = ["filter_test.cc"], + extension_names = ["envoy.filters.http.thrift_to_metadata"], + deps = [ + "//source/extensions/filters/http/thrift_to_metadata:thrift_to_metadata_lib", + "//test/common/stream_info:test_util", + "//test/mocks/server:server_mocks", + "//test/mocks/upstream:upstream_mocks", + ], +) + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.filters.http.thrift_to_metadata"], + deps = [ + "//source/extensions/filters/http/thrift_to_metadata:config", + "//test/mocks/server:server_mocks", + ], +) diff --git a/test/extensions/filters/http/thrift_to_metadata/config_test.cc b/test/extensions/filters/http/thrift_to_metadata/config_test.cc new file mode 100644 index 000000000000..caf9bd83d974 --- /dev/null +++ b/test/extensions/filters/http/thrift_to_metadata/config_test.cc @@ -0,0 +1,66 @@ +#include "source/extensions/filters/http/thrift_to_metadata/config.h" + +#include "test/mocks/server/mocks.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +TEST(Factory, Basic) { + const std::string yaml = R"( +request_rules: +- field: PROTOCOL + on_present: + metadata_namespace: envoy.lb + key: protocol + on_missing: + metadata_namespace: envoy.lb + key: protocol + value: "unknown" +- field: TRANSPORT + on_present: + metadata_namespace: envoy.lb + key: transport + on_missing: + metadata_namespace: envoy.lb + key: transport + value: "unknown" +response_rules: +- field: MESSAGE_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + value: "exception" +- field: REPLY_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + value: "error" + )"; + + ThriftToMetadataConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + NiceMock context; + + auto callback = factory.createFilterFactoryFromProto(*proto_config, "stats", context).value(); + Http::MockFilterChainFactoryCallbacks filter_callback; + EXPECT_CALL(filter_callback, addStreamFilter(_)); + callback(filter_callback); +} + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/thrift_to_metadata/filter_test.cc b/test/extensions/filters/http/thrift_to_metadata/filter_test.cc new file mode 100644 index 000000000000..c7e7aa083648 --- /dev/null +++ b/test/extensions/filters/http/thrift_to_metadata/filter_test.cc @@ -0,0 +1,87 @@ +#include + +#include "envoy/http/header_map.h" + +#include "source/extensions/filters/http/thrift_to_metadata/filter.h" + +#include "test/common/stream_info/test_util.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace ThriftToMetadata { + +class FilterTest : public testing::Test { +public: + FilterTest() = default; + + const std::string config_yaml_ = R"EOF( +request_rules: +- field: PROTOCOL + on_present: + metadata_namespace: envoy.lb + key: protocol + on_missing: + metadata_namespace: envoy.lb + key: protocol + value: "unknown" +- field: TRANSPORT + on_present: + metadata_namespace: envoy.lb + key: transport + on_missing: + metadata_namespace: envoy.lb + key: transport + value: "unknown" +response_rules: +- field: MESSAGE_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_message_type + value: "exception" +- field: REPLY_TYPE + on_present: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + on_missing: + metadata_namespace: envoy.filters.http.thrift_to_metadata + key: response_reply_type + value: "error" +)EOF"; + + void initializeFilter(const std::string& yaml) { + envoy::extensions::filters::http::thrift_to_metadata::v3::ThriftToMetadata config; + TestUtility::loadFromYaml(yaml, config); + config_ = std::make_shared(config, *scope_.rootScope()); + filter_ = std::make_shared(config_); + filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); + } + + NiceMock scope_; + NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; + std::shared_ptr config_; + std::shared_ptr filter_; +}; + +TEST_F(FilterTest, Basic) { + initializeFilter(config_yaml_); + EXPECT_TRUE(true); +} + +} // namespace ThriftToMetadata +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy From 8d3a08fdcc59018ffb28936b8146d844c430fdca Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Fri, 24 May 2024 17:05:40 -0400 Subject: [PATCH 12/22] dns/apple: Ensure the Apple DNS resolver gets all IPs for the host (#34331) If the DNS query protocol to the Apple DNS resolution API DNSServiceGetAddrInfo is either IPv4 | IPv6, or if protocol is set to 0, the resolution callback will be invoked at least twice: at least once for the IPv4 family and at least once for the IPv6 family. Previously, we were not waiting to ensure we received both the IPv4 and the IPv6 response (even if empty), which meant the resolver would finish a DNS resolution without necessarily getting all the responses from the DNSServiceGetAddrInfo call. If the resolver finished while getting an IPv6 address, but not yet having received an IPv4 address for the host, and the device is on a network that doesn't support IPv6, then connections made to the host would all result in a "no route to host" error. This PR fixes the problem by ensuring we don't finish the DNS query resolution until all responses (for both v4 and v6, if requested) have been received in the resolution response callback. Signed-off-by: Ali Beyad --- .../dns_resolver/apple/apple_dns_impl.cc | 61 ++++- .../dns_resolver/apple/apple_dns_impl.h | 27 +- .../dns_resolver/apple/apple_dns_impl_test.cc | 255 +++++++++++++++--- tools/spelling/spelling_dictionary.txt | 2 + 4 files changed, 287 insertions(+), 58 deletions(-) diff --git a/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc b/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc index 1049d806b567..e26a5de555ef 100644 --- a/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc +++ b/source/extensions/network/dns_resolver/apple/apple_dns_impl.cc @@ -145,8 +145,7 @@ AppleDnsResolverImpl::PendingResolution::PendingResolution(AppleDnsResolverImpl& const std::string& dns_name, DnsLookupFamily dns_lookup_family) : parent_(parent), callback_(callback), dispatcher_(dispatcher), dns_name_(dns_name), - pending_response_({ResolutionStatus::Success, {}, {}, {}}), - dns_lookup_family_(dns_lookup_family) {} + pending_response_(PendingResponse()), dns_lookup_family_(dns_lookup_family) {} AppleDnsResolverImpl::PendingResolution::~PendingResolution() { ENVOY_LOG(debug, "Destroying PendingResolution for {}", dns_name_); @@ -242,19 +241,18 @@ void AppleDnsResolverImpl::PendingResolution::finishResolve() { DNSServiceErrorType AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(bool include_unroutable_families) { - DNSServiceProtocol protocol; switch (dns_lookup_family_) { case DnsLookupFamily::V4Only: - protocol = kDNSServiceProtocol_IPv4; + query_protocol_ = kDNSServiceProtocol_IPv4; break; case DnsLookupFamily::V6Only: - protocol = kDNSServiceProtocol_IPv6; + query_protocol_ = kDNSServiceProtocol_IPv6; break; case DnsLookupFamily::Auto: case DnsLookupFamily::V4Preferred: case DnsLookupFamily::All: if (include_unroutable_families) { - protocol = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6; + query_protocol_ = kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6; break; } @@ -270,7 +268,7 @@ AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(bool include_unro * any use anyway. Similarly, if this host has no routable IPv4 address, the call will * not try to look up IPv4 addresses for "hostname". */ - protocol = 0; + query_protocol_ = 0; break; } @@ -278,7 +276,8 @@ AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(bool include_unro // from the cache? // TODO: explore validation via `DNSSEC`? return DnsServiceSingleton::get().dnsServiceGetAddrInfo( - &sd_ref_, kDNSServiceFlagsTimeout, 0, protocol, dns_name_.c_str(), + &sd_ref_, kDNSServiceFlagsTimeout | kDNSServiceFlagsReturnIntermediates, 0, query_protocol_, + dns_name_.c_str(), /* * About Thread Safety (taken from inline documentation there): * The dns_sd.h API does not presuppose any particular threading model, and consequently @@ -304,6 +303,17 @@ AppleDnsResolverImpl::PendingResolution::dnsServiceGetAddrInfo(bool include_unro void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( DNSServiceFlags flags, uint32_t interface_index, DNSServiceErrorType error_code, const char* hostname, const struct sockaddr* address, uint32_t ttl) { + // If the DNS query protocol is (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6) or if it is + // 0, then this callback is expected to be called at least two times: at least once for IPv4 and + // at least once for IPv6. This is true even if there are no DNS records for the given address + // family and/or the network that the code is running on doesn't support the given address family. + // + // That means if the network doesn't support an address family or the hostname doesn't have any + // DNS records for the address family, there will still be at least one callback to + // onDNSServiceGetAddrInfoReply() for requested address family. In such a case, the `address` will + // still be non-null and its `sa_family` will be the address family of the query (even if the + // address itself isn't a meaningful IP address). + ENVOY_LOG(debug, "DNS for {} resolved with: flags={}[MoreComing={}, Add={}], interface_index={}, " "error_code={}, hostname={}", @@ -311,7 +321,17 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( flags & kDNSServiceFlagsAdd ? "yes" : "no", interface_index, error_code, hostname); // Make sure that we trigger the failure callback if we get an error back. - if (error_code != kDNSServiceErr_NoError) { + // NoSuchRecord is *not* considered an error; it indicates that a query was successfully + // completed, but there were no DNS records for that address family. + // + // If the protocol is set to 0 or set to (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6), + // the behavior is undefined in the API docs as to whether there would be more than one callback + // with an error. However, when we receive an error, we call finishResolve(), which results in + // the deletion of this PendingResolution instance, and the destructor ensures the DNSServiceRef + // gets deallocated (via the dnsServiceRefDeallocate() method), which owns the callback + // operation. Hence, after calling finishResolve(), we are guaranteed to not get any more + // callbacks to this method. + if (error_code != kDNSServiceErr_NoError && error_code != kDNSServiceErr_NoSuchRecord) { parent_.chargeGetAddrInfoErrorStats(error_code); pending_response_.status_ = ResolutionStatus::Failure; @@ -324,11 +344,17 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( return; } + ASSERT(address, "address cannot be null"); + if (address->sa_family == AF_INET) { + pending_response_.v4_response_received_ = true; + } else if (address->sa_family == AF_INET6) { + pending_response_.v6_response_received_ = true; + } + // dns_sd.h does not call out behavior where callbacks to DNSServiceGetAddrInfoReply // would respond without the flag. However, Envoy's API is solely additive. // Therefore, only add this address to the list if kDNSServiceFlagsAdd is set. - if (flags & kDNSServiceFlagsAdd) { - ASSERT(address, "invalid to add null address"); + if (error_code == kDNSServiceErr_NoError && (flags & kDNSServiceFlagsAdd)) { auto dns_response = buildDnsResponse(address, ttl); ENVOY_LOG(debug, "Address to add address={}, ttl={}", dns_response.addrInfo().address_->ip()->addressAsString(), ttl); @@ -340,7 +366,8 @@ void AppleDnsResolverImpl::PendingResolution::onDNSServiceGetAddrInfoReply( } } - if (!(flags & kDNSServiceFlagsMoreComing)) { + if (!(flags & kDNSServiceFlagsMoreComing) && isAddressFamilyProcessed(kDNSServiceProtocol_IPv4) && + isAddressFamilyProcessed(kDNSServiceProtocol_IPv6)) { ENVOY_LOG(debug, "DNS Resolver flushing queries pending callback"); finishResolve(); // Note: Nothing can follow this call to finishResolve due to deletion of this @@ -396,6 +423,16 @@ AppleDnsResolverImpl::PendingResolution::buildDnsResponse(const struct sockaddr* return {std::make_shared(&address_in), std::chrono::seconds(ttl)}; } +bool AppleDnsResolverImpl::PendingResolution::isAddressFamilyProcessed( + DNSServiceProtocol protocol) { + // If not expecting a v4/v6 query, or the v4/v6 response has been received, consider the address + // family as having been processed. + const bool response_received = (protocol == kDNSServiceProtocol_IPv4) + ? pending_response_.v4_response_received_ + : pending_response_.v6_response_received_; + return response_received || !((query_protocol_ & protocol) || query_protocol_ == 0); +} + // apple DNS resolver factory class AppleDnsResolverFactory : public DnsResolverFactory { public: diff --git a/source/extensions/network/dns_resolver/apple/apple_dns_impl.h b/source/extensions/network/dns_resolver/apple/apple_dns_impl.h index 68ddbb9bcb69..1768f3684f5e 100644 --- a/source/extensions/network/dns_resolver/apple/apple_dns_impl.h +++ b/source/extensions/network/dns_resolver/apple/apple_dns_impl.h @@ -106,6 +106,10 @@ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggable v4_responses_; - std::list v6_responses_; - std::list all_responses_; + ResolutionStatus status_ = ResolutionStatus::Success; + // `v4_response_received_` and `v6_response_received_` denote whether a callback from the + // `DNSServiceGetAddrInfo` call has been received for the IPv4 address family and IPv6 + // address family, respectively. If the query protocol is set to kDNSServiceProtocol_IPv4 or + // set to 0, at least one callback with the address family (`sa_family`) set to IPv4 + // (AF_INET) will be received. If the query protocol is set to kDNSServiceProtocol_IPv6 or + // set to 0, at least one callback with the address family (`sa_family`) set to IPv6 will + // be received. + // + // If the query protocol is set to (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6), + // or it is set to 0, then at least two callbacks will be received: at least one for the + // IPv4 family and at least one for the IPv6 family. This is true even if the domain doesn't + // exist (NXDOMAIN). + bool v4_response_received_{false}; + bool v6_response_received_{false}; + std::list v4_responses_{}; + std::list v6_responses_{}; + std::list all_responses_{}; }; AppleDnsResolverImpl& parent_; @@ -131,6 +149,7 @@ class AppleDnsResolverImpl : public DnsResolver, protected Logger::Loggablesin6_family = AF_INET6; + return Network::Address::Ipv6Instance(*addr6); + }(); + return address; +} + +Network::Address::Ipv4Instance& emptyV4Address() { + static Network::Address::Ipv4Instance address = [] { + static sockaddr_in* addr4 = new sockaddr_in; + memset(addr4, 0, sizeof(*addr4)); + addr4->sin_family = AF_INET; + return Network::Address::Ipv4Instance(addr4); + }(); + return address; +} + void expectAppleTypedDnsResolverConfig( const envoy::config::core::v3::TypedExtensionConfig& typed_dns_resolver_config) { EXPECT_EQ(typed_dns_resolver_config.name(), std::string(Network::AppleDnsResolver)); @@ -129,6 +154,8 @@ class AppleDnsImplTest : public testing::Test { PANIC("reached unexpected code"); } } + } else { + EXPECT_TRUE(results.empty()); } if (exit_dispatcher) { dispatcher_->exit(); @@ -292,6 +319,125 @@ TEST_F(AppleDnsImplTest, DnsIpAddressVersionV6Only) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +// For the tests below: +// - ipv4.google.com DNS records only resolve to IPv4 addresses, but if on a network that supports +// NAT64/DNS64, DNS can resolve to virtualized IPv6 addresses. +// - ipv6.google.com DNS records only resolve to IPv6 addresses. + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionAllSupportsV4Only) { + EXPECT_NE(nullptr, resolveWithExpectations("ipv4.google.com", DnsLookupFamily::All, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionAllSupportsV6Only) { + auto* dns_query = resolver_->resolve( + "ipv6.google.com", DnsLookupFamily::All, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + // On v4 only networks, there will be no results. + for (const auto& result : results) { + const auto& addrinfo = result.addrInfo(); + EXPECT_THAT(addrinfo.address_->ip()->ipv6(), NotNull()); + EXPECT_THAT(addrinfo.address_->ip()->ipv4(), IsNull()); + } + dispatcher_->exit(); + }); + EXPECT_THAT(dns_query, NotNull()); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV4OnlySupportsV4Only) { + EXPECT_NE(nullptr, resolveWithExpectations("ipv4.google.com", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV4OnlySupportsV6Only) { + EXPECT_NE(nullptr, resolveWithExpectations("ipv6.google.com", DnsLookupFamily::V4Only, + DnsResolver::ResolutionStatus::Success, false)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV6OnlySupportsV4Only) { + auto* dns_query = resolver_->resolve( + "ipv4.google.com", DnsLookupFamily::V6Only, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + for (const auto& result : results) { + const auto& addrinfo = result.addrInfo(); + EXPECT_THAT(addrinfo.address_->ip()->ipv6(), NotNull()); + EXPECT_THAT(addrinfo.address_->ip()->ipv4(), IsNull()); + } + dispatcher_->exit(); + }); + EXPECT_THAT(dns_query, NotNull()); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV6OnlySupportsV6Only) { + auto* dns_query = resolver_->resolve( + "ipv6.google.com", DnsLookupFamily::V6Only, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + EXPECT_FALSE(results.empty()); + for (const auto& result : results) { + const auto& addrinfo = result.addrInfo(); + EXPECT_THAT(addrinfo.address_->ip()->ipv6(), NotNull()); + EXPECT_THAT(addrinfo.address_->ip()->ipv4(), IsNull()); + } + dispatcher_->exit(); + }); + EXPECT_THAT(dns_query, NotNull()); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionAutoSupportsV4Only) { + EXPECT_NE(nullptr, resolveWithExpectations("ipv4.google.com", DnsLookupFamily::Auto, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionAutoSupportsV6Only) { + auto* dns_query = resolver_->resolve( + "ipv6.google.com", DnsLookupFamily::Auto, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + // On v4 only networks, there will be no results. + for (const auto& result : results) { + const auto& addrinfo = result.addrInfo(); + EXPECT_THAT(addrinfo.address_->ip()->ipv6(), NotNull()); + EXPECT_THAT(addrinfo.address_->ip()->ipv4(), IsNull()); + } + dispatcher_->exit(); + }); + EXPECT_THAT(dns_query, NotNull()); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV4PreferredSupportsV4Only) { + EXPECT_NE(nullptr, resolveWithExpectations("ipv4.google.com", DnsLookupFamily::V4Preferred, + DnsResolver::ResolutionStatus::Success, true)); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + +TEST_F(AppleDnsImplTest, DnsIpAddressVersionV4PreferredSupportsV6Only) { + auto* dns_query = resolver_->resolve( + "ipv6.google.com", DnsLookupFamily::V4Preferred, + [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); + // On v4 only networks, there will be no results. + for (const auto& result : results) { + const auto& addrinfo = result.addrInfo(); + EXPECT_THAT(addrinfo.address_->ip()->ipv6(), NotNull()); + EXPECT_THAT(addrinfo.address_->ip()->ipv4(), IsNull()); + } + dispatcher_->exit(); + }); + EXPECT_THAT(dns_query, NotNull()); + dispatcher_->run(Event::Dispatcher::RunType::Block); +} + // dns_sd is very opaque and does not explicitly call out the state that is kept across queries. // The following two tests make sure that two consecutive queries for the same domain result in // successful resolution. This is implicitly testing the behavior of kDNSServiceFlagsAdd across @@ -316,24 +462,25 @@ TEST_F(AppleDnsImplTest, DoubleLookupInOneLoop) { } TEST_F(AppleDnsImplTest, DnsIpAddressVersionInvalid) { + // The DNS queries are successful, but return no results. EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::Auto, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::V4Preferred, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::V4Only, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::V6Only, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_NE(nullptr, resolveWithExpectations("invalidDnsName", DnsLookupFamily::All, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); } @@ -362,9 +509,9 @@ TEST_F(AppleDnsImplTest, Cancel) { dispatcher_->run(Event::Dispatcher::RunType::Block); } -TEST_F(AppleDnsImplTest, Timeout) { +TEST_F(AppleDnsImplTest, NonExistentDomain) { EXPECT_NE(nullptr, resolveWithExpectations("some.domain", DnsLookupFamily::V6Only, - DnsResolver::ResolutionStatus::Failure, false)); + DnsResolver::ResolutionStatus::Success, false)); dispatcher_->run(Event::Dispatcher::RunType::Block); } @@ -437,8 +584,8 @@ class AppleDnsImplFakeApiTest : public testing::Test { Network::Address::Ipv4Instance address(&addr4); absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll( // Have the API call synchronously call the provided callback. WithArgs<5, 6>(Invoke([&](DNSServiceGetAddrInfoReply callback, void* context) -> void { @@ -482,8 +629,8 @@ class AppleDnsImplFakeApiTest : public testing::Test { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -537,10 +684,18 @@ class AppleDnsImplFakeApiTest : public testing::Test { case V4: reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, query); + // There will always be 2 callbacks, one will have NoSuchRecord for the address family that + // wasn't requested. + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); break; case V6: reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address_v6.sockAddr(), 30, query); + // There will always be 2 callbacks, one will have NoSuchRecord for the address family that + // wasn't requested. + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV4Address().sockAddr(), 30, query); break; case Both: reply_callback(nullptr, kDNSServiceFlagsAdd | kDNSServiceFlagsMoreComing, 0, @@ -589,8 +744,8 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInSocketAccess) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(-1)); @@ -625,8 +780,8 @@ TEST_F(AppleDnsImplFakeApiTest, InvalidFileEvent) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -661,8 +816,8 @@ TEST_F(AppleDnsImplFakeApiTest, ErrorInProcessResult) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -720,7 +875,7 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletionUnroutableFamilies) { absl::Notification dns_callback_executed; EXPECT_CALL(dns_service_, - dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll( @@ -728,6 +883,8 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletionUnroutableFamilies) { WithArgs<5, 6>(Invoke([&](DNSServiceGetAddrInfoReply callback, void* context) -> void { callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, context); + callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, context); })), Return(kDNSServiceErr_NoError))); @@ -757,13 +914,15 @@ TEST_F(AppleDnsImplFakeApiTest, QuerySynchronousCompletion) { Network::Address::Ipv4Instance address(&addr4); absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll( // Have the API call synchronously call the provided callback. WithArgs<5, 6>(Invoke([&](DNSServiceGetAddrInfoReply callback, void* context) -> void { callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, context); + callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, context); })), Return(kDNSServiceErr_NoError))); @@ -817,8 +976,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddresses) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -843,6 +1002,9 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddresses) { reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address2.sockAddr(), 30, query); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); + dns_callback_executed.WaitForNotification(); } @@ -895,8 +1057,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleAddressesSecondOneFails) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -945,8 +1107,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { absl::Notification dns_callback_executed2; // Start first query. - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -966,9 +1128,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { ASSERT_NE(nullptr, query); // Start second query. - EXPECT_CALL(dns_service_, - dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, - StrEq(hostname2.c_str()), _, _)) + EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, kDNSServiceProtocol_IPv4, + StrEq(hostname2.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback2), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -991,9 +1152,13 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueries) { // be pending. reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, query); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); reply_callback2(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname2.c_str(), address2.sockAddr(), 30, query2); + reply_callback2(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname2.c_str(), + emptyV6Address().sockAddr(), 30, query2); dns_callback_executed.WaitForNotification(); dns_callback_executed2.WaitForNotification(); @@ -1015,8 +1180,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { absl::Notification dns_callback_executed2; // Start first query. - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -1038,9 +1203,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { ASSERT_NE(nullptr, query); // Start second query. - EXPECT_CALL(dns_service_, - dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, kDNSServiceProtocol_IPv4, - StrEq(hostname2.c_str()), _, _)) + EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, kDNSServiceProtocol_IPv4, + StrEq(hostname2.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback2), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -1059,6 +1223,8 @@ TEST_F(AppleDnsImplFakeApiTest, MultipleQueriesOneFails) { reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, query); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); // The second query fails. reply_callback2(nullptr, 0, 0, kDNSServiceErr_Unknown, hostname2.c_str(), nullptr, 30, query2); @@ -1078,8 +1244,8 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -1098,7 +1264,10 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithOnlyNonAdditiveReplies) { // Reply _without_ add and _without_ more coming flags. This should cause a flush with an empty // response. - reply_callback(nullptr, 0, 0, kDNSServiceErr_NoError, hostname.c_str(), nullptr, 30, query); + reply_callback(nullptr, 0, 0, kDNSServiceErr_NoError, hostname.c_str(), address.sockAddr(), 30, + query); + reply_callback(nullptr, 0, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); dns_callback_executed.WaitForNotification(); } @@ -1112,8 +1281,8 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { Network::Address::Ipv4Instance address(&addr4); DNSServiceGetAddrInfoReply reply_callback; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll(SaveArg<5>(&reply_callback), Return(kDNSServiceErr_NoError))); EXPECT_CALL(dns_service_, dnsServiceRefSockFD(_)).WillOnce(Return(0)); @@ -1127,7 +1296,7 @@ TEST_F(AppleDnsImplFakeApiTest, ResultWithNullAddress) { EXPECT_DEATH(reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), nullptr, 30, query), - "invalid to add null address"); + "address cannot be null"); } TEST_F(AppleDnsImplFakeApiTest, DeallocateOnDestruction) { @@ -1149,8 +1318,8 @@ TEST_F(AppleDnsImplFakeApiTest, DeallocateOnDestruction) { DNSServiceGetAddrInfoReply reply_callback; absl::Notification dns_callback_executed; - EXPECT_CALL(dns_service_, dnsServiceGetAddrInfo(_, kDNSServiceFlagsTimeout, 0, 0, - StrEq(hostname.c_str()), _, _)) + EXPECT_CALL(dns_service_, + dnsServiceGetAddrInfo(_, SERVICE_FLAGS, 0, 0, StrEq(hostname.c_str()), _, _)) .WillOnce(DoAll( SaveArg<5>(&reply_callback), WithArgs<0>(Invoke([](DNSServiceRef* ref) -> void { *ref = new _DNSServiceRef_t{}; })), @@ -1175,6 +1344,8 @@ TEST_F(AppleDnsImplFakeApiTest, DeallocateOnDestruction) { reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoError, hostname.c_str(), address2.sockAddr(), 30, query); + reply_callback(nullptr, kDNSServiceFlagsAdd, 0, kDNSServiceErr_NoSuchRecord, hostname.c_str(), + emptyV6Address().sockAddr(), 30, query); dns_callback_executed.WaitForNotification(); } diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index de4bc0f26c16..f65c10288866 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1452,6 +1452,7 @@ viewport vip virtualhost virtualize +virtualized vptr vtable vtt @@ -1510,4 +1511,5 @@ PCIE EDNS CNAME NAT +NXDOMAIN DNAT From a873580f0f5a62e1fd703ef66f612f05c3e364b9 Mon Sep 17 00:00:00 2001 From: Stephan Zuercher Date: Fri, 24 May 2024 15:24:59 -0700 Subject: [PATCH 13/22] docs: clarify Windows build status (#34328) Signed-off-by: Stephan Zuercher --- ci/README.md | 4 + docs/root/_include/windows_support_ended.rst | 5 + docs/root/faq/overview.rst | 2 + docs/root/faq/windows/win_fips_support.rst | 2 + .../windows/win_not_supported_features.rst | 2 + docs/root/faq/windows/win_performance.rst | 2 + docs/root/faq/windows/win_requirements.rst | 4 +- docs/root/faq/windows/win_run_as_service.rst | 2 + docs/root/faq/windows/win_security.rst | 2 + docs/root/start/building.rst | 8 +- docs/root/start/install.rst | 31 ------ docs/root/start/quick-start/run-envoy.rst | 103 +----------------- docs/root/start/sandboxes/setup.rst | 3 - .../start/sandboxes/win32_front_proxy.rst | 2 + 14 files changed, 33 insertions(+), 139 deletions(-) create mode 100644 docs/root/_include/windows_support_ended.rst diff --git a/ci/README.md b/ci/README.md index 5218934e09fa..a1cfaa6e9af2 100644 --- a/ci/README.md +++ b/ci/README.md @@ -27,6 +27,10 @@ main commit at which the binary was compiled, and `latest` corresponds to a bina ## Windows 2019 Envoy image +On August 31, 2023 the Envoy project ended official Windows support due to a lack of resources. +We will continue to accept patches related to the Windows build. Until further notice, Windows +builds are excluded from Envoy CI, as well as the Envoy release and security processes. + The Windows 2019 based Envoy Docker image at [`envoyproxy/envoy-build-windows2019:`](https://hub.docker.com/r/envoyproxy/envoy-build-windows2019/) is used for CI checks, where `` is specified in [`envoy_build_sha.sh`](https://github.com/envoyproxy/envoy/blob/main/ci/envoy_build_sha.sh). Developers may work with the most recent `envoyproxy/envoy-build-windows2019` image to provide a self-contained environment for building Envoy binaries and diff --git a/docs/root/_include/windows_support_ended.rst b/docs/root/_include/windows_support_ended.rst new file mode 100644 index 000000000000..429b5bb89845 --- /dev/null +++ b/docs/root/_include/windows_support_ended.rst @@ -0,0 +1,5 @@ +.. note:: + + On August 31, 2023 the Envoy project ended official Windows support due to a lack of resources. + We will continue to accept patches related to the Windows build. Until further notice, Windows + builds are excluded from Envoy CI, as well as the Envoy release and security processes. diff --git a/docs/root/faq/overview.rst b/docs/root/faq/overview.rst index d8dc33cba895..c646b34149eb 100644 --- a/docs/root/faq/overview.rst +++ b/docs/root/faq/overview.rst @@ -87,6 +87,8 @@ Extensions Windows ------- +.. include:: ../_include/windows_support_ended.rst + .. toctree:: :maxdepth: 2 diff --git a/docs/root/faq/windows/win_fips_support.rst b/docs/root/faq/windows/win_fips_support.rst index ed23af85d5fa..6d25913c201f 100644 --- a/docs/root/faq/windows/win_fips_support.rst +++ b/docs/root/faq/windows/win_fips_support.rst @@ -1,6 +1,8 @@ Does Envoy on Windows support FIPS? =================================== +.. include:: ../../_include/windows_support_ended.rst + Envoy uses `BoringSSL `_ which is a slimmed down TLS implementation. At the time of writing, BoringSSL does not support a FIPS mode on Windows. As a result, Envoy does not offer support for FIPS on Windows. diff --git a/docs/root/faq/windows/win_not_supported_features.rst b/docs/root/faq/windows/win_not_supported_features.rst index 76c1b8da8467..1f85b1efee90 100644 --- a/docs/root/faq/windows/win_not_supported_features.rst +++ b/docs/root/faq/windows/win_not_supported_features.rst @@ -1,6 +1,8 @@ Which Envoy features are not supported on Windows? ================================================== +.. include:: ../../_include/windows_support_ended.rst + The vast majority of Envoy features are supported on Windows. There are few exceptions that are documented explicitly. The most notable features that are not supported on Windows are: diff --git a/docs/root/faq/windows/win_performance.rst b/docs/root/faq/windows/win_performance.rst index 180238cc10ed..85c419d9ac66 100644 --- a/docs/root/faq/windows/win_performance.rst +++ b/docs/root/faq/windows/win_performance.rst @@ -1,6 +1,8 @@ How fast is Envoy on Windows? ============================= +.. include:: ../../_include/windows_support_ended.rst + Everything that is mentioned in :ref:`How fast is Envoy? ` applies to Windows. We have done some work to improve the event loop on Windows. That being said, we have observed that the tail performance of Envoy on Windows tends to be worse compared to Linux, especially when TLS is involved. diff --git a/docs/root/faq/windows/win_requirements.rst b/docs/root/faq/windows/win_requirements.rst index fa808d3fb307..39c7a9b99da3 100644 --- a/docs/root/faq/windows/win_requirements.rst +++ b/docs/root/faq/windows/win_requirements.rst @@ -1,7 +1,9 @@ What are the requirements to run on Envoy on Windows? ===================================================== -Envoy is tested on Windows Server Core 2019 (Long-Term Servicing Channel). This corresponds to OS version 10.0.17763.1879. We have also tested a few more recent versions of Windows Server +.. include:: ../../_include/windows_support_ended.rst + +Envoy was tested on Windows Server Core 2019 (Long-Term Servicing Channel). This corresponds to OS version 10.0.17763.1879. We have also tested a few more recent versions of Windows Server and in general higher versions will be fully supported. For more info please see `Windows Server Core `_. To build Envoy from source you will need to have at least Windows 10 SDK, version 1803 (10.0.17134.12). Earlier versions will not compile because the ``afunix.h`` header is not available. diff --git a/docs/root/faq/windows/win_run_as_service.rst b/docs/root/faq/windows/win_run_as_service.rst index 9458d901cdcb..588a6fd28185 100644 --- a/docs/root/faq/windows/win_run_as_service.rst +++ b/docs/root/faq/windows/win_run_as_service.rst @@ -1,6 +1,8 @@ Can I run Envoy on Windows under SCM? ===================================== +.. include:: ../../_include/windows_support_ended.rst + .. note:: This feature is still in Experimental state. diff --git a/docs/root/faq/windows/win_security.rst b/docs/root/faq/windows/win_security.rst index 7eb9a653cc06..ae56e948f506 100644 --- a/docs/root/faq/windows/win_security.rst +++ b/docs/root/faq/windows/win_security.rst @@ -1,4 +1,6 @@ What is the security release process? ===================================== +.. include:: ../../_include/windows_support_ended.rst + We follow the process described at `Security Reporting Process `_. diff --git a/docs/root/start/building.rst b/docs/root/start/building.rst index 0a07818efc94..fb9a9aae3c1a 100644 --- a/docs/root/start/building.rst +++ b/docs/root/start/building.rst @@ -6,8 +6,8 @@ Building The Envoy build system uses `Bazel `_. -In order to ease initial building and for a quick start, we provide an Ubuntu 16 and a Windows based docker containers -that have everything needed inside of it to build and *statically link* Envoy, see :repo:`ci/README.md`. +In order to ease initial building and for a quick start, we provide a recent Ubuntu-based docker container +that has everything needed inside of it to build and *statically link* Envoy, see :repo:`ci/README.md`. In order to build without using the Docker container, follow the instructions at :repo:`bazel/README.md`. @@ -33,7 +33,9 @@ as the new tcmalloc code is not guaranteed to compile with lower versions of Cla Windows Target Requirements --------------------------- -Envoy now suports Windows as a target platform. The requirements below only apply if you want to build the Windows +.. include:: ../_include/windows_support_ended.rst + +Envoy supports Windows as a target platform. The requirements below only apply if you want to build the Windows native executable. If you want to build the Linux version of Envoy on Windows either with WSL or Linux containers please see the Linux requirements above. diff --git a/docs/root/start/install.rst b/docs/root/start/install.rst index 4a3158e5a852..a04b04ff2c22 100644 --- a/docs/root/start/install.rst +++ b/docs/root/start/install.rst @@ -73,18 +73,6 @@ You can install Envoy on Mac OSX using the official brew repositories. $ brew update $ brew install envoy -.. _start_install_windows: - -Install Envoy on Windows -~~~~~~~~~~~~~~~~~~~~~~~~ - -You can run Envoy using the official Windows Docker image. - -.. substitution-code-block:: console - - $ docker pull envoyproxy/|envoy_windows_docker_image| - $ docker run --rm envoyproxy/|envoy_windows_docker_image| --version - .. _start_install_docker: Install Envoy using Docker @@ -215,25 +203,6 @@ The following table shows the available Docker tag variants for the latest - :dockerhub_envoy:`tools-dev` -`envoyproxy/envoy-windows `__, `envoyproxy/envoy-windows-dev `__ -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Release binary with symbols stripped on top of a Windows Server 1809 base. - -The ``windows-dev`` image also contains build tools. - -.. list-table:: - :widths: auto - :header-rows: 1 - :stub-columns: 1 - - * - variant - - latest stable (amd64) - - main dev (amd64) - * - envoy-windows - - :dockerhub_envoy:`windows` - - :dockerhub_envoy:`windows-dev` - .. _install_tools: `envoyproxy/envoy-build-ubuntu `__ diff --git a/docs/root/start/quick-start/run-envoy.rst b/docs/root/start/quick-start/run-envoy.rst index 3f47dce68d92..3d54542dbb16 100644 --- a/docs/root/start/quick-start/run-envoy.rst +++ b/docs/root/start/quick-start/run-envoy.rst @@ -31,14 +31,6 @@ Once you have :ref:`installed Envoy `, you can check the version inform envoyproxy/|envoy_docker_image| \ --version ... - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> docker run --rm - 'envoyproxy/|envoy_windows_docker_image|' - --version - ... .. _start_quick_start_help: @@ -66,15 +58,6 @@ flag: --help ... - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> docker run --rm - 'envoyproxy/|envoy_windows_docker_image|' - --help - ... - .. _start_quick_start_config: Run Envoy with the demo configuration @@ -124,33 +107,6 @@ Envoy will parse the config file according to the file extension, please see the -c /envoy-custom.yaml ... - .. tab:: Docker (Windows Image) - - You can start the Envoy Docker image without specifying a configuration file, and - it will use the demo config by default. - - .. substitution-code-block:: powershell - - PS> docker run --rm -it - -p '9901:9901' - -p '10000:10000' - 'envoyproxy/|envoy_windows_docker_image|' - ... - - To specify a custom configuration you can mount the config into the container, and specify the path with ``-c``. - - Assuming you have a custom configuration in the current directory named ``envoy-custom.yaml``, from PowerShell run: - - .. substitution-code-block:: powershell - - PS> docker run --rm -it - -v "$PWD\:`"C:\envoy-configs`"" - -p '9901:9901' - -p '10000:10000' - 'envoyproxy/|envoy_windows_docker_image|' - -c 'C:\envoy-configs\envoy-custom.yaml' - ... - Check Envoy is proxying on http://localhost:10000. .. code-block:: console @@ -201,6 +157,8 @@ Next, start the Envoy server using the override configuration: On Windows run: + .. include:: ../../_include/windows_support_ended.rst + .. code-block:: powershell $ envoy -c envoy-demo.yaml --config-yaml "$(Get-Content -Raw envoy-override.yaml)" @@ -218,18 +176,6 @@ Next, start the Envoy server using the override configuration: --config-yaml "$(cat envoy-override.yaml)" ... - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> docker run --rm -it - -p '9902:9902' - -p '10000:10000' - 'envoyproxy/|envoy_windows_docker_image|' - -c 'C:\ProgramData\envoy.yaml' - --config-yaml "$(Get-Content -Raw envoy-override.yaml)" - ... - The Envoy admin interface should now be available on http://localhost:9902. .. code-block:: console @@ -303,20 +249,6 @@ For invalid configuration the process will print the errors and exit with ``1``. [2020-11-08 12:36:06.549][11][info][config] [source/server/configuration_impl.cc:121] loading stats sink configuration configuration 'my-envoy-config.yaml' OK - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> docker run --rm -it - -v "$PWD\:`"C:\envoy-configs`"" - -p '9901:9901' - -p '10000:10000' - 'envoyproxy/|envoy_windows_docker_image|' - --mode validate - -c 'C:\envoy-configs\my-envoy-config.yaml' - - configuration 'my-envoy-config.yaml' OK - Envoy logging ------------- @@ -346,24 +278,6 @@ This can be overridden using :option:`--log-path`. -c /etc/envoy/envoy.yaml \ --log-path logs/custom.log - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> mkdir logs - PS> docker run --rm -it - -p '10000:10000' - -v "$PWD\logs\:`"C:\logs`"" - 'envoyproxy/|envoy_windows_docker_image|' - -c 'C:\ProgramData\envoy.yaml' - --log-path 'C:\logs\custom.log' - - .. note:: - - Envoy on a Windows system Envoy will output to ``CON`` by default. - - This can also be used as a logging path when configuring logging. - :ref:`Access log ` paths can be set for the :ref:`admin interface `, and for configured :ref:`listeners `. @@ -454,19 +368,6 @@ which are set to ``debug`` and ``trace`` respectively. --component-log-level upstream:debug,connection:trace ... - .. tab:: Docker (Windows Image) - - .. substitution-code-block:: powershell - - PS> mkdir logs - PS> docker run --rm -it - -p '10000:10000' - envoyproxy/|envoy_windws_docker_image| - -c 'C:\ProgramData\envoy.yaml' - -l off - --component-log-level 'upstream:debug,connection:trace' - ... - .. tip:: See ``ALL_LOGGER_IDS`` in :repo:`logger.h ` for a list of components. diff --git a/docs/root/start/sandboxes/setup.rst b/docs/root/start/sandboxes/setup.rst index 19de3de6b066..24b13c957ae5 100644 --- a/docs/root/start/sandboxes/setup.rst +++ b/docs/root/start/sandboxes/setup.rst @@ -37,9 +37,6 @@ The user account running the examples will need to have permission to use Docker Full instructions for installing Docker can be found on the `Docker website `_ -If you want to use the Windows based Envoy images make sure that you -`switch Docker to use Windows containers `_. - .. _start_sandboxes_setup_docker_compose: Install ``docker compose`` diff --git a/docs/root/start/sandboxes/win32_front_proxy.rst b/docs/root/start/sandboxes/win32_front_proxy.rst index 6c711041fa3d..2342bacc7eb3 100644 --- a/docs/root/start/sandboxes/win32_front_proxy.rst +++ b/docs/root/start/sandboxes/win32_front_proxy.rst @@ -1,6 +1,8 @@ Windows based Front proxy ========================= +.. include:: ../../_include/windows_support_ended.rst + .. sidebar:: Requirements .. include:: _include/docker-env-setup-link.rst From d69544eec9f285b9fe05548ea18b5f3dc75fb087 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Fri, 24 May 2024 23:34:01 -0500 Subject: [PATCH 14/22] mobile: Cache the jclass objects (#34357) Caching jclass should help with the performance as per https://developer.android.com/training/articles/perf-jni#jclass,-jmethodid,-and-jfieldid. Risk Level: low Testing: unit tests Docs Changes: n/a Release Notes: n/a Platform Specific Features: mobile Signed-off-by: Fredy Wijaya --- mobile/library/jni/jni_helper.cc | 12 +-- mobile/library/jni/jni_helper.h | 11 +-- mobile/library/jni/jni_impl.cc | 24 +++-- mobile/library/jni/jni_utility.cc | 88 +++++++++---------- mobile/library/jni/jni_utility.h | 8 +- mobile/test/jni/jni_helper_test.cc | 5 +- .../jni/jni_http_proxy_test_server_factory.cc | 14 ++- .../test/jni/jni_http_test_server_factory.cc | 14 ++- mobile/test/jni/jni_utility_test.cc | 11 +++ .../test/jni/jni_xds_test_server_factory.cc | 16 +++- 10 files changed, 118 insertions(+), 85 deletions(-) diff --git a/mobile/library/jni/jni_helper.cc b/mobile/library/jni/jni_helper.cc index dbfe2c1d66c8..0d10b3156abf 100644 --- a/mobile/library/jni/jni_helper.cc +++ b/mobile/library/jni/jni_helper.cc @@ -89,13 +89,7 @@ jmethodID JniHelper::getStaticMethodId(jclass clazz, const char* name, const cha return method_id; } -LocalRefUniquePtr JniHelper::findClass(const char* class_name) { - LocalRefUniquePtr result(env_->FindClass(class_name), LocalRefDeleter(env_)); - rethrowException(); - return result; -} - -jclass JniHelper::findClassFromCache(const char* class_name) { +jclass JniHelper::findClass(const char* class_name) { if (auto i = JCLASS_CACHES.find(class_name); i != JCLASS_CACHES.end()) { return i->second; } @@ -108,9 +102,9 @@ LocalRefUniquePtr JniHelper::getObjectClass(jobject object) { } void JniHelper::throwNew(const char* java_class_name, const char* message) { - LocalRefUniquePtr java_class = findClass(java_class_name); + jclass java_class = findClass(java_class_name); if (java_class != nullptr) { - jint error = env_->ThrowNew(java_class.get(), message); + jint error = env_->ThrowNew(java_class, message); ASSERT(error == JNI_OK, "Failed calling ThrowNew."); } } diff --git a/mobile/library/jni/jni_helper.h b/mobile/library/jni/jni_helper.h index 8fa22b2661a9..36622a8fa357 100644 --- a/mobile/library/jni/jni_helper.h +++ b/mobile/library/jni/jni_helper.h @@ -230,18 +230,11 @@ class JniHelper { jmethodID getStaticMethodId(jclass clazz, const char* name, const char* signature); /** - * Finds the given `class_name` using Java classloader. + * Finds the given `class_name` using from the cache. * * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#findclass */ - [[nodiscard]] LocalRefUniquePtr findClass(const char* class_name); - - /** - * Finds the given `class_name` from the cache. - * - * https://docs.oracle.com/en/java/javase/17/docs/specs/jni/functions.html#findclass - */ - [[nodiscard]] jclass findClassFromCache(const char* class_name); + [[nodiscard]] jclass findClass(const char* class_name); /** * Returns the class of a given `object`. diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 10752af2a148..2a613724708e 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -21,6 +21,18 @@ using Envoy::Platform::EngineBuilder; extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Object"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Integer"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/ClassLoader"); + Envoy::JNI::JniHelper::addClassToCache("java/nio/ByteBuffer"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Throwable"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/UnsupportedOperationException"); + Envoy::JNI::JniHelper::addClassToCache("[B"); + Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); + Envoy::JNI::JniHelper::addClassToCache("java/util/LinkedHashMap"); + Envoy::JNI::JniHelper::addClassToCache("java/util/HashMap"); + Envoy::JNI::JniHelper::addClassToCache("java/util/List"); + Envoy::JNI::JniHelper::addClassToCache("java/util/ArrayList"); Envoy::JNI::JniHelper::addClassToCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); Envoy::JNI::JniHelper::addClassToCache( "io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); @@ -229,15 +241,13 @@ jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& head // Create a "no operation" result: // 1. Tell the filter chain to continue the iteration. // 2. Return headers received on as method's input as part of the method's output. - Envoy::JNI::LocalRefUniquePtr jcls_object_array = - jni_helper.findClass("java/lang/Object"); + jclass jcls_object_array = jni_helper.findClass("java/lang/Object"); Envoy::JNI::LocalRefUniquePtr noopResult = - jni_helper.newObjectArray(2, jcls_object_array.get(), NULL); + jni_helper.newObjectArray(2, jcls_object_array, NULL); - Envoy::JNI::LocalRefUniquePtr jcls_int = jni_helper.findClass("java/lang/Integer"); - jmethodID jmid_intInit = jni_helper.getMethodId(jcls_int.get(), "", "(I)V"); - Envoy::JNI::LocalRefUniquePtr j_status = - jni_helper.newObject(jcls_int.get(), jmid_intInit, 0); + jclass jcls_int = jni_helper.findClass("java/lang/Integer"); + jmethodID jmid_intInit = jni_helper.getMethodId(jcls_int, "", "(I)V"); + Envoy::JNI::LocalRefUniquePtr j_status = jni_helper.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. jni_helper.setObjectArrayElement(noopResult.get(), 0, j_status.get()); diff --git a/mobile/library/jni/jni_utility.cc b/mobile/library/jni/jni_utility.cc index 80d16aa87ffe..8f736258f36b 100644 --- a/mobile/library/jni/jni_utility.cc +++ b/mobile/library/jni/jni_utility.cc @@ -23,9 +23,9 @@ jobject getClassLoader() { LocalRefUniquePtr findClass(const char* class_name) { JniHelper jni_helper(JniHelper::getThreadLocalEnv()); - LocalRefUniquePtr class_loader = jni_helper.findClass("java/lang/ClassLoader"); - jmethodID find_class_method = jni_helper.getMethodId(class_loader.get(), "loadClass", - "(Ljava/lang/String;)Ljava/lang/Class;"); + jclass class_loader = jni_helper.findClass("java/lang/ClassLoader"); + jmethodID find_class_method = + jni_helper.getMethodId(class_loader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); LocalRefUniquePtr str_class_name = jni_helper.newStringUtf(class_name); LocalRefUniquePtr clazz = jni_helper.callObjectMethod( getClassLoader(), find_class_method, str_class_name.get()); @@ -43,8 +43,8 @@ void jniDeleteConstGlobalRef(const void* context) { } int javaIntegerToCppInt(JniHelper& jni_helper, jobject boxed_integer) { - LocalRefUniquePtr jcls_Integer = jni_helper.findClass("java/lang/Integer"); - jmethodID jmid_intValue = jni_helper.getMethodId(jcls_Integer.get(), "intValue", "()I"); + jclass jcls_Integer = jni_helper.findClass("java/lang/Integer"); + jmethodID jmid_intValue = jni_helper.getMethodId(jcls_Integer, "intValue", "()I"); return jni_helper.callIntMethod(boxed_integer, jmid_intValue); } @@ -120,11 +120,11 @@ envoy_data javaByteBufferToEnvoyData(JniHelper& jni_helper, jobject j_data) { jlong data_length = jni_helper.getDirectBufferCapacity(j_data); if (data_length < 0) { - LocalRefUniquePtr jcls_ByteBuffer = jni_helper.findClass("java/nio/ByteBuffer"); + jclass jcls_ByteBuffer = jni_helper.findClass("java/nio/ByteBuffer"); // We skip checking hasArray() because only direct ByteBuffers or array-backed ByteBuffers // are supported. We will crash here if this is an invalid buffer, but guards may be // implemented in the JVM layer. - jmethodID jmid_array = jni_helper.getMethodId(jcls_ByteBuffer.get(), "array", "()[B"); + jmethodID jmid_array = jni_helper.getMethodId(jcls_ByteBuffer, "array", "()[B"); LocalRefUniquePtr array = jni_helper.callObjectMethod(j_data, jmid_array); envoy_data native_data = javaByteArrayToEnvoyData(jni_helper, array.get()); @@ -139,11 +139,11 @@ envoy_data javaByteBufferToEnvoyData(JniHelper& jni_helper, jobject j_data, jlon uint8_t* direct_address = jni_helper.getDirectBufferAddress(j_data); if (direct_address == nullptr) { - LocalRefUniquePtr jcls_ByteBuffer = jni_helper.findClass("java/nio/ByteBuffer"); + jclass jcls_ByteBuffer = jni_helper.findClass("java/nio/ByteBuffer"); // We skip checking hasArray() because only direct ByteBuffers or array-backed ByteBuffers // are supported. We will crash here if this is an invalid buffer, but guards may be // implemented in the JVM layer. - jmethodID jmid_array = jni_helper.getMethodId(jcls_ByteBuffer.get(), "array", "()[B"); + jmethodID jmid_array = jni_helper.getMethodId(jcls_ByteBuffer, "array", "()[B"); LocalRefUniquePtr array = jni_helper.callObjectMethod(j_data, jmid_array); envoy_data native_data = javaByteArrayToEnvoyData(jni_helper, array.get(), data_length); @@ -229,9 +229,9 @@ envoy_map javaArrayOfObjectArrayToEnvoyMap(JniHelper& jni_helper, jobjectArray e LocalRefUniquePtr envoyHeadersToJavaArrayOfObjectArray(JniHelper& jni_helper, const Envoy::Types::ManagedEnvoyHeaders& map) { - LocalRefUniquePtr jcls_byte_array = jni_helper.findClass("java/lang/Object"); + jclass jcls_byte_array = jni_helper.findClass("java/lang/Object"); LocalRefUniquePtr javaArray = - jni_helper.newObjectArray(2 * map.get().length, jcls_byte_array.get(), nullptr); + jni_helper.newObjectArray(2 * map.get().length, jcls_byte_array, nullptr); for (envoy_map_size_t i = 0; i < map.get().length; i++) { LocalRefUniquePtr key = @@ -248,9 +248,9 @@ envoyHeadersToJavaArrayOfObjectArray(JniHelper& jni_helper, LocalRefUniquePtr vectorStringToJavaArrayOfByteArray(JniHelper& jni_helper, const std::vector& v) { - LocalRefUniquePtr jcls_byte_array = jni_helper.findClass("[B"); + jclass jcls_byte_array = jni_helper.findClass("[B"); LocalRefUniquePtr joa = - jni_helper.newObjectArray(v.size(), jcls_byte_array.get(), nullptr); + jni_helper.newObjectArray(v.size(), jcls_byte_array, nullptr); for (size_t i = 0; i < v.size(); ++i) { LocalRefUniquePtr byte_array = byteArrayToJavaByteArray( @@ -367,14 +367,14 @@ absl::flat_hash_map javaMapToCppMap(JniHelper& jni_hel auto java_entry_set_object = jni_helper.callObjectMethod(java_map, java_entry_set_method_id); auto java_set_class = jni_helper.getObjectClass(java_entry_set_object.get()); - auto java_map_entry_class = jni_helper.findClass("java/util/Map$Entry"); + jclass java_map_entry_class = jni_helper.findClass("java/util/Map$Entry"); auto java_iterator_method_id = jni_helper.getMethodId(java_set_class.get(), "iterator", "()Ljava/util/Iterator;"); auto java_get_key_method_id = - jni_helper.getMethodId(java_map_entry_class.get(), "getKey", "()Ljava/lang/Object;"); + jni_helper.getMethodId(java_map_entry_class, "getKey", "()Ljava/lang/Object;"); auto java_get_value_method_id = - jni_helper.getMethodId(java_map_entry_class.get(), "getValue", "()Ljava/lang/Object;"); + jni_helper.getMethodId(java_map_entry_class, "getValue", "()Ljava/lang/Object;"); auto java_iterator_object = jni_helper.callObjectMethod(java_entry_set_object.get(), java_iterator_method_id); @@ -403,18 +403,18 @@ absl::flat_hash_map javaMapToCppMap(JniHelper& jni_hel LocalRefUniquePtr cppHeadersToJavaHeaders(JniHelper& jni_helper, const Http::HeaderMap& cpp_headers) { // Use LinkedHashMap to preserve the insertion order. - auto java_map_class = jni_helper.findClass("java/util/LinkedHashMap"); - auto java_map_init_method_id = jni_helper.getMethodId(java_map_class.get(), "", "()V"); + jclass java_map_class = jni_helper.findClass("java/util/LinkedHashMap"); + auto java_map_init_method_id = jni_helper.getMethodId(java_map_class, "", "()V"); auto java_map_put_method_id = jni_helper.getMethodId( - java_map_class.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + java_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); auto java_map_get_method_id = - jni_helper.getMethodId(java_map_class.get(), "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); - auto java_map_object = jni_helper.newObject(java_map_class.get(), java_map_init_method_id); + jni_helper.getMethodId(java_map_class, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); + auto java_map_object = jni_helper.newObject(java_map_class, java_map_init_method_id); - auto java_list_class = jni_helper.findClass("java/util/ArrayList"); - auto java_list_init_method_id = jni_helper.getMethodId(java_list_class.get(), "", "()V"); + jclass java_list_class = jni_helper.findClass("java/util/ArrayList"); + auto java_list_init_method_id = jni_helper.getMethodId(java_list_class, "", "()V"); auto java_list_add_method_id = - jni_helper.getMethodId(java_list_class.get(), "add", "(Ljava/lang/Object;)Z"); + jni_helper.getMethodId(java_list_class, "add", "(Ljava/lang/Object;)Z"); cpp_headers.iterate([&](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { std::string cpp_key = std::string(header.key().getStringView()); @@ -431,7 +431,7 @@ LocalRefUniquePtr cppHeadersToJavaHeaders(JniHelper& jni_helper, jni_helper.callObjectMethod(java_map_object.get(), java_map_get_method_id, java_key.get()); if (existing_value == nullptr) { // the key does not exist // Create a new list. - auto java_list_object = jni_helper.newObject(java_list_class.get(), java_list_init_method_id); + auto java_list_object = jni_helper.newObject(java_list_class, java_list_init_method_id); jni_helper.callBooleanMethod(java_list_object.get(), java_list_add_method_id, java_value.get()); // Put the new list into the map. @@ -456,14 +456,14 @@ void javaHeadersToCppHeaders(JniHelper& jni_helper, jobject java_headers, auto java_entry_set_object = jni_helper.callObjectMethod(java_headers, java_entry_set_method_id); auto java_set_class = jni_helper.getObjectClass(java_entry_set_object.get()); - auto java_map_entry_class = jni_helper.findClass("java/util/Map$Entry"); + jclass java_map_entry_class = jni_helper.findClass("java/util/Map$Entry"); auto java_map_iter_method_id = jni_helper.getMethodId(java_set_class.get(), "iterator", "()Ljava/util/Iterator;"); auto java_map_get_key_method_id = - jni_helper.getMethodId(java_map_entry_class.get(), "getKey", "()Ljava/lang/Object;"); + jni_helper.getMethodId(java_map_entry_class, "getKey", "()Ljava/lang/Object;"); auto java_map_get_value_method_id = - jni_helper.getMethodId(java_map_entry_class.get(), "getValue", "()Ljava/lang/Object;"); + jni_helper.getMethodId(java_map_entry_class, "getValue", "()Ljava/lang/Object;"); auto java_iter_object = jni_helper.callObjectMethod(java_entry_set_object.get(), java_map_iter_method_id); @@ -473,10 +473,10 @@ void javaHeadersToCppHeaders(JniHelper& jni_helper, jobject java_headers, auto java_iter_next_method_id = jni_helper.getMethodId(java_iterator_class.get(), "next", "()Ljava/lang/Object;"); - auto java_list_class = jni_helper.findClass("java/util/List"); - auto java_list_size_method_id = jni_helper.getMethodId(java_list_class.get(), "size", "()I"); + jclass java_list_class = jni_helper.findClass("java/util/List"); + auto java_list_size_method_id = jni_helper.getMethodId(java_list_class, "size", "()I"); auto java_list_get_method_id = - jni_helper.getMethodId(java_list_class.get(), "get", "(I)Ljava/lang/Object;"); + jni_helper.getMethodId(java_list_class, "get", "(I)Ljava/lang/Object;"); while (jni_helper.callBooleanMethod(java_iter_object.get(), java_iter_has_next_method_id)) { auto java_entry_object = @@ -503,9 +503,9 @@ void javaHeadersToCppHeaders(JniHelper& jni_helper, jobject java_headers, } bool isJavaDirectByteBuffer(JniHelper& jni_helper, jobject java_byte_buffer) { - auto java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); + jclass java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); auto java_byte_buffer_is_direct_method_id = - jni_helper.getMethodId(java_byte_buffer_class.get(), "isDirect", "()Z"); + jni_helper.getMethodId(java_byte_buffer_class, "isDirect", "()Z"); return jni_helper.callBooleanMethod(java_byte_buffer, java_byte_buffer_is_direct_method_id); } @@ -541,9 +541,9 @@ LocalRefUniquePtr cppBufferInstanceToJavaDirectByteBuffer( Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, jobject java_byte_buffer, jlong length) { - auto java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); + jclass java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); auto java_byte_buffer_array_method_id = - jni_helper.getMethodId(java_byte_buffer_class.get(), "array", "()[B"); + jni_helper.getMethodId(java_byte_buffer_class, "array", "()[B"); auto java_byte_array = jni_helper.callObjectMethod(java_byte_buffer, java_byte_buffer_array_method_id); ASSERT(java_byte_array != nullptr, "The ByteBuffer argument is not a non-direct ByteBuffer."); @@ -556,20 +556,20 @@ Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_he LocalRefUniquePtr cppBufferInstanceToJavaNonDirectByteBuffer( JniHelper& jni_helper, const Buffer::Instance& cpp_buffer_instance, uint64_t length) { - auto java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); - auto java_byte_buffer_wrap_method_id = jni_helper.getStaticMethodId( - java_byte_buffer_class.get(), "wrap", "([B)Ljava/nio/ByteBuffer;"); + jclass java_byte_buffer_class = jni_helper.findClass("java/nio/ByteBuffer"); + auto java_byte_buffer_wrap_method_id = + jni_helper.getStaticMethodId(java_byte_buffer_class, "wrap", "([B)Ljava/nio/ByteBuffer;"); auto java_byte_array = jni_helper.newByteArray(static_cast(cpp_buffer_instance.length())); auto java_byte_array_elements = jni_helper.getByteArrayElements(java_byte_array.get(), nullptr); cpp_buffer_instance.copyOut(0, length, static_cast(java_byte_array_elements.get())); - return jni_helper.callStaticObjectMethod(java_byte_buffer_class.get(), - java_byte_buffer_wrap_method_id, java_byte_array.get()); + return jni_helper.callStaticObjectMethod(java_byte_buffer_class, java_byte_buffer_wrap_method_id, + java_byte_array.get()); } std::string getJavaExceptionMessage(JniHelper& jni_helper, jthrowable throwable) { - auto java_throwable_class = jni_helper.findClass("java/lang/Throwable"); + jclass java_throwable_class = jni_helper.findClass("java/lang/Throwable"); auto java_get_message_method_id = - jni_helper.getMethodId(java_throwable_class.get(), "getMessage", "()Ljava/lang/String;"); + jni_helper.getMethodId(java_throwable_class, "getMessage", "()Ljava/lang/String;"); auto java_exception_message = jni_helper.callObjectMethod(throwable, java_get_message_method_id); return javaStringToCppString(jni_helper, java_exception_message.get()); @@ -602,7 +602,7 @@ envoy_stream_intel javaStreamIntelToCppStreamIntel(JniHelper& jni_helper, LocalRefUniquePtr cppStreamIntelToJavaStreamIntel(JniHelper& jni_helper, const envoy_stream_intel& stream_intel) { auto java_stream_intel_class = - jni_helper.findClassFromCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); + jni_helper.findClass("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); auto java_stream_intel_init_method_id = jni_helper.getMethodId(java_stream_intel_class, "", "(JJJJ)V"); return jni_helper.newObject(java_stream_intel_class, java_stream_intel_init_method_id, @@ -688,7 +688,7 @@ LocalRefUniquePtr cppFinalStreamIntelToJavaFinalStreamIntel(JniHelper& jni_helper, const envoy_final_stream_intel& final_stream_intel) { auto java_final_stream_intel_class = - jni_helper.findClassFromCache("io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); + jni_helper.findClass("io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); auto java_final_stream_intel_init_method_id = jni_helper.getMethodId(java_final_stream_intel_class, "", "(JJJJJJJJJJJZJJJJ)V"); return jni_helper.newObject(java_final_stream_intel_class, java_final_stream_intel_init_method_id, diff --git a/mobile/library/jni/jni_utility.h b/mobile/library/jni/jni_utility.h index 0643562eaa32..110948d1e6d9 100644 --- a/mobile/library/jni/jni_utility.h +++ b/mobile/library/jni/jni_utility.h @@ -134,12 +134,12 @@ LocalRefUniquePtr cppStringToJavaString(JniHelper& jni_helper, /** Converts from C++'s map-type to Java `HashMap`. */ template LocalRefUniquePtr cppMapToJavaMap(JniHelper& jni_helper, const MapType& cpp_map) { - auto java_map_class = jni_helper.findClass("java/util/HashMap"); - auto java_map_init_method_id = jni_helper.getMethodId(java_map_class.get(), "", "(I)V"); + jclass java_map_class = jni_helper.findClass("java/util/HashMap"); + auto java_map_init_method_id = jni_helper.getMethodId(java_map_class, "", "(I)V"); auto java_map_put_method_id = jni_helper.getMethodId( - java_map_class.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); + java_map_class, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); auto java_map_object = - jni_helper.newObject(java_map_class.get(), java_map_init_method_id, cpp_map.size()); + jni_helper.newObject(java_map_class, java_map_init_method_id, cpp_map.size()); for (const auto& [cpp_key, cpp_value] : cpp_map) { auto java_key = cppStringToJavaString(jni_helper, cpp_key); auto java_value = cppStringToJavaString(jni_helper, cpp_value); diff --git a/mobile/test/jni/jni_helper_test.cc b/mobile/test/jni/jni_helper_test.cc index 6498c7a25735..1fb46f8be7bb 100644 --- a/mobile/test/jni/jni_helper_test.cc +++ b/mobile/test/jni/jni_helper_test.cc @@ -11,6 +11,8 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Exception"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/RuntimeException"); return Envoy::JNI::JniHelper::getVersion(); } @@ -75,8 +77,7 @@ extern "C" JNIEXPORT jclass JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelper JNIEnv* env, jclass, jstring class_name) { Envoy::JNI::JniHelper jni_helper(env); Envoy::JNI::StringUtfUniquePtr class_name_ptr = jni_helper.getStringUtfChars(class_name, nullptr); - Envoy::JNI::LocalRefUniquePtr clazz = jni_helper.findClass(class_name_ptr.get()); - return clazz.release(); + return jni_helper.findClass(class_name_ptr.get()); } extern "C" JNIEXPORT jclass JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getObjectClass( diff --git a/mobile/test/jni/jni_http_proxy_test_server_factory.cc b/mobile/test/jni/jni_http_proxy_test_server_factory.cc index 4839e41512f3..2314d0868397 100644 --- a/mobile/test/jni/jni_http_proxy_test_server_factory.cc +++ b/mobile/test/jni/jni_http_proxy_test_server_factory.cc @@ -7,6 +7,14 @@ // NOLINT(namespace-envoy) +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); + Envoy::JNI::JniHelper::addClassToCache( + "io/envoyproxy/envoymobile/engine/testing/HttpProxyTestServerFactory$HttpProxyTestServer"); + return Envoy::JNI::JniHelper::getVersion(); +} + extern "C" JNIEXPORT jobject JNICALL Java_io_envoyproxy_envoymobile_engine_testing_HttpProxyTestServerFactory_start(JNIEnv* env, jclass, jint type) { @@ -16,13 +24,13 @@ Java_io_envoyproxy_envoymobile_engine_testing_HttpProxyTestServerFactory_start(J Envoy::TestServer* test_server = new Envoy::TestServer(); test_server->start(static_cast(type)); - auto java_http_proxy_server_factory_class = jni_helper.findClass( + jclass java_http_proxy_server_factory_class = jni_helper.findClass( "io/envoyproxy/envoymobile/engine/testing/HttpProxyTestServerFactory$HttpProxyTestServer"); auto java_init_method_id = - jni_helper.getMethodId(java_http_proxy_server_factory_class.get(), "", "(JI)V"); + jni_helper.getMethodId(java_http_proxy_server_factory_class, "", "(JI)V"); int port = test_server->getPort(); return jni_helper - .newObject(java_http_proxy_server_factory_class.get(), java_init_method_id, + .newObject(java_http_proxy_server_factory_class, java_init_method_id, reinterpret_cast(test_server), static_cast(port)) .release(); } diff --git a/mobile/test/jni/jni_http_test_server_factory.cc b/mobile/test/jni/jni_http_test_server_factory.cc index 5782f4f3cc3f..dd6409b038ab 100644 --- a/mobile/test/jni/jni_http_test_server_factory.cc +++ b/mobile/test/jni/jni_http_test_server_factory.cc @@ -8,6 +8,14 @@ // NOLINT(namespace-envoy) +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); + Envoy::JNI::JniHelper::addClassToCache( + "io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory$HttpTestServer"); + return Envoy::JNI::JniHelper::getVersion(); +} + extern "C" JNIEXPORT jobject JNICALL Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start( JNIEnv* env, jclass, jint type, jobject headers, jstring body, jobject trailers) { @@ -22,15 +30,15 @@ Java_io_envoyproxy_envoymobile_engine_testing_HttpTestServerFactory_start( auto cpp_trailers = Envoy::JNI::javaMapToCppMap(jni_helper, trailers); test_server->setResponse(cpp_headers, cpp_body, cpp_trailers); - auto java_http_server_factory_class = jni_helper.findClass( + jclass java_http_server_factory_class = jni_helper.findClass( "io/envoyproxy/envoymobile/engine/testing/HttpTestServerFactory$HttpTestServer"); - auto java_init_method_id = jni_helper.getMethodId(java_http_server_factory_class.get(), "", + auto java_init_method_id = jni_helper.getMethodId(java_http_server_factory_class, "", "(JLjava/lang/String;ILjava/lang/String;)V"); auto ip_address = Envoy::JNI::cppStringToJavaString(jni_helper, test_server->getIpAddress()); int port = test_server->getPort(); auto address = Envoy::JNI::cppStringToJavaString(jni_helper, test_server->getAddress()); return jni_helper - .newObject(java_http_server_factory_class.get(), java_init_method_id, + .newObject(java_http_server_factory_class, java_init_method_id, reinterpret_cast(test_server), ip_address.get(), static_cast(port), address.get()) .release(); diff --git a/mobile/test/jni/jni_utility_test.cc b/mobile/test/jni/jni_utility_test.cc index 2a96d4a82784..ab5d3c5720c2 100644 --- a/mobile/test/jni/jni_utility_test.cc +++ b/mobile/test/jni/jni_utility_test.cc @@ -13,6 +13,17 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Object"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Integer"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/ClassLoader"); + Envoy::JNI::JniHelper::addClassToCache("java/nio/ByteBuffer"); + Envoy::JNI::JniHelper::addClassToCache("java/lang/Throwable"); + Envoy::JNI::JniHelper::addClassToCache("[B"); + Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); + Envoy::JNI::JniHelper::addClassToCache("java/util/LinkedHashMap"); + Envoy::JNI::JniHelper::addClassToCache("java/util/HashMap"); + Envoy::JNI::JniHelper::addClassToCache("java/util/List"); + Envoy::JNI::JniHelper::addClassToCache("java/util/ArrayList"); Envoy::JNI::JniHelper::addClassToCache("io/envoyproxy/envoymobile/engine/types/EnvoyStreamIntel"); Envoy::JNI::JniHelper::addClassToCache( "io/envoyproxy/envoymobile/engine/types/EnvoyFinalStreamIntel"); diff --git a/mobile/test/jni/jni_xds_test_server_factory.cc b/mobile/test/jni/jni_xds_test_server_factory.cc index b993335b13e9..50f587d20cc3 100644 --- a/mobile/test/jni/jni_xds_test_server_factory.cc +++ b/mobile/test/jni/jni_xds_test_server_factory.cc @@ -8,6 +8,14 @@ // NOLINT(namespace-envoy) +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + Envoy::JNI::JniHelper::initialize(vm); + Envoy::JNI::JniHelper::addClassToCache("java/util/Map$Entry"); + Envoy::JNI::JniHelper::addClassToCache( + "io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory$XdsTestServer"); + return Envoy::JNI::JniHelper::getVersion(); +} + extern "C" JNIEXPORT jobject JNICALL Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_create(JNIEnv* env, jclass) { // This is called via JNI from kotlin tests, and Envoy doesn't consider it a test thread @@ -18,14 +26,14 @@ Java_io_envoyproxy_envoymobile_engine_testing_XdsTestServerFactory_create(JNIEnv Envoy::ExtensionRegistry::registerFactories(); Envoy::XdsTestServer* test_server = new Envoy::XdsTestServer(); - auto java_xds_server_factory_class = jni_helper.findClass( + jclass java_xds_server_factory_class = jni_helper.findClass( "io/envoyproxy/envoymobile/engine/testing/XdsTestServerFactory$XdsTestServer"); - auto java_init_method_id = jni_helper.getMethodId(java_xds_server_factory_class.get(), "", - "(JLjava/lang/String;I)V"); + auto java_init_method_id = + jni_helper.getMethodId(java_xds_server_factory_class, "", "(JLjava/lang/String;I)V"); auto host = Envoy::JNI::cppStringToJavaString(jni_helper, test_server->getHost()); jint port = static_cast(test_server->getPort()); return jni_helper - .newObject(java_xds_server_factory_class.get(), java_init_method_id, + .newObject(java_xds_server_factory_class, java_init_method_id, reinterpret_cast(test_server), host.get(), port) .release(); } From d19462ed2cddbaf23bc04648d2ac9c271b52c57e Mon Sep 17 00:00:00 2001 From: zirain Date: Mon, 27 May 2024 10:32:27 +0800 Subject: [PATCH 15/22] rename getTraceIdAsHex to getTraceId (#34314) * rename getTraceId Signed-off-by: zirain * implement getTraceId Signed-off-by: zirain * fix test Signed-off-by: zirain * fix test Signed-off-by: zirain --------- Signed-off-by: zirain --- envoy/tracing/trace_driver.h | 6 +++--- source/common/formatter/http_specific_formatter.cc | 4 ++-- source/common/tracing/null_span_impl.h | 2 +- .../access_loggers/open_telemetry/access_log_impl.cc | 2 +- .../extensions/tracers/common/ot/opentracing_driver_impl.h | 4 +++- source/extensions/tracers/datadog/span.cc | 2 +- source/extensions/tracers/datadog/span.h | 2 +- .../tracers/opencensus/opencensus_tracer_impl.cc | 4 ++-- source/extensions/tracers/opentelemetry/tracer.cc | 7 +++---- source/extensions/tracers/opentelemetry/tracer.h | 2 +- source/extensions/tracers/skywalking/tracer.h | 2 +- source/extensions/tracers/xray/tracer.h | 2 +- source/extensions/tracers/zipkin/zipkin_tracer_impl.h | 2 +- test/common/formatter/substitution_formatter_test.cc | 3 +-- test/common/tracing/tracer_impl_test.cc | 2 +- .../access_loggers/open_telemetry/access_log_impl_test.cc | 4 ++-- .../extensions/filters/http/ext_proc/tracer_test_filter.cc | 2 +- .../tracers/common/ot/opentracing_driver_impl_test.cc | 2 +- test/extensions/tracers/datadog/span_test.cc | 6 +++--- test/extensions/tracers/opencensus/tracer_test.cc | 4 ++-- .../opentelemetry/opentelemetry_tracer_impl_test.cc | 2 +- test/extensions/tracers/skywalking/tracer_test.cc | 3 +-- test/extensions/tracers/xray/tracer_test.cc | 4 ++-- test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc | 4 ++-- test/mocks/tracing/mocks.h | 2 +- 25 files changed, 39 insertions(+), 40 deletions(-) diff --git a/envoy/tracing/trace_driver.h b/envoy/tracing/trace_driver.h index d57f0a61a630..60e25ec84a68 100644 --- a/envoy/tracing/trace_driver.h +++ b/envoy/tracing/trace_driver.h @@ -15,7 +15,7 @@ class Span; using SpanPtr = std::unique_ptr; /** - * The upstream sevice type. + * The upstream service type. */ enum class ServiceType { // Service type is unknown. @@ -133,9 +133,9 @@ class Span { * Retrieve the trace ID associated with this span. * The trace id may be generated for this span, propagated by parent spans, or * not created yet. - * @return trace ID as a hex string + * @return trace ID */ - virtual std::string getTraceIdAsHex() const PURE; + virtual std::string getTraceId() const PURE; }; /** diff --git a/source/common/formatter/http_specific_formatter.cc b/source/common/formatter/http_specific_formatter.cc index 7623c4b95ac9..7f6376224b79 100644 --- a/source/common/formatter/http_specific_formatter.cc +++ b/source/common/formatter/http_specific_formatter.cc @@ -199,7 +199,7 @@ HeadersByteSizeFormatter::formatValueWithContext(const HttpFormatterContext& con ProtobufWkt::Value TraceIDFormatter::formatValueWithContext(const HttpFormatterContext& context, const StreamInfo::StreamInfo&) const { - auto trace_id = context.activeSpan().getTraceIdAsHex(); + auto trace_id = context.activeSpan().getTraceId(); if (trace_id.empty()) { return SubstitutionFormatUtils::unspecifiedValue(); } @@ -209,7 +209,7 @@ ProtobufWkt::Value TraceIDFormatter::formatValueWithContext(const HttpFormatterC absl::optional TraceIDFormatter::formatWithContext(const HttpFormatterContext& context, const StreamInfo::StreamInfo&) const { - auto trace_id = context.activeSpan().getTraceIdAsHex(); + auto trace_id = context.activeSpan().getTraceId(); if (trace_id.empty()) { return absl::nullopt; } diff --git a/source/common/tracing/null_span_impl.h b/source/common/tracing/null_span_impl.h index 109fce23d961..d573318e5751 100644 --- a/source/common/tracing/null_span_impl.h +++ b/source/common/tracing/null_span_impl.h @@ -25,7 +25,7 @@ class NullSpan : public Span { void injectContext(Tracing::TraceContext&, const UpstreamContext&) override {} void setBaggage(absl::string_view, absl::string_view) override {} std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } - std::string getTraceIdAsHex() const override { return EMPTY_STRING; } + std::string getTraceId() const override { return EMPTY_STRING; } SpanPtr spawnChild(const Config&, const std::string&, SystemTime) override { return SpanPtr{new NullSpan()}; } diff --git a/source/extensions/access_loggers/open_telemetry/access_log_impl.cc b/source/extensions/access_loggers/open_telemetry/access_log_impl.cc index 484bfd73a8b8..e3866dfc98e7 100644 --- a/source/extensions/access_loggers/open_telemetry/access_log_impl.cc +++ b/source/extensions/access_loggers/open_telemetry/access_log_impl.cc @@ -95,7 +95,7 @@ void AccessLog::emitLog(const Formatter::HttpFormatterContext& log_context, // OpenTelemetry trace id is a [16]byte array, backend(e.g. OTel-collector) will reject the // request if the length is not 16. Some trace provider(e.g. zipkin) may return it as a 64-bit hex // string. In this case, we need to convert it to a 128-bit hex string, padding left with zeros. - std::string trace_id_hex = log_context.activeSpan().getTraceIdAsHex(); + std::string trace_id_hex = log_context.activeSpan().getTraceId(); if (trace_id_hex.size() == 32) { *log_entry.mutable_trace_id() = absl::HexStringToBytes(trace_id_hex); } else if (trace_id_hex.size() == 16) { diff --git a/source/extensions/tracers/common/ot/opentracing_driver_impl.h b/source/extensions/tracers/common/ot/opentracing_driver_impl.h index 5ad651d43bf9..5fd46bfe96a4 100644 --- a/source/extensions/tracers/common/ot/opentracing_driver_impl.h +++ b/source/extensions/tracers/common/ot/opentracing_driver_impl.h @@ -46,7 +46,9 @@ class OpenTracingSpan : public Tracing::Span, Logger::Loggable span_; diff --git a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc index 19c870c38581..026f9e70f4cf 100644 --- a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc +++ b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc @@ -80,7 +80,7 @@ class Span : public Tracing::Span { void setBaggage(absl::string_view, absl::string_view) override{}; std::string getBaggage(absl::string_view) override { return EMPTY_STRING; }; - std::string getTraceIdAsHex() const override; + std::string getTraceId() const override; private: ::opencensus::trace::Span span_; @@ -236,7 +236,7 @@ void Span::injectContext(Tracing::TraceContext& trace_context, const Tracing::Up } } -std::string Span::getTraceIdAsHex() const { +std::string Span::getTraceId() const { const auto& ctx = span_.context(); return ctx.trace_id().ToHex(); } diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index aa9307578ce3..5c755bf873f9 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -39,9 +39,8 @@ void callSampler(SamplerSharedPtr sampler, const absl::optional spa if (!sampler) { return; } - const auto sampling_result = - sampler->shouldSample(span_context, new_span.getTraceIdAsHex(), operation_name, - new_span.spankind(), trace_context, {}); + const auto sampling_result = sampler->shouldSample( + span_context, new_span.getTraceId(), operation_name, new_span.spankind(), trace_context, {}); new_span.setSampled(sampling_result.isSampled()); if (sampling_result.attributes) { @@ -70,7 +69,7 @@ Span::Span(const std::string& name, SystemTime start_time, Envoy::TimeSource& ti Tracing::SpanPtr Span::spawnChild(const Tracing::Config&, const std::string& name, SystemTime start_time) { // Build span_context from the current span, then generate the child span from that context. - SpanContext span_context(kDefaultVersion, getTraceIdAsHex(), spanId(), sampled(), tracestate()); + SpanContext span_context(kDefaultVersion, getTraceId(), spanId(), sampled(), tracestate()); return parent_tracer_.startSpan(name, start_time, span_context, {}, ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_CLIENT); } diff --git a/source/extensions/tracers/opentelemetry/tracer.h b/source/extensions/tracers/opentelemetry/tracer.h index 2c9bb216b411..057814365b9a 100644 --- a/source/extensions/tracers/opentelemetry/tracer.h +++ b/source/extensions/tracers/opentelemetry/tracer.h @@ -117,7 +117,7 @@ class Span : Logger::Loggable, public Tracing::Span { span_.set_trace_id(absl::HexStringToBytes(trace_id_hex)); } - std::string getTraceIdAsHex() const override { return absl::BytesToHexString(span_.trace_id()); }; + std::string getTraceId() const override { return absl::BytesToHexString(span_.trace_id()); }; OTelSpanKind spankind() const { return span_.kind(); } diff --git a/source/extensions/tracers/skywalking/tracer.h b/source/extensions/tracers/skywalking/tracer.h index 0df23cf899b0..d5bb1f65f329 100644 --- a/source/extensions/tracers/skywalking/tracer.h +++ b/source/extensions/tracers/skywalking/tracer.h @@ -88,7 +88,7 @@ class Span : public Tracing::Span { void setSampled(bool do_sample) override; std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } void setBaggage(absl::string_view, absl::string_view) override {} - std::string getTraceIdAsHex() const override { return EMPTY_STRING; } + std::string getTraceId() const override { return tracing_context_->traceId(); } const TracingContextPtr tracingContext() { return tracing_context_; } const TracingSpanPtr spanEntity() { return span_entity_; } diff --git a/source/extensions/tracers/xray/tracer.h b/source/extensions/tracers/xray/tracer.h index 4ed4a7533638..bf9d696c0836 100644 --- a/source/extensions/tracers/xray/tracer.h +++ b/source/extensions/tracers/xray/tracer.h @@ -231,7 +231,7 @@ class Span : public Tracing::Span, Logger::Loggable { std::string getBaggage(absl::string_view) override { return EMPTY_STRING; } // TODO: This method is unimplemented for X-Ray. - std::string getTraceIdAsHex() const override { return EMPTY_STRING; }; + std::string getTraceId() const override { return trace_id_; }; /** * Creates a child span. diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h index 9ae05d474f78..cfa3c95a4fc3 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h @@ -83,7 +83,7 @@ class ZipkinSpan : public Tracing::Span { void setBaggage(absl::string_view, absl::string_view) override; std::string getBaggage(absl::string_view) override; - std::string getTraceIdAsHex() const override { return span_.traceIdAsHexString(); }; + std::string getTraceId() const override { return span_.traceIdAsHexString(); }; /** * @return a reference to the Zipkin::Span object. diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index ef0b1704d9b4..36442dc946be 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -2153,8 +2153,7 @@ TEST(SubstitutionFormatterTest, TraceIDFormatter) { std::string body; Tracing::MockSpan active_span; - EXPECT_CALL(active_span, getTraceIdAsHex()) - .WillRepeatedly(Return("ae0046f9075194306d7de2931bd38ce3")); + EXPECT_CALL(active_span, getTraceId()).WillRepeatedly(Return("ae0046f9075194306d7de2931bd38ce3")); { HttpFormatterContext formatter_context(&request_header, &response_header, &response_trailer, diff --git a/test/common/tracing/tracer_impl_test.cc b/test/common/tracing/tracer_impl_test.cc index 8a89138ad5de..851795817631 100644 --- a/test/common/tracing/tracer_impl_test.cc +++ b/test/common/tracing/tracer_impl_test.cc @@ -264,7 +264,7 @@ TEST(NullTracerTest, BasicFunctionality) { span_ptr->setTag("foo", "bar"); span_ptr->setBaggage("key", "value"); ASSERT_EQ("", span_ptr->getBaggage("baggage_key")); - ASSERT_EQ(span_ptr->getTraceIdAsHex(), ""); + ASSERT_EQ(span_ptr->getTraceId(), ""); span_ptr->injectContext(trace_context, upstream_context); span_ptr->log(SystemTime(), "fake_event"); diff --git a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc index 0a163b933c94..b16d47d135c2 100644 --- a/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc +++ b/test/extensions/access_loggers/open_telemetry/access_log_impl_test.cc @@ -175,7 +175,7 @@ TEST_F(AccessLogTest, TraceId) { NiceMock active_span; - EXPECT_CALL(active_span, getTraceIdAsHex()).WillOnce(Return("404142434445464748494a4b4c4d4e4f")); + EXPECT_CALL(active_span, getTraceId()).WillOnce(Return("404142434445464748494a4b4c4d4e4f")); expectLog(R"EOF( trace_id: "QEFCQ0RFRkdISUpLTE1OTw==" time_unix_nano: 3600000000000 @@ -195,7 +195,7 @@ TEST_F(AccessLogTest, ZipkinTraceId) { NiceMock active_span; - EXPECT_CALL(active_span, getTraceIdAsHex()).WillOnce(Return("0ccce09bf12e94df")); + EXPECT_CALL(active_span, getTraceId()).WillOnce(Return("0ccce09bf12e94df")); expectLog(R"EOF( trace_id: "AAAAAAAAAAAMzOCb8S6U3w==" time_unix_nano: 3600000000000 diff --git a/test/extensions/filters/http/ext_proc/tracer_test_filter.cc b/test/extensions/filters/http/ext_proc/tracer_test_filter.cc index ec59c159c555..d58c97372160 100644 --- a/test/extensions/filters/http/ext_proc/tracer_test_filter.cc +++ b/test/extensions/filters/http/ext_proc/tracer_test_filter.cc @@ -69,7 +69,7 @@ class Span : public Tracing::Span { /* not implemented */ return EMPTY_STRING; }; - std::string getTraceIdAsHex() const { + std::string getTraceId() const { /* not implemented */ return EMPTY_STRING; }; diff --git a/test/extensions/tracers/common/ot/opentracing_driver_impl_test.cc b/test/extensions/tracers/common/ot/opentracing_driver_impl_test.cc index 8f88b9b1a632..99c2ef75712e 100644 --- a/test/extensions/tracers/common/ot/opentracing_driver_impl_test.cc +++ b/test/extensions/tracers/common/ot/opentracing_driver_impl_test.cc @@ -352,7 +352,7 @@ TEST_F(OpenTracingDriverTest, GetTraceId) { first_span->finishSpan(); // This method is unimplemented and a noop. - ASSERT_EQ(first_span->getTraceIdAsHex(), ""); + ASSERT_EQ(first_span->getTraceId(), ""); } TEST_F(OpenTracingDriverTest, ExtractUsingForeach) { diff --git a/test/extensions/tracers/datadog/span_test.cc b/test/extensions/tracers/datadog/span_test.cc index f4294ab120ed..2dccafbea424 100644 --- a/test/extensions/tracers/datadog/span_test.cc +++ b/test/extensions/tracers/datadog/span_test.cc @@ -386,9 +386,9 @@ TEST_F(DatadogTracerSpanTest, Baggage) { EXPECT_EQ("", span.getBaggage("foo")); } -TEST_F(DatadogTracerSpanTest, GetTraceIdAsHex) { +TEST_F(DatadogTracerSpanTest, GetTraceId) { Span span{std::move(span_)}; - EXPECT_EQ("cafebabe", span.getTraceIdAsHex()); + EXPECT_EQ("cafebabe", span.getTraceId()); } TEST_F(DatadogTracerSpanTest, NoOpMode) { @@ -429,7 +429,7 @@ TEST_F(DatadogTracerSpanTest, NoOpMode) { span.setSampled(false); EXPECT_EQ("", span.getBaggage("foo")); span.setBaggage("foo", "bar"); - EXPECT_EQ("", span.getTraceIdAsHex()); + EXPECT_EQ("", span.getTraceId()); } } // namespace diff --git a/test/extensions/tracers/opencensus/tracer_test.cc b/test/extensions/tracers/opencensus/tracer_test.cc index 0811f097e035..65a1632596f6 100644 --- a/test/extensions/tracers/opencensus/tracer_test.cc +++ b/test/extensions/tracers/opencensus/tracer_test.cc @@ -129,7 +129,7 @@ TEST(OpenCensusTracerTest, Span) { ASSERT_EQ("", span->getBaggage("baggage_key")); // Trace id is automatically created when no parent context exists. - ASSERT_NE(span->getTraceIdAsHex(), ""); + ASSERT_NE(span->getTraceId(), ""); } // Retrieve SpanData from the OpenCensus trace exporter. @@ -221,7 +221,7 @@ void testIncomingHeaders( // Check contents via public API. // Trace id is set via context propagation headers. - EXPECT_EQ(span->getTraceIdAsHex(), "404142434445464748494a4b4c4d4e4f"); + EXPECT_EQ(span->getTraceId(), "404142434445464748494a4b4c4d4e4f"); } // Retrieve SpanData from the OpenCensus trace exporter. diff --git a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc index cb0227604366..1be53b67e720 100644 --- a/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc +++ b/test/extensions/tracers/opentelemetry/opentelemetry_tracer_impl_test.cc @@ -175,7 +175,7 @@ TEST_F(OpenTelemetryDriverTest, ParseSpanContextFromHeadersTest) { Tracing::SpanPtr span = driver_->startSpan(mock_tracing_config_, request_headers, stream_info_, operation_name_, {Tracing::Reason::Sampling, true}); - EXPECT_EQ(span->getTraceIdAsHex(), trace_id_hex); + EXPECT_EQ(span->getTraceId(), trace_id_hex); // Remove headers, then inject context into header from the span. request_headers.remove(OpenTelemetryConstants::get().TRACE_PARENT.key()); diff --git a/test/extensions/tracers/skywalking/tracer_test.cc b/test/extensions/tracers/skywalking/tracer_test.cc index d0f22d1c3746..ecb4183355df 100644 --- a/test/extensions/tracers/skywalking/tracer_test.cc +++ b/test/extensions/tracers/skywalking/tracer_test.cc @@ -88,8 +88,7 @@ TEST_F(TracerTest, TracerTestCreateNewSpanWithNoPropagationHeaders) { EXPECT_EQ("", span->getBaggage("FakeStringAndNothingToDo")); span->setOperation("FakeStringAndNothingToDo"); span->setBaggage("FakeStringAndNothingToDo", "FakeStringAndNothingToDo"); - // This method is unimplemented and a noop. - ASSERT_EQ(span->getTraceIdAsHex(), ""); + ASSERT_EQ(span->getTraceId(), segment_context->traceId()); // Test whether the basic functions of Span are normal. EXPECT_FALSE(span->spanEntity()->skipAnalysis()); span->setSampled(false); diff --git a/test/extensions/tracers/xray/tracer_test.cc b/test/extensions/tracers/xray/tracer_test.cc index ffda75fe1301..12b7c50238f9 100644 --- a/test/extensions/tracers/xray/tracer_test.cc +++ b/test/extensions/tracers/xray/tracer_test.cc @@ -386,8 +386,8 @@ TEST_F(XRayTracerTest, GetTraceId) { auto span = tracer.createNonSampledSpan(absl::nullopt /*headers*/); span->finishSpan(); - // This method is unimplemented and a noop. - EXPECT_EQ(span->getTraceIdAsHex(), ""); + // Trace ID is always generated + EXPECT_NE(span->getTraceId(), ""); } TEST_F(XRayTracerTest, ChildSpanHasParentInfo) { diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index 0269ec966cd9..384d53bee290 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -725,7 +725,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { // ==== Tracing::SpanPtr span6 = driver_->startSpan(config_, request_headers_, stream_info_, operation_name_, {Tracing::Reason::Sampling, true}); - EXPECT_EQ(span6->getTraceIdAsHex(), "0000000000000000"); + EXPECT_EQ(span6->getTraceId(), "0000000000000000"); } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { @@ -798,7 +798,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { EXPECT_EQ(span_id, zipkin_span->span().idAsHexString()); EXPECT_EQ(parent_id, zipkin_span->span().parentIdAsHexString()); EXPECT_TRUE(zipkin_span->span().sampled()); - EXPECT_EQ(trace_id, zipkin_span->getTraceIdAsHex()); + EXPECT_EQ(trace_id, zipkin_span->getTraceId()); } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { diff --git a/test/mocks/tracing/mocks.h b/test/mocks/tracing/mocks.h index 90905dbf1045..063902b3aadf 100644 --- a/test/mocks/tracing/mocks.h +++ b/test/mocks/tracing/mocks.h @@ -43,7 +43,7 @@ class MockSpan : public Span { MOCK_METHOD(void, setSampled, (const bool sampled)); MOCK_METHOD(void, setBaggage, (absl::string_view key, absl::string_view value)); MOCK_METHOD(std::string, getBaggage, (absl::string_view key)); - MOCK_METHOD(std::string, getTraceIdAsHex, (), (const)); + MOCK_METHOD(std::string, getTraceId, (), (const)); SpanPtr spawnChild(const Config& config, const std::string& name, SystemTime start_time) override { From 144759d4e080cbbb50f29e336edc9c2975b562cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Mon, 27 May 2024 16:22:59 +0800 Subject: [PATCH 16/22] golang: don't log client cancellation as error (#34362) Signed-off-by: spacewander --- .../filters/http/source/go/pkg/http/filter.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/contrib/golang/filters/http/source/go/pkg/http/filter.go b/contrib/golang/filters/http/source/go/pkg/http/filter.go index 421fb603e8ce..7c348890eb7a 100644 --- a/contrib/golang/filters/http/source/go/pkg/http/filter.go +++ b/contrib/golang/filters/http/source/go/pkg/http/filter.go @@ -33,7 +33,7 @@ package http import "C" import ( "fmt" - "runtime" + "runtime/debug" "sync" "sync/atomic" "unsafe" @@ -129,10 +129,13 @@ func (r *httpRequest) sendPanicReply(details string) { func (r *httpRequest) RecoverPanic() { if e := recover(); e != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - api.LogErrorf("http: panic serving: %v\n%s", e, buf) + buf := debug.Stack() + + if e == errRequestFinished || e == errFilterDestroyed { + api.LogInfof("http: panic serving: %v (Client may cancel the request prematurely)\n%s", e, buf) + } else { + api.LogErrorf("http: panic serving: %v\n%s", e, buf) + } switch e { case errRequestFinished, errFilterDestroyed: From a22101c0b84817360c32594f3f96fe4d03606a6f Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 27 May 2024 07:52:38 -0400 Subject: [PATCH 17/22] headers: consistent validation (#34205) Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 4 ++++ source/common/formatter/BUILD | 1 + .../formatter/substitution_format_utility.cc | 20 ++++++++++++++----- source/common/runtime/runtime_features.cc | 1 + .../extensions/filters/common/expr/context.h | 14 ++++++++++--- .../formatter/substitution_formatter_test.cc | 6 +++--- test/extensions/filters/common/expr/BUILD | 1 + .../filters/common/expr/context_test.cc | 14 ++++++++++++- 8 files changed, 49 insertions(+), 12 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 7d4b99887c30..740dee64aae3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -67,6 +67,10 @@ minor_behavior_changes: ``%UPSTREAM_REMOTE_PORT%`` and ``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` access log format specifiers. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.upstream_remote_address_use_connection`` to false. +- area: http + change: | + Changing header validation checks in the substitution format utility and CEL code to do RCF complaint header validation. + This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.consistent_header_validation`` to false. - area: quic change: | When a quic connection socket is created, the socket's detected transport protocol will be set to "quic". diff --git a/source/common/formatter/BUILD b/source/common/formatter/BUILD index 5047a90f877e..fb9952059228 100644 --- a/source/common/formatter/BUILD +++ b/source/common/formatter/BUILD @@ -62,6 +62,7 @@ envoy_cc_library( "//envoy/api:api_interface", "//envoy/http:protocol_interface", "//source/common/api:os_sys_calls_lib", + "//source/common/http:header_utility_lib", "//source/common/http:utility_lib", "//source/common/protobuf:utility_lib", "//source/common/stream_info:utility_lib", diff --git a/source/common/formatter/substitution_format_utility.cc b/source/common/formatter/substitution_format_utility.cc index 72f0daceee09..e28da96c55fe 100644 --- a/source/common/formatter/substitution_format_utility.cc +++ b/source/common/formatter/substitution_format_utility.cc @@ -3,8 +3,10 @@ #include "envoy/api/os_sys_calls.h" #include "source/common/api/os_sys_calls_impl.h" +#include "source/common/http/header_utility.h" #include "source/common/http/utility.h" #include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/stream_info/utility.h" namespace Envoy { @@ -112,11 +114,19 @@ void SubstitutionFormatUtils::parseSubcommandHeaders(const std::string& subcomma absl::StrCat("More than 1 alternative header specified in token: ", subcommand)); } - // The main and alternative header should not contain invalid characters {NUL, LR, CF}. - if (!Envoy::Http::validHeaderString(main_header) || - !Envoy::Http::validHeaderString(alternative_header)) { - throwEnvoyExceptionOrPanic( - "Invalid header configuration. Format string contains null or newline."); + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.consistent_header_validation")) { + if (!Http::HeaderUtility::headerNameIsValid(absl::AsciiStrToLower(main_header)) || + !Http::HeaderUtility::headerNameIsValid(absl::AsciiStrToLower(alternative_header))) { + throwEnvoyExceptionOrPanic( + "Invalid header configuration. Format string contains invalid characters."); + } + } else { + // The main and alternative header should not contain invalid characters {NUL, LR, CF}. + if (!Envoy::Http::validHeaderString(main_header) || + !Envoy::Http::validHeaderString(alternative_header)) { + throwEnvoyExceptionOrPanic( + "Invalid header configuration. Format string contains null or newline."); + } } } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index fb02d63268df..0ca540559f4c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -33,6 +33,7 @@ RUNTIME_GUARD(envoy_reloadable_features_abort_filter_chain_on_stream_reset); RUNTIME_GUARD(envoy_reloadable_features_avoid_zombie_streams); RUNTIME_GUARD(envoy_reloadable_features_check_mep_on_first_eject); RUNTIME_GUARD(envoy_reloadable_features_conn_pool_delete_when_idle); +RUNTIME_GUARD(envoy_reloadable_features_consistent_header_validation); RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); RUNTIME_GUARD(envoy_reloadable_features_dfp_mixed_scheme); RUNTIME_GUARD(envoy_reloadable_features_disallow_quic_client_udp_mmsg); diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index b0487084a19d..61ae0c0d43da 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -6,6 +6,7 @@ #include "source/common/grpc/status.h" #include "source/common/http/header_utility.h" #include "source/common/http/headers.h" +#include "source/common/runtime/runtime_features.h" #include "source/common/singleton/const_singleton.h" #include "eval/public/cel_value.h" @@ -116,9 +117,16 @@ template class HeadersWrapper : public google::api::expr::runtime::Cel return {}; } auto str = std::string(key.StringOrDie().value()); - if (!::Envoy::Http::validHeaderString(str)) { - // Reject key if it is an invalid header string - return {}; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.consistent_header_validation")) { + if (!Http::HeaderUtility::headerNameIsValid(str)) { + // Reject key if it is an invalid header string + return {}; + } + } else { + if (!::Envoy::Http::validHeaderString(str)) { + // Reject key if it is an invalid header string + return {}; + } } return convertHeaderEntry(arena_, ::Envoy::Http::HeaderUtility::getAllOfHeaderAsString( *value_, ::Envoy::Http::LowerCaseString(str))); diff --git a/test/common/formatter/substitution_formatter_test.cc b/test/common/formatter/substitution_formatter_test.cc index 36442dc946be..318df5b8b9bf 100644 --- a/test/common/formatter/substitution_formatter_test.cc +++ b/test/common/formatter/substitution_formatter_test.cc @@ -4071,7 +4071,7 @@ TEST(SubstitutionFormatterTest, CompositeFormatterSuccess) { { NiceMock stream_info; - const std::string format = "{{%PROTOCOL%}} %RESP(not exist)%++%RESP(test)% " + const std::string format = "{{%PROTOCOL%}} %RESP(not_exist)%++%RESP(test)% " "%REQ(FIRST?SECOND)% %RESP(FIRST?SECOND)%" "\t@%TRAILER(THIRD)%@\t%TRAILER(TEST?TEST-2)%[]"; FormatterImpl formatter(format, false); @@ -4269,7 +4269,7 @@ TEST(SubstitutionFormatterTest, CompositeFormatterEmpty) { body); { - const std::string format = "%PROTOCOL%|%RESP(not exist)%|" + const std::string format = "%PROTOCOL%|%RESP(not_exist)%|" "%REQ(FIRST?SECOND)%|%RESP(FIRST?SECOND)%|" "%TRAILER(THIRD)%|%TRAILER(TEST?TEST-2)%"; FormatterImpl formatter(format, false); @@ -4280,7 +4280,7 @@ TEST(SubstitutionFormatterTest, CompositeFormatterEmpty) { } { - const std::string format = "%PROTOCOL%|%RESP(not exist)%|" + const std::string format = "%PROTOCOL%|%RESP(not_exist)%|" "%REQ(FIRST?SECOND)%%RESP(FIRST?SECOND)%|" "%TRAILER(THIRD)%|%TRAILER(TEST?TEST-2)%"; FormatterImpl formatter(format, true); diff --git a/test/extensions/filters/common/expr/BUILD b/test/extensions/filters/common/expr/BUILD index eed363cec5b3..171fbb9041fc 100644 --- a/test/extensions/filters/common/expr/BUILD +++ b/test/extensions/filters/common/expr/BUILD @@ -31,6 +31,7 @@ envoy_extension_cc_test( "//test/mocks/ssl:ssl_mocks", "//test/mocks/stream_info:stream_info_mocks", "//test/mocks/upstream:host_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index bd2b347ad7cc..286bc1f7dcca 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -13,6 +13,7 @@ #include "test/mocks/ssl/mocks.h" #include "test/mocks/stream_info/mocks.h" #include "test/mocks/upstream/host.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "absl/time/time.h" @@ -46,7 +47,18 @@ TEST(Context, InvalidRequest) { Http::TestRequestHeaderMapImpl header_map{{"referer", "dogs.com"}}; Protobuf::Arena arena; HeadersWrapper headers(arena, &header_map); - auto header = headers[CelValue::CreateStringView("dogs.com\n")]; + auto header = headers[CelValue::CreateStringView("referer\n")]; + EXPECT_FALSE(header.has_value()); +} + +TEST(Context, InvalidRequestLegacy) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.consistent_header_validation", "false"}}); + + Http::TestRequestHeaderMapImpl header_map{{"referer", "dogs.com"}}; + Protobuf::Arena arena; + HeadersWrapper headers(arena, &header_map); + auto header = headers[CelValue::CreateStringView("referer\n")]; EXPECT_FALSE(header.has_value()); } From 0c20871d76df417df0111ee0a55009a4fa60a802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 09:11:49 +0100 Subject: [PATCH 18/22] build(deps): bump redis from `5905bf0` to `01afb31` in /examples/redis (#34370) Bumps redis from `5905bf0` to `01afb31`. --- updated-dependencies: - dependency-name: redis dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/redis/Dockerfile-redis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index 78f6fa41dfcc..e68d96f33c8a 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:5905bf01cdc654a1a3a2289b14be3e8701afb0ffc43295b969880e8eb2841a24 +FROM redis@sha256:01afb31d6d633451d84475ff3eb95f8c48bf0ee59ec9c948b161adb4da882053 From 77f9591ae0aa84e420be7e3969f1e57f6f95ed01 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 09:11:58 +0100 Subject: [PATCH 19/22] build(deps): bump openzipkin/zipkin from `7089838` to `da81773` in /examples/zipkin (#34373) build(deps): bump openzipkin/zipkin in /examples/zipkin Bumps openzipkin/zipkin from `7089838` to `da81773`. --- updated-dependencies: - dependency-name: openzipkin/zipkin dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/zipkin/Dockerfile-zipkin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zipkin/Dockerfile-zipkin b/examples/zipkin/Dockerfile-zipkin index ad731c0d46df..f78c42d87ec3 100644 --- a/examples/zipkin/Dockerfile-zipkin +++ b/examples/zipkin/Dockerfile-zipkin @@ -1 +1 @@ -FROM openzipkin/zipkin:latest@sha256:708983880c013c2232e75e3609b6e4c148ac869e51c72a109261688be3eb9229 +FROM openzipkin/zipkin:latest@sha256:da8177371c4a7aed7fd673196593c2ff104ce908f300e1971493587622f7a297 From d86f4c0e8356b87e1dd4a42ecda130837d959cee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 09:12:07 +0100 Subject: [PATCH 20/22] build(deps-dev): bump eslint-plugin-react from 7.34.1 to 7.34.2 in /examples/single-page-app/ui (#34379) build(deps-dev): bump eslint-plugin-react Bumps [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) from 7.34.1 to 7.34.2. - [Release notes](https://github.com/jsx-eslint/eslint-plugin-react/releases) - [Changelog](https://github.com/jsx-eslint/eslint-plugin-react/blob/v7.34.2/CHANGELOG.md) - [Commits](https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.1...v7.34.2) --- updated-dependencies: - dependency-name: eslint-plugin-react dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/single-page-app/ui/package.json | 2 +- examples/single-page-app/ui/yarn.lock | 90 ++++++++++++------------ 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/examples/single-page-app/ui/package.json b/examples/single-page-app/ui/package.json index b675c8b187ac..ad86839ba219 100644 --- a/examples/single-page-app/ui/package.json +++ b/examples/single-page-app/ui/package.json @@ -31,7 +31,7 @@ "eslint-plugin-import": "^2.25.2", "eslint-plugin-n": "^15.0.0 || ^16.0.0 ", "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react": "^7.34.2", "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-refresh": "^0.4.7", "typescript": "*", diff --git a/examples/single-page-app/ui/yarn.lock b/examples/single-page-app/ui/yarn.lock index a01bf61fdd1d..27066ad90d56 100644 --- a/examples/single-page-app/ui/yarn.lock +++ b/examples/single-page-app/ui/yarn.lock @@ -1795,15 +1795,16 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" -array-includes@^3.1.6, array-includes@^3.1.7: - version "3.1.7" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" - integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== +array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: + version "3.1.8" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" + integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" + get-intrinsic "^1.2.4" is-string "^1.0.7" array-union@^2.1.0: @@ -1811,7 +1812,7 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.findlast@^1.2.4: +array.prototype.findlast@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz#3e4fbcb30a15a7f5bf64cf2faae22d139c2e4904" integrity sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ== @@ -2263,7 +2264,7 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: +es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2, es-abstract@^1.23.3: version "1.23.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== @@ -2327,14 +2328,14 @@ es-errors@^1.1.0, es-errors@^1.2.1, es-errors@^1.3.0: resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -es-iterator-helpers@^1.0.17: - version "1.0.18" - resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.18.tgz#4d3424f46b24df38d064af6fbbc89274e29ea69d" - integrity sha512-scxAJaewsahbqTYrGKJihhViaM6DDZDDoucfvzNbK0pOren1g/daDQ3IAhzn+1G14rBG7w+i5N+qul60++zlKA== +es-iterator-helpers@^1.0.19: + version "1.0.19" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz#117003d0e5fec237b4b5c08aded722e0c6d50ca8" + integrity sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw== dependencies: call-bind "^1.0.7" define-properties "^1.2.1" - es-abstract "^1.23.0" + es-abstract "^1.23.3" es-errors "^1.3.0" es-set-tostringtag "^2.0.3" function-bind "^1.1.2" @@ -2530,29 +2531,29 @@ eslint-plugin-react-refresh@^0.4.7: resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz#1f597f9093b254f10ee0961c139a749acb19af7d" integrity sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw== -eslint-plugin-react@^7.34.1: - version "7.34.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz#6806b70c97796f5bbfb235a5d3379ece5f4da997" - integrity sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw== +eslint-plugin-react@^7.34.2: + version "7.34.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.2.tgz#2780a1a35a51aca379d86d29b9a72adc6bfe6b66" + integrity sha512-2HCmrU+/JNigDN6tg55cRDKCQWicYAPB38JGSFDQt95jDm8rrvSUo7YPkOIm5l6ts1j1zCvysNcasvfTMQzUOw== dependencies: - array-includes "^3.1.7" - array.prototype.findlast "^1.2.4" + array-includes "^3.1.8" + array.prototype.findlast "^1.2.5" array.prototype.flatmap "^1.3.2" array.prototype.toreversed "^1.1.2" array.prototype.tosorted "^1.1.3" doctrine "^2.1.0" - es-iterator-helpers "^1.0.17" + es-iterator-helpers "^1.0.19" estraverse "^5.3.0" jsx-ast-utils "^2.4.1 || ^3.0.0" minimatch "^3.1.2" - object.entries "^1.1.7" - object.fromentries "^2.0.7" - object.hasown "^1.1.3" - object.values "^1.1.7" + object.entries "^1.1.8" + object.fromentries "^2.0.8" + object.hasown "^1.1.4" + object.values "^1.2.0" prop-types "^15.8.1" resolve "^2.0.0-next.5" semver "^6.3.1" - string.prototype.matchall "^4.0.10" + string.prototype.matchall "^4.0.11" eslint-scope@^7.2.2: version "7.2.2" @@ -3470,7 +3471,7 @@ object.assign@^4.1.4, object.assign@^4.1.5: has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.7: +object.entries@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.8.tgz#bffe6f282e01f4d17807204a24f8edd823599c41" integrity sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ== @@ -3479,14 +3480,15 @@ object.entries@^1.1.7: define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" - integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== +object.fromentries@^2.0.7, object.fromentries@^2.0.8: + version "2.0.8" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" + integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" object.groupby@^1.0.1: version "1.0.1" @@ -3498,7 +3500,7 @@ object.groupby@^1.0.1: es-abstract "^1.22.1" get-intrinsic "^1.2.1" -object.hasown@^1.1.3: +object.hasown@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== @@ -3507,14 +3509,14 @@ object.hasown@^1.1.3: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.values@^1.1.6, object.values@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" - integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== +object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" + integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" once@^1.3.0: version "1.4.0" @@ -3982,7 +3984,7 @@ source-map@^0.5.7: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== -string.prototype.matchall@^4.0.10: +string.prototype.matchall@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz#1092a72c59268d2abaad76582dccc687c0297e0a" integrity sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg== From d9ef4872f64c6bc1fabef183bc2a624288ae41fd Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 28 May 2024 08:37:41 -0400 Subject: [PATCH 21/22] mobile: more flow control improvements (#34303) follow-ups on #34211 correctly handling fin-with-no-payload as well. Commit Message: Additional Description: Risk Level: Testing: Docs Changes: Release Notes: Fixes #34288 Signed-off-by: Alyssa Wilk --- mobile/library/common/http/client.cc | 10 +-- mobile/library/common/http/client.h | 6 +- .../integration/client_integration_test.cc | 74 ++++++++++++++++--- .../AndroidEngineExplicitFlowTest.java | 2 - 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/mobile/library/common/http/client.cc b/mobile/library/common/http/client.cc index 5c3cc5795ffa..a1522e4982c3 100644 --- a/mobile/library/common/http/client.cc +++ b/mobile/library/common/http/client.cc @@ -132,7 +132,8 @@ void Client::DirectStreamCallbacks::encodeData(Buffer::Instance& data, bool end_ // Send data if in default flow control mode, or if resumeData has been called in explicit // flow control mode. if (bytes_to_send_ > 0 || !explicit_flow_control_) { - ASSERT(!hasBufferedData()); + // We shouldn't be calling sendDataToBridge with newly arrived data if there's buffered data. + ASSERT(!response_data_.get() || response_data_->length() == 0); sendDataToBridge(data, end_stream); } @@ -235,14 +236,13 @@ void Client::DirectStreamCallbacks::resumeData(size_t bytes_to_send) { // Make sure to send end stream with data only if // 1) it has been received from the peer and // 2) there are no trailers - if (hasBufferedData() || - (remote_end_stream_received_ && !remote_end_stream_forwarded_ && !response_trailers_)) { + if (hasDataToSend()) { sendDataToBridge(*response_data_, remote_end_stream_received_ && !response_trailers_.get()); bytes_to_send_ = 0; } // If all buffered data has been sent, send and free up trailers. - if (!hasBufferedData() && response_trailers_.get() && bytes_to_send_ > 0) { + if (!hasDataToSend() && response_trailers_.get() && bytes_to_send_ > 0) { sendTrailersToBridge(*response_trailers_); response_trailers_.reset(); bytes_to_send_ = 0; @@ -305,7 +305,7 @@ void Client::DirectStreamCallbacks::onError() { // TODO(goaway): What is the expected behavior when an error is received, held, and then another // error occurs (e.g., timeout)? - if (explicit_flow_control_ && response_data_ && response_data_->length() != 0) { + if (explicit_flow_control_ && (hasDataToSend() || response_trailers_.get())) { ENVOY_LOG(debug, "[S{}] defering remote reset stream due to explicit flow control", direct_stream_.stream_handle_); if (direct_stream_.parent_.getStream(direct_stream_.stream_handle_, diff --git a/mobile/library/common/http/client.h b/mobile/library/common/http/client.h index ac21de62c700..e59517b39376 100644 --- a/mobile/library/common/http/client.h +++ b/mobile/library/common/http/client.h @@ -190,7 +190,11 @@ class Client : public Logger::Loggable { void latchError(); private: - bool hasBufferedData() { return response_data_.get() && response_data_->length() != 0; } + bool hasDataToSend() { + return ( + (response_data_ && response_data_->length() != 0) || + (remote_end_stream_received_ && !remote_end_stream_forwarded_ && !response_trailers_)); + } void sendDataToBridge(Buffer::Instance& data, bool end_stream); void sendTrailersToBridge(const ResponseTrailerMap& trailers); diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index 0b9798b3d149..e9d57dc0886b 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -153,7 +153,7 @@ class ClientIntegrationTest } void basicTest(); - void trickleTest(); + void trickleTest(bool final_chunk_has_data); void explicitFlowControlWithCancels(uint32_t body_size = 1000, bool terminate_engine = false); static std::string protocolToString(Http::CodecType type) { @@ -276,19 +276,26 @@ TEST_P(ClientIntegrationTest, LargeResponse) { } } -void ClientIntegrationTest::trickleTest() { +void ClientIntegrationTest::trickleTest(bool final_chunk_has_data) { autonomous_upstream_ = false; initialize(); EnvoyStreamCallbacks stream_callbacks = createDefaultStreamCallbacks(); - stream_callbacks.on_data_ = [this](const Buffer::Instance&, uint64_t /* length */, - bool /* end_stream */, envoy_stream_intel) { + stream_callbacks.on_data_ = [this, + final_chunk_has_data](const Buffer::Instance&, uint64_t /* length */, + bool /* end_stream */, envoy_stream_intel) { if (explicit_flow_control_) { // Allow reading up to 100 bytes. stream_->readData(100); } cc_.on_data_calls_++; + if (cc_.on_data_calls_ < 10) { + upstream_request_->encodeData(1, cc_.on_data_calls_ == 9 && final_chunk_has_data); + } + if (cc_.on_data_calls_ == 10 && !final_chunk_has_data) { + upstream_request_->encodeData(0, true); + } }; stream_ = createNewStream(std::move(stream_callbacks)); @@ -308,22 +315,71 @@ void ClientIntegrationTest::trickleTest() { ASSERT_TRUE(upstream_request_->waitForEndStream(*BaseIntegrationTest::dispatcher_)); upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); - for (int i = 0; i < 10; ++i) { - upstream_request_->encodeData(1, i == 9); - } + // This will be read immediately. on_data_ will kick off more chunks. + upstream_request_->encodeData(1, false); terminal_callback_.waitReady(); } TEST_P(ClientIntegrationTest, Trickle) { - trickleTest(); + trickleTest(true); ASSERT_LE(cc_.on_data_calls_, 11); + ASSERT_EQ(cc_.on_error_calls_, 0); + ASSERT_EQ(cc_.on_complete_calls_, 1); } TEST_P(ClientIntegrationTest, TrickleExplicitFlowControl) { explicit_flow_control_ = true; - trickleTest(); + trickleTest(true); ASSERT_LE(cc_.on_data_calls_, 11); + ASSERT_EQ(cc_.on_error_calls_, 0); + ASSERT_EQ(cc_.on_complete_calls_, 1); +} + +TEST_P(ClientIntegrationTest, TrickleFinalChunkEmpty) { + trickleTest(false); + ASSERT_EQ(cc_.on_data_calls_, 11); + ASSERT_EQ(cc_.on_error_calls_, 0); + ASSERT_EQ(cc_.on_complete_calls_, 1); +} + +TEST_P(ClientIntegrationTest, TrickleExplicitFlowControlFinalChunkEmpty) { + explicit_flow_control_ = true; + trickleTest(false); + ASSERT_EQ(cc_.on_data_calls_, 11); + ASSERT_EQ(cc_.on_error_calls_, 0); + ASSERT_EQ(cc_.on_complete_calls_, 1); +} + +TEST_P(ClientIntegrationTest, ExplicitFlowControlEmptyChunkThenReadData) { + expect_data_streams_ = false; // Don't validate intel. + explicit_flow_control_ = true; + autonomous_upstream_ = false; + initialize(); + + EnvoyStreamCallbacks stream_callbacks = createDefaultStreamCallbacks(); + + stream_ = createNewStream(std::move(stream_callbacks)); + stream_->sendHeaders(std::make_unique(default_request_headers_), + false); + + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*BaseIntegrationTest::dispatcher_, + upstream_connection_)); + ASSERT_TRUE( + upstream_connection_->waitForNewStream(*BaseIntegrationTest::dispatcher_, upstream_request_)); + + upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); + upstream_request_->encodeData(0, true); + + // Wait for the chunk to hopefully arrive. + sleep(1); + stream_->readData(100); + + terminal_callback_.waitReady(); + + ASSERT_EQ(cc_.on_data_calls_, 1); + ASSERT_EQ(cc_.on_error_calls_, 0); + ASSERT_EQ(cc_.on_complete_calls_, 1); } TEST_P(ClientIntegrationTest, ManyStreamExplicitFlowControl) { diff --git a/mobile/test/java/integration/AndroidEngineExplicitFlowTest.java b/mobile/test/java/integration/AndroidEngineExplicitFlowTest.java index a13bec04b0e1..59e0f42d0805 100644 --- a/mobile/test/java/integration/AndroidEngineExplicitFlowTest.java +++ b/mobile/test/java/integration/AndroidEngineExplicitFlowTest.java @@ -32,7 +32,6 @@ import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -343,7 +342,6 @@ public void get_veryLargeResponse() throws Exception { // This was supposed to be a simple post, but because the stream is not properly closed, it // actually ends up testing sending a post, getting a response, and Envoy resetting the // "incomplete" request stream. - @Ignore("https://github.com/envoyproxy/envoy/issues/34288") @Test public void post_simple() throws Exception { mockWebServer.setDispatcher(new Dispatcher() { From fd1d7ed96a4e0982a01fbb988272a881f62eb741 Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Tue, 28 May 2024 06:30:19 -0700 Subject: [PATCH 22/22] [dns]fix a switch statement syntax typo (#34311) * fix a switch statement sytax typo Signed-off-by: Renjie Tang * remove white space Signed-off-by: Renjie Tang --------- Signed-off-by: Renjie Tang --- .../network/dns_resolver/getaddrinfo/getaddrinfo.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc b/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc index 8c65137c6443..4a69dabfd312 100644 --- a/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc +++ b/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc @@ -70,8 +70,8 @@ GetAddrInfoDnsResolver::processResponse(const PendingQuery& query, } std::list final_results; - switch (query.dns_lookup_family_) - case DnsLookupFamily::All: { + switch (query.dns_lookup_family_) { + case DnsLookupFamily::All: final_results = std::move(v4_results); final_results.splice(final_results.begin(), v6_results); break; @@ -98,10 +98,10 @@ GetAddrInfoDnsResolver::processResponse(const PendingQuery& query, break; } - ENVOY_LOG(debug, "getaddrinfo resolution complete for host '{}': {}", query.dns_name_, - accumulateToString(final_results, [](const auto& dns_response) { - return dns_response.addrInfo().address_->asString(); - })); + ENVOY_LOG(debug, "getaddrinfo resolution complete for host '{}': {}", query.dns_name_, + accumulateToString(final_results, [](const auto& dns_response) { + return dns_response.addrInfo().address_->asString(); + })); return std::make_pair(ResolutionStatus::Success, final_results); }