476 lines
20 KiB
Java
476 lines
20 KiB
Java
![]() |
// Copyright 2016 The Chromium Authors
|
||
|
// Use of this source code is governed by a BSD-style license that can be
|
||
|
// found in the LICENSE file.
|
||
|
package org.chromium.net;
|
||
|
|
||
|
import androidx.annotation.VisibleForTesting;
|
||
|
|
||
|
import org.json.JSONException;
|
||
|
import org.json.JSONObject;
|
||
|
|
||
|
import org.chromium.net.DnsOptions.StaleDnsOptions;
|
||
|
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Collections;
|
||
|
import java.util.Date;
|
||
|
import java.util.HashSet;
|
||
|
import java.util.List;
|
||
|
import java.util.Set;
|
||
|
|
||
|
/**
|
||
|
* An implementation of ICronetEngineBuilder which handles translation of configuration options to
|
||
|
* json-based experimental options, if necessary.
|
||
|
*
|
||
|
* <p>{@hide internal class}
|
||
|
*/
|
||
|
@VisibleForTesting
|
||
|
public final class ExperimentalOptionsTranslatingCronetEngineBuilder extends ICronetEngineBuilder {
|
||
|
private static final Set<Integer> SUPPORTED_OPTIONS =
|
||
|
Collections.unmodifiableSet(
|
||
|
new HashSet(
|
||
|
Arrays.asList(
|
||
|
ICronetEngineBuilder.CONNECTION_MIGRATION_OPTIONS,
|
||
|
ICronetEngineBuilder.DNS_OPTIONS,
|
||
|
ICronetEngineBuilder.QUIC_OPTIONS)));
|
||
|
|
||
|
private JSONObject mParsedExperimentalOptions;
|
||
|
private final List<ExperimentalOptionsPatch> mExperimentalOptionsPatches = new ArrayList<>();
|
||
|
|
||
|
private final ICronetEngineBuilder mDelegate;
|
||
|
|
||
|
ExperimentalOptionsTranslatingCronetEngineBuilder(ICronetEngineBuilder delegate) {
|
||
|
this.mDelegate = delegate;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setQuicOptions(QuicOptions options) {
|
||
|
// If the delegate builder supports enabling connection migration directly, just use it
|
||
|
if (mDelegate.getSupportedConfigOptions().contains(ICronetEngineBuilder.QUIC_OPTIONS)) {
|
||
|
mDelegate.setQuicOptions(options);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
// If not, we'll have to work around it by modifying the experimental options JSON.
|
||
|
mExperimentalOptionsPatches.add(
|
||
|
(experimentalOptions) -> {
|
||
|
JSONObject quicOptions = createDefaultIfAbsent(experimentalOptions, "QUIC");
|
||
|
|
||
|
// Note: using the experimental APIs always overwrites what's in the
|
||
|
// experimental JSON, even though "repeated" fields could in theory be
|
||
|
// additive.
|
||
|
if (!options.getQuicHostAllowlist().isEmpty()) {
|
||
|
quicOptions.put(
|
||
|
"host_whitelist", String.join(",", options.getQuicHostAllowlist()));
|
||
|
}
|
||
|
if (!options.getEnabledQuicVersions().isEmpty()) {
|
||
|
quicOptions.put(
|
||
|
"quic_version", String.join(",", options.getEnabledQuicVersions()));
|
||
|
}
|
||
|
if (!options.getConnectionOptions().isEmpty()) {
|
||
|
quicOptions.put(
|
||
|
"connection_options",
|
||
|
String.join(",", options.getConnectionOptions()));
|
||
|
}
|
||
|
if (!options.getClientConnectionOptions().isEmpty()) {
|
||
|
quicOptions.put(
|
||
|
"client_connection_options",
|
||
|
String.join(",", options.getClientConnectionOptions()));
|
||
|
}
|
||
|
if (!options.getExtraQuicheFlags().isEmpty()) {
|
||
|
quicOptions.put(
|
||
|
"set_quic_flags", String.join(",", options.getExtraQuicheFlags()));
|
||
|
}
|
||
|
|
||
|
if (options.getInMemoryServerConfigsCacheSize() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_server_configs_stored_in_properties",
|
||
|
options.getInMemoryServerConfigsCacheSize());
|
||
|
}
|
||
|
|
||
|
if (options.getHandshakeUserAgent() != null) {
|
||
|
quicOptions.put("user_agent_id", options.getHandshakeUserAgent());
|
||
|
}
|
||
|
|
||
|
if (options.getRetryWithoutAltSvcOnQuicErrors() != null) {
|
||
|
quicOptions.put(
|
||
|
"retry_without_alt_svc_on_quic_errors",
|
||
|
options.getRetryWithoutAltSvcOnQuicErrors());
|
||
|
}
|
||
|
|
||
|
if (options.getEnableTlsZeroRtt() != null) {
|
||
|
quicOptions.put("disable_tls_zero_rtt", !options.getEnableTlsZeroRtt());
|
||
|
}
|
||
|
|
||
|
if (options.getPreCryptoHandshakeIdleTimeoutSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_idle_time_before_crypto_handshake_seconds",
|
||
|
options.getPreCryptoHandshakeIdleTimeoutSeconds());
|
||
|
}
|
||
|
|
||
|
if (options.getCryptoHandshakeTimeoutSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_time_before_crypto_handshake_seconds",
|
||
|
options.getCryptoHandshakeTimeoutSeconds());
|
||
|
}
|
||
|
|
||
|
if (options.getIdleConnectionTimeoutSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"idle_connection_timeout_seconds",
|
||
|
options.getIdleConnectionTimeoutSeconds());
|
||
|
}
|
||
|
|
||
|
if (options.getRetransmittableOnWireTimeoutMillis() != null) {
|
||
|
quicOptions.put(
|
||
|
"retransmittable_on_wire_timeout_milliseconds",
|
||
|
options.getRetransmittableOnWireTimeoutMillis());
|
||
|
}
|
||
|
|
||
|
if (options.getCloseSessionsOnIpChange() != null) {
|
||
|
quicOptions.put(
|
||
|
"close_sessions_on_ip_change",
|
||
|
options.getCloseSessionsOnIpChange());
|
||
|
}
|
||
|
|
||
|
if (options.getGoawaySessionsOnIpChange() != null) {
|
||
|
quicOptions.put(
|
||
|
"goaway_sessions_on_ip_change",
|
||
|
options.getGoawaySessionsOnIpChange());
|
||
|
}
|
||
|
|
||
|
if (options.getInitialBrokenServicePeriodSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"initial_delay_for_broken_alternative_service_seconds",
|
||
|
options.getInitialBrokenServicePeriodSeconds());
|
||
|
}
|
||
|
|
||
|
if (options.getIncreaseBrokenServicePeriodExponentially() != null) {
|
||
|
quicOptions.put(
|
||
|
"exponential_backoff_on_initial_delay",
|
||
|
options.getIncreaseBrokenServicePeriodExponentially());
|
||
|
}
|
||
|
|
||
|
if (options.getDelayJobsWithAvailableSpdySession() != null) {
|
||
|
quicOptions.put(
|
||
|
"delay_main_job_with_available_spdy_session",
|
||
|
options.getDelayJobsWithAvailableSpdySession());
|
||
|
}
|
||
|
});
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setDnsOptions(DnsOptions options) {
|
||
|
// If the delegate builder supports enabling connection migration directly, just use it
|
||
|
if (mDelegate.getSupportedConfigOptions().contains(ICronetEngineBuilder.DNS_OPTIONS)) {
|
||
|
mDelegate.setDnsOptions(options);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
// If not, we'll have to work around it by modifying the experimental options JSON.
|
||
|
mExperimentalOptionsPatches.add(
|
||
|
(experimentalOptions) -> {
|
||
|
JSONObject asyncDnsOptions =
|
||
|
createDefaultIfAbsent(experimentalOptions, "AsyncDNS");
|
||
|
|
||
|
if (options.getUseBuiltInDnsResolver() != null) {
|
||
|
asyncDnsOptions.put("enable", options.getUseBuiltInDnsResolver());
|
||
|
}
|
||
|
|
||
|
JSONObject staleDnsOptions =
|
||
|
createDefaultIfAbsent(experimentalOptions, "StaleDNS");
|
||
|
|
||
|
if (options.getEnableStaleDns() != null) {
|
||
|
staleDnsOptions.put("enable", options.getEnableStaleDns());
|
||
|
}
|
||
|
|
||
|
if (options.getPersistHostCache() != null) {
|
||
|
staleDnsOptions.put("persist_to_disk", options.getPersistHostCache());
|
||
|
}
|
||
|
|
||
|
if (options.getPersistHostCachePeriodMillis() != null) {
|
||
|
staleDnsOptions.put(
|
||
|
"persist_delay_ms", options.getPersistHostCachePeriodMillis());
|
||
|
}
|
||
|
|
||
|
if (options.getStaleDnsOptions() != null) {
|
||
|
StaleDnsOptions staleDnsOptionsJava = options.getStaleDnsOptions();
|
||
|
|
||
|
if (staleDnsOptionsJava.getAllowCrossNetworkUsage() != null) {
|
||
|
staleDnsOptions.put(
|
||
|
"allow_other_network",
|
||
|
staleDnsOptionsJava.getAllowCrossNetworkUsage());
|
||
|
}
|
||
|
|
||
|
if (staleDnsOptionsJava.getFreshLookupTimeoutMillis() != null) {
|
||
|
staleDnsOptions.put(
|
||
|
"delay_ms", staleDnsOptionsJava.getFreshLookupTimeoutMillis());
|
||
|
}
|
||
|
|
||
|
if (staleDnsOptionsJava.getUseStaleOnNameNotResolved() != null) {
|
||
|
staleDnsOptions.put(
|
||
|
"use_stale_on_name_not_resolved",
|
||
|
staleDnsOptionsJava.getUseStaleOnNameNotResolved());
|
||
|
}
|
||
|
|
||
|
if (staleDnsOptionsJava.getMaxExpiredDelayMillis() != null) {
|
||
|
staleDnsOptions.put(
|
||
|
"max_expired_time_ms",
|
||
|
staleDnsOptionsJava.getMaxExpiredDelayMillis());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
JSONObject quicOptions = createDefaultIfAbsent(experimentalOptions, "QUIC");
|
||
|
if (options.getPreestablishConnectionsToStaleDnsResults() != null) {
|
||
|
quicOptions.put(
|
||
|
"race_stale_dns_on_connection",
|
||
|
options.getPreestablishConnectionsToStaleDnsResults());
|
||
|
}
|
||
|
});
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setConnectionMigrationOptions(ConnectionMigrationOptions options) {
|
||
|
// If the delegate builder supports enabling connection migration directly, just use it
|
||
|
if (mDelegate
|
||
|
.getSupportedConfigOptions()
|
||
|
.contains(ICronetEngineBuilder.CONNECTION_MIGRATION_OPTIONS)) {
|
||
|
mDelegate.setConnectionMigrationOptions(options);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
// If not, we'll have to work around it by modifying the experimental options JSON.
|
||
|
mExperimentalOptionsPatches.add(
|
||
|
(experimentalOptions) -> {
|
||
|
JSONObject quicOptions = createDefaultIfAbsent(experimentalOptions, "QUIC");
|
||
|
|
||
|
if (options.getEnableDefaultNetworkMigration() != null) {
|
||
|
quicOptions.put(
|
||
|
"migrate_sessions_on_network_change_v2",
|
||
|
options.getEnableDefaultNetworkMigration());
|
||
|
}
|
||
|
if (options.getAllowServerMigration() != null) {
|
||
|
quicOptions.put(
|
||
|
"allow_server_migration", options.getAllowServerMigration());
|
||
|
}
|
||
|
if (options.getMigrateIdleConnections() != null) {
|
||
|
quicOptions.put(
|
||
|
"migrate_idle_sessions", options.getMigrateIdleConnections());
|
||
|
}
|
||
|
if (options.getIdleMigrationPeriodSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"idle_session_migration_period_seconds",
|
||
|
options.getIdleMigrationPeriodSeconds());
|
||
|
}
|
||
|
if (options.getRetryPreHandshakeErrorsOnAlternateNetwork() != null) {
|
||
|
quicOptions.put(
|
||
|
"retry_on_alternate_network_before_handshake",
|
||
|
options.getRetryPreHandshakeErrorsOnAlternateNetwork());
|
||
|
}
|
||
|
if (options.getMaxTimeOnNonDefaultNetworkSeconds() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_time_on_non_default_network_seconds",
|
||
|
options.getMaxTimeOnNonDefaultNetworkSeconds());
|
||
|
}
|
||
|
if (options.getMaxPathDegradingEagerMigrationsCount() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_migrations_to_non_default_network_on_path_degrading",
|
||
|
options.getMaxPathDegradingEagerMigrationsCount());
|
||
|
}
|
||
|
if (options.getMaxWriteErrorEagerMigrationsCount() != null) {
|
||
|
quicOptions.put(
|
||
|
"max_migrations_to_non_default_network_on_write_error",
|
||
|
options.getMaxWriteErrorEagerMigrationsCount());
|
||
|
}
|
||
|
if (options.getEnablePathDegradationMigration() != null) {
|
||
|
boolean pathDegradationValue = options.getEnablePathDegradationMigration();
|
||
|
|
||
|
boolean skipPortMigrationFlag = false;
|
||
|
|
||
|
if (options.getAllowNonDefaultNetworkUsage() != null) {
|
||
|
boolean nonDefaultNetworkValue =
|
||
|
options.getAllowNonDefaultNetworkUsage();
|
||
|
if (!pathDegradationValue && nonDefaultNetworkValue) {
|
||
|
// Misconfiguration which doesn't translate easily to the JSON flags
|
||
|
throw new IllegalArgumentException(
|
||
|
"Unable to turn on non-default network usage without path "
|
||
|
+ "degradation migration!");
|
||
|
} else if (pathDegradationValue && nonDefaultNetworkValue) {
|
||
|
// Both values being true results in the non-default network
|
||
|
// migration being enabled.
|
||
|
quicOptions.put("migrate_sessions_early_v2", true);
|
||
|
skipPortMigrationFlag = true;
|
||
|
} else {
|
||
|
quicOptions.put("migrate_sessions_early_v2", false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!skipPortMigrationFlag) {
|
||
|
quicOptions.put("allow_port_migration", pathDegradationValue);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setExperimentalOptions(String options) {
|
||
|
if (options == null || options.isEmpty()) {
|
||
|
mParsedExperimentalOptions = null;
|
||
|
} else {
|
||
|
mParsedExperimentalOptions = parseExperimentalOptions(options);
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected Set<Integer> getSupportedConfigOptions() {
|
||
|
return SUPPORTED_OPTIONS;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ExperimentalCronetEngine build() {
|
||
|
if (mParsedExperimentalOptions == null && mExperimentalOptionsPatches.isEmpty()) {
|
||
|
return mDelegate.build();
|
||
|
}
|
||
|
|
||
|
if (mParsedExperimentalOptions == null) {
|
||
|
mParsedExperimentalOptions = new JSONObject();
|
||
|
}
|
||
|
|
||
|
for (ExperimentalOptionsPatch patch : mExperimentalOptionsPatches) {
|
||
|
try {
|
||
|
patch.applyTo(mParsedExperimentalOptions);
|
||
|
} catch (JSONException e) {
|
||
|
throw new IllegalStateException("Unable to apply JSON patch!", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mDelegate.setExperimentalOptions(mParsedExperimentalOptions.toString());
|
||
|
return mDelegate.build();
|
||
|
}
|
||
|
|
||
|
private static JSONObject parseExperimentalOptions(String jsonString) {
|
||
|
try {
|
||
|
return new JSONObject(jsonString);
|
||
|
} catch (JSONException e) {
|
||
|
throw new IllegalArgumentException("Experimental options parsing failed", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static JSONObject createDefaultIfAbsent(JSONObject jsonObject, String key) {
|
||
|
JSONObject object = jsonObject.optJSONObject(key);
|
||
|
if (object == null) {
|
||
|
object = new JSONObject();
|
||
|
try {
|
||
|
jsonObject.put(key, object);
|
||
|
} catch (JSONException e) {
|
||
|
throw new IllegalArgumentException(
|
||
|
"Failed adding a default object for key [" + key + "]", e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return object;
|
||
|
}
|
||
|
|
||
|
@VisibleForTesting
|
||
|
public ICronetEngineBuilder getDelegate() {
|
||
|
return mDelegate;
|
||
|
}
|
||
|
|
||
|
@FunctionalInterface
|
||
|
private interface ExperimentalOptionsPatch {
|
||
|
void applyTo(JSONObject experimentalOptions) throws JSONException;
|
||
|
}
|
||
|
|
||
|
// Delegating-only methods
|
||
|
@Override
|
||
|
public ICronetEngineBuilder addPublicKeyPins(
|
||
|
String hostName,
|
||
|
Set<byte[]> pinsSha256,
|
||
|
boolean includeSubdomains,
|
||
|
Date expirationDate) {
|
||
|
mDelegate.addPublicKeyPins(hostName, pinsSha256, includeSubdomains, expirationDate);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder addQuicHint(String host, int port, int alternatePort) {
|
||
|
mDelegate.addQuicHint(host, port, alternatePort);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableHttp2(boolean value) {
|
||
|
mDelegate.enableHttp2(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableHttpCache(int cacheMode, long maxSize) {
|
||
|
mDelegate.enableHttpCache(cacheMode, maxSize);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) {
|
||
|
mDelegate.enablePublicKeyPinningBypassForLocalTrustAnchors(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableQuic(boolean value) {
|
||
|
mDelegate.enableQuic(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableSdch(boolean value) {
|
||
|
mDelegate.enableSdch(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableBrotli(boolean value) {
|
||
|
mDelegate.enableBrotli(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setLibraryLoader(CronetEngine.Builder.LibraryLoader loader) {
|
||
|
mDelegate.setLibraryLoader(loader);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setStoragePath(String value) {
|
||
|
mDelegate.setStoragePath(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setUserAgent(String userAgent) {
|
||
|
mDelegate.setUserAgent(userAgent);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public String getDefaultUserAgent() {
|
||
|
return mDelegate.getDefaultUserAgent();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder enableNetworkQualityEstimator(boolean value) {
|
||
|
mDelegate.enableNetworkQualityEstimator(value);
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public ICronetEngineBuilder setThreadPriority(int priority) {
|
||
|
mDelegate.setThreadPriority(priority);
|
||
|
return this;
|
||
|
}
|
||
|
}
|