diff --git a/envoy_build_config/BUILD b/envoy_build_config/BUILD index 90ec0d6f08..8660a2d463 100644 --- a/envoy_build_config/BUILD +++ b/envoy_build_config/BUILD @@ -16,6 +16,7 @@ envoy_cc_library( "@envoy//source/common/network:socket_lib", "@envoy//source/common/upstream:logical_dns_cluster_lib", "@envoy//source/extensions/clusters/dynamic_forward_proxy:cluster", + "@envoy//source/extensions/compression/brotli/decompressor:config", "@envoy//source/extensions/compression/gzip/decompressor:config", "@envoy//source/extensions/filters/http/buffer:config", "@envoy//source/extensions/filters/http/decompressor:config", diff --git a/envoy_build_config/extension_registry.cc b/envoy_build_config/extension_registry.cc index d1d82bc7a3..a5fcfb5c85 100644 --- a/envoy_build_config/extension_registry.cc +++ b/envoy_build_config/extension_registry.cc @@ -3,12 +3,14 @@ #include "source/common/network/socket_interface_impl.h" #include "source/common/upstream/logical_dns_cluster.h" #include "source/extensions/clusters/dynamic_forward_proxy/cluster.h" +#include "source/extensions/compression/brotli/decompressor/config.h" #include "source/extensions/compression/gzip/decompressor/config.h" #include "source/extensions/filters/http/buffer/config.h" #include "source/extensions/filters/http/decompressor/config.h" #include "source/extensions/filters/http/dynamic_forward_proxy/config.h" #include "source/extensions/filters/http/router/config.h" #include "source/extensions/filters/network/http_connection_manager/config.h" +#include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" #include "source/extensions/http/original_ip_detection/xff/config.h" #include "source/extensions/stat_sinks/metrics_service/config.h" #include "source/extensions/transport_sockets/raw_buffer/config.h" @@ -18,12 +20,18 @@ #include "extension_registry_platform_additions.h" #include "library/common/extensions/filters/http/assertion/config.h" +#include "library/common/extensions/filters/http/local_error/config.h" +#include "library/common/extensions/filters/http/network_configuration/config.h" #include "library/common/extensions/filters/http/platform_bridge/config.h" +#include "library/common/extensions/filters/http/route_cache_reset/config.h" +#include "library/common/extensions/retry/options/network_configuration/config.h" namespace Envoy { void ExtensionRegistry::registerFactories() { Envoy::Extensions::Clusters::DynamicForwardProxy::forceRegisterClusterFactory(); + Envoy::Extensions::Compression::Brotli::Decompressor:: + forceRegisterBrotliDecompressorLibraryFactory(); Envoy::Extensions::Compression::Gzip::Decompressor::forceRegisterGzipDecompressorLibraryFactory(); Envoy::Extensions::Http::OriginalIPDetection::Xff::forceRegisterXffIPDetectionFactory(); Envoy::Extensions::HttpFilters::Assertion::forceRegisterAssertionFilterFactory(); diff --git a/envoy_build_config/extension_registry.h b/envoy_build_config/extension_registry.h index e9ad316a22..5f5be0ae03 100644 --- a/envoy_build_config/extension_registry.h +++ b/envoy_build_config/extension_registry.h @@ -1,29 +1,5 @@ #pragma once -#include "source/common/upstream/logical_dns_cluster.h" -#include "source/extensions/clusters/dynamic_forward_proxy/cluster.h" -#include "source/extensions/compression/gzip/decompressor/config.h" -#include "source/extensions/filters/http/buffer/config.h" -#include "source/extensions/filters/http/decompressor/config.h" -#include "source/extensions/filters/http/dynamic_forward_proxy/config.h" -#include "source/extensions/filters/http/router/config.h" -#include "source/extensions/filters/network/http_connection_manager/config.h" -#include "source/extensions/http/header_formatters/preserve_case/preserve_case_formatter.h" -#include "source/extensions/http/original_ip_detection/xff/config.h" -#include "source/extensions/stat_sinks/metrics_service/config.h" -#include "source/extensions/transport_sockets/raw_buffer/config.h" -#include "source/extensions/transport_sockets/tls/cert_validator/default_validator.h" -#include "source/extensions/transport_sockets/tls/config.h" -#include "source/extensions/upstreams/http/generic/config.h" - -#include "extension_registry_platform_additions.h" -#include "library/common/extensions/filters/http/assertion/config.h" -#include "library/common/extensions/filters/http/local_error/config.h" -#include "library/common/extensions/filters/http/network_configuration/config.h" -#include "library/common/extensions/filters/http/platform_bridge/config.h" -#include "library/common/extensions/filters/http/route_cache_reset/config.h" -#include "library/common/extensions/retry/options/network_configuration/config.h" - namespace Envoy { class ExtensionRegistry { public: diff --git a/envoy_build_config/extensions_build_config.bzl b/envoy_build_config/extensions_build_config.bzl index 82e876ba73..325a6bf335 100644 --- a/envoy_build_config/extensions_build_config.bzl +++ b/envoy_build_config/extensions_build_config.bzl @@ -6,6 +6,7 @@ EXTENSIONS = { "envoy.filters.connection_pools.http.generic": "//source/extensions/upstreams/http/generic:config", "envoy.filters.http.assertion": "@envoy_mobile//library/common/extensions/filters/http/assertion:config", "envoy.filters.http.buffer": "//source/extensions/filters/http/buffer:config", + "envoy.filters.http.decompressor": "//source/extensions/filters/http/decompressor:config", "envoy.filters.http.dynamic_forward_proxy": "//source/extensions/filters/http/dynamic_forward_proxy:config", "envoy.filters.http.local_error": "@envoy_mobile//library/common/extensions/filters/http/local_error:config", "envoy.filters.http.platform_bridge": "@envoy_mobile//library/common/extensions/filters/http/platform_bridge:config", diff --git a/library/java/org/chromium/net/impl/CronetEngineBuilderImpl.java b/library/java/org/chromium/net/impl/CronetEngineBuilderImpl.java index fb2720a1b5..23d20b39c2 100644 --- a/library/java/org/chromium/net/impl/CronetEngineBuilderImpl.java +++ b/library/java/org/chromium/net/impl/CronetEngineBuilderImpl.java @@ -5,7 +5,6 @@ import android.content.Context; import android.util.Base64; import androidx.annotation.IntDef; -import androidx.annotation.VisibleForTesting; import java.io.File; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -78,7 +77,6 @@ final static class Pkp { private int mHttpCacheMode; private long mHttpCacheMaxSize; private String mExperimentalOptions; - protected long mMockCertVerifier; private boolean mNetworkQualityEstimatorEnabled; private int mThreadPriority = INVALID_THREAD_PRIORITY; private String mLogLevel = "info"; @@ -318,21 +316,6 @@ public CronetEngineBuilderImpl setExperimentalOptions(String options) { public String experimentalOptions() { return mExperimentalOptions; } - /** - * Sets a native MockCertVerifier for testing. See {@code MockCertVerifier.createMockCertVerifier} - * for a method that can be used to create a MockCertVerifier. - * - * @param mockCertVerifier pointer to native MockCertVerifier. - * @return the builder to facilitate chaining. - */ - @VisibleForTesting - public CronetEngineBuilderImpl setMockCertVerifierForTesting(long mockCertVerifier) { - mMockCertVerifier = mockCertVerifier; - return this; - } - - long mockCertVerifier() { return mMockCertVerifier; } - /** * @return true if the network quality estimator has been enabled for * this builder. diff --git a/library/java/org/chromium/net/impl/CronetUrlRequest.java b/library/java/org/chromium/net/impl/CronetUrlRequest.java index 5985cd4e05..2105045b37 100644 --- a/library/java/org/chromium/net/impl/CronetUrlRequest.java +++ b/library/java/org/chromium/net/impl/CronetUrlRequest.java @@ -871,6 +871,9 @@ public void onTrailers(Map> trailers, EnvoyStreamIntel stre if (completeAbandonIfAny(originalState, updatedState)) { return; } + if (mState.compareAndSet(State.READING, State.COMPLETE)) { + mCronvoyCallbacks.successReady(SucceededState.FINAL_READ_DONE); + } } @Override diff --git a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java index dfaecbf22d..39f88f6cdb 100644 --- a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java +++ b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java @@ -3,6 +3,7 @@ import static io.envoyproxy.envoymobile.engine.EnvoyConfiguration.TrustChainVerification.VERIFY_TRUST_CHAIN; import android.content.Context; +import androidx.annotation.VisibleForTesting; import io.envoyproxy.envoymobile.engine.AndroidEngineImpl; import io.envoyproxy.envoymobile.engine.AndroidJniLibrary; import io.envoyproxy.envoymobile.engine.AndroidNetworkMonitor; @@ -15,6 +16,7 @@ import io.envoyproxy.envoymobile.engine.types.EnvoyLogger; import io.envoyproxy.envoymobile.engine.types.EnvoyOnEngineRunning; import io.envoyproxy.envoymobile.engine.types.EnvoyStringAccessor; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; @@ -26,6 +28,24 @@ */ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl { + private static final String BROTLI_CONFIG = + "\n" + + + " \"@type\": type.googleapis.com/envoy.extensions.filters.http.decompressor.v3.Decompressor\n" + + " decompressor_library:\n" + + " name: text_optimized\n" + + " typed_config:\n" + + + " \"@type\": type.googleapis.com/envoy.extensions.compression.brotli.decompressor.v3.Brotli\n" + + " request_direction_config:\n" + + " common_config:\n" + + " enabled:\n" + + " default_value: false\n" + + " runtime_key: request_decompressor_enabled\n" + + " response_direction_config:\n" + + " common_config:\n" + + " ignore_no_transform_header: true\n"; + private final EnvoyLogger mEnvoyLogger = null; private final EnvoyEventTracker mEnvoyEventTracker = null; private boolean mAdminInterfaceEnabled = false; @@ -51,9 +71,6 @@ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl { private String mAppId = "unspecified"; private TrustChainVerification mTrustChainVerification = VERIFY_TRUST_CHAIN; private String mVirtualClusters = "[]"; - private List mPlatformFilterChain = Collections.emptyList(); - private List mNativeFilterChain = Collections.emptyList(); - private Map mStringAccessors = Collections.emptyMap(); /** * Builder for Native Cronet Engine. Default config enables SPDY, disables QUIC and HTTP cache. @@ -62,6 +79,17 @@ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl { */ public NativeCronetEngineBuilderImpl(Context context) { super(context); } + /** + * Indicates to skip the TLS certificate verification. + * + * @return the builder to facilitate chaining. + */ + @VisibleForTesting + public CronetEngineBuilderImpl setMockCertVerifierForTesting() { + mTrustChainVerification = TrustChainVerification.ACCEPT_UNTRUSTED; + return this; + } + @Override public ExperimentalCronetEngine build() { if (getUserAgent() == null) { @@ -80,6 +108,13 @@ EnvoyEngine createEngine(EnvoyOnEngineRunning onEngineRunning) { } private EnvoyConfiguration createEnvoyConfiguration() { + List platformFilterChain = Collections.emptyList(); + List nativeFilterChain = new ArrayList<>(); + Map stringAccessors = Collections.emptyMap(); + if (brotliEnabled()) { + nativeFilterChain.add( + new EnvoyNativeFilterConfig("envoy.filters.http.decompressor", BROTLI_CONFIG)); + } return new EnvoyConfiguration( mAdminInterfaceEnabled, mGrpcStatsDomain, mStatsDPort, mConnectTimeoutSeconds, mDnsRefreshSeconds, mDnsFailureRefreshSecondsBase, mDnsFailureRefreshSecondsMax, @@ -87,7 +122,7 @@ private EnvoyConfiguration createEnvoyConfiguration() { mEnableDnsFilterUnroutableFamilies, mEnableHappyEyeballs, mEnableInterfaceBinding, mH2ConnectionKeepaliveIdleIntervalMilliseconds, mH2ConnectionKeepaliveTimeoutSeconds, mH2RawDomains, mStatsFlushSeconds, mStreamIdleTimeoutSeconds, mPerTryIdleTimeoutSeconds, - mAppVersion, mAppId, mTrustChainVerification, mVirtualClusters, mNativeFilterChain, - mPlatformFilterChain, mStringAccessors); + mAppVersion, mAppId, mTrustChainVerification, mVirtualClusters, nativeFilterChain, + platformFilterChain, stringAccessors); } } diff --git a/test/java/org/chromium/net/BUILD b/test/java/org/chromium/net/BUILD index 16ab07e6c7..17f33ec87f 100644 --- a/test/java/org/chromium/net/BUILD +++ b/test/java/org/chromium/net/BUILD @@ -8,6 +8,7 @@ envoy_package() envoy_mobile_android_test( name = "net_tests", srcs = [ + "BrotliTest.java", "CronetEngineBuilderTest.java", "CronetStressTest.java", "DiskStorageTest.java", diff --git a/test/java/org/chromium/net/BrotliTest.java b/test/java/org/chromium/net/BrotliTest.java new file mode 100644 index 0000000000..1923103b68 --- /dev/null +++ b/test/java/org/chromium/net/BrotliTest.java @@ -0,0 +1,105 @@ +package org.chromium.net; + +import static org.chromium.net.testing.CronetTestRule.SERVER_CERT_PEM; +import static org.chromium.net.testing.CronetTestRule.SERVER_KEY_PKCS8_PEM; +import static org.chromium.net.testing.CronetTestRule.getContext; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import org.chromium.net.testing.CronetTestRule; +import org.chromium.net.testing.CronetTestRule.OnlyRunNativeCronet; +import org.chromium.net.testing.CronetTestUtil; +import org.chromium.net.testing.Feature; +import org.chromium.net.testing.Http2TestServer; +import org.chromium.net.testing.TestFilesInstaller; +import org.chromium.net.testing.TestUrlRequestCallback; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Simple test for Brotli support. + */ +@RunWith(RobolectricTestRunner.class) +public class BrotliTest { + @Rule public final CronetTestRule mTestRule = new CronetTestRule(); + + private CronetEngine mCronetEngine; + + @Before + public void setUp() throws Exception { + TestFilesInstaller.installIfNeeded(getContext()); + assertTrue( + Http2TestServer.startHttp2TestServer(getContext(), SERVER_CERT_PEM, SERVER_KEY_PKCS8_PEM)); + } + + @After + public void tearDown() throws Exception { + assertTrue(Http2TestServer.shutdownHttp2TestServer()); + if (mCronetEngine != null) { + mCronetEngine.shutdown(); + } + } + + @Test + @SmallTest + @Feature({"Cronet"}) + @OnlyRunNativeCronet + public void testBrotliAdvertised() throws Exception { + ExperimentalCronetEngine.Builder builder = new ExperimentalCronetEngine.Builder(getContext()); + builder.enableBrotli(true); + CronetTestUtil.setMockCertVerifierForTesting(builder); + mCronetEngine = builder.build(); + String url = Http2TestServer.getEchoAllHeadersUrl(); + TestUrlRequestCallback callback = startAndWaitForComplete(url); + assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); + // TODO(carloseltuerto): also support "deflate" decompressor - Cronet does. + assertTrue(callback.mResponseAsString.contains("accept-encoding: br,gzip")); + } + + @Test + @SmallTest + @Feature({"Cronet"}) + @OnlyRunNativeCronet + public void testBrotliNotAdvertised() throws Exception { + ExperimentalCronetEngine.Builder builder = new ExperimentalCronetEngine.Builder(getContext()); + CronetTestUtil.setMockCertVerifierForTesting(builder); + mCronetEngine = builder.build(); + String url = Http2TestServer.getEchoAllHeadersUrl(); + TestUrlRequestCallback callback = startAndWaitForComplete(url); + assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); + assertFalse(callback.mResponseAsString.contains("br")); + } + + @Test + @SmallTest + @Feature({"Cronet"}) + @OnlyRunNativeCronet + public void testBrotliDecoded() throws Exception { + ExperimentalCronetEngine.Builder builder = new ExperimentalCronetEngine.Builder(getContext()); + builder.enableBrotli(true); + CronetTestUtil.setMockCertVerifierForTesting(builder); + mCronetEngine = builder.build(); + String url = Http2TestServer.getServeSimpleBrotliResponse(); + TestUrlRequestCallback callback = startAndWaitForComplete(url); + assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); + String expectedResponse = "The quick brown fox jumps over the lazy dog"; + assertEquals(expectedResponse, callback.mResponseAsString); + // TODO(https://github.com/envoyproxy/envoy-mobile/issues/2086): uncomment this line. + // assertEquals(callback.mResponseInfo.getAllHeaders().get("content-encoding").get(0),"br"); + } + + private TestUrlRequestCallback startAndWaitForComplete(String url) { + TestUrlRequestCallback callback = new TestUrlRequestCallback(); + UrlRequest.Builder builder = + mCronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor()); + builder.build().start(); + callback.blockForDone(); + return callback; + } +} diff --git a/test/java/org/chromium/net/testing/BUILD b/test/java/org/chromium/net/testing/BUILD index 3a03ea6098..f36f0ea28c 100644 --- a/test/java/org/chromium/net/testing/BUILD +++ b/test/java/org/chromium/net/testing/BUILD @@ -13,6 +13,7 @@ android_library( "ConditionVariable.java", "ContextUtils.java", "CronetTestRule.java", + "CronetTestUtil.java", "FailurePhase.java", "Feature.java", "FileUtils.java", diff --git a/test/java/org/chromium/net/testing/CronetTestUtil.java b/test/java/org/chromium/net/testing/CronetTestUtil.java new file mode 100644 index 0000000000..7bd5bb417c --- /dev/null +++ b/test/java/org/chromium/net/testing/CronetTestUtil.java @@ -0,0 +1,21 @@ +package org.chromium.net.testing; + +import org.chromium.net.ExperimentalCronetEngine; +import org.chromium.net.impl.NativeCronetEngineBuilderImpl; + +/** + * Utilities for Cronet testing + */ +public final class CronetTestUtil { + + public static void setMockCertVerifierForTesting(ExperimentalCronetEngine.Builder builder) { + getCronetEngineBuilderImpl(builder).setMockCertVerifierForTesting(); + } + + public static NativeCronetEngineBuilderImpl + getCronetEngineBuilderImpl(ExperimentalCronetEngine.Builder builder) { + return (NativeCronetEngineBuilderImpl)builder.getBuilderDelegate(); + } + + private CronetTestUtil() {} +}