diff --git a/bazel/envoy_mobile_test_extensions.bzl b/bazel/envoy_mobile_test_extensions.bzl index d7edf2bb58..6652ea669d 100644 --- a/bazel/envoy_mobile_test_extensions.bzl +++ b/bazel/envoy_mobile_test_extensions.bzl @@ -6,4 +6,5 @@ TEST_EXTENSIONS = [ "//library/common/extensions/filters/http/test_accessor:config", "//library/common/extensions/filters/http/test_event_tracker:config", "//library/common/extensions/filters/http/test_kv_store:config", + "//test/integration/filters/http/test_read:config", ] diff --git a/library/java/org/chromium/net/impl/Annotations.java b/library/java/org/chromium/net/impl/Annotations.java index b5b1d86f5a..d1ff82d154 100644 --- a/library/java/org/chromium/net/impl/Annotations.java +++ b/library/java/org/chromium/net/impl/Annotations.java @@ -19,16 +19,6 @@ public final class Annotations { int HIGHEST = 5; } - /** Subset of errors defined in chromium/src/net/base/net_error_list.h */ - @IntDef({NetError.ERR_HTTP2_PING_FAILED, NetError.ERR_QUIC_HANDSHAKE_FAILED}) - @Retention(RetentionPolicy.SOURCE) - public @interface NetError { - int ERR_NETWORK_CHANGED = -21; - int ERR_HTTP2_PING_FAILED = -352; - int ERR_QUIC_PROTOCOL_ERROR = -356; - int ERR_QUIC_HANDSHAKE_FAILED = -358; - } - /** Enum defined here: chromium/src/components/cronet/url_request_context_config.h, line 37 */ @IntDef({HttpCacheType.DISABLED, HttpCacheType.DISK, HttpCacheType.MEMORY}) @Retention(RetentionPolicy.SOURCE) diff --git a/library/java/org/chromium/net/impl/BUILD b/library/java/org/chromium/net/impl/BUILD index b53ef2567f..aa7c3c2d53 100644 --- a/library/java/org/chromium/net/impl/BUILD +++ b/library/java/org/chromium/net/impl/BUILD @@ -24,6 +24,7 @@ android_library( "CronetUploadDataStream.java", "CronetUrlRequest.java", "CronetUrlRequestContext.java", + "Errors.java", "Executors.java", "HttpReason.java", "ImplVersion.java", diff --git a/library/java/org/chromium/net/impl/BidirectionalStreamNetworkException.java b/library/java/org/chromium/net/impl/BidirectionalStreamNetworkException.java index 61dfa25686..0fe8e8d2db 100644 --- a/library/java/org/chromium/net/impl/BidirectionalStreamNetworkException.java +++ b/library/java/org/chromium/net/impl/BidirectionalStreamNetworkException.java @@ -1,6 +1,6 @@ package org.chromium.net.impl; -import org.chromium.net.impl.Annotations.NetError; +import org.chromium.net.impl.Errors.NetError; /** * Used in {@link CronetBidirectionalStream}. Implements {@link NetworkExceptionImpl}. @@ -13,13 +13,11 @@ public BidirectionalStreamNetworkException(String message, int errorCode, @Override public boolean immediatelyRetryable() { - switch (mCronetInternalErrorCode) { - case NetError.ERR_HTTP2_PING_FAILED: - case NetError.ERR_QUIC_HANDSHAKE_FAILED: + if (mCronetInternalErrorCode == NetError.ERR_HTTP2_PING_FAILED.getErrorCode() || + mCronetInternalErrorCode == NetError.ERR_QUIC_HANDSHAKE_FAILED.getErrorCode()) { assert mErrorCode == ERROR_OTHER; return true; - default: - return super.immediatelyRetryable(); } + return super.immediatelyRetryable(); } } diff --git a/library/java/org/chromium/net/impl/CronetBidirectionalStream.java b/library/java/org/chromium/net/impl/CronetBidirectionalStream.java index 0c9f0425ff..b72ed3042c 100644 --- a/library/java/org/chromium/net/impl/CronetBidirectionalStream.java +++ b/library/java/org/chromium/net/impl/CronetBidirectionalStream.java @@ -1,5 +1,9 @@ package org.chromium.net.impl; +import static org.chromium.net.impl.Errors.isQuicException; +import static org.chromium.net.impl.Errors.mapEnvoyMobileErrorToNetError; +import static org.chromium.net.impl.Errors.mapNetErrorToCronetApiErrorCode; + import android.util.Log; import androidx.annotation.Nullable; @@ -15,6 +19,7 @@ import org.chromium.net.impl.Annotations.RequestPriority; import org.chromium.net.impl.CronetBidirectionalState.Event; import org.chromium.net.impl.CronetBidirectionalState.NextAction; +import org.chromium.net.impl.Errors.NetError; import org.chromium.net.impl.UrlResponseInfoImpl.HeaderBlockImpl; import java.net.MalformedURLException; @@ -643,21 +648,23 @@ public void run() { }); } - private void onErrorReceived(int errorCode, int nativeError, int nativeQuicError, - String errorString, long receivedByteCount) { + private void onErrorReceived(int errorCode, EnvoyFinalStreamIntel finalStreamIntel) { if (mResponseInfo != null) { - mResponseInfo.setReceivedByteCount(receivedByteCount); + mResponseInfo.setReceivedByteCount(finalStreamIntel.getReceivedByteCount()); } - CronetException exception; - if (errorCode == NetworkException.ERROR_QUIC_PROTOCOL_FAILED || - errorCode == NetworkException.ERROR_NETWORK_CHANGED) { - exception = new QuicExceptionImpl("Exception in BidirectionalStream: " + errorString, - errorCode, nativeError, nativeQuicError); + + NetError netError = mapEnvoyMobileErrorToNetError(finalStreamIntel.getResponseFlags()); + int javaError = mapNetErrorToCronetApiErrorCode(netError); + + if (isQuicException(javaError)) { + mException.set(new QuicExceptionImpl("Exception in BidirectionalStream: " + netError, + javaError, netError.getErrorCode(), + /*nativeQuicError*/ 0)); } else { - exception = new BidirectionalStreamNetworkException( - "Exception in BidirectionalStream: " + errorString, errorCode, nativeError); + mException.set(new BidirectionalStreamNetworkException( + "Exception in BidirectionalStream: " + netError, javaError, netError.getErrorCode())); } - mException.set(exception); + failWithException(); } @@ -1031,9 +1038,7 @@ public void onError(int errorCode, String message, int attemptCount, EnvoyStream mEnvoyFinalStreamIntel = finalStreamIntel; switch (mState.nextAction(Event.ON_ERROR)) { case NextAction.NOTIFY_USER_NETWORK_ERROR: - // TODO(https://github.com/envoyproxy/envoy-mobile/issues/1594): fix error scheme. - onErrorReceived(errorCode, /* nativeError= */ -1, - /* nativeQuicError */ 0, message, finalStreamIntel.getReceivedByteCount()); + onErrorReceived(errorCode, finalStreamIntel); break; case NextAction.NOTIFY_USER_FAILED: // There was already an error in-progress - the network error came too late and is ignored. diff --git a/library/java/org/chromium/net/impl/CronetUrlRequest.java b/library/java/org/chromium/net/impl/CronetUrlRequest.java index 50058aea60..e9b27db1dc 100644 --- a/library/java/org/chromium/net/impl/CronetUrlRequest.java +++ b/library/java/org/chromium/net/impl/CronetUrlRequest.java @@ -1,5 +1,9 @@ package org.chromium.net.impl; +import static org.chromium.net.impl.Errors.isQuicException; +import static org.chromium.net.impl.Errors.mapEnvoyMobileErrorToNetError; +import static org.chromium.net.impl.Errors.mapNetErrorToCronetApiErrorCode; + import android.os.ConditionVariable; import android.util.Log; import androidx.annotation.IntDef; @@ -30,9 +34,11 @@ import org.chromium.net.CallbackException; import org.chromium.net.CronetException; import org.chromium.net.InlineExecutionProhibitedException; +import org.chromium.net.NetworkException; import org.chromium.net.RequestFinishedInfo; import org.chromium.net.RequestFinishedInfo.Metrics; import org.chromium.net.UploadDataProvider; +import org.chromium.net.impl.Errors.NetError; /** UrlRequest, backed by Envoy-Mobile. */ public final class CronetUrlRequest extends UrlRequestBase { @@ -448,6 +454,7 @@ private static int determineNextErrorState(boolean streamEnded, @State int origi } } + // No-op if already in a terminal state. private void enterErrorState(CronetException error) { @State int originalState; @State int updatedState; @@ -927,10 +934,18 @@ public void onError(int errorCode, String message, int attemptCount, return; } - String errorMessage = "failed with error after " + attemptCount + " attempts. Message=[" + - message + "] Code=[" + errorCode + "]"; - CronetException exception = new CronetExceptionImpl(errorMessage, /* cause= */ null); - enterErrorState(exception); // No-op if already in a terminal state. + NetError netError = mapEnvoyMobileErrorToNetError(finalStreamIntel.getResponseFlags()); + int javaError = mapNetErrorToCronetApiErrorCode(netError); + + if (isQuicException(javaError)) { + enterErrorState(new QuicExceptionImpl("Exception in CronetUrlRequest: " + netError, + javaError, netError.getErrorCode(), + /*nativeQuicError*/ 0)); + return; + } + + enterErrorState(new NetworkExceptionImpl("Exception in CronetUrlRequest: " + netError, + javaError, netError.getErrorCode())); } @Override diff --git a/library/java/org/chromium/net/impl/Errors.java b/library/java/org/chromium/net/impl/Errors.java new file mode 100644 index 0000000000..85add92f5c --- /dev/null +++ b/library/java/org/chromium/net/impl/Errors.java @@ -0,0 +1,129 @@ +package org.chromium.net.impl; + +import android.util.Log; +import androidx.annotation.LongDef; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.chromium.net.NetworkException; + +/** + * Handles mapping of the error codes that exist in the Cronvoy space. That is, + * from Envoymobile error to Chromium neterror and finally to the public Network Exception. + */ +public class Errors { + private static final Map ENVOYMOBILE_ERROR_TO_NET_ERROR = buildErrorMap(); + + /**Subset of errors defined in + * https://github.com/envoyproxy/envoy/blob/main/envoy/stream_info/stream_info.h */ + @LongDef(flag = true, + value = {EnvoyMobileError.DNS_RESOLUTION_FAILED, EnvoyMobileError.DURATION_TIMEOUT, + EnvoyMobileError.STREAM_IDLE_TIMEOUT, + EnvoyMobileError.UPSTREAM_CONNECTION_FAILURE, + EnvoyMobileError.UPSTREAM_CONNECTION_TERMINATION, + EnvoyMobileError.UPSTREAM_REMOTE_RESET}) + @Retention(RetentionPolicy.SOURCE) + public @interface EnvoyMobileError { + long DNS_RESOLUTION_FAILED = 0x4000000; + long DURATION_TIMEOUT = 0x400000; + long STREAM_IDLE_TIMEOUT = 0x10000; + long UPSTREAM_CONNECTION_FAILURE = 0x20; + long UPSTREAM_CONNECTION_TERMINATION = 0x40; + long UPSTREAM_REMOTE_RESET = 0x10; + } + + /** Subset of errors defined in chromium/src/net/base/net_error_list.h */ + public enum NetError { + ERR_NETWORK_CHANGED(-21), + ERR_HTTP2_PING_FAILED(-352), + ERR_QUIC_PROTOCOL_ERROR(-356), + ERR_QUIC_HANDSHAKE_FAILED(-358), + ERR_NAME_NOT_RESOLVED(-105), + ERR_INTERNET_DISCONNECTED(-106), + ERR_TIMED_OUT(-7), + ERR_CONNECTION_CLOSED(-100), + ERR_CONNECTION_TIMED_OUT(-118), + ERR_CONNECTION_REFUSED(-102), + ERR_CONNECTION_RESET(-101), + ERR_ADDRESS_UNREACHABLE(-109), + ERR_OTHER(-1000); + + private final int errorCode; + + NetError(int errorCode) { this.errorCode = errorCode; } + + public int getErrorCode() { return errorCode; } + + @Override + public String toString() { + return "net::" + name(); + } + } + + /** + * Maps Envoymobile's errorcode to chromium's net errorcode + * @param responseFlag envoymobile's finalStreamIntel responseFlag + * @return the NetError that the EnvoyMobileError maps to + */ + public static NetError mapEnvoyMobileErrorToNetError(long responseFlag) { + /* Todo(https://github.com/envoyproxy/envoy-mobile/issues/1594): + * if (EnvoyMobileError.DNS_RESOLUTION_FAILED || EnvoyMobileError.UPSTREAM_CONNECTION_FAILURE) + * && NetworkChangeNotifier.isOffline return NetError.ERR_INTERNET_DISCONNECTED + * + * if negotiated_protocol is quic return QUIC_PROTOCOL_FAILED + */ + return ENVOYMOBILE_ERROR_TO_NET_ERROR.getOrDefault(responseFlag, NetError.ERR_OTHER); + } + + /** + * Maps chromium's net errorcode to Cronet API errorcode + * @return the corresponding NetworkException errorcode + */ + public static int mapNetErrorToCronetApiErrorCode(NetError netError) { + switch (netError) { + case ERR_NAME_NOT_RESOLVED: + return NetworkException.ERROR_HOSTNAME_NOT_RESOLVED; + case ERR_TIMED_OUT: + return NetworkException.ERROR_TIMED_OUT; + case ERR_CONNECTION_CLOSED: + return NetworkException.ERROR_CONNECTION_CLOSED; + case ERR_CONNECTION_RESET: + return NetworkException.ERROR_CONNECTION_RESET; + case ERR_CONNECTION_REFUSED: + return NetworkException.ERROR_CONNECTION_REFUSED; + case ERR_OTHER: + return NetworkException.ERROR_OTHER; + case ERR_INTERNET_DISCONNECTED: + return NetworkException.ERROR_INTERNET_DISCONNECTED; + case ERR_NETWORK_CHANGED: + return NetworkException.ERROR_NETWORK_CHANGED; + case ERR_QUIC_PROTOCOL_ERROR: + return NetworkException.ERROR_QUIC_PROTOCOL_FAILED; + } + Log.e(CronetUrlRequestContext.LOG_TAG, "Unknown error code: " + netError); + return NetworkException.ERROR_OTHER; + } + + /** + * Returns {@code true} if the error may contain QUIC specific errorcode + */ + public static boolean isQuicException(int javaError) { + return javaError == NetworkException.ERROR_QUIC_PROTOCOL_FAILED || + javaError == NetworkException.ERROR_NETWORK_CHANGED; + } + + private static Map buildErrorMap() { + Map errorMap = new HashMap<>(); + errorMap.put(EnvoyMobileError.DNS_RESOLUTION_FAILED, NetError.ERR_NAME_NOT_RESOLVED); + errorMap.put(EnvoyMobileError.DURATION_TIMEOUT, NetError.ERR_TIMED_OUT); + errorMap.put(EnvoyMobileError.STREAM_IDLE_TIMEOUT, NetError.ERR_TIMED_OUT); + errorMap.put(EnvoyMobileError.UPSTREAM_CONNECTION_TERMINATION, NetError.ERR_CONNECTION_CLOSED); + errorMap.put(EnvoyMobileError.UPSTREAM_REMOTE_RESET, NetError.ERR_CONNECTION_RESET); + errorMap.put(EnvoyMobileError.UPSTREAM_CONNECTION_FAILURE, NetError.ERR_CONNECTION_REFUSED); + return Collections.unmodifiableMap(errorMap); + } + + private Errors() {} +} diff --git a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java index f017ed2528..1e4b779dc1 100644 --- a/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java +++ b/library/java/org/chromium/net/impl/NativeCronetEngineBuilderImpl.java @@ -29,6 +29,8 @@ */ public class NativeCronetEngineBuilderImpl extends CronetEngineBuilderImpl { + // TODO(refactor) move unshared variables into their specific methods. + private final List nativeFilterChain = new ArrayList<>(); private final EnvoyLogger mEnvoyLogger = null; private final EnvoyEventTracker mEnvoyEventTracker = null; private boolean mAdminInterfaceEnabled = false; @@ -81,6 +83,19 @@ public CronetEngineBuilderImpl setMockCertVerifierForTesting() { return this; } + /** + * Adds url interceptors to the cronetEngine + * + * @return the builder to facilitate chaining. + */ + @VisibleForTesting + public CronetEngineBuilderImpl addUrlInterceptorsForTesting() { + nativeFilterChain.add(new EnvoyNativeFilterConfig( + "envoy.filters.http.test_read", + "{\"@type\": type.googleapis.com/envoymobile.test.integration.filters.http.test_read.TestRead}")); + return this; + } + @Override public ExperimentalCronetEngine build() { if (getUserAgent() == null) { @@ -100,7 +115,6 @@ EnvoyEngine createEngine(EnvoyOnEngineRunning onEngineRunning) { private EnvoyConfiguration createEnvoyConfiguration() { List platformFilterChain = Collections.emptyList(); - List nativeFilterChain = Collections.emptyList(); Map stringAccessors = Collections.emptyMap(); Map keyValueStores = Collections.emptyMap(); List statSinks = Collections.emptyList(); diff --git a/test/integration/filters/http/test_read/BUILD b/test/integration/filters/http/test_read/BUILD new file mode 100644 index 0000000000..fe4b8c8236 --- /dev/null +++ b/test/integration/filters/http/test_read/BUILD @@ -0,0 +1,42 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_package", + "envoy_proto_library", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_proto_library( + name = "filter", + srcs = ["filter.proto"], + deps = [ + "@envoy_api//envoy/config/common/matcher/v3:pkg", + ], +) + +envoy_cc_extension( + name = "test_read_filter_lib", + srcs = ["filter.cc"], + hdrs = ["filter.h"], + repository = "@envoy", + deps = [ + "filter_cc_proto", + "@envoy//source/common/http:utility_lib", + "@envoy//source/common/stream_info:stream_info_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + deps = [ + ":test_read_filter_lib", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + ], +) diff --git a/test/integration/filters/http/test_read/config.cc b/test/integration/filters/http/test_read/config.cc new file mode 100644 index 0000000000..4ef1848523 --- /dev/null +++ b/test/integration/filters/http/test_read/config.cc @@ -0,0 +1,24 @@ +#include "test/integration/filters/http/test_read/config.h" + +#include "test/integration/filters/http/test_read/filter.h" + +namespace Envoy { +namespace HttpFilters { +namespace TestRead { + +Http::FilterFactoryCb TestReadFilterFactory::createFilterFactoryFromProtoTyped( + const envoymobile::test::integration::filters::http::test_read::TestRead& /*config*/, + const std::string&, Server::Configuration::FactoryContext& /*context*/) { + return [](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamDecoderFilter(std::make_shared()); + }; +} + +/** + * Static registration for the TestRead filter. @see NamedHttpFilterConfigFactory. + */ +REGISTER_FACTORY(TestReadFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace TestRead +} // namespace HttpFilters +} // namespace Envoy diff --git a/test/integration/filters/http/test_read/config.h b/test/integration/filters/http/test_read/config.h new file mode 100644 index 0000000000..bed1a2dc84 --- /dev/null +++ b/test/integration/filters/http/test_read/config.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "source/extensions/filters/http/common/factory_base.h" + +#include "test/integration/filters/http/test_read/filter.pb.h" +#include "test/integration/filters/http/test_read/filter.pb.validate.h" + +namespace Envoy { +namespace HttpFilters { +namespace TestRead { + +/** + * Config registration for the TestRead filter. @see NamedHttpFilterConfigFactory. + */ +class TestReadFilterFactory + : public Envoy::Extensions::HttpFilters::Common::FactoryBase< + envoymobile::test::integration::filters::http::test_read::TestRead> { +public: + TestReadFilterFactory() : FactoryBase("test_read") {} + +private: + ::Envoy::Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoymobile::test::integration::filters::http::test_read::TestRead& config, + const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override; +}; + +DECLARE_FACTORY(TestReadFilterFactory); + +} // namespace TestRead +} // namespace HttpFilters +} // namespace Envoy diff --git a/test/integration/filters/http/test_read/filter.cc b/test/integration/filters/http/test_read/filter.cc new file mode 100644 index 0000000000..1803da2dd1 --- /dev/null +++ b/test/integration/filters/http/test_read/filter.cc @@ -0,0 +1,44 @@ +#include "test/integration/filters/http/test_read/filter.h" + +#include "envoy/server/filter_config.h" + +namespace Envoy { +namespace HttpFilters { +namespace TestRead { + +Http::FilterHeadersStatus TestReadFilter::decodeHeaders(Http::RequestHeaderMap& request_headers, + bool) { + // sample path is /failed?start=0x10000 + Http::Utility::QueryParams query_parameters = + Http::Utility::parseQueryString(request_headers.Path()->value().getStringView()); + uint64_t response_flag; + if (absl::SimpleAtoi(query_parameters.at("start"), &response_flag)) { + decoder_callbacks_->streamInfo().setResponseFlag( + TestReadFilter::mapErrorToResponseFlag(response_flag)); + decoder_callbacks_->sendLocalReply(Http::Code::BadRequest, "test_read filter threw: ", nullptr, + absl::nullopt, ""); + } + return Http::FilterHeadersStatus::StopIteration; +} + +StreamInfo::ResponseFlag TestReadFilter::mapErrorToResponseFlag(uint64_t errorCode) { + switch (errorCode) { + case 0x4000000: + return StreamInfo::DnsResolutionFailed; + case 0x40: + return StreamInfo::UpstreamConnectionTermination; + case 0x20: + return StreamInfo::UpstreamConnectionFailure; + case 0x10: + return StreamInfo::UpstreamRemoteReset; + case 0x10000: + return StreamInfo::StreamIdleTimeout; + default: + // Any other error that we aren't interested in. I picked a random error. + return StreamInfo::RateLimitServiceError; + } +} + +} // namespace TestRead +} // namespace HttpFilters +} // namespace Envoy diff --git a/test/integration/filters/http/test_read/filter.h b/test/integration/filters/http/test_read/filter.h new file mode 100644 index 0000000000..c65dd7d5de --- /dev/null +++ b/test/integration/filters/http/test_read/filter.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/http/filter.h" + +#include "source/common/common/logger.h" +#include "source/common/http/utility.h" +#include "source/common/stream_info/stream_info_impl.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" + +#include "test/integration/filters/http/test_read/filter.pb.h" + +namespace Envoy { +namespace HttpFilters { +namespace TestRead { + +/** + * This is a test-only filter to return specified error code based on a request header. + */ +class TestReadFilter final : public Http::PassThroughFilter, + public Logger::Loggable { +public: + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& request_headers, bool) override; + +private: + /* A mapping of the envoymobile errors we care about for testing + * From https://github.com/envoyproxy/envoy/blob/main/envoy/stream_info/stream_info.h + */ + StreamInfo::ResponseFlag mapErrorToResponseFlag(uint64_t errorCode); +}; + +} // namespace TestRead +} // namespace HttpFilters +} // namespace Envoy diff --git a/test/integration/filters/http/test_read/filter.proto b/test/integration/filters/http/test_read/filter.proto new file mode 100644 index 0000000000..05fd68d940 --- /dev/null +++ b/test/integration/filters/http/test_read/filter.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package envoymobile.test.integration.filters.http.test_read; + +import "validate/validate.proto"; + +message TestRead { +} diff --git a/test/java/org/chromium/net/BidirectionalStreamTest.java b/test/java/org/chromium/net/BidirectionalStreamTest.java index 8acc4be21d..2ff6bc2ced 100644 --- a/test/java/org/chromium/net/BidirectionalStreamTest.java +++ b/test/java/org/chromium/net/BidirectionalStreamTest.java @@ -20,9 +20,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import org.chromium.net.impl.Annotations.NetError; import org.chromium.net.impl.BidirectionalStreamNetworkException; import org.chromium.net.impl.CronetBidirectionalStream; +import org.chromium.net.impl.Errors.NetError; import org.chromium.net.testing.CronetTestRule; import org.chromium.net.testing.CronetTestUtil; import org.chromium.net.testing.Feature; @@ -1540,35 +1540,29 @@ public void testCronetEngineShutdownAfterStreamCancel() throws Exception { @Feature({"Cronet"}) @Test @OnlyRunNativeCronet - @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1594") public void testErrorCodes() throws Exception { - // TODO(Augustyniak) cannot find symbol checkSpecificErrorCode(NetError.ERR_ADDRESS_UNREACHABLE" // Non-BidirectionalStream specific error codes. - // checkSpecificErrorCode(NetError.ERR_NAME_NOT_RESOLVED, - // NetworkException.ERROR_HOSTNAME_NOT_RESOLVED, false); - // checkSpecificErrorCode(NetError.ERR_INTERNET_DISCONNECTED, - // NetworkException.ERROR_INTERNET_DISCONNECTED, false); - // checkSpecificErrorCode(NetError.ERR_NETWORK_CHANGED, NetworkException.ERROR_NETWORK_CHANGED, - // true); - // checkSpecificErrorCode(NetError.ERR_CONNECTION_CLOSED, - // NetworkException.ERROR_CONNECTION_CLOSED, - // true); - // checkSpecificErrorCode(NetError.ERR_CONNECTION_REFUSED, - // NetworkException.ERROR_CONNECTION_REFUSED, false); - // checkSpecificErrorCode(NetError.ERR_CONNECTION_RESET, - // NetworkException.ERROR_CONNECTION_RESET, - // true); - // checkSpecificErrorCode(NetError.ERR_CONNECTION_TIMED_OUT, - // NetworkException.ERROR_CONNECTION_TIMED_OUT, true); - // checkSpecificErrorCode(NetError.ERR_TIMED_OUT, NetworkException.ERROR_TIMED_OUT, true); - // checkSpecificErrorCode(NetError.ERR_ADDRESS_UNREACHABLE, - // NetworkException.ERROR_ADDRESS_UNREACHABLE, false); - - // TODO(https://github.com/envoyproxy/envoy-mobile/issues/1594) Missing error - code this. + checkSpecificErrorCode(NetError.ERR_NAME_NOT_RESOLVED, + NetworkException.ERROR_HOSTNAME_NOT_RESOLVED, false); + checkSpecificErrorCode(NetError.ERR_INTERNET_DISCONNECTED, + NetworkException.ERROR_INTERNET_DISCONNECTED, false); + checkSpecificErrorCode(NetError.ERR_NETWORK_CHANGED, NetworkException.ERROR_NETWORK_CHANGED, + true); + checkSpecificErrorCode(NetError.ERR_CONNECTION_CLOSED, NetworkException.ERROR_CONNECTION_CLOSED, + true); + checkSpecificErrorCode(NetError.ERR_CONNECTION_REFUSED, + NetworkException.ERROR_CONNECTION_REFUSED, false); + checkSpecificErrorCode(NetError.ERR_CONNECTION_RESET, NetworkException.ERROR_CONNECTION_RESET, + true); + checkSpecificErrorCode(NetError.ERR_CONNECTION_TIMED_OUT, + NetworkException.ERROR_CONNECTION_TIMED_OUT, true); + checkSpecificErrorCode(NetError.ERR_TIMED_OUT, NetworkException.ERROR_TIMED_OUT, true); + checkSpecificErrorCode(NetError.ERR_ADDRESS_UNREACHABLE, + NetworkException.ERROR_ADDRESS_UNREACHABLE, false); + // BidirectionalStream specific retryable error codes. - // checkSpecificErrorCode(NetError.ERR_HTTP2_PING_FAILED, NetworkException.ERROR_OTHER, true); - // checkSpecificErrorCode( - // NetError.ERR_QUIC_HANDSHAKE_FAILED, NetworkException.ERROR_OTHER, true); + checkSpecificErrorCode(NetError.ERR_HTTP2_PING_FAILED, NetworkException.ERROR_OTHER, true); + checkSpecificErrorCode(NetError.ERR_QUIC_HANDSHAKE_FAILED, NetworkException.ERROR_OTHER, true); } // Returns the contents of byteBuffer, from its position() to its limit(), @@ -1583,11 +1577,12 @@ private static String bufferContentsToString(ByteBuffer byteBuffer, int start, i return new String(contents); } - private static void checkSpecificErrorCode(int netError, int errorCode, + private static void checkSpecificErrorCode(NetError netError, int errorCode, boolean immediatelyRetryable) throws Exception { - NetworkException exception = new BidirectionalStreamNetworkException("", errorCode, netError); + NetworkException exception = + new BidirectionalStreamNetworkException("", errorCode, netError.getErrorCode()); assertEquals(immediatelyRetryable, exception.immediatelyRetryable()); - assertEquals(netError, exception.getCronetInternalErrorCode()); + assertEquals(netError.getErrorCode(), exception.getCronetInternalErrorCode()); assertEquals(errorCode, exception.getErrorCode()); } diff --git a/test/java/org/chromium/net/CronetUrlRequestTest.java b/test/java/org/chromium/net/CronetUrlRequestTest.java index 414d19d96a..845f09e39d 100644 --- a/test/java/org/chromium/net/CronetUrlRequestTest.java +++ b/test/java/org/chromium/net/CronetUrlRequestTest.java @@ -28,8 +28,9 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.chromium.net.impl.Annotations.NetError; import org.chromium.net.impl.CronetUrlRequest; +import org.chromium.net.impl.Errors.EnvoyMobileError; +import org.chromium.net.impl.Errors.NetError; import org.chromium.net.impl.UrlResponseInfoImpl; import org.chromium.net.testing.CronetTestRule; import org.chromium.net.testing.CronetTestRule.CronetTestFramework; @@ -74,7 +75,7 @@ public void setUp() { assertTrue(NativeTestServer.startNativeTestServer(getContext())); // Add url interceptors after native application context is initialized. if (!mTestRule.testingJavaImpl()) { - mMockUrlRequestJobFactory = new MockUrlRequestJobFactory(mTestFramework.mCronetEngine); + mMockUrlRequestJobFactory = new MockUrlRequestJobFactory(mTestFramework.mBuilder); } } @@ -86,11 +87,11 @@ public void tearDown() { NativeTestServer.shutdownNativeTestServer(); } - private TestUrlRequestCallback startAndWaitForComplete(String url) throws Exception { + private TestUrlRequestCallback startAndWaitForComplete(CronetEngine engine, String url) + throws Exception { TestUrlRequestCallback callback = new TestUrlRequestCallback(); // Create request. - UrlRequest.Builder builder = - mTestFramework.mCronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor()); + UrlRequest.Builder builder = engine.newUrlRequestBuilder(url, callback, callback.getExecutor()); UrlRequest urlRequest = builder.build(); urlRequest.start(); callback.blockForDone(); @@ -154,7 +155,7 @@ public void testBuilderChecks() throws Exception { @Feature({"Cronet"}) public void testSimpleGet() throws Exception { String url = NativeTestServer.getEchoMethodURL(); - TestUrlRequestCallback callback = startAndWaitForComplete(url); + TestUrlRequestCallback callback = startAndWaitForComplete(mTestFramework.mCronetEngine, url); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); // Default method is 'GET'. assertEquals("GET", callback.mResponseAsString); @@ -372,7 +373,7 @@ public void onCanceled(UrlRequest request, UrlResponseInfo info) { @Feature({"Cronet"}) public void testNotFound() throws Exception { String url = NativeTestServer.getFileURL("/notfound.html"); - TestUrlRequestCallback callback = startAndWaitForComplete(url); + TestUrlRequestCallback callback = startAndWaitForComplete(mTestFramework.mCronetEngine, url); checkResponseInfo(callback.mResponseInfo, url, 404, "Not Found"); assertEquals("\n\n\nNot found\n" + "

Test page loaded.

\n\n\n", @@ -391,7 +392,7 @@ public void testNotFound() throws Exception { @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1550") public void testContentLengthMismatchFailsOnce() throws Exception { String url = NativeTestServer.getFileURL("/content_length_mismatch.html"); - TestUrlRequestCallback callback = startAndWaitForComplete(url); + TestUrlRequestCallback callback = startAndWaitForComplete(mTestFramework.mCronetEngine, url); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); // The entire response body will be read before the error is returned. // This is because the network stack returns data as it's read from the @@ -621,7 +622,8 @@ public void testDefaultUserAgent() throws Exception { @SmallTest @Feature({"Cronet"}) public void testMockSuccess() throws Exception { - TestUrlRequestCallback callback = startAndWaitForComplete(NativeTestServer.getSuccessURL()); + TestUrlRequestCallback callback = + startAndWaitForComplete(mTestFramework.mCronetEngine, NativeTestServer.getSuccessURL()); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); assertEquals(0, callback.mRedirectResponseInfoList.size()); assertTrue(callback.mHttpResponseDataLength != 0); @@ -638,7 +640,8 @@ public void testMockSuccess() throws Exception { @SmallTest @Feature({"Cronet"}) public void testResponseHeadersList() throws Exception { - TestUrlRequestCallback callback = startAndWaitForComplete(NativeTestServer.getSuccessURL()); + TestUrlRequestCallback callback = + startAndWaitForComplete(mTestFramework.mCronetEngine, NativeTestServer.getSuccessURL()); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); List> responseHeaders = callback.mResponseInfo.getAllHeadersAsList(); @@ -659,8 +662,8 @@ public void testResponseHeadersList() throws Exception { @SmallTest @Feature({"Cronet"}) public void testMockMultiRedirect() throws Exception { - TestUrlRequestCallback callback = - startAndWaitForComplete(NativeTestServer.getMultiRedirectURL()); + TestUrlRequestCallback callback = startAndWaitForComplete( + mTestFramework.mCronetEngine, NativeTestServer.getMultiRedirectURL()); UrlResponseInfo mResponseInfo = callback.mResponseInfo; assertEquals(2, callback.mRedirectCount); assertEquals(200, mResponseInfo.getHttpStatusCode()); @@ -693,7 +696,8 @@ public void testMockMultiRedirect() throws Exception { @SmallTest @Feature({"Cronet"}) public void testMockNotFound() throws Exception { - TestUrlRequestCallback callback = startAndWaitForComplete(NativeTestServer.getNotFoundURL()); + TestUrlRequestCallback callback = + startAndWaitForComplete(mTestFramework.mCronetEngine, NativeTestServer.getNotFoundURL()); UrlResponseInfo expected = createUrlResponseInfo(new String[] {NativeTestServer.getNotFoundURL()}, "Not Found", 404, 142, "Content-Length", "96"); @@ -712,6 +716,7 @@ public void testMockNotFound() throws Exception { public void testMockStartAsyncError() throws Exception { final int arbitraryNetError = -3; TestUrlRequestCallback callback = startAndWaitForComplete( + mMockUrlRequestJobFactory.getCronetEngine(), MockUrlRequestJobFactory.getMockUrlWithFailure(FailurePhase.START, arbitraryNetError)); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); @@ -730,6 +735,7 @@ public void testMockStartAsyncError() throws Exception { public void testMockReadDataSyncError() throws Exception { final int arbitraryNetError = -4; TestUrlRequestCallback callback = startAndWaitForComplete( + mMockUrlRequestJobFactory.getCronetEngine(), MockUrlRequestJobFactory.getMockUrlWithFailure(FailurePhase.READ_SYNC, arbitraryNetError)); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); assertEquals(15, callback.mResponseInfo.getReceivedByteCount()); @@ -751,7 +757,8 @@ public void testMockReadDataSyncError() throws Exception { @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1549") public void testMockClientCertificateRequested() throws Exception { TestUrlRequestCallback callback = - startAndWaitForComplete(MockUrlRequestJobFactory.getMockUrlForClientCertificateRequest()); + startAndWaitForComplete(mMockUrlRequestJobFactory.getCronetEngine(), + MockUrlRequestJobFactory.getMockUrlForClientCertificateRequest()); assertNotNull(callback.mResponseInfo); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); assertEquals("data", callback.mResponseAsString); @@ -770,7 +777,8 @@ public void testMockClientCertificateRequested() throws Exception { @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1549") public void testMockSSLCertificateError() throws Exception { TestUrlRequestCallback callback = - startAndWaitForComplete(MockUrlRequestJobFactory.getMockUrlForSSLCertificateError()); + startAndWaitForComplete(mMockUrlRequestJobFactory.getCronetEngine(), + MockUrlRequestJobFactory.getMockUrlForSSLCertificateError()); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); assertTrue(callback.mOnErrorCalled); @@ -2044,24 +2052,26 @@ public void testDestroyUploadDataStreamAdapterOnSucceededCallback() throws Excep @SmallTest @Feature({"Cronet"}) @OnlyRunNativeCronet // Java impl doesn't support MockUrlRequestJobFactory - @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1549") public void testErrorCodes() throws Exception { - checkSpecificErrorCode(-105, NetworkException.ERROR_HOSTNAME_NOT_RESOLVED, "NAME_NOT_RESOLVED", - false); - checkSpecificErrorCode(-106, NetworkException.ERROR_INTERNET_DISCONNECTED, - "INTERNET_DISCONNECTED", false); - checkSpecificErrorCode(-21, NetworkException.ERROR_NETWORK_CHANGED, "NETWORK_CHANGED", true); - checkSpecificErrorCode(-100, NetworkException.ERROR_CONNECTION_CLOSED, "CONNECTION_CLOSED", - true); - checkSpecificErrorCode(-102, NetworkException.ERROR_CONNECTION_REFUSED, "CONNECTION_REFUSED", + checkSpecificErrorCode(EnvoyMobileError.DNS_RESOLUTION_FAILED, NetError.ERR_NAME_NOT_RESOLVED, + NetworkException.ERROR_HOSTNAME_NOT_RESOLVED, "NAME_NOT_RESOLVED", false); - checkSpecificErrorCode(-101, NetworkException.ERROR_CONNECTION_RESET, "CONNECTION_RESET", true); - checkSpecificErrorCode(-118, NetworkException.ERROR_CONNECTION_TIMED_OUT, - "CONNECTION_TIMED_OUT", true); - checkSpecificErrorCode(-7, NetworkException.ERROR_TIMED_OUT, "TIMED_OUT", true); - checkSpecificErrorCode(-109, NetworkException.ERROR_ADDRESS_UNREACHABLE, "ADDRESS_UNREACHABLE", + checkSpecificErrorCode(EnvoyMobileError.UPSTREAM_CONNECTION_TERMINATION, + NetError.ERR_CONNECTION_CLOSED, NetworkException.ERROR_CONNECTION_CLOSED, + "CONNECTION_CLOSED", true); + checkSpecificErrorCode(EnvoyMobileError.UPSTREAM_CONNECTION_FAILURE, + NetError.ERR_CONNECTION_REFUSED, + NetworkException.ERROR_CONNECTION_REFUSED, "CONNECTION_REFUSED", false); + checkSpecificErrorCode(EnvoyMobileError.UPSTREAM_REMOTE_RESET, NetError.ERR_CONNECTION_RESET, + NetworkException.ERROR_CONNECTION_RESET, "CONNECTION_RESET", true); + checkSpecificErrorCode(EnvoyMobileError.STREAM_IDLE_TIMEOUT, NetError.ERR_TIMED_OUT, + NetworkException.ERROR_TIMED_OUT, "TIMED_OUT", true); + checkSpecificErrorCode(0x2000, NetError.ERR_OTHER, NetworkException.ERROR_OTHER, "OTHER", false); - checkSpecificErrorCode(-2, NetworkException.ERROR_OTHER, "FAILED", false); + // Todo(colibie): https://github.com/envoyproxy/envoy-mobile/issues/1549 + // checkSpecificErrorCode(-106, NetworkException.ERROR_INTERNET_DISCONNECTED, + // "INTERNET_DISCONNECTED", false); + // checkSpecificErrorCode(-21, NetworkException.ERROR_NETWORK_CHANGED, "NETWORK_CHANGED", true); } /* @@ -2073,14 +2083,14 @@ public void testErrorCodes() throws Exception { public void testCookiesArentSavedOrSent() throws Exception { // Make a request to a url that sets the cookie String url = NativeTestServer.getFileURL("/set_cookie.html"); - TestUrlRequestCallback callback = startAndWaitForComplete(url); + TestUrlRequestCallback callback = startAndWaitForComplete(mTestFramework.mCronetEngine, url); assertEquals(200, callback.mResponseInfo.getHttpStatusCode()); assertEquals("A=B", callback.mResponseInfo.getAllHeaders().get("Set-Cookie").get(0)); // Make a request that check that cookie header isn't sent. String headerName = "Cookie"; String url2 = NativeTestServer.getEchoHeaderURL(headerName); - TestUrlRequestCallback callback2 = startAndWaitForComplete(url2); + TestUrlRequestCallback callback2 = startAndWaitForComplete(mTestFramework.mCronetEngine, url2); assertEquals(200, callback2.mResponseInfo.getHttpStatusCode()); assertEquals("Header not found. :(", callback2.mResponseAsString); } @@ -2091,9 +2101,10 @@ public void testCookiesArentSavedOrSent() throws Exception { @OnlyRunNativeCronet @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1549") public void testQuicErrorCode() throws Exception { - TestUrlRequestCallback callback = - startAndWaitForComplete(MockUrlRequestJobFactory.getMockUrlWithFailure( - FailurePhase.START, NetError.ERR_QUIC_PROTOCOL_ERROR)); + TestUrlRequestCallback callback = startAndWaitForComplete( + mMockUrlRequestJobFactory.getCronetEngine(), + MockUrlRequestJobFactory.getMockUrlWithFailure( + FailurePhase.START, NetError.ERR_QUIC_PROTOCOL_ERROR.getErrorCode())); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); assertEquals(NetworkException.ERROR_QUIC_PROTOCOL_FAILED, @@ -2110,9 +2121,10 @@ public void testQuicErrorCode() throws Exception { @OnlyRunNativeCronet @Ignore("https://github.com/envoyproxy/envoy-mobile/issues/1549") public void testQuicErrorCodeForNetworkChanged() throws Exception { - TestUrlRequestCallback callback = - startAndWaitForComplete(MockUrlRequestJobFactory.getMockUrlWithFailure( - FailurePhase.START, NetError.ERR_NETWORK_CHANGED)); + TestUrlRequestCallback callback = startAndWaitForComplete( + mMockUrlRequestJobFactory.getCronetEngine(), + MockUrlRequestJobFactory.getMockUrlWithFailure( + FailurePhase.START, NetError.ERR_NETWORK_CHANGED.getErrorCode())); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); assertEquals(NetworkException.ERROR_NETWORK_CHANGED, @@ -2189,13 +2201,16 @@ public void onCanceled(UrlRequest request, UrlResponseInfo info) { assertFalse(failedExpectation.get()); } - private void checkSpecificErrorCode(int netError, int errorCode, String name, - boolean immediatelyRetryable) throws Exception { + private void checkSpecificErrorCode(@EnvoyMobileError long envoyMobileError, NetError netError, + int errorCode, String name, boolean immediatelyRetryable) + throws Exception { TestUrlRequestCallback callback = startAndWaitForComplete( - MockUrlRequestJobFactory.getMockUrlWithFailure(FailurePhase.START, netError)); + mMockUrlRequestJobFactory.getCronetEngine(), + MockUrlRequestJobFactory.getMockUrlWithFailure(FailurePhase.START, envoyMobileError)); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); - assertEquals(netError, ((NetworkException)callback.mError).getCronetInternalErrorCode()); + assertEquals(netError.getErrorCode(), + ((NetworkException)callback.mError).getCronetInternalErrorCode()); assertEquals(errorCode, ((NetworkException)callback.mError).getErrorCode()); assertEquals(immediatelyRetryable, ((NetworkException)callback.mError).immediatelyRetryable()); assertContains("Exception in CronetUrlRequest: net::ERR_" + name, callback.mError.getMessage()); @@ -2237,7 +2252,7 @@ public void testCleartextTrafficBlocked() throws Exception { // https requests to it are tested in QuicTest, so this checks that we're only blocking // cleartext. final String url = "http://example.com/simple.txt"; - TestUrlRequestCallback callback = startAndWaitForComplete(url); + TestUrlRequestCallback callback = startAndWaitForComplete(mTestFramework.mCronetEngine, url); assertNull(callback.mResponseInfo); assertNotNull(callback.mError); assertEquals(cleartextNotPermitted, diff --git a/test/java/org/chromium/net/testing/FailurePhase.java b/test/java/org/chromium/net/testing/FailurePhase.java index c5fd04f945..ccc68de266 100644 --- a/test/java/org/chromium/net/testing/FailurePhase.java +++ b/test/java/org/chromium/net/testing/FailurePhase.java @@ -1,10 +1,12 @@ package org.chromium.net.testing; -public final class FailurePhase { +public enum FailurePhase { + START, + READ_SYNC, + READ_ASYNC; - public static final int START = 0; - public static final int READ_SYNC = 1; - public static final int READ_ASYNC = 2; - - private FailurePhase() {} + @Override + public String toString() { + return name().toLowerCase(); + } } diff --git a/test/java/org/chromium/net/testing/MockUrlRequestJobFactory.java b/test/java/org/chromium/net/testing/MockUrlRequestJobFactory.java index bb924a6ff6..6c6393e587 100644 --- a/test/java/org/chromium/net/testing/MockUrlRequestJobFactory.java +++ b/test/java/org/chromium/net/testing/MockUrlRequestJobFactory.java @@ -3,12 +3,16 @@ import static junit.framework.Assert.assertTrue; import org.chromium.net.CronetEngine; +import org.chromium.net.ExperimentalCronetEngine; /** * Helper class to set up url interceptors for testing purposes. + * TODO("https://github.com/envoyproxy/envoy-mobile/issues/1549") */ public final class MockUrlRequestJobFactory { + private static final String TEST_URL = "http://0.0.0.0:10000"; + private final CronetEngine mCronetEngine; /** @@ -20,31 +24,46 @@ public MockUrlRequestJobFactory(CronetEngine cronetEngine) { // Add a filter to immediately return a response } + /** + * Sets up URL interceptors. + */ + public MockUrlRequestJobFactory(ExperimentalCronetEngine.Builder builder) { + // Add a filter to immediately return a response + mCronetEngine = + CronetTestUtil.getCronetEngineBuilderImpl(builder).addUrlInterceptorsForTesting().build(); + } + /** * Remove URL Interceptors. */ public void shutdown() { // Remove the filter; + mCronetEngine.shutdown(); } + public CronetEngine getCronetEngine() { return mCronetEngine; } + /** * Constructs a mock URL that hangs or fails at certain phase. * * @param phase at which request fails. It should be a value in * org.chromium.net.test.FailurePhase. - * @param netError reported by UrlRequestJob. Passing -1, results in hang. + * @param @param envoyMobileError reported by the engine. */ - public static String getMockUrlWithFailure(int phase, int netError) { - assertTrue(netError < 0); + public static String getMockUrlWithFailure(FailurePhase phase, long envoyMobileError) { switch (phase) { - case FailurePhase.START: - case FailurePhase.READ_SYNC: - case FailurePhase.READ_ASYNC: + case START: + case READ_SYNC: + case READ_ASYNC: break; default: throw new IllegalArgumentException("phase not in org.chromium.net.test.FailurePhase"); } - return "To be implemented"; + return TEST_URL + "/failed?" + phase + "=" + envoyMobileError; + } + + public static String getMockUrlWithFailure(FailurePhase phase, int netError) { + throw new UnsupportedOperationException("To be implemented"); } /**