diff --git a/mobile/library/cc/BUILD b/mobile/library/cc/BUILD index f3f8e2e55eca..929e80af84c8 100644 --- a/mobile/library/cc/BUILD +++ b/mobile/library/cc/BUILD @@ -113,6 +113,7 @@ envoy_cc_library( "//library/common/api:c_types", "//library/common/data:utility_lib", "//library/common/extensions/key_value/platform:config", + "@envoy//source/common/buffer:buffer_lib", "@envoy//source/common/http:header_map_lib", "@envoy//source/common/http:utility_lib", ], diff --git a/mobile/library/cc/stream.cc b/mobile/library/cc/stream.cc index 36e15d3ef22c..73723a51b738 100644 --- a/mobile/library/cc/stream.cc +++ b/mobile/library/cc/stream.cc @@ -1,6 +1,7 @@ #include "stream.h" #include "library/cc/bridge_utility.h" +#include "library/common/data/utility.h" #include "library/common/http/header_utility.h" #include "library/common/internal_engine.h" #include "library/common/types/c_types.h" @@ -32,7 +33,12 @@ Stream& Stream::sendHeaders(Http::RequestHeaderMapPtr headers, bool end_stream) } Stream& Stream::sendData(envoy_data data) { - engine_->sendData(handle_, data, false); + Buffer::InstancePtr buffer = Data::Utility::toInternalData(data); + return sendData(std::move(buffer)); +} + +Stream& Stream::sendData(Buffer::InstancePtr buffer) { + engine_->sendData(handle_, std::move(buffer), false); return *this; } @@ -55,7 +61,14 @@ void Stream::close(Http::RequestTrailerMapPtr trailers) { engine_->sendTrailers(handle_, std::move(trailers)); } -void Stream::close(envoy_data data) { engine_->sendData(handle_, data, true); } +void Stream::close(envoy_data data) { + Buffer::InstancePtr buffer = Data::Utility::toInternalData(data); + close(std::move(buffer)); +} + +void Stream::close(Buffer::InstancePtr buffer) { + engine_->sendData(handle_, std::move(buffer), true); +} void Stream::cancel() { engine_->cancelStream(handle_); } diff --git a/mobile/library/cc/stream.h b/mobile/library/cc/stream.h index 9a7677ff08c0..66578da02923 100644 --- a/mobile/library/cc/stream.h +++ b/mobile/library/cc/stream.h @@ -2,6 +2,7 @@ #include +#include "envoy/buffer/buffer.h" #include "envoy/http/header_map.h" #include "library/cc/request_headers.h" @@ -27,7 +28,14 @@ class Stream { */ Stream& sendHeaders(Http::RequestHeaderMapPtr headers, bool end_stream); - Stream& sendData(envoy_data data); + [[deprecated]] Stream& sendData(envoy_data data); + + /** + * Send data over an open HTTP stream. This method can be invoked multiple times. + * + * @param buffer the data to send. + */ + Stream& sendData(Buffer::InstancePtr buffer); Stream& readData(size_t bytes_to_read); @@ -41,7 +49,15 @@ class Stream { */ void close(Http::RequestTrailerMapPtr trailers); - void close(envoy_data data); + [[deprecated]] void close(envoy_data data); + + /** + * Send data over an open HTTP stream and closes the stream.. This method can only be invoked + * once. + * + * @param buffer the last data to send. + */ + void close(Buffer::InstancePtr buffer); void cancel(); diff --git a/mobile/library/common/http/client.cc b/mobile/library/common/http/client.cc index e52030201e70..97db31ba152e 100644 --- a/mobile/library/common/http/client.cc +++ b/mobile/library/common/http/client.cc @@ -10,7 +10,6 @@ #include "library/common/bridge/utility.h" #include "library/common/data/utility.h" #include "library/common/http/header_utility.h" -#include "library/common/http/headers.h" #include "library/common/stream_info/extra_stream_info.h" #include "library/common/system/system_helper.h" @@ -596,16 +595,11 @@ void Client::readData(envoy_stream_t stream, size_t bytes_to_read) { } } -void Client::sendData(envoy_stream_t stream, envoy_data data, bool end_stream) { +void Client::sendData(envoy_stream_t stream, Buffer::InstancePtr buffer, bool end_stream) { ASSERT(dispatcher_.isThreadSafe()); Client::DirectStreamSharedPtr direct_stream = getStream(stream, GetStreamFilters::ALLOW_ONLY_FOR_OPEN_STREAMS); - // Take ownership of data early, in case of early returns. - // The buffer is moved internally, in a synchronous fashion, so we don't need the lifetime - // of the InstancePtr to outlive this function call. - Buffer::InstancePtr buf = Data::Utility::toInternalData(data); - // If direct_stream is not found, it means the stream has already closed or been reset // and the appropriate callback has been issued to the caller. There's nothing to do here // except silently swallow this. @@ -623,9 +617,9 @@ void Client::sendData(envoy_stream_t stream, envoy_data data, bool end_stream) { ScopeTrackerScopeState scope(direct_stream.get(), scopeTracker()); - ENVOY_LOG(debug, "[S{}] request data for stream (length={} end_stream={})\n", stream, data.length, - end_stream); - request_decoder->decodeData(*buf, end_stream); + ENVOY_LOG(debug, "[S{}] request data for stream (length={} end_stream={})\n", stream, + buffer->length(), end_stream); + request_decoder->decodeData(*buffer, end_stream); if (direct_stream->explicit_flow_control_ && !end_stream) { if (direct_stream->read_disable_count_ == 0) { diff --git a/mobile/library/common/http/client.h b/mobile/library/common/http/client.h index 84593f72fd14..de46f2982d96 100644 --- a/mobile/library/common/http/client.h +++ b/mobile/library/common/http/client.h @@ -90,11 +90,11 @@ class Client : public Logger::Loggable { /** * Send data over an open HTTP stream. This method can be invoked multiple times. - * @param stream, the stream to send data over. - * @param data, the data to send. - * @param end_stream, indicates whether to close the stream locally after sending this frame. + * @param stream the stream to send data over. + * @param buffer the data to send. + * @param end_stream indicates whether to close the stream locally after sending this frame. */ - void sendData(envoy_stream_t stream, envoy_data data, bool end_stream); + void sendData(envoy_stream_t stream, Buffer::InstancePtr buffer, bool end_stream); /** * Send metadata over an HTTP stream. This method can be invoked multiple times. diff --git a/mobile/library/common/internal_engine.cc b/mobile/library/common/internal_engine.cc index d9c3741ab8f5..2fea1a05bd19 100644 --- a/mobile/library/common/internal_engine.cc +++ b/mobile/library/common/internal_engine.cc @@ -74,9 +74,11 @@ envoy_status_t InternalEngine::readData(envoy_stream_t stream, size_t bytes_to_r [&, stream, bytes_to_read]() { http_client_->readData(stream, bytes_to_read); }); } -envoy_status_t InternalEngine::sendData(envoy_stream_t stream, envoy_data data, bool end_stream) { - return dispatcher_->post( - [&, stream, data, end_stream]() { http_client_->sendData(stream, data, end_stream); }); +envoy_status_t InternalEngine::sendData(envoy_stream_t stream, Buffer::InstancePtr buffer, + bool end_stream) { + return dispatcher_->post([&, stream, buffer = std::move(buffer), end_stream]() mutable { + http_client_->sendData(stream, std::move(buffer), end_stream); + }); } envoy_status_t InternalEngine::sendTrailers(envoy_stream_t stream, diff --git a/mobile/library/common/internal_engine.h b/mobile/library/common/internal_engine.h index 7f01bc2cb526..92e7cd11a090 100644 --- a/mobile/library/common/internal_engine.h +++ b/mobile/library/common/internal_engine.h @@ -80,7 +80,14 @@ class InternalEngine : public Logger::Loggable { envoy_status_t readData(envoy_stream_t stream, size_t bytes_to_read); - envoy_status_t sendData(envoy_stream_t stream, envoy_data data, bool end_stream); + /** + * Send data over an open HTTP stream. This method can be invoked multiple times. + * + * @param stream the stream to send data over. + * @param buffer the data to send. + * @param end_stream indicates whether to close the stream locally after sending this frame. + */ + envoy_status_t sendData(envoy_stream_t stream, Buffer::InstancePtr buffer, bool end_stream); /** * Send trailers over an open HTTP stream. This method can only be invoked once per stream. diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java index b05de6ab9907..c8f0f0d6f87f 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/EnvoyHTTPStream.java @@ -66,21 +66,12 @@ public void sendData(ByteBuffer data, boolean endStream) { * @param data, the data to send. * @param length, number of bytes to send: 0 <= length <= ByteBuffer.capacity() * @param endStream, supplies whether this is the last data in the streamHandle. - * @throws UnsupportedOperationException - if the provided buffer is neither a - * direct ByteBuffer nor backed by an - * on-heap byte array. */ public void sendData(ByteBuffer data, int length, boolean endStream) { if (length < 0 || length > data.capacity()) { throw new IllegalArgumentException("Length out of bound"); } - if (data.isDirect()) { - JniLibrary.sendData(engineHandle, streamHandle, data, length, endStream); - } else if (data.hasArray()) { - JniLibrary.sendDataByteArray(engineHandle, streamHandle, data.array(), length, endStream); - } else { - throw new UnsupportedOperationException("Unsupported ByteBuffer implementation."); - } + JniLibrary.sendData(engineHandle, streamHandle, data, length, endStream); } /** diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java index 8b819efa6120..c85d1e247141 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/JniLibrary.java @@ -87,26 +87,12 @@ protected static native int sendHeaders(long engine, long stream, * Send data over an open HTTP stream. This method can be invoked multiple * times. * - * @param engine, the stream's associated engine. - * @param stream, the stream to send data over. - * @param data, the data to send. - * @param length, the size in bytes of the data to send. 0 <= length <= data.length - * @param endStream, supplies whether this is the last data in the stream. - * @return int, the resulting status of the operation. - */ - protected static native int sendDataByteArray(long engine, long stream, byte[] data, int length, - boolean endStream); - - /** - * Send data over an open HTTP stream. This method can be invoked multiple - * times. - * - * @param engine, the stream's associated engine. - * @param stream, the stream to send data over. - * @param data, the data to send; must be a direct ByteBuffer. - * @param length, the size in bytes of the data to send. 0 <= length <= data.capacity() - * @param endStream, supplies whether this is the last data in the stream. - * @return int, the resulting status of the operation. + * @param engine the stream's associated engine. + * @param stream the stream to send data over. + * @param data the data to send. It can be direct or non-direct byteBuffer. + * @param length the size in bytes of the data to send. 0 <= length <= data.capacity() + * @param endStream supplies whether this is the last data in the stream. + * @return int the resulting status of the operation. */ protected static native int sendData(long engine, long stream, ByteBuffer data, int length, boolean endStream); diff --git a/mobile/library/jni/BUILD b/mobile/library/jni/BUILD index 3f0f8efa31de..d38f9c43bb2f 100644 --- a/mobile/library/jni/BUILD +++ b/mobile/library/jni/BUILD @@ -43,6 +43,7 @@ envoy_cc_library( "//library/jni/import:jni_import_lib", "//library/jni/types:jni_env_lib", "//library/jni/types:jni_exception_lib", + "@envoy//source/common/buffer:buffer_lib", "@envoy//source/common/common:assert_lib", "@envoy//source/common/http:header_map_lib", "@envoy//source/common/protobuf", diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index a3a84ca01b44..b6da58ca93b1 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -954,31 +954,23 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra ->readData(static_cast(stream_handle), byte_count); } -// The Java counterpart guarantees to invoke this method with a non-null direct ByteBuffer where the -// provided length is between 0 and ByteBuffer.capacity(), inclusively. +// This function can accept either direct (off the JVM heap) ByteBuffer or non-direct (on the JVM +// heap) ByteBuffer. extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendData( - JNIEnv* env, jclass, jlong engine_handle, jlong stream_handle, jobject data, jint length, + JNIEnv* env, jclass, jlong engine_handle, jlong stream_handle, jobject byte_buffer, jint length, jboolean end_stream) { Envoy::JNI::JniHelper jni_helper(env); + Envoy::Buffer::InstancePtr cpp_buffer_instance; + if (Envoy::JNI::isJavaDirectByteBuffer(jni_helper, byte_buffer)) { + cpp_buffer_instance = + Envoy::JNI::javaDirectByteBufferToCppBufferInstance(jni_helper, byte_buffer, length); + } else { + cpp_buffer_instance = + Envoy::JNI::javaNonDirectByteBufferToCppBufferInstance(jni_helper, byte_buffer, length); + } return reinterpret_cast(engine_handle) - ->sendData(static_cast(stream_handle), - Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, data, length), end_stream); -} - -// The Java counterpart guarantees to invoke this method with a non-null jbyteArray where the -// provided length is between 0 and the size of the jbyteArray, inclusively. And given that this -// jbyteArray comes from a ByteBuffer, it is also guaranteed that its length will not be greater -// than 2^31 - this is why the length type is jint. -extern "C" JNIEXPORT jint JNICALL -Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendDataByteArray(JNIEnv* env, jclass, - jlong engine_handle, - jlong stream_handle, - jbyteArray data, jint length, - jboolean end_stream) { - Envoy::JNI::JniHelper jni_helper(env); - return reinterpret_cast(engine_handle) - ->sendData(static_cast(stream_handle), - Envoy::JNI::javaByteArrayToEnvoyData(jni_helper, data, length), end_stream); + ->sendData(static_cast(stream_handle), std::move(cpp_buffer_instance), + end_stream); } extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibrary_sendHeaders( diff --git a/mobile/library/jni/jni_utility.cc b/mobile/library/jni/jni_utility.cc index 29b671e32741..406f22794bfe 100644 --- a/mobile/library/jni/jni_utility.cc +++ b/mobile/library/jni/jni_utility.cc @@ -3,12 +3,12 @@ #include #include +#include "source/common/buffer/buffer_impl.h" #include "source/common/common/assert.h" #include "library/common/types/matcher_data.h" #include "library/jni/jni_support.h" #include "library/jni/types/env.h" -#include "library/jni/types/exception.h" namespace Envoy { namespace JNI { @@ -527,5 +527,69 @@ 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"); + auto java_byte_buffer_is_direct_method_id = + jni_helper.getMethodId(java_byte_buffer_class.get(), "isDirect", "()Z"); + return jni_helper.callBooleanMethod(java_byte_buffer, java_byte_buffer_is_direct_method_id); +} + +Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, + jobject java_byte_buffer, + jlong length) { + void* java_byte_buffer_address = jni_helper.getDirectBufferAddress(java_byte_buffer); + RELEASE_ASSERT(java_byte_buffer != nullptr, + "The ByteBuffer argument is not a direct ByteBuffer."); + Buffer::BufferFragmentImpl* byte_buffer_fragment = new Buffer::BufferFragmentImpl( + java_byte_buffer_address, static_cast(length), + [](const void*, size_t, const Buffer::BufferFragmentImpl* this_fragment) { + delete this_fragment; + }); + Buffer::InstancePtr cpp_buffer_instance = std::make_unique(); + cpp_buffer_instance->addBufferFragment(*byte_buffer_fragment); + 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 java_byte_buffer = + jni_helper.newDirectByteBuffer(raw_slice.mem_, static_cast(raw_slice.len_)); + return java_byte_buffer; +} + +Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, + jobject java_byte_buffer, + jlong length) { + auto 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"); + 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."); + 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()), + static_cast(length)); + return cpp_buffer_instance; +} + +LocalRefUniquePtr +cppBufferInstanceToJavaNonDirectByteBuffer(JniHelper& jni_helper, + const Buffer::Instance& cpp_buffer_instance) { + 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())); + return jni_helper.callStaticObjectMethod(java_byte_buffer_class.get(), + java_byte_buffer_wrap_method_id, java_byte_array.get()); +} + } // namespace JNI } // namespace Envoy diff --git a/mobile/library/jni/jni_utility.h b/mobile/library/jni/jni_utility.h index 3d4ff0b7d6f8..8677f47b2a56 100644 --- a/mobile/library/jni/jni_utility.h +++ b/mobile/library/jni/jni_utility.h @@ -3,6 +3,7 @@ #include #include +#include "envoy/buffer/buffer.h" #include "envoy/http/header_map.h" #include "source/common/protobuf/protobuf.h" @@ -183,5 +184,48 @@ LocalRefUniquePtr cppHeadersToJavaHeaders(JniHelper& jni_helper, void javaHeadersToCppHeaders(JniHelper& jni_helper, jobject java_headers, Http::HeaderMap& cpp_headers); +/** + * Returns true if the specified `java_byte_buffer` is a direct ByteBuffer; false otherwise. + */ +bool isJavaDirectByteBuffer(JniHelper& jni_helper, jobject java_byte_buffer); + +/** + * Converts from Java direct `ByteBuffer` (off the JVM heap) to `Envoy::Buffer::Instance` up to the + * specified length. + * + * The function will avoid copying the data from the Java `ByteBuffer` into + * `Envoy::Buffer::Instance`. + */ +Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, + jobject java_byte_buffer, jlong length); + +/** + * Converts from `Envoy::Buffer::Instance` to Java direct `ByteBuffer` (off the JVM heap). + * + * 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); + +/** + * Converts from Java non-direct `ByteBuffer` (on the JVM heap) to `Envoy::Buffer::Instance` up + * to the specified length. + * + * The function will copy the data from the Java `ByteBuffer` into `Envoy::Buffer::Instance`. + */ +Buffer::InstancePtr javaNonDirectByteBufferToCppBufferInstance(JniHelper& jni_helper, + jobject java_byte_buffer, + jlong length); + +/** + * Converts from `Envoy::Buffer::Instance` to Java non-direct `ByteBuffer` (off the JVM heap). + * + * The function will copy the data from `Envoy::Buffer::Instance` into the `ByteBuffer`. + */ +LocalRefUniquePtr +cppBufferInstanceToJavaNonDirectByteBuffer(JniHelper& jni_helper, + const Buffer::Instance& cpp_buffer_instance); + } // namespace JNI } // namespace Envoy diff --git a/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm b/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm index 3fd310b2b512..4274863a3772 100644 --- a/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm +++ b/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm @@ -1,6 +1,8 @@ #import "library/objective-c/EnvoyEngine.h" #import "library/objective-c/EnvoyBridgeUtility.h" +#include "source/common/buffer/buffer_impl.h" + #import "library/common/types/c_types.h" #import "library/common/internal_engine.h" #include "library/common/http/header_utility.h" @@ -196,7 +198,9 @@ - (void)sendHeaders:(EnvoyHeaders *)headers close:(BOOL)close { } - (void)sendData:(NSData *)data close:(BOOL)close { - _engine->sendData(_streamHandle, toNativeData(data), close); + Envoy::Buffer::InstancePtr buffer = std::make_unique(); + buffer->add([data bytes], data.length); + _engine->sendData(_streamHandle, std::move(buffer), close); } - (void)readData:(size_t)byteCount { diff --git a/mobile/test/cc/integration/BUILD b/mobile/test/cc/integration/BUILD index cf7f85dc0816..e3a60f2ceef3 100644 --- a/mobile/test/cc/integration/BUILD +++ b/mobile/test/cc/integration/BUILD @@ -30,6 +30,19 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "send_data_test", + srcs = ["send_data_test.cc"], + repository = "@envoy", + deps = [ + "//library/cc:engine_builder_lib", + "//library/common/http:header_utility_lib", + "//test/common/integration:engine_with_test_server", + "//test/common/integration:test_server_lib", + "@envoy_build_config//:test_extensions", + ], +) + envoy_cc_test( name = "lifetimes_test", srcs = ["lifetimes_test.cc"], diff --git a/mobile/test/cc/integration/send_data_test.cc b/mobile/test/cc/integration/send_data_test.cc new file mode 100644 index 000000000000..375c0ec9f677 --- /dev/null +++ b/mobile/test/cc/integration/send_data_test.cc @@ -0,0 +1,75 @@ +#include "test/common/integration/engine_with_test_server.h" +#include "test/common/integration/test_server.h" + +#include "absl/strings/str_format.h" +#include "absl/synchronization/notification.h" +#include "gtest/gtest.h" +#include "library/cc/engine_builder.h" +#include "library/cc/envoy_error.h" +#include "library/common/http/header_utility.h" + +namespace Envoy { + +inline constexpr absl::string_view ASSERTION_FILTER_TEXT_PROTO = R"( + [type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion] { + match_config { + http_request_generic_body_match: { + patterns: { + string_match: 'request body' + } + } + } + } +)"; + +TEST(SendDataTest, Success) { + absl::Notification engine_running; + Platform::EngineBuilder engine_builder; + engine_builder.enforceTrustChainVerification(false) + .setLogLevel(Logger::Logger::debug) +#ifdef ENVOY_ENABLE_FULL_PROTOS + .addNativeFilter("envoy.filters.http.assertion", std::string(ASSERTION_FILTER_TEXT_PROTO)) +#endif + .setOnEngineRunning([&]() { engine_running.Notify(); }); + EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS); + engine_running.WaitForNotification(); + + int actual_status_code; + bool actual_end_stream; + absl::Notification stream_complete; + auto stream_prototype = engine_with_test_server.engine()->streamClient()->newStreamPrototype(); + Platform::StreamSharedPtr stream = + (*stream_prototype) + .setOnHeaders( + [&](Platform::ResponseHeadersSharedPtr headers, bool end_stream, envoy_stream_intel) { + actual_status_code = headers->httpStatus(); + actual_end_stream = end_stream; + }) + .setOnData([&](envoy_data data, bool end_stream) { + actual_end_stream = end_stream; + data.release(data.context); + }) + .setOnComplete( + [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) + .setOnError([&](Platform::EnvoyErrorSharedPtr, envoy_stream_intel, + envoy_final_stream_intel) { stream_complete.Notify(); }) + .setOnCancel( + [&](envoy_stream_intel, envoy_final_stream_intel) { stream_complete.Notify(); }) + .start(); + + auto headers = Http::Utility::createRequestHeaderMapPtr(); + headers->addCopy(Http::LowerCaseString(":method"), "GET"); + headers->addCopy(Http::LowerCaseString(":scheme"), "https"); + headers->addCopy(Http::LowerCaseString(":authority"), + absl::StrFormat("localhost:%d", engine_with_test_server.testServer().getPort())); + headers->addCopy(Http::LowerCaseString(":path"), "/"); + stream->sendHeaders(std::move(headers), false); + stream->sendData(std::make_unique("request body")); + stream->close(std::make_unique("request body")); + stream_complete.WaitForNotification(); + + EXPECT_EQ(actual_status_code, 200); + EXPECT_TRUE(actual_end_stream); +} + +} // namespace Envoy diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index 5e680b3ed401..f50cd9dfcc1f 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -211,8 +211,7 @@ TEST_P(ClientTest, BasicStreamData) { }; // Build body data - Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); - envoy_data c_data = Data::Utility::toBridgeData(request_data); + auto request_data = std::make_unique("request body"); // Create a stream, and set up request_decoder_ and response_encoder_ createStream(); @@ -222,7 +221,7 @@ TEST_P(ClientTest, BasicStreamData) { EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body"), true)); - http_client_.sendData(stream_, c_data, true); + http_client_.sendData(stream_, std::move(request_data), true); resumeDataIfExplicitFlowControl(20); // Encode response data. @@ -286,12 +285,9 @@ TEST_P(ClientTest, MultipleDataStream) { cc_.end_stream_with_headers_ = false; // Build first body data - Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); - envoy_data c_data = Data::Utility::toBridgeData(request_data); - + auto request_data1 = std::make_unique("request body1"); // Build second body data - Buffer::OwnedImpl request_data2 = Buffer::OwnedImpl("request body2"); - envoy_data c_data2 = Data::Utility::toBridgeData(request_data2); + auto request_data2 = std::make_unique("request body2"); // Create a stream, and set up request_decoder_ and response_encoder_ createStream(); @@ -305,8 +301,8 @@ TEST_P(ClientTest, MultipleDataStream) { // Send request data. EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); - EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body"), false)); - http_client_.sendData(stream_, c_data, false); + EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body1"), false)); + http_client_.sendData(stream_, std::move(request_data1), false); EXPECT_EQ(cc_.on_send_window_available_calls, 0); if (explicit_flow_control_) { EXPECT_TRUE(process_buffered_data_callback->enabled_); @@ -319,7 +315,7 @@ TEST_P(ClientTest, MultipleDataStream) { EXPECT_CALL(dispatcher_, pushTrackedObject(_)); EXPECT_CALL(dispatcher_, popTrackedObject(_)); EXPECT_CALL(*request_decoder_, decodeData(BufferStringEqual("request body2"), true)); - http_client_.sendData(stream_, c_data2, true); + http_client_.sendData(stream_, std::move(request_data2), true); // The stream is done: no further on_send_window_available calls should happen. EXPECT_EQ(cc_.on_send_window_available_calls, explicit_flow_control_ ? 1 : 0); diff --git a/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java index 506b1506b91d..324fd2626a25 100644 --- a/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java +++ b/mobile/test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java @@ -8,6 +8,7 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -25,6 +26,11 @@ public class JniUtilityTest { public static native Map javaCppMapConversion(Map map); public static native Map> javaCppHeadersConversion(Map> headers); + public static native boolean isJavaDirectByteBuffer(ByteBuffer byteBuffer); + public static native ByteBuffer javaCppDirectByteBufferConversion(ByteBuffer byteBuffer, + long length); + public static native ByteBuffer javaCppNonDirectByteBufferConversion(ByteBuffer byteBuffer, + long length); @Test public void testProtoJavaByteArrayConversion() throws Exception { @@ -61,4 +67,80 @@ public void testJavaCppHeadersConversion() { headers.put("Mixed_Case_Key_3", Arrays.asList("value1")); assertThat(javaCppHeadersConversion(headers)).isEqualTo(headers); } + + @Test + public void testIsJavaDirectByteBuffer() { + assertThat(isJavaDirectByteBuffer(ByteBuffer.allocate(3))).isFalse(); + assertThat(isJavaDirectByteBuffer(ByteBuffer.allocateDirect(3))).isTrue(); + } + + @Test + public void testJavaCppDirectByteBufferFullLengthConversion() { + ByteBuffer inByteBuffer = ByteBuffer.allocateDirect(5); + inByteBuffer.put((byte)'h'); + inByteBuffer.put((byte)'e'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'o'); + inByteBuffer.flip(); + + ByteBuffer outByteBuffer = + javaCppDirectByteBufferConversion(inByteBuffer, inByteBuffer.capacity()); + assertThat(outByteBuffer.isDirect()).isTrue(); + assertThat(outByteBuffer).isEqualTo(inByteBuffer); + assertThat(outByteBuffer.capacity()).isEqualTo(5); + byte[] outBytes = new byte[5]; + outByteBuffer.get(outBytes); + assertThat(outBytes).isEqualTo(new byte[] {'h', 'e', 'l', 'l', 'o'}); + } + + @Test + public void testJavaCppDirectByteBufferNotFullLengthConversion() { + ByteBuffer inByteBuffer = ByteBuffer.allocateDirect(5); + inByteBuffer.put((byte)'h'); + inByteBuffer.put((byte)'e'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'o'); + inByteBuffer.flip(); + + ByteBuffer outByteBuffer = javaCppDirectByteBufferConversion(inByteBuffer, 3); + assertThat(outByteBuffer.isDirect()).isTrue(); + assertThat(outByteBuffer.capacity()).isEqualTo(3); + byte[] outBytes = new byte[3]; + outByteBuffer.get(outBytes); + assertThat(outBytes).isEqualTo(new byte[] {'h', 'e', 'l'}); + } + + @Test + public void testJavaCppNonDirectByteBufferFullLengthConversion() { + ByteBuffer inByteBuffer = ByteBuffer.allocate(5); + inByteBuffer.put((byte)'h'); + inByteBuffer.put((byte)'e'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'o'); + inByteBuffer.flip(); + + ByteBuffer outByteBuffer = + javaCppNonDirectByteBufferConversion(inByteBuffer, inByteBuffer.capacity()); + assertThat(outByteBuffer.isDirect()).isFalse(); + assertThat(outByteBuffer).isEqualTo(inByteBuffer); + assertThat(outByteBuffer.array()).isEqualTo(new byte[] {'h', 'e', 'l', 'l', 'o'}); + } + + @Test + public void testJavaCppNonDirectByteBufferNotFullLengthConversion() { + ByteBuffer inByteBuffer = ByteBuffer.allocate(5); + inByteBuffer.put((byte)'h'); + inByteBuffer.put((byte)'e'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'l'); + inByteBuffer.put((byte)'o'); + inByteBuffer.flip(); + + ByteBuffer outByteBuffer = javaCppNonDirectByteBufferConversion(inByteBuffer, 3); + assertThat(outByteBuffer.isDirect()).isFalse(); + assertThat(outByteBuffer.array()).isEqualTo(new byte[] {'h', 'e', 'l'}); + } } diff --git a/mobile/test/jni/jni_utility_test.cc b/mobile/test/jni/jni_utility_test.cc index ce7dded01530..964eb63758df 100644 --- a/mobile/test/jni/jni_utility_test.cc +++ b/mobile/test/jni/jni_utility_test.cc @@ -44,3 +44,30 @@ Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppHeadersConversion(JNIEn Envoy::JNI::javaHeadersToCppHeaders(jni_helper, java_headers, *cpp_headers); return Envoy::JNI::cppHeadersToJavaHeaders(jni_helper, *cpp_headers).release(); } + +extern "C" JNIEXPORT jboolean JNICALL +Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_isJavaDirectByteBuffer(JNIEnv* env, jclass, + jobject java_byte_buffer) { + Envoy::JNI::JniHelper jni_helper(env); + return Envoy::JNI::isJavaDirectByteBuffer(jni_helper, java_byte_buffer); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppDirectByteBufferConversion( + JNIEnv* env, jclass, jobject java_byte_buffer, jlong length) { + 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) + .release(); +} + +extern "C" JNIEXPORT jobject JNICALL +Java_io_envoyproxy_envoymobile_jni_JniUtilityTest_javaCppNonDirectByteBufferConversion( + JNIEnv* env, jclass, jobject java_byte_buffer, jlong length) { + 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) + .release(); +} diff --git a/mobile/test/kotlin/integration/SendDataTest.kt b/mobile/test/kotlin/integration/SendDataTest.kt index a814eb1fb7d5..411e6c6e5759 100644 --- a/mobile/test/kotlin/integration/SendDataTest.kt +++ b/mobile/test/kotlin/integration/SendDataTest.kt @@ -17,9 +17,18 @@ import org.junit.Assert.fail import org.junit.Before import org.junit.Test -private const val ASSERTION_FILTER_TYPE = - "type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion" -private const val REQUEST_STRING_MATCH = "match_me" +private const val ASSERTION_FILTER_TEXT_PROTO = + """ + [type.googleapis.com/envoymobile.extensions.filters.http.assertion.Assertion] { + match_config { + http_request_generic_body_match: { + patterns: { + string_match: 'request body' + } + } + } +} +""" class SendDataTest { init { @@ -52,10 +61,7 @@ class SendDataTest { .addLogLevel(LogLevel.DEBUG) .setLogger { _, msg -> print(msg) } .setTrustChainVerification(TrustChainVerification.ACCEPT_UNTRUSTED) - .addNativeFilter( - "envoy.filters.http.assertion", - "[$ASSERTION_FILTER_TYPE] { match_config { http_request_generic_body_match: { patterns: { string_match: '$REQUEST_STRING_MATCH'}}}}" - ) + .addNativeFilter("envoy.filters.http.assertion", ASSERTION_FILTER_TEXT_PROTO) .build() val client = engine.streamClient() @@ -69,8 +75,6 @@ class SendDataTest { ) .build() - val body = ByteBuffer.wrap(REQUEST_STRING_MATCH.toByteArray(Charsets.UTF_8)) - var responseStatus: Int? = null var responseEndStream = false client @@ -88,7 +92,7 @@ class SendDataTest { .setOnError { _, _ -> fail("Unexpected error") } .start() .sendHeaders(requestHeaders, false) - .close(body) + .close(ByteBuffer.wrap("request body".toByteArray(Charsets.UTF_8))) expectation.await(10, TimeUnit.SECONDS)